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:
@@ -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 -->
|
||||
|
||||
24
core/res/res/drawable-nodpi/alert_window_layer.xml
Normal file
24
core/res/res/drawable-nodpi/alert_window_layer.xml
Normal 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>
|
||||
@@ -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 />
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user