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 new file mode 100644 index 0000000000000..bb9462f5b0341 --- /dev/null +++ b/services/core/java/com/android/server/wm/ActivityInterceptorCallbackRegistry.java @@ -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. + * + *
Only system services which are part of mainline modules should call this function. + * + *
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();
+ }
+}
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/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
index ec486437734b5..c63bd52900e8d 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java
@@ -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();
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 08c85748cb675..c7aeecba11532 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -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