diff --git a/services/api/current.txt b/services/api/current.txt index da5b1fcaae99f..b5798d56f0c92 100644 --- a/services/api/current.txt +++ b/services/api/current.txt @@ -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); + } + +} + diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java index c593fa3d91b97..ff1d44293dca8 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallback.java @@ -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} + * + *

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; + } } } diff --git a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java index cb66a39376a2f..bb9462f5b0341 100644 --- a/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.annotation.SystemApi; import android.os.Binder; import android.os.Process; @@ -30,6 +31,7 @@ import com.android.server.LocalServices; * int, ActivityInterceptorCallback)}. * @hide */ +@SystemApi(client = SystemApi.Client.SYSTEM_SERVER) public class ActivityInterceptorCallbackRegistry { private static final ActivityInterceptorCallbackRegistry sInstance = diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java index 84100a7ab02d6..d4d0256d52566 100644 --- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java +++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java @@ -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*/); diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java index b0461c2276dde..7d16fb2e02554 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStartInterceptorTest.java @@ -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);