From 2f91fbf991f2f3896ae3f08f04828ed26be4c1d5 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Fri, 8 Jan 2021 17:52:43 +0000 Subject: [PATCH] Window Z rule for closing the notification shade We found a few use-cases where the app was displaying a window on top of the notification shade and wanted to start an activity as a result of user interaction. In order to show the activity to the user, the app closes the notification shade using Intent.ACSD. For example, the accessibility menu (b/119653645) and the IME when replying to a notification inline (b/74180647), which makes it stay above the shade. Since these apps already have the privilege to show windows on top of the shade (because they are doing so), they could already prevent the user from accessing the shade, so we can allow them to close the shade. So, exempting this use-case from Intent.ACSD restrictions for apps w/ targetSdk < S. For targetSdk S+, we don't want apps to be using Intent.ACSD and we judged that the valid use-case to close the shade is tied to the activity start. So, in S, we'll automatically close the shade for apps in that situtation on activity start. Bug: 159105552 Test: CTS coming Test: 1. As an a11y service, display a11y windows and send Intent.ACSD 2. Verify it collapses the shade 3. Deactivate the service and verify Intent.ACSD doesn't close the shade Test: 1. As an a11y service, display a11y windows and start an activity 2. Verify the shade is closed 3. Deactivate the service and verify the shade is not closed when activity is started Change-Id: I783610c1b56739e2926fe9db0c941d40f74121ae --- .../statusbar/StatusBarManagerInternal.java | 3 ++ .../statusbar/StatusBarManagerService.java | 10 ++++++ .../android/server/wm/ActivityStarter.java | 15 +++++++++ .../server/wm/ActivityTaskManagerService.java | 31 ++++++++++++++++--- .../server/wm/RootWindowContainer.java | 22 +++++++++++++ 5 files changed, 77 insertions(+), 4 deletions(-) diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java index ebfffec1774ea..00ab973b9c90e 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java @@ -40,6 +40,9 @@ public interface StatusBarManagerInternal { void hideRecentApps(boolean triggeredFromAltTab, boolean triggeredFromHomeKey); + /** Collapses the notification shade. */ + void collapsePanels(); + void dismissKeyboardShortcutsMenu(); void toggleKeyboardShortcutsMenu(int deviceId); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index bd2d382c8010d..891a683ca9c02 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -378,6 +378,16 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } } + @Override + public void collapsePanels() { + if (mBar != null) { + try { + mBar.animateCollapsePanels(); + } catch (RemoteException ex) { + } + } + } + @Override public void dismissKeyboardShortcutsMenu() { if (mBar != null) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 7a4bcb1b8c37f..20625c83a9246 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -118,6 +118,7 @@ import com.android.internal.protolog.common.ProtoLog; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.InstantAppResolver; import com.android.server.power.ShutdownCheckPoints; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.wm.ActivityMetricsLogger.LaunchingState; import com.android.server.wm.ActivityTaskSupervisor.PendingActivityLaunch; @@ -1589,6 +1590,20 @@ class ActivityStarter { newTransition.abort(); } } else { + if (!mAvoidMoveToFront && mDoResume + && mRootWindowContainer.hasVisibleWindowAboveNotificationShade( + r.launchedFromUid)) { + // If the UID launching the activity has a visible window on top of the + // notification shade and it's launching an activity that's going to be at the + // front, we should move the shade out of the way so the user can see it. + // We want to avoid the case where the activity is launched on top of a + // background task which is not moved to the front. + StatusBarManagerInternal statusBar = mService.getStatusBarManagerInternal(); + if (statusBar != null) { + // This results in a async call since the interface is one-way + statusBar.collapsePanels(); + } + } if (result == START_SUCCESS || result == START_TASK_TO_FRONT) { // The activity is started new rather than just brought forward, so record // it as an existence change. diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 039f22da86d8c..9318103b1e05e 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -28,6 +28,8 @@ import static android.Manifest.permission.READ_FRAME_BUFFER; import static android.Manifest.permission.REMOVE_TASKS; import static android.Manifest.permission.START_TASKS_FROM_RECENTS; import static android.Manifest.permission.STOP_APP_SWITCHES; +import static android.app.ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS; +import static android.app.ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS; import static android.app.ActivityManager.LOCK_TASK_MODE_NONE; import static android.app.ActivityManagerInternal.ALLOW_NON_FULL; import static android.app.ActivityTaskManager.INVALID_TASK_ID; @@ -253,6 +255,7 @@ import com.android.server.firewall.IntentFirewall; import com.android.server.inputmethod.InputMethodSystemProperty; import com.android.server.pm.UserManagerService; import com.android.server.policy.PermissionPolicyInternal; +import com.android.server.statusbar.StatusBarManagerInternal; import com.android.server.uri.NeededUriGrants; import com.android.server.uri.UriGrantsManagerInternal; @@ -342,6 +345,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { /** The cached sys ui service component name from package manager. */ private ComponentName mSysUiServiceComponent; private PermissionPolicyInternal mPermissionPolicyInternal; + private StatusBarManagerInternal mStatusBarManagerInternal; @VisibleForTesting final ActivityTaskManagerInternal mInternal; PowerManagerInternal mPowerManagerInternal; @@ -2927,14 +2931,12 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } if (!canCloseSystemDialogs(pid, uid, process)) { // The app can't close system dialogs, throw only if it targets S+ - if (CompatChanges.isChangeEnabled( - ActivityManager.LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, uid)) { + if (CompatChanges.isChangeEnabled(LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, uid)) { throw new SecurityException( "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS + " broadcast from " + caller + " requires " + Manifest.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS + "."); - } else if (CompatChanges.isChangeEnabled( - ActivityManager.DROP_CLOSE_SYSTEM_DIALOGS, uid)) { + } else if (CompatChanges.isChangeEnabled(DROP_CLOSE_SYSTEM_DIALOGS, uid)) { Slog.e(TAG, "Permission Denial: " + Intent.ACTION_CLOSE_SYSTEM_DIALOGS + " broadcast from " + caller + " requires " @@ -2978,6 +2980,20 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return true; } } + // This covers the case where the app is displaying some UI on top of the notification shade + // and wants to start an activity. The app then sends the intent in order to move the + // notification shade out of the way and show the activity to the user. This is fine since + // the caller already has privilege to show a visible window on top of the notification + // shade, so it can already prevent the user from accessing the shade if it wants to. + // We only allow for targetSdk < S, for S+ we automatically collapse the shade on + // startActivity() for these apps. + if (!CompatChanges.isChangeEnabled(LOCK_DOWN_CLOSE_SYSTEM_DIALOGS, uid)) { + synchronized (mGlobalLock) { + if (mRootWindowContainer.hasVisibleWindowAboveNotificationShade(uid)) { + return true; + } + } + } return false; } @@ -4782,6 +4798,13 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { return mPermissionPolicyInternal; } + StatusBarManagerInternal getStatusBarManagerInternal() { + if (mStatusBarManagerInternal == null) { + mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class); + } + return mStatusBarManagerInternal; + } + AppWarnings getAppWarningsLocked() { return mAppWarnings; } diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java index 54996a66ecd7a..bc92324a68217 100644 --- a/services/core/java/com/android/server/wm/RootWindowContainer.java +++ b/services/core/java/com/android/server/wm/RootWindowContainer.java @@ -36,6 +36,7 @@ import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SUSTAINED_PERFORMANCE_MODE; import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG; +import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_FLAG_APP_CRASHED; import static android.view.WindowManager.TRANSIT_NONE; @@ -3137,6 +3138,27 @@ class RootWindowContainer extends WindowContainer }); } + /** + * Returns {@code true} if {@code uid} has a visible window that's above a window of type {@link + * WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}. If there is no window with type {@link + * WindowManager.LayoutParams#TYPE_NOTIFICATION_SHADE}, it returns {@code false}. + */ + boolean hasVisibleWindowAboveNotificationShade(int uid) { + boolean[] visibleWindowFound = {false}; + // We only return true if we found the notification shade (ie. window of type + // TYPE_NOTIFICATION_SHADE). Usually, it should always be there, but if for some reason + // it isn't, we should better be on the safe side and return false for this. + return forAllWindows(w -> { + if (w.mOwnerUid == uid && w.isVisible()) { + visibleWindowFound[0] = true; + } + if (w.mAttrs.type == TYPE_NOTIFICATION_SHADE) { + return visibleWindowFound[0]; + } + return false; + }, true /* traverseTopToBottom */); + } + private boolean shouldCloseAssistant(ActivityRecord r, String reason) { if (!r.isActivityTypeAssistant()) return false; if (reason == SYSTEM_DIALOG_REASON_ASSIST) return false;