Merge "Add internal WM API for registering activity interceptors." into sc-v2-dev

This commit is contained in:
Lucas Silva
2021-10-07 14:31:48 +00:00
committed by Android (Google) Code Review
6 changed files with 267 additions and 6 deletions

View File

@@ -0,0 +1,98 @@
/*
* 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;
}
}
}

View File

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

View File

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

View File

@@ -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;
@@ -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;
@@ -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);
}
@@ -4738,7 +4746,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
mContext.getText(R.string.heavy_weight_notification_detail))
.setContentIntent(PendingIntent.getActivityAsUser(mContext, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT
| PendingIntent.FLAG_IMMUTABLE, null,
| PendingIntent.FLAG_IMMUTABLE, null,
new UserHandle(userId)))
.build();
try {
@@ -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);
}
}
}
}

View File

@@ -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;
@@ -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;
@@ -64,7 +66,7 @@ import org.mockito.MockitoAnnotations;
* Unit tests for {@link ActivityStartInterceptorTest}.
*
* Build/Install/Run:
* atest WmTests:ActivityStartInterceptorTest
* atest WmTests:ActivityStartInterceptorTest
*/
@SmallTest
@Presubmit
@@ -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);
@@ -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;
@@ -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());
}
}

View File

@@ -26,6 +26,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
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.ActivityRecord.State.PAUSED;
import static com.android.server.wm.ActivityRecord.State.PAUSING;
import static com.android.server.wm.ActivityRecord.State.RESUMED;
@@ -42,12 +44,14 @@ import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.when;
import android.annotation.Nullable;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.IApplicationThread;
import android.app.PictureInPictureParams;
import android.app.servertransaction.ClientTransaction;
import android.app.servertransaction.EnterPipRequestedItem;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.res.Configuration;
@@ -846,7 +850,64 @@ public class ActivityTaskManagerServiceTests extends WindowTestsBase {
return wpc;
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_IndexTooSmall() {
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID - 1,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_IndexTooLarge() {
mAtm.mInternal.registerActivityStartInterceptor(LAST_ORDERED_ID + 1,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test(expected = IllegalArgumentException.class)
public void testRegisterActivityStartInterceptor_DuplicateId() {
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
}
@Test
public void testRegisterActivityStartInterceptor() {
assertEquals(0, mAtm.getActivityInterceptorCallbacks().size());
mAtm.mInternal.registerActivityStartInterceptor(FIRST_ORDERED_ID,
new ActivityInterceptorCallback() {
@Nullable
@Override
public Intent intercept(ActivityInterceptorInfo info) {
return null;
}
});
assertEquals(1, mAtm.getActivityInterceptorCallbacks().size());
assertTrue(mAtm.getActivityInterceptorCallbacks().contains(FIRST_ORDERED_ID));
}
}