From 44036d209682bd326227cff55a05920d36d02f22 Mon Sep 17 00:00:00 2001 From: Mohammed Rashidy Date: Tue, 6 Dec 2022 16:28:39 +0000 Subject: [PATCH 1/2] Adding ActivityInterceptorCallbackRegistry Adding ActivityInterceptorCallbackRegistry as a class which is visible to the mainline modules, as it is not feasible to unhide ActivityTaskManagerInternal. Mainline modules will use this class to register ActivityInterceptorCallback. Adding undegister function to follow go/android-api-guidelines Test: atest com.android.server.wm.ActivityInterceptorCallbackRegistryTest && atest com.android.server.wm.ActivityStartInterceptorTest && atest com.android.server.wm.ActivityTaskManagerServiceTests Bug: 248531721 CTS-Coverage-Bug: 261598402 API-Coverage-Bug: 261598402 Change-Id: I790ea5672d39f7e1a7ffc936477f848ac5c559e3 --- .../ActivityInterceptorCallbackRegistry.java | 120 +++++++++++++++ .../wm/ActivityTaskManagerInternal.java | 13 +- .../server/wm/ActivityTaskManagerService.java | 16 ++ ...tivityInterceptorCallbackRegistryTest.java | 141 ++++++++++++++++++ .../wm/ActivityTaskManagerServiceTests.java | 21 +++ 5 files changed, 310 insertions(+), 1 deletion(-) create mode 100644 services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java create mode 100644 services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java new file mode 100644 index 0000000000000..cb66a39376a2f --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2022 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.NonNull; +import android.os.Binder; +import android.os.Process; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +/** + * This class should be used by system services which are part of mainline modules to register + * {@link ActivityInterceptorCallback}. For other system services, this function should be used + * instead {@link ActivityTaskManagerInternal#registerActivityStartInterceptor( + * int, ActivityInterceptorCallback)}. + * @hide + */ +public class ActivityInterceptorCallbackRegistry { + + private static final ActivityInterceptorCallbackRegistry sInstance = + new ActivityInterceptorCallbackRegistry(); + + private ActivityInterceptorCallbackRegistry() {} + + /** Returns an already initialised singleton instance of this class. */ + @NonNull + public static ActivityInterceptorCallbackRegistry getInstance() { + return sInstance; + } + + /** + * Registers a callback which can intercept activity launching flow. + * + *

Only system services which are part of mainline modules should call this function. + * + *

To avoid Activity launch delays, the callbacks must execute quickly and avoid acquiring + * other system process locks. + * + * @param mainlineOrderId has to be one of the following [{@link + * ActivityInterceptorCallback#MAINLINE_FIRST_ORDERED_ID}]. + * @param callback the {@link ActivityInterceptorCallback} to register. + * @throws IllegalArgumentException if duplicate ids are provided, the provided id is not the + * mainline module range or the provided {@code callback} is null. + */ + // ExecutorRegistration is suppressed as the callback is called synchronously in the system + // server. + @SuppressWarnings("ExecutorRegistration") + public void registerActivityInterceptorCallback( + @ActivityInterceptorCallback.OrderedId int mainlineOrderId, + @NonNull ActivityInterceptorCallback callback) { + if (getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only system server can register " + + "ActivityInterceptorCallback"); + } + if (!ActivityInterceptorCallback.isValidMainlineOrderId(mainlineOrderId)) { + throw new IllegalArgumentException("id is not in the mainline modules range, please use" + + "ActivityTaskManagerInternal.registerActivityStartInterceptor(OrderedId, " + + "ActivityInterceptorCallback) instead."); + } + if (callback == null) { + throw new IllegalArgumentException("The passed ActivityInterceptorCallback can not be " + + "null"); + } + ActivityTaskManagerInternal activityTaskManagerInternal = + LocalServices.getService(ActivityTaskManagerInternal.class); + activityTaskManagerInternal.registerActivityStartInterceptor(mainlineOrderId, callback); + } + + /** + * Unregisters an already registered {@link ActivityInterceptorCallback}. + * + * @param mainlineOrderId the order id of the {@link ActivityInterceptorCallback} should be + * unregistered, this callback should be registered before by calling + * {@link #registerActivityInterceptorCallback(int, + * ActivityInterceptorCallback)} using the same order id. + * @throws IllegalArgumentException if the provided id is not the mainline module range or is + * not registered + */ + public void unregisterActivityInterceptorCallback( + @ActivityInterceptorCallback.OrderedId int mainlineOrderId) { + if (getCallingUid() != Process.SYSTEM_UID) { + throw new SecurityException("Only system server can register " + + "ActivityInterceptorCallback"); + } + if (!ActivityInterceptorCallback.isValidMainlineOrderId(mainlineOrderId)) { + throw new IllegalArgumentException("id is not in the mainline modules range, please use" + + "ActivityTaskManagerInternal.unregisterActivityStartInterceptor(OrderedId) " + + "instead."); + } + ActivityTaskManagerInternal activityTaskManagerInternal = + LocalServices.getService(ActivityTaskManagerInternal.class); + activityTaskManagerInternal.unregisterActivityStartInterceptor(mainlineOrderId); + } + + /** + * This hidden function is for unit tests as a way to behave like as if they are called from + * system server process uid by mocking it and returning {@link Process#SYSTEM_UID}. + * Do not make this {@code public} to apps as apps should not have a way to change the uid. + * @hide + */ + @VisibleForTesting + int getCallingUid() { + return Binder.getCallingUid(); + } +} diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index ec486437734b5..c63bd52900e8d 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -680,12 +680,23 @@ public abstract class ActivityTaskManagerInternal { /** * Registers a callback which can intercept activity starts. - * @throws IllegalArgumentException if duplicate ids are provided + * @throws IllegalArgumentException if duplicate ids are provided or the provided {@code + * callback} is null + * @see ActivityInterceptorCallbackRegistry + * #registerActivityInterceptorCallback(int, ActivityInterceptorCallback) */ public abstract void registerActivityStartInterceptor( @ActivityInterceptorCallback.OrderedId int id, ActivityInterceptorCallback callback); + /** + * Unregisters an {@link ActivityInterceptorCallback}. + * @throws IllegalArgumentException if id is not registered + * @see ActivityInterceptorCallbackRegistry#unregisterActivityInterceptorCallback(int) + */ + public abstract void unregisterActivityStartInterceptor( + @ActivityInterceptorCallback.OrderedId int id); + /** Get the most recent task excluding the first running task (the one on the front most). */ public abstract ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index c2d4bfdab4e5f..9c41a2bca0eca 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -6813,6 +6813,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { if (mActivityInterceptorCallbacks.contains(id)) { throw new IllegalArgumentException("Duplicate id provided: " + id); } + if (callback == null) { + throw new IllegalArgumentException("The passed ActivityInterceptorCallback " + + "can not be null"); + } if (!ActivityInterceptorCallback.isValidOrderId(id)) { throw new IllegalArgumentException( "Provided id " + id + " is not in range of valid ids for system " @@ -6825,6 +6829,18 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void unregisterActivityStartInterceptor( + @ActivityInterceptorCallback.OrderedId int id) { + synchronized (mGlobalLock) { + if (!mActivityInterceptorCallbacks.contains(id)) { + throw new IllegalArgumentException( + "ActivityInterceptorCallback with id (" + id + ") is not registered"); + } + mActivityInterceptorCallbacks.remove(id); + } + } + @Override public ActivityManager.RecentTaskInfo getMostRecentTaskFromBackground() { List runningTaskInfoList = getTasks(1); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java new file mode 100644 index 0000000000000..3646f1a8f5c8a --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityInterceptorCallbackRegistryTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2022 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 static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_FIRST_ORDERED_ID; +import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_LAST_ORDERED_ID; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.spy; + +import android.os.Process; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.MediumTest; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; + +/** + * Tests for the {@link ActivityInterceptorCallbackRegistry} class. + */ +@Presubmit +@MediumTest +@RunWith(WindowTestRunner.class) +public final class ActivityInterceptorCallbackRegistryTest extends WindowTestsBase { + + private ActivityInterceptorCallbackRegistry mRegistry; + + @Before + public void setUp() { + mRegistry = spy(ActivityInterceptorCallbackRegistry.getInstance()); + Mockito.doReturn(Process.SYSTEM_UID).when(mRegistry).getCallingUid(); + } + + @Test + public void registerActivityInterceptorCallbackFailIfNotSystemId() { + // default registry with test app uid + ActivityInterceptorCallbackRegistry registry = spy( + ActivityInterceptorCallbackRegistry.getInstance()); + assertThrows( + SecurityException.class, + () -> registry.registerActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1, + info -> null) + ); + } + + @Test + public void registerActivityInterceptorCallbackFailIfIdNotInRange() { + assertThrows( + IllegalArgumentException.class, + () -> mRegistry.registerActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1, + info -> null) + ); + + assertThrows( + IllegalArgumentException.class, + () -> mRegistry.registerActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID - 1, + info -> null) + ); + } + + @Test + public void registerActivityInterceptorCallbackFailIfCallbackIsNull() { + assertThrows( + IllegalArgumentException.class, + () -> mRegistry.registerActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID, + null) + ); + } + + @Test + public void registerActivityInterceptorCallbackSuccessfully() { + int size = mAtm.getActivityInterceptorCallbacks().size(); + int orderId = MAINLINE_FIRST_ORDERED_ID; + mRegistry.registerActivityInterceptorCallback(orderId, + info -> null); + assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size()); + assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId)); + } + + @Test + public void unregisterActivityInterceptorCallbackFailIfNotSystemId() { + // default registry with test app uid + ActivityInterceptorCallbackRegistry registry = spy( + ActivityInterceptorCallbackRegistry.getInstance()); + assertThrows( + SecurityException.class, + () -> registry.unregisterActivityInterceptorCallback(MAINLINE_LAST_ORDERED_ID + 1) + ); + } + + @Test + public void unRegisterActivityInterceptorCallbackFailIfIdNotInRange() { + assertThrows( + IllegalArgumentException.class, + () -> mRegistry.unregisterActivityInterceptorCallback( + MAINLINE_LAST_ORDERED_ID + 1)); + } + + @Test + public void unregisterActivityInterceptorCallbackFailIfNotRegistered() { + assertThrows( + IllegalArgumentException.class, + () -> mRegistry.unregisterActivityInterceptorCallback(MAINLINE_FIRST_ORDERED_ID) + ); + } + + @Test + public void unregisterActivityInterceptorCallbackSuccessfully() { + int size = mAtm.getActivityInterceptorCallbacks().size(); + int orderId = MAINLINE_FIRST_ORDERED_ID; + mRegistry.registerActivityInterceptorCallback(orderId, + info -> null); + assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size()); + assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId)); + + mRegistry.unregisterActivityInterceptorCallback(orderId); + assertEquals(size, mAtm.getActivityInterceptorCallbacks().size()); + assertFalse(mAtm.getActivityInterceptorCallbacks().contains(orderId)); + + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java index 693d32eba3e8a..3dcae91f5c89a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityTaskManagerServiceTests.java @@ -1027,4 +1027,25 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase { public void testSystemAndMainlineOrderIdsNotOverlapping() { assertTrue(MAINLINE_FIRST_ORDERED_ID - SYSTEM_LAST_ORDERED_ID > 1); } + + @Test + public void testUnregisterActivityStartInterceptor() { + int size = mAtm.getActivityInterceptorCallbacks().size(); + int orderId = SYSTEM_FIRST_ORDERED_ID; + + mAtm.mInternal.registerActivityStartInterceptor(orderId, + (ActivityInterceptorCallback) info -> null); + assertEquals(size + 1, mAtm.getActivityInterceptorCallbacks().size()); + assertTrue(mAtm.getActivityInterceptorCallbacks().contains(orderId)); + + mAtm.mInternal.unregisterActivityStartInterceptor(orderId); + assertEquals(size, mAtm.getActivityInterceptorCallbacks().size()); + assertFalse(mAtm.getActivityInterceptorCallbacks().contains(orderId)); + } + + @Test(expected = IllegalArgumentException.class) + public void testUnregisterActivityStartInterceptor_IdNotExist() { + assertEquals(0, mAtm.getActivityInterceptorCallbacks().size()); + mAtm.mInternal.unregisterActivityStartInterceptor(SYSTEM_FIRST_ORDERED_ID); + } } From 0fb4f28f5dd87643a0abf1e9532188add297cbee Mon Sep 17 00:00:00 2001 From: Mohammed Rashidy Date: Tue, 6 Dec 2022 16:28:39 +0000 Subject: [PATCH 2/2] Disable resolving while intercepting based on a flag Adding a flag to ActivityInterceptResult which could disable resolving if interception happened. This flag is needed in case that SDK sandbox is intercepting the activity starting flow, as it would modify ActivityInfo (not the intent), so there is no need to resolve it again. Test: atest WmTests:ActivityStartInterceptorTest CTS-Coverage-Bug: 261605379 API-Coverage-Bug: 261605379 Bug: 248531721 Change-Id: Ie4b018d9fdf996bd10aa2e9a1350c6046f28fd14 --- services/api/current.txt | 49 +++++++++++++++++++ .../wm/ActivityInterceptorCallback.java | 37 ++++++++++++-- .../ActivityInterceptorCallbackRegistry.java | 2 + .../server/wm/ActivityStartInterceptor.java | 3 ++ .../wm/ActivityStartInterceptorTest.java | 34 ++++++++++++- 5 files changed, 121 insertions(+), 4 deletions(-) diff --git a/services/api/current.txt b/services/api/current.txt index da5b1fcaae99f..b5798d56f0c92 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -169,3 +169,52 @@ package com.android.server.wifi { } +package com.android.server.wm { + + public interface ActivityInterceptorCallback { + method public default void onActivityLaunched(@NonNull android.app.TaskInfo, @NonNull android.content.pm.ActivityInfo, @NonNull com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo); + method @Nullable public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptResult onInterceptActivityLaunch(@NonNull com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo); + field public static final int MAINLINE_SDK_SANDBOX_ORDER_ID = 1001; // 0x3e9 + } + + public static final class ActivityInterceptorCallback.ActivityInterceptResult { + ctor public ActivityInterceptorCallback.ActivityInterceptResult(@NonNull android.content.Intent, @NonNull android.app.ActivityOptions, boolean); + method @NonNull public android.app.ActivityOptions getActivityOptions(); + method @NonNull public android.content.Intent getIntent(); + method public boolean isActivityResolved(); + } + + public static final class ActivityInterceptorCallback.ActivityInterceptorInfo { + method @NonNull public android.content.pm.ActivityInfo getActivityInfo(); + method @Nullable public String getCallingFeatureId(); + method @Nullable public String getCallingPackage(); + method public int getCallingPid(); + method public int getCallingUid(); + method @Nullable public android.app.ActivityOptions getCheckedOptions(); + method @Nullable public Runnable getClearOptionsAnimationRunnable(); + method @NonNull public android.content.Intent getIntent(); + method public int getRealCallingPid(); + method public int getRealCallingUid(); + method @NonNull public android.content.pm.ResolveInfo getResolveInfo(); + method @Nullable public String getResolvedType(); + method public int getUserId(); + } + + public static final class ActivityInterceptorCallback.ActivityInterceptorInfo.Builder { + ctor public ActivityInterceptorCallback.ActivityInterceptorInfo.Builder(int, int, int, int, int, @NonNull android.content.Intent, @NonNull android.content.pm.ResolveInfo, @NonNull android.content.pm.ActivityInfo); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo build(); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingFeatureId(@NonNull String); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCallingPackage(@NonNull String); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setCheckedOptions(@NonNull android.app.ActivityOptions); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setClearOptionsAnimationRunnable(@NonNull Runnable); + method @NonNull public com.android.server.wm.ActivityInterceptorCallback.ActivityInterceptorInfo.Builder setResolvedType(@NonNull String); + } + + public class ActivityInterceptorCallbackRegistry { + method @NonNull public static com.android.server.wm.ActivityInterceptorCallbackRegistry getInstance(); + method public void registerActivityInterceptorCallback(int, @NonNull com.android.server.wm.ActivityInterceptorCallback); + method public void unregisterActivityInterceptorCallback(int); + } + +} + diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index c593fa3d91b97..ff1d44293dca8 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -19,6 +19,7 @@ package com.android.server.wm; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SystemApi; import android.app.ActivityOptions; import android.app.TaskInfo; import android.content.Intent; @@ -33,6 +34,7 @@ import java.lang.annotation.RetentionPolicy; * be called with the WindowManagerGlobalLock held. * @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public interface ActivityInterceptorCallback { /** * Called to allow intercepting activity launching based on the provided launch parameters and @@ -165,6 +167,7 @@ public interface ActivityInterceptorCallback { * Data class for storing the various arguments needed for activity interception. * @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) final class ActivityInterceptorInfo { private final int mCallingUid; private final int mCallingPid; @@ -389,6 +392,7 @@ public interface ActivityInterceptorCallback { * Data class for storing the intercept result. * @hide */ + @SystemApi(client = SystemApi.Client.SYSTEM_SERVER) final class ActivityInterceptResult { @NonNull private final Intent mIntent; @@ -396,15 +400,35 @@ public interface ActivityInterceptorCallback { @NonNull private final ActivityOptions mActivityOptions; - /** Generates the result of intercepting launching the {@link android.app.Activity} + private final boolean mActivityResolved; + + /** + * This constructor should only be used if both {@link ActivityInfo} and {@link ResolveInfo} + * did not get resolved while interception. + * @hide + */ + public ActivityInterceptResult(@NonNull Intent intent, + @NonNull ActivityOptions activityOptions) { + this(intent, activityOptions, false /* activityResolved */); + } + + /** + * Generates the result of intercepting launching the {@link android.app.Activity} + * + *

Interceptor should return non-{@code null} result when {@link + * #onInterceptActivityLaunch(ActivityInterceptorInfo)} gets called as an indicator that + * interception has happened. * * @param intent is the modified {@link Intent} after interception. * @param activityOptions holds the {@link ActivityOptions} after interception. + * @param activityResolved should be {@code true} only if {@link ActivityInfo} or {@link + * ResolveInfo} gets resolved, otherwise should be {@code false}. */ - public ActivityInterceptResult( - @NonNull Intent intent, @NonNull ActivityOptions activityOptions) { + public ActivityInterceptResult(@NonNull Intent intent, + @NonNull ActivityOptions activityOptions, boolean activityResolved) { this.mIntent = intent; this.mActivityOptions = activityOptions; + this.mActivityResolved = activityResolved; } /** Returns the intercepted {@link Intent} */ @@ -419,5 +443,12 @@ public interface ActivityInterceptorCallback { public ActivityOptions getActivityOptions() { return mActivityOptions; } + + /** + * Returns if the {@link ActivityInfo} or {@link ResolveInfo} gets resolved. + */ + public boolean isActivityResolved() { + return mActivityResolved; + } } } diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java index cb66a39376a2f..bb9462f5b0341 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Binder; import android.os.Process; @@ -30,6 +31,7 @@ import com.android.server.LocalServices; * int, ActivityInterceptorCallback)}. * @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public class ActivityInterceptorCallbackRegistry { private static final ActivityInterceptorCallbackRegistry sInstance = diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 84100a7ab02d6..d4d0256d52566 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -244,6 +244,9 @@ class ActivityStartInterceptor { mActivityOptions = interceptResult.getActivityOptions(); mCallingPid = mRealCallingPid; mCallingUid = mRealCallingUid; + if (interceptResult.isActivityResolved()) { + return true; + } mRInfo = mSupervisor.resolveIntent(mIntent, null, mUserId, 0, mRealCallingUid); mAInfo = mSupervisor.resolveActivity(mIntent, mRInfo, mStartFlags, null /*profilerInfo*/); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index b0461c2276dde..7d16fb2e02554 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -30,6 +30,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.MAINLINE_SDK_SAN import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; @@ -358,6 +359,12 @@ public class ActivityStartInterceptorTest { public void addMockInterceptorCallback( @Nullable Intent intent, @Nullable ActivityOptions activityOptions) { + addMockInterceptorCallback(intent, activityOptions, false); + } + + public void addMockInterceptorCallback( + @Nullable Intent intent, @Nullable ActivityOptions activityOptions, + boolean skipResolving) { int size = mActivityInterceptorCallbacks.size(); mActivityInterceptorCallbacks.put(size, new ActivityInterceptorCallback() { @Override @@ -368,7 +375,8 @@ public class ActivityStartInterceptorTest { } return new ActivityInterceptResult( intent != null ? intent : info.getIntent(), - activityOptions != null ? activityOptions : info.getCheckedOptions()); + activityOptions != null ? activityOptions : info.getCheckedOptions(), + skipResolving); } }); } @@ -400,6 +408,30 @@ public class ActivityStartInterceptorTest { assertEquals("android.test.second", mInterceptor.mIntent.getAction()); } + @Test + public void testInterceptionCallback_skipResolving() { + addMockInterceptorCallback( + new Intent("android.test.foo"), + ActivityOptions.makeBasic().setLaunchDisplayId(3), true); + ActivityInfo aInfo = mAInfo; + assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null)); + assertEquals("android.test.foo", mInterceptor.mIntent.getAction()); + assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId()); + assertEquals(aInfo, mInterceptor.mAInfo); // mAInfo should not be resolved + } + + @Test + public void testInterceptionCallback_NoSkipResolving() throws InterruptedException { + addMockInterceptorCallback( + new Intent("android.test.foo"), + ActivityOptions.makeBasic().setLaunchDisplayId(3)); + ActivityInfo aInfo = mAInfo; + assertTrue(mInterceptor.intercept(null, null, aInfo, null, null, null, 0, 0, null)); + assertEquals("android.test.foo", mInterceptor.mIntent.getAction()); + assertEquals(3, mInterceptor.mActivityOptions.getLaunchDisplayId()); + assertNotEquals(aInfo, mInterceptor.mAInfo); // mAInfo should be resolved after intercept + } + @Test public void testActivityLaunchedCallback_singleCallback() { addMockInterceptorCallback(null, null);