Loading packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java +74 −1 Original line number Diff line number Diff line Loading @@ -27,9 +27,15 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.IBinder; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.internal.statusbar.IAppClipsService; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Application; Loading @@ -37,6 +43,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.wm.shell.bubbles.Bubbles; import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.inject.Inject; Loading @@ -46,21 +53,63 @@ import javax.inject.Inject; */ public class AppClipsService extends Service { private static final String TAG = AppClipsService.class.getSimpleName(); @Application private final Context mContext; private final FeatureFlags mFeatureFlags; private final Optional<Bubbles> mOptionalBubbles; private final DevicePolicyManager mDevicePolicyManager; private final UserManager mUserManager; private final boolean mAreTaskAndTimeIndependentPrerequisitesMet; @VisibleForTesting() @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile; @Inject public AppClipsService(@Application Context context, FeatureFlags featureFlags, Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) { Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager, UserManager userManager) { mContext = context; mFeatureFlags = featureFlags; mOptionalBubbles = optionalBubbles; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; // The consumer of this service are apps that call through StatusBarManager API to query if // it can use app clips API. Since these apps can be launched as work profile users, this // service will start as work profile user. SysUI doesn't share injected instances for // different users. This is why the bubbles instance injected will be incorrect. As the apps // don't generally have permission to connect to a service running as different user, we // start a proxy connection to communicate with the main user's version of this service. if (mUserManager.isManagedProfile()) { // No need to check for prerequisites in this case as those are incorrect for work // profile user instance of the service and the main user version of the service will // take care of this check. mAreTaskAndTimeIndependentPrerequisitesMet = false; // Get the main user so that we can connect to the main user's version of the service. UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { // If main user is not available there isn't much we can do, no apps can use app // clips. return; } // Set up the connection to be used later during onBind callback. mProxyConnectorToMainProfile = new ServiceConnector.Impl<>( context, new Intent(context, AppClipsService.class), Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE, mainUser.getIdentifier(), IAppClipsService.Stub::asInterface); return; } mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables(); mProxyConnectorToMainProfile = null; } private boolean checkIndependentVariables() { Loading Loading @@ -95,6 +144,13 @@ public class AppClipsService extends Service { return new IAppClipsService.Stub() { @Override public boolean canLaunchCaptureContentActivityForNote(int taskId) { // In case of managed profile, use the main user's instance of the service. Callers // cannot directly connect to the main user's instance as they may not have the // permission to interact across users. if (mUserManager.isManagedProfile()) { return canLaunchCaptureContentActivityForNoteFromMainUser(taskId); } if (!mAreTaskAndTimeIndependentPrerequisitesMet) { return false; } Loading @@ -107,4 +163,21 @@ public class AppClipsService extends Service { } }; } /** Returns whether the app clips API can be used by querying the service as the main user. */ private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) { if (mProxyConnectorToMainProfile == null) { return false; } try { AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult( service -> service.canLaunchCaptureContentActivityForNote(taskId)); return future.get(); } catch (ExecutionException | InterruptedException e) { Log.d(TAG, "Exception from service\n" + e); } return false; } } packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +27 −7 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.Nullable; Loading Loading @@ -79,13 +81,10 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0); private final DevicePolicyManager mDevicePolicyManager; Loading @@ -95,6 +94,7 @@ public class AppClipsTrampolineActivity extends Activity { private final PackageManager mPackageManager; private final UserTracker mUserTracker; private final UiEventLogger mUiEventLogger; private final UserManager mUserManager; private final ResultReceiver mResultReceiver; private Intent mKillAppClipsBroadcastIntent; Loading @@ -103,7 +103,7 @@ public class AppClipsTrampolineActivity extends Activity { public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController, PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, @Main Handler mainHandler) { UserManager userManager, @Main Handler mainHandler) { mDevicePolicyManager = devicePolicyManager; mFeatureFlags = flags; mOptionalBubbles = optionalBubbles; Loading @@ -111,6 +111,7 @@ public class AppClipsTrampolineActivity extends Activity { mPackageManager = packageManager; mUserTracker = userTracker; mUiEventLogger = uiEventLogger; mUserManager = userManager; mResultReceiver = createResultReceiver(mainHandler); } Loading @@ -123,6 +124,12 @@ public class AppClipsTrampolineActivity extends Activity { return; } if (mUserManager.isManagedProfile()) { maybeStartActivityForWPUser(); finish(); return; } if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) { finish(); return; Loading Loading @@ -191,6 +198,19 @@ public class AppClipsTrampolineActivity extends Activity { } } private void maybeStartActivityForWPUser() { UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED); return; } // Start the activity as the main user with activity result forwarding. startActivityAsUser( new Intent(this, AppClipsTrampolineActivity.class) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser); } private void setErrorResultAndFinish(int errorCode) { setResult(RESULT_OK, new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode)); Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java +44 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,10 @@ import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; Loading @@ -29,6 +31,8 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import androidx.test.runner.AndroidJUnit4; Loading @@ -42,6 +46,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Optional; Loading @@ -58,6 +63,9 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Mock private Optional<Bubbles> mOptionalBubbles; @Mock private Bubbles mBubbles; @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private UserManager mUserManager; private AppClipsService mAppClipsService; @Before public void setUp() { Loading Loading @@ -119,26 +127,53 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Test public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException { mockToSatisfyAllPrerequisites(); assertThat(getInterfaceWithRealContext() .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); } @Test public void isManagedProfile_shouldUseProxyConnection() throws RemoteException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); IAppClipsService service = getInterfaceWithRealContext(); mAppClipsService.mProxyConnectorToMainProfile = Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile); service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID); verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any()); } @Test public void isManagedProfile_noMainUser_shouldReturnFalse() { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(null); getInterfaceWithRealContext(); assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull(); } private void mockToSatisfyAllPrerequisites() { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); when(mOptionalBubbles.get()).thenReturn(mBubbles); when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true); when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false); assertThat(getInterfaceWithRealContext() .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); } private IAppClipsService getInterfaceWithRealContext() { AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags, mOptionalBubbles, mDevicePolicyManager); return getInterfaceFromService(appClipsService); mAppClipsService = new AppClipsService(getContext(), mFeatureFlags, mOptionalBubbles, mDevicePolicyManager, mUserManager); return getInterfaceFromService(mAppClipsService); } private IAppClipsService getInterfaceWithMockContext() { AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags, mOptionalBubbles, mDevicePolicyManager); return getInterfaceFromService(appClipsService); mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags, mOptionalBubbles, mDevicePolicyManager, mUserManager); return getInterfaceFromService(mAppClipsService); } private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) { Loading packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java +51 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.testing.AndroidTestingRunner; import androidx.test.rule.ActivityTestRule; Loading Loading @@ -98,6 +100,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private UiEventLogger mUiEventLogger; @Mock private UserManager mUserManager; @Main private Handler mMainHandler; Loading @@ -109,7 +114,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { protected AppClipsTrampolineActivityTestable create(Intent unUsed) { return new AppClipsTrampolineActivityTestable(mDevicePolicyManager, mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager, mUserTracker, mUiEventLogger, mMainHandler); mUserTracker, mUiEventLogger, mUserManager, mMainHandler); } }; Loading Loading @@ -264,6 +269,40 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE); } @Test public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity() throws NameNotFoundException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); mockToSatisfyAllPrerequisites(); AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent); waitForIdleSync(); Intent actualIntent = activity.mStartedIntent; assertThat(actualIntent.getComponent()).isEqualTo( new ComponentName(mContext, AppClipsTrampolineActivity.class)); assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT); assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM); } @Test public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed() throws NameNotFoundException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(null); mockToSatisfyAllPrerequisites(); mActivityRule.launchActivity(mActivityIntent); waitForIdleSync(); ActivityResult actualResult = mActivityRule.getActivityResult(); assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK); assertThat(getStatusCodeExtra(actualResult.getResultData())) .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED); } private void mockToSatisfyAllPrerequisites() throws NameNotFoundException { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); Loading @@ -282,6 +321,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public static final class AppClipsTrampolineActivityTestable extends AppClipsTrampolineActivity { Intent mStartedIntent; UserHandle mStartingUser; public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, Loading @@ -289,9 +331,10 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, UserManager userManager, @Main Handler mainHandler) { super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager, userTracker, uiEventLogger, mainHandler); userTracker, uiEventLogger, userManager, mainHandler); } @Override Loading @@ -303,6 +346,12 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public void startActivity(Intent unUsed) { // Ignore this intent to avoid App Clips screenshot editing activity from starting. } @Override public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) { mStartedIntent = startedIntent; mStartingUser = startingUser; } } private static int getStatusCodeExtra(Intent intent) { Loading Loading
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsService.java +74 −1 Original line number Diff line number Diff line Loading @@ -27,9 +27,15 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.os.IBinder; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.internal.infra.AndroidFuture; import com.android.internal.infra.ServiceConnector; import com.android.internal.statusbar.IAppClipsService; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.Application; Loading @@ -37,6 +43,7 @@ import com.android.systemui.flags.FeatureFlags; import com.android.wm.shell.bubbles.Bubbles; import java.util.Optional; import java.util.concurrent.ExecutionException; import javax.inject.Inject; Loading @@ -46,21 +53,63 @@ import javax.inject.Inject; */ public class AppClipsService extends Service { private static final String TAG = AppClipsService.class.getSimpleName(); @Application private final Context mContext; private final FeatureFlags mFeatureFlags; private final Optional<Bubbles> mOptionalBubbles; private final DevicePolicyManager mDevicePolicyManager; private final UserManager mUserManager; private final boolean mAreTaskAndTimeIndependentPrerequisitesMet; @VisibleForTesting() @Nullable ServiceConnector<IAppClipsService> mProxyConnectorToMainProfile; @Inject public AppClipsService(@Application Context context, FeatureFlags featureFlags, Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager) { Optional<Bubbles> optionalBubbles, DevicePolicyManager devicePolicyManager, UserManager userManager) { mContext = context; mFeatureFlags = featureFlags; mOptionalBubbles = optionalBubbles; mDevicePolicyManager = devicePolicyManager; mUserManager = userManager; // The consumer of this service are apps that call through StatusBarManager API to query if // it can use app clips API. Since these apps can be launched as work profile users, this // service will start as work profile user. SysUI doesn't share injected instances for // different users. This is why the bubbles instance injected will be incorrect. As the apps // don't generally have permission to connect to a service running as different user, we // start a proxy connection to communicate with the main user's version of this service. if (mUserManager.isManagedProfile()) { // No need to check for prerequisites in this case as those are incorrect for work // profile user instance of the service and the main user version of the service will // take care of this check. mAreTaskAndTimeIndependentPrerequisitesMet = false; // Get the main user so that we can connect to the main user's version of the service. UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { // If main user is not available there isn't much we can do, no apps can use app // clips. return; } // Set up the connection to be used later during onBind callback. mProxyConnectorToMainProfile = new ServiceConnector.Impl<>( context, new Intent(context, AppClipsService.class), Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY | Context.BIND_NOT_VISIBLE, mainUser.getIdentifier(), IAppClipsService.Stub::asInterface); return; } mAreTaskAndTimeIndependentPrerequisitesMet = checkIndependentVariables(); mProxyConnectorToMainProfile = null; } private boolean checkIndependentVariables() { Loading Loading @@ -95,6 +144,13 @@ public class AppClipsService extends Service { return new IAppClipsService.Stub() { @Override public boolean canLaunchCaptureContentActivityForNote(int taskId) { // In case of managed profile, use the main user's instance of the service. Callers // cannot directly connect to the main user's instance as they may not have the // permission to interact across users. if (mUserManager.isManagedProfile()) { return canLaunchCaptureContentActivityForNoteFromMainUser(taskId); } if (!mAreTaskAndTimeIndependentPrerequisitesMet) { return false; } Loading @@ -107,4 +163,21 @@ public class AppClipsService extends Service { } }; } /** Returns whether the app clips API can be used by querying the service as the main user. */ private boolean canLaunchCaptureContentActivityForNoteFromMainUser(int taskId) { if (mProxyConnectorToMainProfile == null) { return false; } try { AndroidFuture<Boolean> future = mProxyConnectorToMainProfile.postForResult( service -> service.canLaunchCaptureContentActivityForNote(taskId)); return future.get(); } catch (ExecutionException | InterruptedException e) { Log.d(TAG, "Exception from service\n" + e); } return false; } }
packages/SystemUI/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivity.java +27 −7 Original line number Diff line number Diff line Loading @@ -40,6 +40,8 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcel; import android.os.ResultReceiver; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import androidx.annotation.Nullable; Loading Loading @@ -79,13 +81,10 @@ public class AppClipsTrampolineActivity extends Activity { private static final String TAG = AppClipsTrampolineActivity.class.getSimpleName(); static final String PERMISSION_SELF = "com.android.systemui.permission.SELF"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; static final String EXTRA_SCREENSHOT_URI = TAG + "SCREENSHOT_URI"; static final String ACTION_FINISH_FROM_TRAMPOLINE = TAG + "FINISH_FROM_TRAMPOLINE"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE) public static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; static final String EXTRA_RESULT_RECEIVER = TAG + "RESULT_RECEIVER"; static final String EXTRA_CALLING_PACKAGE_NAME = TAG + "CALLING_PACKAGE_NAME"; private static final ApplicationInfoFlags APPLICATION_INFO_FLAGS = ApplicationInfoFlags.of(0); private final DevicePolicyManager mDevicePolicyManager; Loading @@ -95,6 +94,7 @@ public class AppClipsTrampolineActivity extends Activity { private final PackageManager mPackageManager; private final UserTracker mUserTracker; private final UiEventLogger mUiEventLogger; private final UserManager mUserManager; private final ResultReceiver mResultReceiver; private Intent mKillAppClipsBroadcastIntent; Loading @@ -103,7 +103,7 @@ public class AppClipsTrampolineActivity extends Activity { public AppClipsTrampolineActivity(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, NoteTaskController noteTaskController, PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, @Main Handler mainHandler) { UserManager userManager, @Main Handler mainHandler) { mDevicePolicyManager = devicePolicyManager; mFeatureFlags = flags; mOptionalBubbles = optionalBubbles; Loading @@ -111,6 +111,7 @@ public class AppClipsTrampolineActivity extends Activity { mPackageManager = packageManager; mUserTracker = userTracker; mUiEventLogger = uiEventLogger; mUserManager = userManager; mResultReceiver = createResultReceiver(mainHandler); } Loading @@ -123,6 +124,12 @@ public class AppClipsTrampolineActivity extends Activity { return; } if (mUserManager.isManagedProfile()) { maybeStartActivityForWPUser(); finish(); return; } if (!mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)) { finish(); return; Loading Loading @@ -191,6 +198,19 @@ public class AppClipsTrampolineActivity extends Activity { } } private void maybeStartActivityForWPUser() { UserHandle mainUser = mUserManager.getMainUser(); if (mainUser == null) { setErrorResultAndFinish(CAPTURE_CONTENT_FOR_NOTE_FAILED); return; } // Start the activity as the main user with activity result forwarding. startActivityAsUser( new Intent(this, AppClipsTrampolineActivity.class) .addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT), mainUser); } private void setErrorResultAndFinish(int errorCode) { setResult(RESULT_OK, new Intent().putExtra(EXTRA_CAPTURE_CONTENT_FOR_NOTE_STATUS_CODE, errorCode)); Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsServiceTest.java +44 −9 Original line number Diff line number Diff line Loading @@ -20,8 +20,10 @@ import static com.android.systemui.flags.Flags.SCREENSHOT_APP_CLIPS; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.admin.DevicePolicyManager; Loading @@ -29,6 +31,8 @@ import android.content.Context; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import androidx.test.runner.AndroidJUnit4; Loading @@ -42,6 +46,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.Optional; Loading @@ -58,6 +63,9 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Mock private Optional<Bubbles> mOptionalBubbles; @Mock private Bubbles mBubbles; @Mock private DevicePolicyManager mDevicePolicyManager; @Mock private UserManager mUserManager; private AppClipsService mAppClipsService; @Before public void setUp() { Loading Loading @@ -119,26 +127,53 @@ public final class AppClipsServiceTest extends SysuiTestCase { @Test public void allPrerequisitesSatisfy_shouldReturnTrue() throws RemoteException { mockToSatisfyAllPrerequisites(); assertThat(getInterfaceWithRealContext() .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); } @Test public void isManagedProfile_shouldUseProxyConnection() throws RemoteException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); IAppClipsService service = getInterfaceWithRealContext(); mAppClipsService.mProxyConnectorToMainProfile = Mockito.spy(mAppClipsService.mProxyConnectorToMainProfile); service.canLaunchCaptureContentActivityForNote(FAKE_TASK_ID); verify(mAppClipsService.mProxyConnectorToMainProfile).postForResult(any()); } @Test public void isManagedProfile_noMainUser_shouldReturnFalse() { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(null); getInterfaceWithRealContext(); assertThat(mAppClipsService.mProxyConnectorToMainProfile).isNull(); } private void mockToSatisfyAllPrerequisites() { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); when(mOptionalBubbles.get()).thenReturn(mBubbles); when(mBubbles.isAppBubbleTaskId(eq((FAKE_TASK_ID)))).thenReturn(true); when(mDevicePolicyManager.getScreenCaptureDisabled(eq(null))).thenReturn(false); assertThat(getInterfaceWithRealContext() .canLaunchCaptureContentActivityForNote(FAKE_TASK_ID)).isTrue(); } private IAppClipsService getInterfaceWithRealContext() { AppClipsService appClipsService = new AppClipsService(getContext(), mFeatureFlags, mOptionalBubbles, mDevicePolicyManager); return getInterfaceFromService(appClipsService); mAppClipsService = new AppClipsService(getContext(), mFeatureFlags, mOptionalBubbles, mDevicePolicyManager, mUserManager); return getInterfaceFromService(mAppClipsService); } private IAppClipsService getInterfaceWithMockContext() { AppClipsService appClipsService = new AppClipsService(mMockContext, mFeatureFlags, mOptionalBubbles, mDevicePolicyManager); return getInterfaceFromService(appClipsService); mAppClipsService = new AppClipsService(mMockContext, mFeatureFlags, mOptionalBubbles, mDevicePolicyManager, mUserManager); return getInterfaceFromService(mAppClipsService); } private static IAppClipsService getInterfaceFromService(AppClipsService appClipsService) { Loading
packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsTrampolineActivityTest.java +51 −2 Original line number Diff line number Diff line Loading @@ -49,6 +49,8 @@ import android.content.pm.ResolveInfo; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.UserHandle; import android.os.UserManager; import android.testing.AndroidTestingRunner; import androidx.test.rule.ActivityTestRule; Loading Loading @@ -98,6 +100,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { private UserTracker mUserTracker; @Mock private UiEventLogger mUiEventLogger; @Mock private UserManager mUserManager; @Main private Handler mMainHandler; Loading @@ -109,7 +114,7 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { protected AppClipsTrampolineActivityTestable create(Intent unUsed) { return new AppClipsTrampolineActivityTestable(mDevicePolicyManager, mFeatureFlags, mOptionalBubbles, mNoteTaskController, mPackageManager, mUserTracker, mUiEventLogger, mMainHandler); mUserTracker, mUiEventLogger, mUserManager, mMainHandler); } }; Loading Loading @@ -264,6 +269,40 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { verify(mUiEventLogger).log(SCREENSHOT_FOR_NOTE_TRIGGERED, TEST_UID, TEST_CALLING_PACKAGE); } @Test public void startAppClipsActivity_throughWPUser_shouldStartMainUserActivity() throws NameNotFoundException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(UserHandle.SYSTEM); mockToSatisfyAllPrerequisites(); AppClipsTrampolineActivityTestable activity = mActivityRule.launchActivity(mActivityIntent); waitForIdleSync(); Intent actualIntent = activity.mStartedIntent; assertThat(actualIntent.getComponent()).isEqualTo( new ComponentName(mContext, AppClipsTrampolineActivity.class)); assertThat(actualIntent.getFlags()).isEqualTo(Intent.FLAG_ACTIVITY_FORWARD_RESULT); assertThat(activity.mStartingUser).isEqualTo(UserHandle.SYSTEM); } @Test public void startAppClipsActivity_throughWPUser_noMainUser_shouldFinishWithFailed() throws NameNotFoundException { when(mUserManager.isManagedProfile()).thenReturn(true); when(mUserManager.getMainUser()).thenReturn(null); mockToSatisfyAllPrerequisites(); mActivityRule.launchActivity(mActivityIntent); waitForIdleSync(); ActivityResult actualResult = mActivityRule.getActivityResult(); assertThat(actualResult.getResultCode()).isEqualTo(Activity.RESULT_OK); assertThat(getStatusCodeExtra(actualResult.getResultData())) .isEqualTo(CAPTURE_CONTENT_FOR_NOTE_FAILED); } private void mockToSatisfyAllPrerequisites() throws NameNotFoundException { when(mFeatureFlags.isEnabled(SCREENSHOT_APP_CLIPS)).thenReturn(true); when(mOptionalBubbles.isEmpty()).thenReturn(false); Loading @@ -282,6 +321,9 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public static final class AppClipsTrampolineActivityTestable extends AppClipsTrampolineActivity { Intent mStartedIntent; UserHandle mStartingUser; public AppClipsTrampolineActivityTestable(DevicePolicyManager devicePolicyManager, FeatureFlags flags, Optional<Bubbles> optionalBubbles, Loading @@ -289,9 +331,10 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { PackageManager packageManager, UserTracker userTracker, UiEventLogger uiEventLogger, UserManager userManager, @Main Handler mainHandler) { super(devicePolicyManager, flags, optionalBubbles, noteTaskController, packageManager, userTracker, uiEventLogger, mainHandler); userTracker, uiEventLogger, userManager, mainHandler); } @Override Loading @@ -303,6 +346,12 @@ public final class AppClipsTrampolineActivityTest extends SysuiTestCase { public void startActivity(Intent unUsed) { // Ignore this intent to avoid App Clips screenshot editing activity from starting. } @Override public void startActivityAsUser(Intent startedIntent, UserHandle startingUser) { mStartedIntent = startedIntent; mStartingUser = startingUser; } } private static int getStatusCodeExtra(Intent intent) { Loading