From 0446fab7561430f2c87beb8b1046db05f6c6b302 Mon Sep 17 00:00:00 2001 From: Susi Kharraz-Post Date: Thu, 21 Feb 2019 09:42:31 -0500 Subject: [PATCH] Add logging for profile switching for sharesheet We want to know which profile people share from and if users are switching from work to personal profile when sharing and vice versa. This adds the current profile to the sharesheet and also logs when the profile is switched. Bug: 124503277 Test: Added new unit tests for ChooserActivity and IntentForwardActivity Change-Id: Ideb83682bf3395258ff457988f4a3e9f519a844c --- .../android/internal/app/ChooserActivity.java | 12 ++++ .../internal/app/IntentForwarderActivity.java | 20 ++++++ .../internal/app/ChooserActivityTest.java | 46 +++++++++--- .../internal/app/ChooserWrapperActivity.java | 11 +++ .../app/IntentForwarderActivityTest.java | 70 +++++++++++++++++++ .../metrics_constants/metrics_constants.proto | 10 +++ 6 files changed, 161 insertions(+), 8 deletions(-) diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index 0fbd4dca700b3..89e3d6b6460fa 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -381,6 +381,8 @@ public class ChooserActivity extends ResolverActivity { final long systemCost = mChooserShownTime - intentReceivedTime; getMetricsLogger().write(new LogMaker(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN) + .setSubtype(isWorkProfile() ? MetricsEvent.MANAGED_PROFILE : + MetricsEvent.PARENT_PROFILE) .addTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE, target.getType()) .addTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS, systemCost)); @@ -417,6 +419,16 @@ public class ChooserActivity extends ResolverActivity { } } + /** + * Check if the profile currently used is a work profile. + * @return true if it is work profile, false if it is parent profile (or no work profile is + * set up) + */ + protected boolean isWorkProfile() { + return ((UserManager) getSystemService(Context.USER_SERVICE)) + .getUserInfo(UserHandle.myUserId()).isManagedProfile(); + } + /** * Override method to add content preview area, specific to the chooser activity. */ diff --git a/core/java/com/android/internal/app/IntentForwarderActivity.java b/core/java/com/android/internal/app/IntentForwarderActivity.java index d13bcf2aa186b..64f00103eb4bf 100644 --- a/core/java/com/android/internal/app/IntentForwarderActivity.java +++ b/core/java/com/android/internal/app/IntentForwarderActivity.java @@ -31,6 +31,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.metrics.LogMaker; import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; @@ -39,6 +40,8 @@ import android.util.Slog; import android.widget.Toast; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.Arrays; import java.util.HashSet; @@ -66,6 +69,8 @@ public class IntentForwarderActivity extends Activity { private Injector mInjector; + private MetricsLogger mMetricsLogger; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -78,9 +83,17 @@ public class IntentForwarderActivity extends Activity { if (className.equals(FORWARD_INTENT_TO_PARENT)) { userMessageId = com.android.internal.R.string.forward_intent_to_owner; targetUserId = getProfileParent(); + + getMetricsLogger().write( + new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) + .setSubtype(MetricsEvent.PARENT_PROFILE)); } else if (className.equals(FORWARD_INTENT_TO_MANAGED_PROFILE)) { userMessageId = com.android.internal.R.string.forward_intent_to_work; targetUserId = getManagedProfile(); + + getMetricsLogger().write( + new LogMaker(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE) + .setSubtype(MetricsEvent.MANAGED_PROFILE)); } else { Slog.wtf(TAG, IntentForwarderActivity.class.getName() + " cannot be called directly"); userMessageId = -1; @@ -257,6 +270,13 @@ public class IntentForwarderActivity extends Activity { intent.setComponent(null); } + protected MetricsLogger getMetricsLogger() { + if (mMetricsLogger == null) { + mMetricsLogger = new MetricsLogger(); + } + return mMetricsLogger; + } + @VisibleForTesting protected Injector createInjector() { return new InjectorImpl(); diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java index f27f3f9ca427c..9fbc1666aed16 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityTest.java @@ -57,7 +57,7 @@ import androidx.test.rule.ActivityTestRule; import com.android.internal.R; import com.android.internal.app.ResolverActivity.ResolvedComponentInfo; import com.android.internal.logging.MetricsLogger; -import com.android.internal.logging.nano.MetricsProto; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import org.junit.Before; import org.junit.Rule; @@ -524,15 +524,45 @@ public class ChooserActivityTest { waitForIdle(); verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture()); assertThat(logMakerCaptor.getAllValues().get(0).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); + is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); assertThat(logMakerCaptor .getAllValues().get(0) - .getTaggedData(MetricsProto.MetricsEvent.FIELD_TIME_TO_APP_TARGETS), + .getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS), is(notNullValue())); assertThat(logMakerCaptor .getAllValues().get(0) - .getTaggedData(MetricsProto.MetricsEvent.FIELD_SHARESHEET_MIMETYPE), + .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE), is("TestType")); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getSubtype(), + is(MetricsEvent.PARENT_PROFILE)); + } + + @Test + public void testOnCreateLoggingFromWorkProfile() { + Intent sendIntent = createSendTextIntent(); + sendIntent.setType("TestType"); + sOverrides.alternateProfileSetting = MetricsEvent.MANAGED_PROFILE; + MetricsLogger mockLogger = sOverrides.metricsLogger; + ArgumentCaptor logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + mActivityRule.launchActivity(Intent.createChooser(sendIntent, "logger test")); + waitForIdle(); + verify(mockLogger, atLeastOnce()).write(logMakerCaptor.capture()); + assertThat(logMakerCaptor.getAllValues().get(0).getCategory(), + is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getTaggedData(MetricsEvent.FIELD_TIME_TO_APP_TARGETS), + is(notNullValue())); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getTaggedData(MetricsEvent.FIELD_SHARESHEET_MIMETYPE), + is("TestType")); + assertThat(logMakerCaptor + .getAllValues().get(0) + .getSubtype(), + is(MetricsEvent.MANAGED_PROFILE)); } @Test @@ -547,7 +577,7 @@ public class ChooserActivityTest { verify(mockLogger, Mockito.times(1)).write(logMakerCaptor.capture()); // First invocation is from onCreate assertThat(logMakerCaptor.getAllValues().get(0).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); + is(MetricsEvent.ACTION_ACTIVITY_CHOOSER_SHOWN)); } @Test @@ -569,7 +599,7 @@ public class ChooserActivityTest { verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture()); // First invocation is from onCreate assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), is(CONTENT_PREVIEW_TEXT)); } @@ -599,11 +629,11 @@ public class ChooserActivityTest { verify(mockLogger, Mockito.times(3)).write(logMakerCaptor.capture()); // First invocation is from onCreate assertThat(logMakerCaptor.getAllValues().get(1).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); assertThat(logMakerCaptor.getAllValues().get(1).getSubtype(), is(CONTENT_PREVIEW_IMAGE)); assertThat(logMakerCaptor.getAllValues().get(2).getCategory(), - is(MetricsProto.MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); + is(MetricsEvent.ACTION_SHARE_WITH_PREVIEW)); assertThat(logMakerCaptor.getAllValues().get(2).getSubtype(), is(CONTENT_PREVIEW_IMAGE)); } diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java index 57c84ff5c8acc..a8dd69a2666d7 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserWrapperActivity.java @@ -28,6 +28,7 @@ import android.net.Uri; import android.util.Size; import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; import java.util.function.Function; @@ -112,6 +113,14 @@ public class ChooserWrapperActivity extends ChooserActivity { return super.queryResolver(resolver, uri); } + @Override + protected boolean isWorkProfile() { + if (sOverrides.alternateProfileSetting != 0) { + return sOverrides.alternateProfileSetting == MetricsEvent.MANAGED_PROFILE; + } + return super.isWorkProfile(); + } + /** * We cannot directly mock the activity created since instrumentation creates it. *

@@ -128,6 +137,7 @@ public class ChooserWrapperActivity extends ChooserActivity { public boolean resolverForceException; public Bitmap previewThumbnail; public MetricsLogger metricsLogger; + public int alternateProfileSetting; public void reset() { onSafelyStartCallback = null; @@ -139,6 +149,7 @@ public class ChooserWrapperActivity extends ChooserActivity { resolverForceException = false; resolverListController = mock(ResolverListController.class); metricsLogger = mock(MetricsLogger.class); + alternateProfileSetting = 0; } } } diff --git a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java index 9b13af2a43577..abfb4fb3cedc3 100644 --- a/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java +++ b/core/tests/coretests/src/com/android/internal/app/IntentForwarderActivityTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -40,6 +41,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; +import android.metrics.LogMaker; import android.net.Uri; import android.os.Bundle; import android.os.IBinder; @@ -51,6 +53,9 @@ import androidx.test.InstrumentationRegistry; import androidx.test.rule.ActivityTestRule; import androidx.test.runner.AndroidJUnit4; +import com.android.internal.logging.MetricsLogger; +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; + import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -70,6 +75,11 @@ public class IntentForwarderActivityTest { "android", IntentForwarderActivity.FORWARD_INTENT_TO_MANAGED_PROFILE ); + private static final ComponentName FORWARD_TO_PARENT_COMPONENT_NAME = + new ComponentName( + "android", + IntentForwarderActivity.FORWARD_INTENT_TO_PARENT + ); private static final String TYPE_PLAIN_TEXT = "text/plain"; private static UserInfo MANAGED_PROFILE_INFO = new UserInfo(); @@ -522,6 +532,60 @@ public class IntentForwarderActivityTest { verify(sInjector).showToast(anyInt(), anyInt()); } + @Test + public void forwardToManagedProfile_LoggingTest() throws Exception { + sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; + + // Intent can be forwarded. + when(mIPm.canForwardTo( + any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); + + // Managed profile exists. + List profiles = new ArrayList<>(); + profiles.add(CURRENT_USER_INFO); + profiles.add(MANAGED_PROFILE_INFO); + when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); + + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class); + intent.setAction(Intent.ACTION_SEND); + intent.setType(TYPE_PLAIN_TEXT); + IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); + + ArgumentCaptor logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + verify(activity.getMetricsLogger()).write(logMakerCaptor.capture()); + assertEquals(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE, + logMakerCaptor.getValue().getCategory()); + assertEquals(MetricsEvent.MANAGED_PROFILE, + logMakerCaptor.getValue().getSubtype()); + } + + @Test + public void forwardToParent_LoggingTest() throws Exception { + sComponentName = FORWARD_TO_PARENT_COMPONENT_NAME; + + // Intent can be forwarded. + when(mIPm.canForwardTo( + any(Intent.class), nullable(String.class), anyInt(), anyInt())).thenReturn(true); + + // Managed profile exists. + List profiles = new ArrayList<>(); + profiles.add(CURRENT_USER_INFO); + profiles.add(MANAGED_PROFILE_INFO); + when(mUserManager.getProfiles(anyInt())).thenReturn(profiles); + + Intent intent = new Intent(mContext, IntentForwarderWrapperActivity.class); + intent.setAction(Intent.ACTION_SEND); + intent.setType(TYPE_PLAIN_TEXT); + IntentForwarderWrapperActivity activity = mActivityRule.launchActivity(intent); + + ArgumentCaptor logMakerCaptor = ArgumentCaptor.forClass(LogMaker.class); + verify(activity.getMetricsLogger()).write(logMakerCaptor.capture()); + assertEquals(MetricsEvent.ACTION_SWITCH_SHARE_PROFILE, + logMakerCaptor.getValue().getCategory()); + assertEquals(MetricsEvent.PARENT_PROFILE, + logMakerCaptor.getValue().getSubtype()); + } + private void setupShouldSkipDisclosureTest() throws RemoteException { sComponentName = FORWARD_TO_MANAGED_PROFILE_COMPONENT_NAME; sActivityName = "MyTestActivity"; @@ -541,6 +605,7 @@ public class IntentForwarderActivityTest { private Intent mStartActivityIntent; private int mUserIdActivityLaunchedIn; + private MetricsLogger mMetricsLogger = mock(MetricsLogger.class); @Override public void onCreate(@Nullable Bundle savedInstanceState) { @@ -559,6 +624,11 @@ public class IntentForwarderActivityTest { mStartActivityIntent = intent; mUserIdActivityLaunchedIn = userId; } + + @Override + protected MetricsLogger getMetricsLogger() { + return mMetricsLogger; + } } public class TestInjector implements IntentForwarderActivity.Injector { diff --git a/proto/src/metrics_constants/metrics_constants.proto b/proto/src/metrics_constants/metrics_constants.proto index 530d1156a5e4b..009d33a404277 100644 --- a/proto/src/metrics_constants/metrics_constants.proto +++ b/proto/src/metrics_constants/metrics_constants.proto @@ -247,6 +247,12 @@ message MetricsEvent { LOCATION_GONE = 6; // the view isn't laid out at all } + // Subtypes for profile logging + enum ActiveUserProfile { + PARENT_PROFILE = 1; + MANAGED_PROFILE = 2; + } + // Known visual elements: views or controls. enum View { // Unknown view @@ -7002,6 +7008,10 @@ message MetricsEvent { // Different display can have different orientations, so need to log display id FIELD_DISPLAY_ID = 1660; + // ACTION: Changing from work to parent profile or vice versa + // OS: Q + ACTION_SWITCH_SHARE_PROFILE = 1661; + // ---- End Q Constants, all Q constants go above this line ---- // Add new aosp constants above this line. // END OF AOSP CONSTANTS