From 44036d209682bd326227cff55a05920d36d02f22 Mon Sep 17 00:00:00 2001 From: Mohammed Rashidy Date: Tue, 6 Dec 2022 16:28:39 +0000 Subject: [PATCH] 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); + } }