summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--config/preloaded-classes3
-rw-r--r--core/java/android/app/Activity.java20
-rw-r--r--core/java/android/app/ActivityClient.java4
-rw-r--r--core/java/android/app/IActivityClientController.aidl4
-rw-r--r--core/java/android/app/IRequestFinishCallback.aidl27
-rw-r--r--services/core/java/com/android/server/wm/ActivityClientController.java69
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java2
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java13
8 files changed, 128 insertions, 14 deletions
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<Activity> mActivityRef;
+
+ RequestFinishCallback(WeakReference<Activity> 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<ResolveInfo> 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<PendingTaskEvent> pendingEvents = getTaskPendingEvent(stack);