From 387e4c61338ddf7aee89d10c8aac329e9562b831 Mon Sep 17 00:00:00 2001 From: Wale Ogunwale Date: Mon, 13 Feb 2017 09:50:02 -0800 Subject: [PATCH] Display on-going notification for apps using alert windows. Allows the user to associate alert windows with specific apps and revoke the permission if they want. Test: manual Bug: 33256752 Change-Id: Ie28325b6bb799b3df253770ebe655f97ebbadd90 --- core/res/AndroidManifest.xml | 1 + .../res/drawable-nodpi/alert_window_layer.xml | 24 +++ core/res/res/values/strings.xml | 15 ++ core/res/res/values/symbols.xml | 8 + .../server/wm/AlertWindowNotification.java | 144 ++++++++++++++++++ .../java/com/android/server/wm/Session.java | 24 ++- .../server/wm/WindowManagerService.java | 47 ++++-- .../com/android/server/wm/WindowState.java | 2 +- 8 files changed, 248 insertions(+), 17 deletions(-) create mode 100644 core/res/res/drawable-nodpi/alert_window_layer.xml create mode 100644 services/core/java/com/android/server/wm/AlertWindowNotification.java diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 97fbfa5a55b67..2f4b74e137954 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -520,6 +520,7 @@ + diff --git a/core/res/res/drawable-nodpi/alert_window_layer.xml b/core/res/res/drawable-nodpi/alert_window_layer.xml new file mode 100644 index 0000000000000..f9b38c8e57c45 --- /dev/null +++ b/core/res/res/drawable-nodpi/alert_window_layer.xml @@ -0,0 +1,24 @@ + + + + diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 8faa76cda8084..7069d16c0a323 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -3116,6 +3116,21 @@ \u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ \u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ + + + + %s draw over other apps + + %s app displaying on top. + + Parts of this app may remain visible at all times. If this feature isn\'t working correctly, turn it off. + + TURN OFF + + diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 0d63a1e484206..a4605c3fa1602 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2867,4 +2867,12 @@ + + + + + + + + diff --git a/services/core/java/com/android/server/wm/AlertWindowNotification.java b/services/core/java/com/android/server/wm/AlertWindowNotification.java new file mode 100644 index 0000000000000..0d282ef3f8d60 --- /dev/null +++ b/services/core/java/com/android/server/wm/AlertWindowNotification.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2017 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 static android.app.Notification.VISIBILITY_PRIVATE; +import static android.app.NotificationManager.IMPORTANCE_MIN; +import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; +import static android.content.Context.NOTIFICATION_SERVICE; +import static android.content.Intent.EXTRA_PACKAGE_NAME; +import static android.content.Intent.EXTRA_UID; +import static com.android.server.wm.WindowManagerService.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.Context; + +import android.content.Intent; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import com.android.internal.R; + +/** Displays an ongoing notification for a process displaying an alert window */ +class AlertWindowNotification { + private static final String CHANNEL_PREFIX = "com.android.server.wm.AlertWindowNotification - "; + private static final int NOTIFICATION_ID = 0; + + private static int sNextRequestCode = 0; + private final int mRequestCode; + private final WindowManagerService mService; + private String mNotificationTag; + private final NotificationManager mNotificationManager; + private final String mPackageName; + private final int mUid; + private boolean mCancelled; + + AlertWindowNotification(WindowManagerService service, String packageName, int uid) { + mService = service; + mPackageName = packageName; + mUid = uid; + mNotificationManager = + (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE); + mNotificationTag = CHANNEL_PREFIX + mPackageName; + mRequestCode = sNextRequestCode++; + + // We can't create/post the notification while the window manager lock is held since it will + // end up calling into activity manager. So, we post a message to do it later. + mService.mH.post(this::postNotification); + } + + /** Cancels the notification */ + void cancel() { + mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID); + mCancelled = true; + } + + /** Don't call with the window manager lock held! */ + private void postNotification() { + final Context context = mService.mContext; + final PackageManager pm = context.getPackageManager(); + final ApplicationInfo aInfo = getApplicationInfo(pm, mPackageName); + final String appName = (aInfo != null) + ? pm.getApplicationLabel(aInfo).toString() : mPackageName; + + createNotificationChannelIfNeeded(context, appName); + + final String message = context.getString(R.string.alert_windows_notification_message); + final Notification.Builder builder = new Notification.Builder(context, mNotificationTag) + .setOngoing(true) + .setContentTitle( + context.getString(R.string.alert_windows_notification_title, appName)) + .setContentText(message) + .setSmallIcon(R.drawable.alert_window_layer) + .setColor(context.getColor(R.color.system_notification_accent_color)) + .setStyle(new Notification.BigTextStyle().bigText(message)) + .setLocalOnly(true) + .addAction(getTurnOffAction(context, mPackageName, mUid)); + + if (aInfo != null) { + final Bitmap bitmap = ((BitmapDrawable) pm.getApplicationIcon(aInfo)).getBitmap(); + builder.setLargeIcon(bitmap); + } + + synchronized (mService.mWindowMap) { + if (mCancelled) { + // Notification was cancelled, so nothing more to do... + return; + } + mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build()); + } + } + + private Notification.Action getTurnOffAction(Context context, String packageName, int uid) { + final Intent intent = new Intent(ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION); + intent.putExtra(EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(EXTRA_UID, uid); + // Calls into activity manager... + final PendingIntent pendingIntent = PendingIntent.getBroadcast(context, mRequestCode, + intent, FLAG_CANCEL_CURRENT); + return new Notification.Action.Builder(R.drawable.alert_window_layer, + context.getString(R.string.alert_windows_notification_turn_off_action), + pendingIntent).build(); + + } + + private void createNotificationChannelIfNeeded(Context context, String appName) { + if (mNotificationManager.getNotificationChannel(mNotificationTag) != null) { + return; + } + final String nameChannel = + context.getString(R.string.alert_windows_notification_channel_name, appName); + final NotificationChannel channel = + new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN); + channel.enableLights(false); + channel.enableVibration(false); + mNotificationManager.createNotificationChannel(channel); + } + + + private ApplicationInfo getApplicationInfo(PackageManager pm, String packageName) { + try { + return pm.getApplicationInfo(packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + } +} diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index e6fd0abf21727..5355f3159c866 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -80,8 +80,10 @@ public class Session extends IWindowSession.Stub // Set of visible alert window surfaces connected to this session. private final Set mAlertWindowSurfaces = new HashSet<>(); final boolean mCanAddInternalSystemWindow; + private AlertWindowNotification mAlertWindowNotification; private boolean mClientDead = false; private float mLastReportedAnimatorScale; + private String mPackageName; public Session(WindowManagerService service, IWindowSessionCallback callback, IInputMethodClient client, IInputContext inputContext) { @@ -555,7 +557,8 @@ public class Session extends IWindowSession.Stub } } - void windowAddedLocked() { + void windowAddedLocked(String packageName) { + mPackageName = packageName; if (mSurfaceSession == null) { if (WindowManagerService.localLOGV) Slog.v( TAG_WM, "First window added to " + this + ", creating SurfaceSession"); @@ -595,7 +598,12 @@ public class Session extends IWindowSession.Stub } if (changed) { - // TODO: Update notification. + if (mAlertWindowSurfaces.isEmpty()) { + cancelAlertWindowNotification(); + } else if (mAlertWindowNotification == null){ + mAlertWindowNotification = new AlertWindowNotification( + mService, mPackageName, mUid); + } } } @@ -636,14 +644,24 @@ public class Session extends IWindowSession.Stub + " in session " + this + ": " + e.toString()); } mSurfaceSession = null; + mAlertWindowSurfaces.clear(); + mAppOverlaySurfaces.clear(); setHasOverlayUi(false); - // TODO: Update notification + cancelAlertWindowNotification(); } private void setHasOverlayUi(boolean hasOverlayUi) { mService.mH.obtainMessage(H.SET_HAS_OVERLAY_UI, mPid, hasOverlayUi ? 1 : 0).sendToTarget(); } + private void cancelAlertWindowNotification() { + if (mAlertWindowNotification == null) { + return; + } + mAlertWindowNotification.cancel(); + mAlertWindowNotification = null; + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("mNumWindow="); pw.print(mNumWindow); pw.print(" mAppOverlaySurfaces="); pw.print(mAppOverlaySurfaces); diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 1df9e433bb1ed..02c52a7bf21cc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -21,8 +21,13 @@ import static android.Manifest.permission.MANAGE_APP_TOKENS; import static android.Manifest.permission.REGISTER_WINDOW_MANAGER_LISTENERS; import static android.app.ActivityManager.DOCKED_STACK_CREATE_MODE_TOP_OR_LEFT; import static android.app.ActivityManager.StackId.PINNED_STACK_ID; +import static android.app.AppOpsManager.MODE_IGNORED; +import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW; import static android.app.StatusBarManager.DISABLE_MASK; +import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED; import static android.content.Intent.ACTION_USER_REMOVED; +import static android.content.Intent.EXTRA_PACKAGE_NAME; +import static android.content.Intent.EXTRA_UID; import static android.content.Intent.EXTRA_USER_HANDLE; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; @@ -66,6 +71,7 @@ import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_END; import static com.android.server.wm.AppWindowAnimator.PROLONG_ANIMATION_AT_START; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_DOCKED_DIVIDER; import static com.android.server.wm.DragResizeMode.DRAG_RESIZE_MODE_FREEFORM; +import static com.android.server.wm.KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ADD_REMOVE; import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_ANIM; @@ -343,20 +349,34 @@ public class WindowManagerService extends IWindowManager.Stub final private KeyguardDisableHandler mKeyguardDisableHandler; - final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + static final String ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION = + "com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION"; + + private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - final String action = intent.getAction(); - if (DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED.equals(action)) { - mKeyguardDisableHandler.sendEmptyMessage( - KeyguardDisableHandler.KEYGUARD_POLICY_CHANGED); - } else if (ACTION_USER_REMOVED.equals(action)) { - final int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL); - if (userId != USER_NULL) { - synchronized (mWindowMap) { - mScreenCaptureDisabled.remove(userId); + switch (intent.getAction()) { + case ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED: + mKeyguardDisableHandler.sendEmptyMessage(KEYGUARD_POLICY_CHANGED); + break; + case ACTION_USER_REMOVED: + final int userId = intent.getIntExtra(EXTRA_USER_HANDLE, USER_NULL); + if (userId != USER_NULL) { + synchronized (mWindowMap) { + mScreenCaptureDisabled.remove(userId); + } } - } + break; + case ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION: + final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); + final int uid = intent.getIntExtra(EXTRA_UID, -1); + if (packageName != null && uid != -1) { + synchronized (mWindowMap) { + // Revoke permission. + mAppOps.setMode(OP_SYSTEM_ALERT_WINDOW, uid, packageName, MODE_IGNORED); + } + } + break; } } }; @@ -1018,7 +1038,7 @@ public class WindowManagerService extends IWindowManager.Stub updateAppOpsState(); } }; - mAppOps.startWatchingMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, null, opListener); + mAppOps.startWatchingMode(OP_SYSTEM_ALERT_WINDOW, null, opListener); mAppOps.startWatchingMode(AppOpsManager.OP_TOAST_WINDOW, null, opListener); // Get persisted window scale setting @@ -1034,9 +1054,10 @@ public class WindowManagerService extends IWindowManager.Stub IntentFilter filter = new IntentFilter(); // Track changes to DevicePolicyManager state so we can enable/disable keyguard. - filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); + filter.addAction(ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); // Listen to user removal broadcasts so that we can remove the user-specific data. filter.addAction(Intent.ACTION_USER_REMOVED); + filter.addAction(ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION); mContext.registerReceiver(mBroadcastReceiver, filter); mSettingsObserver = new SettingsObserver(); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 867080ed2450a..0337ba4813598 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -665,7 +665,7 @@ class WindowState extends WindowContainer implements WindowManagerP void attach() { if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken); - mSession.windowAddedLocked(); + mSession.windowAddedLocked(mAttrs.packageName); } @Override