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
This commit is contained in:
Wale Ogunwale
2017-02-13 09:50:02 -08:00
parent 943002b473
commit 387e4c6133
8 changed files with 248 additions and 17 deletions

View File

@@ -520,6 +520,7 @@
<!-- TODO: temporary broadcast used by AutoFillManagerServiceImpl; will be removed -->
<protected-broadcast android:name="com.android.internal.autofill.action.REQUEST_AUTOFILL" />
<protected-broadcast android:name="android.app.action.APPLICATION_DELEGATION_SCOPES_CHANGED" />
<protected-broadcast android:name="com.android.server.wm.ACTION_REVOKE_SYSTEM_ALERT_WINDOW_PERMISSION" />
<!-- ====================================================================== -->
<!-- RUNTIME PERMISSIONS -->

View File

@@ -0,0 +1,24 @@
<!--
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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.99,18.54l-7.37,-5.73L3,14.07l9,7 9,-7 -1.63,-1.27 -7.38,5.74zM12,16l7.36,-5.73L21,9l-9,-7 -9,7 1.63,1.27L12,16z"/>
</vector>

View File

@@ -3116,6 +3116,21 @@
<string name="fast_scroll_alphabet">\u0020ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<string name="fast_scroll_numeric_alphabet">\u00200123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ</string>
<!-- Alert windows notification strings -->
<skip />
<!-- Name of notification channel the system post notification to inform the use about apps
that are drawing ui on-top of other apps (alert-windows) [CHAR LIMIT=NONE] -->
<string name="alert_windows_notification_channel_name"><xliff:g id="name" example="Google Maps">%s</xliff:g> draw over other apps</string>
<!-- Notification title when an application is displaying ui on-top of other apps
[CHAR LIMIT=30] -->
<string name="alert_windows_notification_title"><xliff:g id="name" example="Google Maps">%s</xliff:g> app displaying on top.</string>
<!-- Notification body when an application is displaying ui on-top of other apps
[CHAR LIMIT=NONE] -->
<string name="alert_windows_notification_message">Parts of this app may remain visible at all times. If this feature isn\'t working correctly, turn it off.</string>
<!-- Notification action to turn-off app displaying on-top of other apps. [CHAR LIMIT=20] -->
<string name="alert_windows_notification_turn_off_action">TURN OFF</string>
<!-- External media notification strings -->
<skip />

View File

@@ -2867,4 +2867,12 @@
<!-- resolver activity -->
<java-symbol type="drawable" name="resolver_icon_placeholder" />
<!-- Alert windows notification -->
<java-symbol type="string" name="alert_windows_notification_channel_name" />
<java-symbol type="string" name="alert_windows_notification_title" />
<java-symbol type="string" name="alert_windows_notification_message" />
<java-symbol type="string" name="alert_windows_notification_turn_off_action" />
<java-symbol type="drawable" name="alert_window_layer" />
</resources>

View File

@@ -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;
}
}
}

View File

@@ -80,8 +80,10 @@ public class Session extends IWindowSession.Stub
// Set of visible alert window surfaces connected to this session.
private final Set<WindowSurfaceController> 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);

View File

@@ -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();

View File

@@ -665,7 +665,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
void attach() {
if (localLOGV) Slog.v(TAG, "Attaching " + this + " token=" + mToken);
mSession.windowAddedLocked();
mSession.windowAddedLocked(mAttrs.packageName);
}
@Override