Loading services/core/java/com/android/server/wm/ActivityInterceptorCallback.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.server.wm; import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityOptions; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Callback to intercept activity starts and possibly block/redirect them. */ public abstract class ActivityInterceptorCallback { /** * Intercept the launch intent based on various signals. If an interception happened, returns * a new/existing non-null {@link Intent} which may redirect to another activity. * * @return null if no interception occurred, or a non-null intent which replaces the * existing intent. */ public abstract @Nullable Intent intercept(ActivityInterceptorInfo info); /** * The unique id of each interceptor which determines the order it will execute in. */ @IntDef(suffix = { "_ORDERED_ID" }, value = { FIRST_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids }) @Retention(RetentionPolicy.SOURCE) public @interface OrderedId {} /** * The first id, used by the framework to determine the valid range of ids. */ static final int FIRST_ORDERED_ID = 0; /** * The final id, used by the framework to determine the valid range of ids. Update this when * adding new ids. */ static final int LAST_ORDERED_ID = FIRST_ORDERED_ID; /** * Data class for storing the various arguments needed for activity interception. */ public static final class ActivityInterceptorInfo { public final int realCallingUid; public final int realCallingPid; public final int userId; public final String callingPackage; public final String callingFeatureId; public final Intent intent; public final ResolveInfo rInfo; public final ActivityInfo aInfo; public final String resolvedType; public final int callingPid; public final int callingUid; public final ActivityOptions checkedOptions; public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId, String callingPackage, String callingFeatureId, Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid, int callingUid, ActivityOptions checkedOptions) { this.realCallingUid = realCallingUid; this.realCallingPid = realCallingPid; this.userId = userId; this.callingPackage = callingPackage; this.callingFeatureId = callingFeatureId; this.intent = intent; this.rInfo = rInfo; this.aInfo = aInfo; this.resolvedType = resolvedType; this.callingPid = callingPid; this.callingUid = callingUid; this.checkedOptions = checkedOptions; } } } services/core/java/com/android/server/wm/ActivityStartInterceptor.java +28 −1 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppActivity; Loading Loading @@ -178,7 +179,33 @@ class ActivityStartInterceptor { // before issuing the work challenge. return true; } return interceptLockedManagedProfileIfNeeded(); if (interceptLockedManagedProfileIfNeeded()) { return true; } final SparseArray<ActivityInterceptorCallback> callbacks = mService.getActivityInterceptorCallbacks(); final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo = new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid, mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent, mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid, mActivityOptions); for (int i = 0; i < callbacks.size(); i++) { final ActivityInterceptorCallback callback = callbacks.valueAt(i); final Intent newIntent = callback.intercept(interceptorInfo); if (newIntent == null) { continue; } mIntent = newIntent; mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid); mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); return true; } return false; } private boolean hasCrossProfileAnimation() { Loading services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +8 −0 Original line number Diff line number Diff line Loading @@ -678,4 +678,12 @@ public abstract class ActivityTaskManagerInternal { /** Called when the device is waking up */ public abstract void notifyWakingUp(); /** * Registers a callback which can intercept activity starts. * @throws IllegalArgumentException if duplicate ids are provided */ public abstract void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback); } services/core/java/com/android/server/wm/ActivityTaskManagerService.java +26 −1 Original line number Diff line number Diff line Loading @@ -92,6 +92,8 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE; import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen; import static com.android.server.am.EventLogTags.writeConfigurationChanged; import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID; import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; Loading Loading @@ -458,6 +460,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The controller for all operations related to locktask. */ private LockTaskController mLockTaskController; private ActivityStartController mActivityStartController; private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); PackageConfigPersister mPackageConfigPersister; boolean mSuppressResizeConfigChanges; Loading Loading @@ -1115,6 +1119,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mBackgroundActivityStartCallback; } SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() { return mActivityInterceptorCallbacks; } private void start() { LocalServices.addService(ActivityTaskManagerInternal.class, mInternal); } Loading Loading @@ -6583,5 +6591,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */, null /* trigger */, mRootWindowContainer.getDefaultDisplay()); } @Override public void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback) { synchronized (mGlobalLock) { if (mActivityInterceptorCallbacks.contains(id)) { throw new IllegalArgumentException("Duplicate id provided: " + id); } if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) { throw new IllegalArgumentException( "Provided id " + id + " is not in range of valid ids [" + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]"); } mActivityInterceptorCallbacks.put(id, callback); } } } } services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +43 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; Loading @@ -42,6 +43,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.testing.DexmakerShareClassLoaderRule; import android.util.SparseArray; import androidx.test.filters.SmallTest; Loading Loading @@ -114,6 +116,9 @@ public class ActivityStartInterceptorTest { private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); @Before public void setUp() { MockitoAnnotations.initMocks(this); Loading Loading @@ -157,6 +162,9 @@ public class ActivityStartInterceptorTest { TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) .thenReturn(true); // Mock the activity start callbacks when(mService.getActivityInterceptorCallbacks()).thenReturn(mActivityInterceptorCallbacks); // Initialise activity info mAInfo.applicationInfo = new ApplicationInfo(); mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; Loading Loading @@ -285,4 +293,38 @@ public class ActivityStartInterceptorTest { // THEN calling intercept returns false assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } public void addMockInterceptorCallback(@Nullable Intent intent) { int size = mActivityInterceptorCallbacks.size(); mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() { @Override public Intent intercept(ActivityInterceptorInfo info) { return intent; } }); } @Test public void testInterceptionCallback_singleCallback() { addMockInterceptorCallback(new Intent("android.test.foo")); assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); assertEquals("android.test.foo", mInterceptor.mIntent.getAction()); } @Test public void testInterceptionCallback_singleCallbackReturnsNull() { addMockInterceptorCallback(null); assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } @Test public void testInterceptionCallback_fallbackToSecondCallback() { addMockInterceptorCallback(null); addMockInterceptorCallback(new Intent("android.test.second")); assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); assertEquals("android.test.second", mInterceptor.mIntent.getAction()); } } Loading
services/core/java/com/android/server/wm/ActivityInterceptorCallback.java 0 → 100644 +98 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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.server.wm; import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityOptions; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ResolveInfo; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * Callback to intercept activity starts and possibly block/redirect them. */ public abstract class ActivityInterceptorCallback { /** * Intercept the launch intent based on various signals. If an interception happened, returns * a new/existing non-null {@link Intent} which may redirect to another activity. * * @return null if no interception occurred, or a non-null intent which replaces the * existing intent. */ public abstract @Nullable Intent intercept(ActivityInterceptorInfo info); /** * The unique id of each interceptor which determines the order it will execute in. */ @IntDef(suffix = { "_ORDERED_ID" }, value = { FIRST_ORDERED_ID, LAST_ORDERED_ID // Update this when adding new ids }) @Retention(RetentionPolicy.SOURCE) public @interface OrderedId {} /** * The first id, used by the framework to determine the valid range of ids. */ static final int FIRST_ORDERED_ID = 0; /** * The final id, used by the framework to determine the valid range of ids. Update this when * adding new ids. */ static final int LAST_ORDERED_ID = FIRST_ORDERED_ID; /** * Data class for storing the various arguments needed for activity interception. */ public static final class ActivityInterceptorInfo { public final int realCallingUid; public final int realCallingPid; public final int userId; public final String callingPackage; public final String callingFeatureId; public final Intent intent; public final ResolveInfo rInfo; public final ActivityInfo aInfo; public final String resolvedType; public final int callingPid; public final int callingUid; public final ActivityOptions checkedOptions; public ActivityInterceptorInfo(int realCallingUid, int realCallingPid, int userId, String callingPackage, String callingFeatureId, Intent intent, ResolveInfo rInfo, ActivityInfo aInfo, String resolvedType, int callingPid, int callingUid, ActivityOptions checkedOptions) { this.realCallingUid = realCallingUid; this.realCallingPid = realCallingPid; this.userId = userId; this.callingPackage = callingPackage; this.callingFeatureId = callingFeatureId; this.intent = intent; this.rInfo = rInfo; this.aInfo = aInfo; this.resolvedType = resolvedType; this.callingPid = callingPid; this.callingUid = callingUid; this.checkedOptions = checkedOptions; } } }
services/core/java/com/android/server/wm/ActivityStartInterceptor.java +28 −1 Original line number Diff line number Diff line Loading @@ -51,6 +51,7 @@ import android.os.Bundle; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.SparseArray; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.BlockedAppActivity; Loading Loading @@ -178,7 +179,33 @@ class ActivityStartInterceptor { // before issuing the work challenge. return true; } return interceptLockedManagedProfileIfNeeded(); if (interceptLockedManagedProfileIfNeeded()) { return true; } final SparseArray<ActivityInterceptorCallback> callbacks = mService.getActivityInterceptorCallbacks(); final ActivityInterceptorCallback.ActivityInterceptorInfo interceptorInfo = new ActivityInterceptorCallback.ActivityInterceptorInfo(mRealCallingUid, mRealCallingPid, mUserId, mCallingPackage, mCallingFeatureId, mIntent, mRInfo, mAInfo, mResolvedType, mCallingPid, mCallingUid, mActivityOptions); for (int i = 0; i < callbacks.size(); i++) { final ActivityInterceptorCallback callback = callbacks.valueAt(i); final Intent newIntent = callback.intercept(interceptorInfo); if (newIntent == null) { continue; } mIntent = newIntent; mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid); mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); return true; } return false; } private boolean hasCrossProfileAnimation() { Loading
services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +8 −0 Original line number Diff line number Diff line Loading @@ -678,4 +678,12 @@ public abstract class ActivityTaskManagerInternal { /** Called when the device is waking up */ public abstract void notifyWakingUp(); /** * Registers a callback which can intercept activity starts. * @throws IllegalArgumentException if duplicate ids are provided */ public abstract void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback); }
services/core/java/com/android/server/wm/ActivityTaskManagerService.java +26 −1 Original line number Diff line number Diff line Loading @@ -92,6 +92,8 @@ import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.Scr import static com.android.server.am.ActivityManagerServiceDumpProcessesProto.ScreenCompatPackage.PACKAGE; import static com.android.server.am.EventLogTags.writeBootProgressEnableScreen; import static com.android.server.am.EventLogTags.writeConfigurationChanged; import static com.android.server.wm.ActivityInterceptorCallback.FIRST_ORDERED_ID; import static com.android.server.wm.ActivityInterceptorCallback.LAST_ORDERED_ID; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; Loading Loading @@ -458,6 +460,8 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The controller for all operations related to locktask. */ private LockTaskController mLockTaskController; private ActivityStartController mActivityStartController; private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); PackageConfigPersister mPackageConfigPersister; boolean mSuppressResizeConfigChanges; Loading Loading @@ -1115,6 +1119,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mBackgroundActivityStartCallback; } SparseArray<ActivityInterceptorCallback> getActivityInterceptorCallbacks() { return mActivityInterceptorCallbacks; } private void start() { LocalServices.addService(ActivityTaskManagerInternal.class, mInternal); } Loading Loading @@ -6583,5 +6591,22 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { getTransitionController().requestTransitionIfNeeded(TRANSIT_WAKE, 0 /* flags */, null /* trigger */, mRootWindowContainer.getDefaultDisplay()); } @Override public void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback) { synchronized (mGlobalLock) { if (mActivityInterceptorCallbacks.contains(id)) { throw new IllegalArgumentException("Duplicate id provided: " + id); } if (id > LAST_ORDERED_ID || id < FIRST_ORDERED_ID) { throw new IllegalArgumentException( "Provided id " + id + " is not in range of valid ids [" + FIRST_ORDERED_ID + "," + LAST_ORDERED_ID + "]"); } mActivityInterceptorCallbacks.put(id, callback); } } } }
services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +43 −1 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.nullable; import android.annotation.Nullable; import android.app.ActivityManagerInternal; import android.app.KeyguardManager; import android.app.admin.DevicePolicyManagerInternal; Loading @@ -42,6 +43,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.Presubmit; import android.testing.DexmakerShareClassLoaderRule; import android.util.SparseArray; import androidx.test.filters.SmallTest; Loading Loading @@ -114,6 +116,9 @@ public class ActivityStartInterceptorTest { private ActivityStartInterceptor mInterceptor; private ActivityInfo mAInfo = new ActivityInfo(); private SparseArray<ActivityInterceptorCallback> mActivityInterceptorCallbacks = new SparseArray<>(); @Before public void setUp() { MockitoAnnotations.initMocks(this); Loading Loading @@ -157,6 +162,9 @@ public class ActivityStartInterceptorTest { TEST_USER_ID, TEST_PACKAGE_NAME, LOCK_TASK_LAUNCH_MODE_DEFAULT)) .thenReturn(true); // Mock the activity start callbacks when(mService.getActivityInterceptorCallbacks()).thenReturn(mActivityInterceptorCallbacks); // Initialise activity info mAInfo.applicationInfo = new ApplicationInfo(); mAInfo.packageName = mAInfo.applicationInfo.packageName = TEST_PACKAGE_NAME; Loading Loading @@ -285,4 +293,38 @@ public class ActivityStartInterceptorTest { // THEN calling intercept returns false assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } public void addMockInterceptorCallback(@Nullable Intent intent) { int size = mActivityInterceptorCallbacks.size(); mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() { @Override public Intent intercept(ActivityInterceptorInfo info) { return intent; } }); } @Test public void testInterceptionCallback_singleCallback() { addMockInterceptorCallback(new Intent("android.test.foo")); assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); assertEquals("android.test.foo", mInterceptor.mIntent.getAction()); } @Test public void testInterceptionCallback_singleCallbackReturnsNull() { addMockInterceptorCallback(null); assertFalse(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); } @Test public void testInterceptionCallback_fallbackToSecondCallback() { addMockInterceptorCallback(null); addMockInterceptorCallback(new Intent("android.test.second")); assertTrue(mInterceptor.intercept(null, null, mAInfo, null, null, 0, 0, null)); assertEquals("android.test.second", mInterceptor.mIntent.getAction()); } }