/* * 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.NotificationManager.IMPORTANCE_MIN; import static android.app.PendingIntent.FLAG_CANCEL_CURRENT; import static android.content.Context.NOTIFICATION_SERVICE; import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.provider.Settings.ACTION_MANAGE_OVERLAY_PERMISSION; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; 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.Drawable; import android.net.Uri; import com.android.internal.R; import com.android.server.policy.IconUtilities; /** 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 static NotificationChannelGroup sChannelGroup; private final int mRequestCode; private final WindowManagerService mService; private String mNotificationTag; private final NotificationManager mNotificationManager; private final String mPackageName; private boolean mPosted; private IconUtilities mIconUtilities; AlertWindowNotification(WindowManagerService service, String packageName) { mService = service; mPackageName = packageName; mNotificationManager = (NotificationManager) mService.mContext.getSystemService(NOTIFICATION_SERVICE); mNotificationTag = CHANNEL_PREFIX + mPackageName; mRequestCode = sNextRequestCode++; mIconUtilities = new IconUtilities(mService.mContext); } void post() { // 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::onPostNotification); } /** Cancels the notification */ void cancel() { // We can't call into NotificationManager with WM lock held since it might call into AM. // So, we post a message to do it later. mService.mH.post(this::onCancelNotification); } /** Don't call with the window manager lock held! */ private void onCancelNotification() { if (!mPosted) { // Notification isn't currently posted... return; } mPosted = false; mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID); } /** Don't call with the window manager lock held! */ private void onPostNotification() { if (mPosted) { // Notification already posted... return; } mPosted = true; 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; createNotificationChannel(context, appName); final String message = context.getString(R.string.alert_windows_notification_message, appName); 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) .setContentIntent(getContentIntent(context, mPackageName)); if (aInfo != null) { final Drawable drawable = pm.getApplicationIcon(aInfo); if (drawable != null) { final Bitmap bitmap = mIconUtilities.createIconBitmap(drawable); builder.setLargeIcon(bitmap); } } mNotificationManager.notify(mNotificationTag, NOTIFICATION_ID, builder.build()); } private PendingIntent getContentIntent(Context context, String packageName) { final Intent intent = new Intent(ACTION_MANAGE_OVERLAY_PERMISSION, Uri.fromParts("package", packageName, null)); intent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK); // Calls into activity manager... return PendingIntent.getActivity(context, mRequestCode, intent, FLAG_CANCEL_CURRENT); } private void createNotificationChannel(Context context, String appName) { if (sChannelGroup == null) { sChannelGroup = new NotificationChannelGroup(CHANNEL_PREFIX, mService.mContext.getString( R.string.alert_windows_notification_channel_group_name)); mNotificationManager.createNotificationChannelGroup(sChannelGroup); } 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); channel.setBlockableSystem(true); channel.setGroup(sChannelGroup.getId()); mNotificationManager.createNotificationChannel(channel); } private ApplicationInfo getApplicationInfo(PackageManager pm, String packageName) { try { return pm.getApplicationInfo(packageName, 0); } catch (PackageManager.NameNotFoundException e) { return null; } } }