Merge "Add internal WM API for registering activity interceptors." into sc-v2-dev
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user