Merge changes Ie4b018d9,I790ea567

* changes:
  Disable resolving while intercepting based on a flag
  Adding ActivityInterceptorCallbackRegistry
This commit is contained in:
Mohammed Rashidy
2022-12-20 21:12:48 +00:00
committed by Android (Google) Code Review
9 changed files with 431 additions and 5 deletions

View File

@@ -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);
}
}

View File

@@ -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}
*
* <p>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;
}
}
}

View File

@@ -0,0 +1,122 @@
/*
* 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.annotation.SystemApi;
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
*/
@SystemApi(client = SystemApi.Client.SYSTEM_SERVER)
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.
*
* <p>Only system services which are part of mainline modules should call this function.
*
* <p>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();
}
}

View File

@@ -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*/);

View File

@@ -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();

View File

@@ -6801,6 +6801,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 "
@@ -6813,6 +6817,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<ActivityManager.RunningTaskInfo> runningTaskInfoList = getTasks(1);

View File

@@ -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));
}
}

View File

@@ -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);

View File

@@ -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);
}
}