/* * 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.logging; import static com.google.common.truth.Truth.assertThat; import static org.mockito.AdditionalMatchers.gt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Intent; import android.metrics.LogMaker; import com.android.intentresolver.contentpreview.ContentPreviewType; import com.android.intentresolver.logging.EventLogImpl.SharesheetStandardEvent; import com.android.intentresolver.logging.EventLogImpl.SharesheetStartedEvent; import com.android.intentresolver.logging.EventLogImpl.SharesheetTargetSelectedEvent; import com.android.internal.logging.InstanceId; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLogger.UiEventEnum; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import com.android.internal.util.FrameworkStatsLog; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public final class EventLogImplTest { @Mock private UiEventLogger mUiEventLog; @Mock private FrameworkStatsLogger mFrameworkLog; @Mock private MetricsLogger mMetricsLogger; private EventLogImpl mChooserLogger; private final InstanceIdSequence mSequence = EventLogImpl.newIdSequence(); @Before public void setUp() { mChooserLogger = new EventLogImpl(mUiEventLog, mFrameworkLog, mMetricsLogger, mSequence.newInstanceId()); } @After public void tearDown() { verifyNoMoreInteractions(mUiEventLog); verifyNoMoreInteractions(mFrameworkLog); verifyNoMoreInteractions(mMetricsLogger); } @Test public void testLogChooserActivityShown_personalProfile() { final boolean isWorkProfile = false; final String mimeType = "application/TestType"; final long systemCost = 456; mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); assertThat(event.getSubtype()).isEqualTo(MetricsEvent.PARENT_PROFILE); assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType); assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS)) .isEqualTo(systemCost); } @Test public void testLogChooserActivityShown_workProfile() { final boolean isWorkProfile = true; final String mimeType = "application/TestType"; final long systemCost = 456; mChooserLogger.logChooserActivityShown(isWorkProfile, mimeType, systemCost); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN); assertThat(event.getSubtype()).isEqualTo(MetricsEvent.MANAGED_PROFILE); assertThat(event.getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE)).isEqualTo(mimeType); assertThat(event.getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS)) .isEqualTo(systemCost); } @Test public void testLogShareStarted() { final String packageName = "com.test.foo"; final String mimeType = "text/plain"; final int appProvidedDirectTargets = 123; final int appProvidedAppTargets = 456; final boolean workProfile = true; final int previewType = ContentPreviewType.CONTENT_PREVIEW_FILE; final String intentAction = Intent.ACTION_SENDTO; final int numCustomActions = 3; final boolean modifyShareProvided = true; mChooserLogger.logShareStarted( packageName, mimeType, appProvidedDirectTargets, appProvidedAppTargets, workProfile, previewType, intentAction, numCustomActions, modifyShareProvided); verify(mFrameworkLog).write( eq(FrameworkStatsLog.SHARESHEET_STARTED), eq(SharesheetStartedEvent.SHARE_STARTED.getId()), eq(packageName), /* instanceId=*/ gt(0), eq(mimeType), eq(appProvidedDirectTargets), eq(appProvidedAppTargets), eq(workProfile), eq(FrameworkStatsLog.SHARESHEET_STARTED__PREVIEW_TYPE__CONTENT_PREVIEW_FILE), eq(FrameworkStatsLog.SHARESHEET_STARTED__INTENT_TYPE__INTENT_ACTION_SENDTO), /* custom actions provided */ eq(numCustomActions), /* reselection action provided */ eq(modifyShareProvided)); } @Test public void testLogShareTargetSelected() { final int targetType = EventLogImpl.SELECTION_TYPE_SERVICE; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; final int callerTargetCount = 0; final boolean isPinned = true; final boolean isSuccessfullySelected = true; final long selectionCost = 456; mChooserLogger.logShareTargetSelected( targetType, packageName, positionPicked, directTargetAlsoRanked, callerTargetCount, /* directTargetHashed= */ null, isPinned, isSuccessfullySelected, selectionCost); verify(mFrameworkLog).write( eq(FrameworkStatsLog.RANKING_SELECTED), eq(SharesheetTargetSelectedEvent.SHARESHEET_SERVICE_TARGET_SELECTED.getId()), eq(packageName), /* instanceId=*/ gt(0), eq(positionPicked), eq(isPinned)); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo( MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET); assertThat(event.getSubtype()).isEqualTo(positionPicked); } @Test public void testLogActionSelected() { mChooserLogger.logActionSelected(EventLogImpl.SELECTION_TYPE_COPY); verify(mFrameworkLog).write( eq(FrameworkStatsLog.RANKING_SELECTED), eq(SharesheetTargetSelectedEvent.SHARESHEET_COPY_TARGET_SELECTED.getId()), eq(""), /* instanceId=*/ gt(0), eq(-1), eq(false)); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo( MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SYSTEM_TARGET); assertThat(event.getSubtype()).isEqualTo(1); } @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; mChooserLogger.logDirectShareTargetReceived(category, latency); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo(category); assertThat(event.getSubtype()).isEqualTo(latency); } @Test public void testLogActionShareWithPreview() { final int previewType = ContentPreviewType.CONTENT_PREVIEW_TEXT; mChooserLogger.logActionShareWithPreview(previewType); ArgumentCaptor eventCaptor = ArgumentCaptor.forClass(LogMaker.class); verify(mMetricsLogger).write(eventCaptor.capture()); LogMaker event = eventCaptor.getValue(); assertThat(event.getCategory()).isEqualTo(MetricsEvent.ACTION_SHARE_WITH_PREVIEW); assertThat(event.getSubtype()).isEqualTo(previewType); } @Test public void testLogSharesheetTriggered() { mChooserLogger.logSharesheetTriggered(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_TRIGGERED), eq(0), isNull(), any()); } @Test public void testLogSharesheetAppLoadComplete() { mChooserLogger.logSharesheetAppLoadComplete(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_APP_LOAD_COMPLETE), eq(0), isNull(), any()); } @Test public void testLogSharesheetDirectLoadComplete() { mChooserLogger.logSharesheetDirectLoadComplete(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_COMPLETE), eq(0), isNull(), any()); } @Test public void testLogSharesheetDirectLoadTimeout() { mChooserLogger.logSharesheetDirectLoadTimeout(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_DIRECT_LOAD_TIMEOUT), eq(0), isNull(), any()); } @Test public void testLogSharesheetProfileChanged() { mChooserLogger.logSharesheetProfileChanged(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_PROFILE_CHANGED), eq(0), isNull(), any()); } @Test public void testLogSharesheetExpansionChanged_collapsed() { mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ true); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_COLLAPSED), eq(0), isNull(), any()); } @Test public void testLogSharesheetExpansionChanged_expanded() { mChooserLogger.logSharesheetExpansionChanged(/* isCollapsed=*/ false); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_EXPANDED), eq(0), isNull(), any()); } @Test public void testLogSharesheetAppShareRankingTimeout() { mChooserLogger.logSharesheetAppShareRankingTimeout(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_APP_SHARE_RANKING_TIMEOUT), eq(0), isNull(), any()); } @Test public void testLogSharesheetEmptyDirectShareRow() { mChooserLogger.logSharesheetEmptyDirectShareRow(); verify(mUiEventLog).logWithInstanceId( eq(SharesheetStandardEvent.SHARESHEET_EMPTY_DIRECT_SHARE_ROW), eq(0), isNull(), any()); } @Test public void testDifferentLoggerInstancesUseDifferentInstanceIds() { ArgumentCaptor idIntCaptor = ArgumentCaptor.forClass(Integer.class); EventLogImpl chooserLogger2 = new EventLogImpl(mUiEventLog, mFrameworkLog, mMetricsLogger, mSequence.newInstanceId()); final int targetType = EventLogImpl.SELECTION_TYPE_COPY; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; final int callerTargetCount = 0; final boolean isPinned = true; final boolean isSuccessfullySelected = true; final long selectionCost = 456; mChooserLogger.logShareTargetSelected( targetType, packageName, positionPicked, directTargetAlsoRanked, callerTargetCount, /* directTargetHashed= */ null, isPinned, isSuccessfullySelected, selectionCost); chooserLogger2.logShareTargetSelected( targetType, packageName, positionPicked, directTargetAlsoRanked, callerTargetCount, /* directTargetHashed= */ null, isPinned, isSuccessfullySelected, selectionCost); verify(mFrameworkLog, times(2)).write( anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean()); int id1 = idIntCaptor.getAllValues().get(0); int id2 = idIntCaptor.getAllValues().get(1); assertThat(id1).isGreaterThan(0); assertThat(id2).isGreaterThan(0); assertThat(id1).isNotEqualTo(id2); } @Test public void testUiAndFrameworkEventsUseSameInstanceIdForSameLoggerInstance() { ArgumentCaptor idIntCaptor = ArgumentCaptor.forClass(Integer.class); ArgumentCaptor idObjectCaptor = ArgumentCaptor.forClass(InstanceId.class); final int targetType = EventLogImpl.SELECTION_TYPE_COPY; final String packageName = "com.test.foo"; final int positionPicked = 123; final int directTargetAlsoRanked = -1; final int callerTargetCount = 0; final boolean isPinned = true; final boolean isSuccessfullySelected = true; final long selectionCost = 456; mChooserLogger.logShareTargetSelected( targetType, packageName, positionPicked, directTargetAlsoRanked, callerTargetCount, /* directTargetHashed= */ null, isPinned, isSuccessfullySelected, selectionCost); verify(mFrameworkLog).write( anyInt(), anyInt(), anyString(), idIntCaptor.capture(), anyInt(), anyBoolean()); mChooserLogger.logSharesheetTriggered(); verify(mUiEventLog).logWithInstanceId( any(UiEventEnum.class), anyInt(), any(), idObjectCaptor.capture()); assertThat(idIntCaptor.getValue()).isGreaterThan(0); assertThat(idObjectCaptor.getValue().getId()).isEqualTo(idIntCaptor.getValue()); } @Test public void testTargetSelectionCategories() { assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_SERVICE)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_SERVICE_TARGET); assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_APP)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_APP_TARGET); assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_STANDARD)) .isEqualTo(MetricsEvent.ACTION_ACTIVITY_CHOOSER_PICKED_STANDARD_TARGET); assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_COPY)).isEqualTo(0); assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_NEARBY)).isEqualTo(0); assertThat(EventLogImpl.getTargetSelectionCategory( EventLogImpl.SELECTION_TYPE_EDIT)).isEqualTo(0); } }