diff options
| author | 2023-02-21 16:43:04 +0000 | |
|---|---|---|
| committer | 2023-02-22 03:42:49 +0000 | |
| commit | c1fdc6cb1540098cb9feed1147141c9a28df7043 (patch) | |
| tree | 849d644a53e502889a47142ebe18920d6766e613 /java | |
| parent | 622b5d33df631a7bd6fc5482a9a80931eece0c2d (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')
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;  |