diff --git a/config/preloaded-classes b/config/preloaded-classes index 0c440e810ea8..c6ec37690d88 100644 --- a/config/preloaded-classes +++ b/config/preloaded-classes @@ -400,6 +400,9 @@ android.app.INotificationManager android.app.IProcessObserver$Stub$Proxy android.app.IProcessObserver$Stub android.app.IProcessObserver +android.app.IRequestFinishCallback$Stub$Proxy +android.app.IRequestFinishCallback$Stub +android.app.IRequestFinishCallback android.app.ISearchManager$Stub$Proxy android.app.ISearchManager$Stub android.app.ISearchManager diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 992d054737b9..a73fe71cfe1a 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -156,6 +156,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -3811,6 +3812,22 @@ public class Activity extends ContextThemeWrapper return false; } + private static final class RequestFinishCallback extends IRequestFinishCallback.Stub { + private final WeakReference mActivityRef; + + RequestFinishCallback(WeakReference activityRef) { + mActivityRef = activityRef; + } + + @Override + public void requestFinish() { + Activity activity = mActivityRef.get(); + if (activity != null) { + activity.mHandler.post(activity::finishAfterTransition); + } + } + } + /** * Called when the activity has detected the user's press of the back * key. The default implementation simply finishes the current activity, @@ -3834,7 +3851,8 @@ public class Activity extends ContextThemeWrapper // Inform activity task manager that the activity received a back press while at the // root of the task. This call allows ActivityTaskManager to intercept or move the task // to the back. - ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken); + ActivityClient.getInstance().onBackPressedOnTaskRoot(mToken, + new RequestFinishCallback(new WeakReference<>(this))); // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must // be restored now. diff --git a/core/java/android/app/ActivityClient.java b/core/java/android/app/ActivityClient.java index e3b5e9a32324..fbabfac706e1 100644 --- a/core/java/android/app/ActivityClient.java +++ b/core/java/android/app/ActivityClient.java @@ -480,9 +480,9 @@ public class ActivityClient { } } - void onBackPressedOnTaskRoot(IBinder token) { + void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) { try { - getActivityClientController().onBackPressedOnTaskRoot(token); + getActivityClientController().onBackPressedOnTaskRoot(token, callback); } catch (RemoteException e) { e.rethrowFromSystemServer(); } diff --git a/core/java/android/app/IActivityClientController.aidl b/core/java/android/app/IActivityClientController.aidl index bb743b89e00f..573931ed228e 100644 --- a/core/java/android/app/IActivityClientController.aidl +++ b/core/java/android/app/IActivityClientController.aidl @@ -17,6 +17,7 @@ package android.app; import android.app.ActivityManager; +import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; import android.content.ComponentName; import android.content.Intent; @@ -141,7 +142,8 @@ interface IActivityClientController { * Reports that an Activity received a back key press when there were no additional activities * on the back stack. */ - oneway void onBackPressedOnTaskRoot(in IBinder token); + oneway void onBackPressedOnTaskRoot(in IBinder activityToken, + in IRequestFinishCallback callback); /** Reports that the splash screen view has attached to activity. */ oneway void splashScreenAttached(in IBinder token); diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl new file mode 100644 index 000000000000..22c20c840e99 --- /dev/null +++ b/core/java/android/app/IRequestFinishCallback.aidl @@ -0,0 +1,27 @@ +/* + * 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 android.app; + +/** + * This callback allows ActivityTaskManager to ask the calling Activity + * to finish in response to a call to onBackPressedOnTaskRoot. + * + * {@hide} + */ +oneway interface IRequestFinishCallback { + void requestFinish(); +} diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index c63a0f093dea..904bbe83e020 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -43,6 +43,7 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityTaskManager; import android.app.IActivityClientController; +import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; @@ -50,6 +51,8 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; @@ -1124,24 +1127,78 @@ class ActivityClientController extends IActivityClientController.Stub { } @Override - public void onBackPressedOnTaskRoot(IBinder token) { + public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) { final long origId = Binder.clearCallingIdentity(); try { + final Intent baseActivityIntent; + final boolean launchedFromHome; + synchronized (mGlobalLock) { final ActivityRecord r = ActivityRecord.isInRootTaskLocked(token); - if (r == null) { - return; - } + if (r == null) return; + if (mService.mWindowOrganizerController.mTaskOrganizerController .handleInterceptBackPressedOnTaskRoot(r.getRootTask())) { // This task is handled by a task organizer that has requested the back pressed // callback. - } else { - moveActivityTaskToBack(token, false /* nonRoot */); + return; } + + final Intent baseIntent = r.getTask().getBaseIntent(); + final boolean activityIsBaseActivity = baseIntent != null + && r.mActivityComponent.equals(baseIntent.getComponent()); + baseActivityIntent = activityIsBaseActivity ? r.intent : null; + launchedFromHome = r.launchedFromHomeProcess; + } + + // If the activity is one of the main entry points for the application, then we should + // refrain from finishing the activity and instead move it to the back to keep it in + // memory. The requirements for this are: + // 1. The current activity is the base activity for the task. + // 2. a. If the activity was launched by the home process, we trust that its intent + // was resolved, so we check if the it is a main intent for the application. + // b. Otherwise, we query Package Manager to verify whether the activity is a + // launcher activity for the application. + if (baseActivityIntent != null + && ((launchedFromHome && ActivityRecord.isMainIntent(baseActivityIntent)) + || isLauncherActivity(baseActivityIntent.getComponent()))) { + moveActivityTaskToBack(token, false /* nonRoot */); + return; + } + + // The default option for handling the back button is to finish the Activity. + try { + callback.requestFinish(); + } catch (RemoteException e) { + Slog.e(TAG, "Failed to invoke request finish callback", e); } } finally { Binder.restoreCallingIdentity(origId); } } + + /** + * Queries PackageManager to see if the given activity is one of the main entry point for the + * application. This should not be called with the WM lock held. + */ + @SuppressWarnings("unchecked") + private boolean isLauncherActivity(@NonNull ComponentName activity) { + final Intent queryIntent = new Intent(Intent.ACTION_MAIN); + queryIntent.addCategory(Intent.CATEGORY_LAUNCHER); + queryIntent.setPackage(activity.getPackageName()); + try { + final ParceledListSlice resolved = + mService.getPackageManager().queryIntentActivities( + queryIntent, null, 0, mContext.getUserId()); + if (resolved == null) return false; + for (final ResolveInfo ri : resolved.getList()) { + if (ri.getComponentInfo().getComponentName().equals(activity)) { + return true; + } + } + } catch (RemoteException e) { + Slog.e(TAG, "Failed to query intent activities", e); + } + return false; + } } diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 9bf6df41a93b..8c3722dbbe76 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -436,6 +436,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A final int launchedFromUid; // always the uid who started the activity. final String launchedFromPackage; // always the package who started the activity. final @Nullable String launchedFromFeatureId; // always the feature in launchedFromPackage + final boolean launchedFromHomeProcess; // as per original caller final Intent intent; // the original intent that generated us final String shortComponentName; // the short component name of the intent final String resolvedType; // as per original caller; @@ -1667,6 +1668,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A launchedFromUid = _launchedFromUid; launchedFromPackage = _launchedFromPackage; launchedFromFeatureId = _launchedFromFeature; + launchedFromHomeProcess = _caller != null && _caller.isHomeProcess(); shortComponentName = _intent.getComponent().flattenToShortString(); resolvedType = _resolvedType; componentSpecified = _componentSpecified; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java index 9c1614393554..2c2c09a5750a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java @@ -61,6 +61,7 @@ import static org.mockito.Mockito.clearInvocations; import android.app.ActivityManager; import android.app.ActivityManager.RunningTaskInfo; import android.app.ActivityTaskManager.RootTaskInfo; +import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; import android.content.pm.ActivityInfo; import android.content.pm.ParceledListSlice; @@ -976,7 +977,8 @@ public class WindowOrganizerTests extends WindowTestsBase { assertTrue(stack2.isOrganized()); // Verify a back pressed does not call the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token); + mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, never()).onBackPressedOnTaskRoot(any()); @@ -986,7 +988,8 @@ public class WindowOrganizerTests extends WindowTestsBase { stack.mRemoteToken.toWindowContainerToken(), true); // Verify now that the back press does call the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token); + mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(1)).onBackPressedOnTaskRoot(any()); @@ -996,7 +999,8 @@ public class WindowOrganizerTests extends WindowTestsBase { stack.mRemoteToken.toWindowContainerToken(), false); // Verify now that the back press no longer calls the organizer - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token); + mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(activity.token, + new IRequestFinishCallback.Default()); // Ensure events dispatch to organizer. mWm.mAtmService.mTaskOrganizerController.dispatchPendingEvents(); verify(organizer, times(1)).onBackPressedOnTaskRoot(any()); @@ -1201,7 +1205,8 @@ public class WindowOrganizerTests extends WindowTestsBase { mWm.mWindowPlacerLocked.deferLayout(); stack.removeImmediately(); - mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token); + mWm.mAtmService.mActivityClientController.onBackPressedOnTaskRoot(record.token, + new IRequestFinishCallback.Default()); waitUntilHandlersIdle(); ArrayList pendingEvents = getTaskPendingEvent(stack);