summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
author Matt Casey <mrcasey@google.com> 2023-02-21 16:43:04 +0000
committer Matt Casey <mrcasey@google.com> 2023-02-22 03:42:49 +0000
commitc1fdc6cb1540098cb9feed1147141c9a28df7043 (patch)
tree849d644a53e502889a47142ebe18920d6766e613 /java
parent622b5d33df631a7bd6fc5482a9a80931eece0c2d (diff)
Add logging for modify share and custom action clicks
Just a regular UiEvent log for modify share. Include position information for custom actions (*just* the position within the custom action set, ignoring other system actions that may be within the display container). Bug: 265504112 Test: atest ChooserActivityLoggerTest Test: atest ChooserActivityFactoryTest Change-Id: If64db5c1afccf6571d23395624d6ffbbef677188
Diffstat (limited to 'java')
-rw-r--r--java/src/com/android/intentresolver/ChooserActionFactory.java57
-rw-r--r--java/src/com/android/intentresolver/ChooserActivityLogger.java27
-rw-r--r--java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java2
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt154
-rw-r--r--java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java11
5 files changed, 233 insertions, 18 deletions
diff --git a/java/src/com/android/intentresolver/ChooserActionFactory.java b/java/src/com/android/intentresolver/ChooserActionFactory.java
index 1fe55890..566b2546 100644
--- a/java/src/com/android/intentresolver/ChooserActionFactory.java
+++ b/java/src/com/android/intentresolver/ChooserActionFactory.java
@@ -49,7 +49,6 @@ import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
-import java.util.stream.Collectors;
/**
* Implementation of {@link ChooserContentPreviewUi.ActionFactory} specialized to the application
@@ -96,9 +95,10 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
private final TargetInfo mNearbySharingTarget;
private final Runnable mOnNearbyButtonClicked;
private final ImmutableList<ChooserAction> mCustomActions;
- private final PendingIntent mReselectionIntent;
+ private final Runnable mOnModifyShareClicked;
private final Consumer<Boolean> mExcludeSharedTextAction;
private final Consumer</* @Nullable */ Integer> mFinishCallback;
+ private final ChooserActivityLogger mLogger;
/**
* @param context
@@ -160,8 +160,13 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
logger),
chooserRequest.getChooserActions(),
(featureFlagRepository.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)
- ? chooserRequest.getModifyShareAction() : null),
+ ? createModifyShareRunnable(
+ chooserRequest.getModifyShareAction(),
+ finishCallback,
+ logger)
+ : null),
onUpdateSharedTextIsExcluded,
+ logger,
finishCallback);
}
@@ -176,8 +181,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
TargetInfo nearbySharingTarget,
Runnable onNearbyButtonClicked,
List<ChooserAction> customActions,
- @Nullable PendingIntent reselectionIntent,
+ @Nullable Runnable onModifyShareClicked,
Consumer<Boolean> onUpdateSharedTextIsExcluded,
+ ChooserActivityLogger logger,
Consumer</* @Nullable */ Integer> finishCallback) {
mContext = context;
mCopyButtonLabel = copyButtonLabel;
@@ -188,8 +194,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
mNearbySharingTarget = nearbySharingTarget;
mOnNearbyButtonClicked = onNearbyButtonClicked;
mCustomActions = ImmutableList.copyOf(customActions);
- mReselectionIntent = reselectionIntent;
+ mOnModifyShareClicked = onModifyShareClicked;
mExcludeSharedTextAction = onUpdateSharedTextIsExcluded;
+ mLogger = logger;
mFinishCallback = finishCallback;
}
@@ -236,10 +243,15 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
/** Create custom actions */
@Override
public List<ActionRow.Action> createCustomActions() {
- return mCustomActions.stream()
- .map(target -> createCustomAction(mContext, target, mFinishCallback))
- .filter(action -> action != null)
- .collect(Collectors.toList());
+ List<ActionRow.Action> actions = new ArrayList<>();
+ for (int i = 0; i < mCustomActions.size(); i++) {
+ ActionRow.Action actionRow = createCustomAction(
+ mContext, mCustomActions.get(i), mFinishCallback, i, mLogger);
+ if (actionRow != null) {
+ actions.add(actionRow);
+ }
+ }
+ return actions;
}
/**
@@ -248,18 +260,25 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
@Override
@Nullable
public Runnable getModifyShareAction() {
- return (mReselectionIntent == null) ? null : createReselectionRunnable(mReselectionIntent);
+ return mOnModifyShareClicked;
}
- private Runnable createReselectionRunnable(PendingIntent pendingIntent) {
+ private static Runnable createModifyShareRunnable(
+ PendingIntent pendingIntent,
+ Consumer<Integer> finishCallback,
+ ChooserActivityLogger logger) {
+ if (pendingIntent == null) {
+ return null;
+ }
+
return () -> {
try {
pendingIntent.send();
} catch (PendingIntent.CanceledException e) {
Log.d(TAG, "Payload reselection action has been cancelled");
}
- // TODO: add reporting
- mFinishCallback.accept(Activity.RESULT_OK);
+ logger.logActionSelected(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE);
+ finishCallback.accept(Activity.RESULT_OK);
};
}
@@ -402,7 +421,9 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
Intent originalIntent,
ChooserIntegratedDeviceComponents integratedComponents) {
final ComponentName cn = integratedComponents.getNearbySharingComponent();
- if (cn == null) return null;
+ if (cn == null) {
+ return null;
+ }
final Intent resolveIntent = new Intent(originalIntent);
resolveIntent.setComponent(cn);
@@ -455,7 +476,11 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
@Nullable
private static ActionRow.Action createCustomAction(
- Context context, ChooserAction action, Consumer<Integer> finishCallback) {
+ Context context,
+ ChooserAction action,
+ Consumer<Integer> finishCallback,
+ int position,
+ ChooserActivityLogger logger) {
Drawable icon = action.getIcon().loadDrawable(context);
if (icon == null && TextUtils.isEmpty(action.getLabel())) {
return null;
@@ -469,7 +494,7 @@ public final class ChooserActionFactory implements ChooserContentPreviewUi.Actio
} catch (PendingIntent.CanceledException e) {
Log.d(TAG, "Custom action, " + action.getLabel() + ", has been cancelled");
}
- // TODO: add reporting
+ logger.logCustomActionSelected(position);
finishCallback.accept(Activity.RESULT_OK);
}
);
diff --git a/java/src/com/android/intentresolver/ChooserActivityLogger.java b/java/src/com/android/intentresolver/ChooserActivityLogger.java
index 1725a7bf..f298955b 100644
--- a/java/src/com/android/intentresolver/ChooserActivityLogger.java
+++ b/java/src/com/android/intentresolver/ChooserActivityLogger.java
@@ -48,6 +48,8 @@ public class ChooserActivityLogger {
public static final int SELECTION_TYPE_COPY = 4;
public static final int SELECTION_TYPE_NEARBY = 5;
public static final int SELECTION_TYPE_EDIT = 6;
+ public static final int SELECTION_TYPE_MODIFY_SHARE = 7;
+ public static final int SELECTION_TYPE_CUSTOM_ACTION = 8;
/**
* This shim is provided only for testing. In production, clients will only ever use a
@@ -134,6 +136,21 @@ public class ChooserActivityLogger {
}
/**
+ * Log that a custom action has been tapped by the user.
+ *
+ * @param positionPicked index of the custom action within the list of custom actions.
+ */
+ public void logCustomActionSelected(int positionPicked) {
+ mFrameworkStatsLogger.write(FrameworkStatsLog.RANKING_SELECTED,
+ /* event_id = 1 */
+ SharesheetTargetSelectedEvent.SHARESHEET_CUSTOM_ACTION_SELECTED.getId(),
+ /* package_name = 2 */ null,
+ /* instance_id = 3 */ getInstanceId().getId(),
+ /* position_picked = 4 */ positionPicked,
+ /* is_pinned = 5 */ false);
+ }
+
+ /**
* Logs a UiEventReported event for the system sharesheet when the user selects a target.
* TODO: document parameters and/or consider breaking up by targetType so we don't have to
* support an overly-generic signature.
@@ -332,7 +349,11 @@ public class ChooserActivityLogger {
@UiEvent(doc = "User selected the nearby target.")
SHARESHEET_NEARBY_TARGET_SELECTED(626),
@UiEvent(doc = "User selected the edit target.")
- SHARESHEET_EDIT_TARGET_SELECTED(669);
+ SHARESHEET_EDIT_TARGET_SELECTED(669),
+ @UiEvent(doc = "User selected the modify share target.")
+ SHARESHEET_MODIFY_SHARE_SELECTED(1316),
+ @UiEvent(doc = "User selected a custom action.")
+ SHARESHEET_CUSTOM_ACTION_SELECTED(1317);
private final int mId;
SharesheetTargetSelectedEvent(int id) {
@@ -356,6 +377,10 @@ public class ChooserActivityLogger {
return SHARESHEET_NEARBY_TARGET_SELECTED;
case SELECTION_TYPE_EDIT:
return SHARESHEET_EDIT_TARGET_SELECTED;
+ case SELECTION_TYPE_MODIFY_SHARE:
+ return SHARESHEET_MODIFY_SHARE_SELECTED;
+ case SELECTION_TYPE_CUSTOM_ACTION:
+ return SHARESHEET_CUSTOM_ACTION_SELECTED;
default:
return INVALID;
}
diff --git a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java b/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
index 9b124c20..14255ca0 100644
--- a/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
+++ b/java/src/com/android/intentresolver/ChooserIntegratedDeviceComponents.java
@@ -32,7 +32,7 @@ import com.android.internal.annotations.VisibleForTesting;
* Because this describes the app's external execution environment, test methods may prefer to
* provide explicit values to override the default lookup logic.
*/
-public final class ChooserIntegratedDeviceComponents {
+public class ChooserIntegratedDeviceComponents {
@Nullable
private final ComponentName mEditSharingComponent;
diff --git a/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
new file mode 100644
index 00000000..af134fcd
--- /dev/null
+++ b/java/tests/src/com/android/intentresolver/ChooserActionFactoryTest.kt
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.intentresolver
+
+import android.app.Activity
+import android.app.PendingIntent
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.content.res.Resources
+import android.graphics.drawable.Icon
+import android.service.chooser.ChooserAction
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import com.android.intentresolver.flags.FeatureFlagRepository
+import com.android.intentresolver.flags.Flags
+import com.google.common.collect.ImmutableList
+import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito
+import java.util.concurrent.Callable
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import java.util.function.Consumer
+
+@RunWith(AndroidJUnit4::class)
+class ChooserActionFactoryTest {
+ private val context = InstrumentationRegistry.getInstrumentation().getContext()
+
+ private val logger = mock<ChooserActivityLogger>()
+ private val flags = mock<FeatureFlagRepository>()
+ private val actionLabel = "Action label"
+ private val testAction = "com.android.intentresolver.testaction"
+ private val countdown = CountDownLatch(1)
+ private val testReceiver: BroadcastReceiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ // Just doing at most a single countdown per test.
+ countdown.countDown()
+ }
+ }
+ private object resultConsumer : Consumer<Int> {
+ var latestReturn = Integer.MIN_VALUE
+
+ override fun accept(resultCode: Int) {
+ latestReturn = resultCode
+ }
+
+ }
+
+ @Before
+ fun setup() {
+ whenever(flags.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)).thenReturn(true)
+ context.registerReceiver(testReceiver, IntentFilter(testAction))
+ }
+
+ @After
+ fun teardown() {
+ context.unregisterReceiver(testReceiver)
+ }
+
+ @Test
+ fun testCreateCustomActions() {
+ val factory = createFactory()
+
+ val customActions = factory.createCustomActions()
+
+ assertThat(customActions.size).isEqualTo(1)
+ assertThat(customActions[0].label).isEqualTo(actionLabel)
+
+ // click it
+ customActions[0].onClicked.run()
+
+ Mockito.verify(logger).logCustomActionSelected(eq(0))
+ assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
+ // Verify the pendingintent has been called
+ countdown.await(500, TimeUnit.MILLISECONDS)
+ }
+
+ @Test
+ fun testNoModifyShareAction() {
+ val factory = createFactory(includeModifyShare = false)
+
+ assertThat(factory.modifyShareAction).isNull()
+ }
+
+ @Test
+ fun testNoModifyShareAction_flagDisabled() {
+ whenever(flags.isEnabled(Flags.SHARESHEET_RESELECTION_ACTION)).thenReturn(false)
+ val factory = createFactory(includeModifyShare = true)
+
+ assertThat(factory.modifyShareAction).isNull()
+ }
+
+ @Test
+ fun testModifyShareAction() {
+ val factory = createFactory(includeModifyShare = true)
+
+ factory.modifyShareAction!!.run()
+
+ Mockito.verify(logger).logActionSelected(
+ eq(ChooserActivityLogger.SELECTION_TYPE_MODIFY_SHARE))
+ assertEquals(Activity.RESULT_OK, resultConsumer.latestReturn)
+ // Verify the pendingintent has been called
+ countdown.await(500, TimeUnit.MILLISECONDS)
+ }
+
+ private fun createFactory(includeModifyShare: Boolean = false): ChooserActionFactory {
+ val testPendingIntent = PendingIntent.getActivity(context, 0, Intent(testAction),0)
+ val targetIntent = Intent()
+ val action = ChooserAction.Builder(
+ Icon.createWithResource("", Resources.ID_NULL),
+ actionLabel,
+ testPendingIntent
+ ).build()
+ val chooserRequest = mock<ChooserRequestParameters>()
+ whenever(chooserRequest.targetIntent).thenReturn(targetIntent)
+ whenever(chooserRequest.chooserActions).thenReturn(ImmutableList.of(action))
+
+ if (includeModifyShare) {
+ whenever(chooserRequest.modifyShareAction).thenReturn(testPendingIntent)
+ }
+
+ return ChooserActionFactory(
+ context,
+ chooserRequest,
+ flags,
+ mock<ChooserIntegratedDeviceComponents>(),
+ logger,
+ Consumer<Boolean>{},
+ Callable<View?>{null},
+ mock<ChooserActionFactory.ActionActivityStarter>(),
+ resultConsumer)
+ }
+} \ No newline at end of file
diff --git a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java b/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java
index c6a9b63f..d8868fc1 100644
--- a/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java
+++ b/java/tests/src/com/android/intentresolver/ChooserActivityLoggerTest.java
@@ -205,6 +205,17 @@ public final class ChooserActivityLoggerTest {
}
@Test
+ public void testLogCustomActionSelected() {
+ final int position = 4;
+ mChooserLogger.logCustomActionSelected(position);
+
+ verify(mFrameworkLog).write(
+ eq(FrameworkStatsLog.RANKING_SELECTED),
+ eq(SharesheetTargetSelectedEvent.SHARESHEET_CUSTOM_ACTION_SELECTED.getId()),
+ any(), anyInt(), eq(position), eq(false));
+ }
+
+ @Test
public void testLogDirectShareTargetReceived() {
final int category = MetricsEvent.ACTION_DIRECT_SHARE_TARGETS_LOADED_SHORTCUT_MANAGER;
final int latency = 123;