Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5c971042 authored by Lucas Silva's avatar Lucas Silva
Browse files

Add internal WM API for registering activity interceptors.

Each callback will map to a unique id. The id is stored in an enum, and we use the ordering of the enum to determine the order the callbacks should run in.

Test: locally on device
Test: atest WmTests:ActivityStartInterceptorTest
Test: atest WmTests:ActivityTaskManagerServiceTests
Bug: 191994709
Bug: 191996331
Bug: 200324021
Change-Id: I34a1e88852b51444fff751b25e138a44f45e1773
parent a4717281
Loading
Loading
Loading
Loading
+98 −0
Original line number Diff line number Diff line
/*
 * 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;
        }
    }
}
+28 −1
Original line number Diff line number Diff line
@@ -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() {
+8 −0
Original line number Diff line number Diff line
@@ -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);
}
+26 −1
Original line number Diff line number Diff line
@@ -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);
    }
@@ -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);
            }
        }
    }
}
+43 −1
Original line number Diff line number Diff line
@@ -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;

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