Adds OnBackPressedOnTaskRoot

This allows Bubbles to be collapsed instead of finished
when there is only one activity in the stack.

Bug: 126852149
Test: launch activity, press back
Change-Id: Iad8db0549853e3f385d54fc6b6cea5e502d37139
diff --git a/Android.bp b/Android.bp
index abf95a8..2ccddd2 100644
--- a/Android.bp
+++ b/Android.bp
@@ -73,6 +73,7 @@
         "core/java/android/app/IInstrumentationWatcher.aidl",
         "core/java/android/app/INotificationManager.aidl",
         "core/java/android/app/IProcessObserver.aidl",
+        "core/java/android/app/IRequestFinishCallback.aidl",
         "core/java/android/app/ISearchManager.aidl",
         "core/java/android/app/ISearchManagerCallback.aidl",
         "core/java/android/app/IServiceConnection.aidl",
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2914f6c..c08ed26 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -3666,7 +3666,25 @@
 
         FragmentManager fragmentManager = mFragments.getFragmentManager();
 
-        if (fragmentManager.isStateSaved() || !fragmentManager.popBackStackImmediate()) {
+        if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
+            return;
+        }
+        if (!isTaskRoot()) {
+            // If the activity is not the root of the task, allow finish to proceed normally.
+            finishAfterTransition();
+            return;
+        }
+        try {
+            // 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 defer finishing.
+            ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
+                    new IRequestFinishCallback.Stub() {
+                        public void requestFinish() {
+                            finishAfterTransition();
+                        }
+                    });
+        } catch (RemoteException e) {
             finishAfterTransition();
         }
     }
diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl
index 7953d42..26720fc 100644
--- a/core/java/android/app/IActivityTaskManager.aidl
+++ b/core/java/android/app/IActivityTaskManager.aidl
@@ -26,6 +26,7 @@
 import android.app.IAssistDataReceiver;
 import android.app.IInstrumentationWatcher;
 import android.app.IProcessObserver;
+import android.app.IRequestFinishCallback;
 import android.app.IServiceConnection;
 import android.app.IStopUserCallback;
 import android.app.ITaskStackListener;
@@ -484,4 +485,12 @@
      * @param activityToken The token of the target activity to restart.
      */
     void restartActivityProcessIfVisible(in IBinder activityToken);
+
+    /**
+     * Reports that an Activity received a back key press when there were no additional activities
+     * on the back stack. If the Activity should be finished, the callback will be invoked. A
+     * callback is used instead of finishing the activity directly from the server such that the
+     * client may perform actions prior to finishing.
+     */
+    void onBackPressedOnTaskRoot(in IBinder activityToken, in IRequestFinishCallback callback);
 }
diff --git a/core/java/android/app/IRequestFinishCallback.aidl b/core/java/android/app/IRequestFinishCallback.aidl
new file mode 100644
index 0000000..3270565
--- /dev/null
+++ b/core/java/android/app/IRequestFinishCallback.aidl
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2019 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/core/java/android/app/ITaskStackListener.aidl b/core/java/android/app/ITaskStackListener.aidl
index 841ff6a..1fdc8ca5 100644
--- a/core/java/android/app/ITaskStackListener.aidl
+++ b/core/java/android/app/ITaskStackListener.aidl
@@ -161,4 +161,12 @@
      * @see com.android.server.wm.AppWindowToken#inSizeCompatMode
      */
     void onSizeCompatModeActivityChanged(int displayId, in IBinder activityToken);
+
+    /**
+     * Reports that an Activity received a back key press when there were no additional activities
+     * on the back stack.
+     *
+     * @param taskInfo info about the task which received the back press
+     */
+    void onBackPressedOnTaskRoot(in ActivityManager.RunningTaskInfo taskInfo);
 }
diff --git a/core/java/android/app/TaskStackListener.java b/core/java/android/app/TaskStackListener.java
index a4a97c4..00f3ad5 100644
--- a/core/java/android/app/TaskStackListener.java
+++ b/core/java/android/app/TaskStackListener.java
@@ -168,4 +168,9 @@
     public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken)
             throws RemoteException {
     }
+
+    @Override
+    public void onBackPressedOnTaskRoot(ActivityManager.RunningTaskInfo taskInfo)
+            throws RemoteException {
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
index f2a961d..21b3a00 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListener.java
@@ -76,6 +76,8 @@
     public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation) { }
     public void onSizeCompatModeActivityChanged(int displayId, IBinder activityToken) { }
 
+    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) { }
+
     /**
      * Checks that the current user matches the process. Since
      * {@link android.app.ITaskStackListener} is not multi-user aware, handlers of
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
index d250acc..814db19 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/TaskStackChangeListeners.java
@@ -184,6 +184,11 @@
     }
 
     @Override
+    public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) throws RemoteException {
+        mHandler.obtainMessage(H.ON_BACK_PRESSED_ON_TASK_ROOT, taskInfo).sendToTarget();
+    }
+
+    @Override
     public void onActivityRequestedOrientationChanged(int taskId, int requestedOrientation)
             throws RemoteException {
         mHandler.obtainMessage(H.ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE, taskId,
@@ -214,6 +219,7 @@
         private static final int ON_ACTIVITY_REQUESTED_ORIENTATION_CHANGE = 15;
         private static final int ON_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED = 16;
         private static final int ON_SIZE_COMPAT_MODE_ACTIVITY_CHANGED = 17;
+        private static final int ON_BACK_PRESSED_ON_TASK_ROOT = 18;
 
 
         public H(Looper looper) {
@@ -343,6 +349,12 @@
                         }
                         break;
                     }
+                    case ON_BACK_PRESSED_ON_TASK_ROOT: {
+                        for (int i = mTaskStackListeners.size() - 1; i >= 0; i--) {
+                            mTaskStackListeners.get(i).onBackPressedOnTaskRoot(
+                                    (RunningTaskInfo) msg.obj);
+                        }
+                    }
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
index cff03c9..7d189b2 100644
--- a/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
+++ b/packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java
@@ -719,6 +719,13 @@
                 mBubbleData.setExpanded(false);
             }
         }
+
+        @Override
+        public void onBackPressedOnTaskRoot(RunningTaskInfo taskInfo) {
+            if (mStackView != null && taskInfo.displayId == getExpandedDisplayId(mContext)) {
+                mBubbleData.setExpanded(false);
+            }
+        }
     }
 
     private static boolean shouldAutoBubbleMessages(Context context) {
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4a6aa33..b234bc6 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -141,6 +141,7 @@
 import android.app.IApplicationThread;
 import android.app.IAssistDataReceiver;
 import android.app.INotificationManager;
+import android.app.IRequestFinishCallback;
 import android.app.ITaskStackListener;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -2288,6 +2289,32 @@
         }
     }
 
+    @Override
+    public void onBackPressedOnTaskRoot(IBinder token, IRequestFinishCallback callback) {
+        synchronized (mGlobalLock) {
+            ActivityRecord r = ActivityRecord.isInStackLocked(token);
+            if (r == null) {
+                return;
+            }
+            ActivityStack stack = r.getActivityStack();
+            if (stack != null && stack.isSingleTaskInstance()) {
+                // Single-task stacks are used for activities which are presented in floating
+                // windows above full screen activities. Instead of directly finishing the
+                // task, a task change listener is used to notify SystemUI so the action can be
+                // handled specially.
+                final TaskRecord task = r.getTaskRecord();
+                mTaskChangeNotificationController
+                        .notifyBackPressedOnTaskRoot(task.getTaskInfo());
+            } else {
+                try {
+                    callback.requestFinish();
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Failed to invoke request finish callback", e);
+                }
+            }
+        }
+    }
+
     /**
      * TODO: Add mController hook
      */
diff --git a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
index 3d57219..66200e3 100644
--- a/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
+++ b/services/core/java/com/android/server/wm/TaskChangeNotificationController.java
@@ -53,6 +53,7 @@
     private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_FAILED_MSG = 18;
     private static final int NOTIFY_ACTIVITY_LAUNCH_ON_SECONDARY_DISPLAY_REROUTED_MSG = 19;
     private static final int NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG = 20;
+    private static final int NOTIFY_BACK_PRESSED_ON_TASK_ROOT = 21;
 
     // Delay in notifying task stack change listeners (in millis)
     private static final int NOTIFY_TASK_STACK_CHANGE_LISTENERS_DELAY = 100;
@@ -92,6 +93,10 @@
         l.onTaskDescriptionChanged((RunningTaskInfo) m.obj);
     };
 
+    private final TaskStackConsumer mNotifyBackPressedOnTaskRoot = (l, m) -> {
+        l.onBackPressedOnTaskRoot((RunningTaskInfo) m.obj);
+    };
+
     private final TaskStackConsumer mNotifyActivityRequestedOrientationChanged = (l, m) -> {
         l.onActivityRequestedOrientationChanged(m.arg1, m.arg2);
     };
@@ -225,6 +230,9 @@
                 case NOTIFY_SIZE_COMPAT_MODE_ACTIVITY_CHANGED_MSG:
                     forAllRemoteListeners(mOnSizeCompatModeActivityChanged, msg);
                     break;
+                case NOTIFY_BACK_PRESSED_ON_TASK_ROOT:
+                    forAllRemoteListeners(mNotifyBackPressedOnTaskRoot, msg);
+                    break;
             }
         }
     }
@@ -458,4 +466,15 @@
         forAllLocalListeners(mOnSizeCompatModeActivityChanged, msg);
         msg.sendToTarget();
     }
+
+    /**
+     * Notify listeners that an activity received a back press when there are no other activities
+     * in the back stack.
+     */
+    void notifyBackPressedOnTaskRoot(TaskInfo taskInfo) {
+        final Message msg = mHandler.obtainMessage(NOTIFY_BACK_PRESSED_ON_TASK_ROOT,
+                taskInfo);
+        forAllLocalListeners(mNotifyBackPressedOnTaskRoot, msg);
+        msg.sendToTarget();
+    }
 }