content: /*
   * 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 android.os.Bundle;
  
  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(boolean deleteChannel) {
          // 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(() -> onCancelNotification(deleteChannel));
      }
  
      /** Don't call with the window manager lock held! */
      private void onCancelNotification(boolean deleteChannel) {
          if (!mPosted) {
              // Notification isn't currently posted...
              return;
          }
          mPosted = false;
          mNotificationManager.cancel(mNotificationTag, NOTIFICATION_ID);
          if (deleteChannel) {
              mNotificationManager.deleteNotificationChannel(mNotificationTag);
          }
      }
  
      /** 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);
          Bundle extras = new Bundle();
          extras.putStringArray(Notification.EXTRA_FOREGROUND_APPS, new String[] {mPackageName});
          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)
                  .addExtras(extras)
                  .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);
  
          NotificationChannel channel = mNotificationManager.getNotificationChannel(mNotificationTag);
          if (channel != null) {
              return;
          }
          channel = new NotificationChannel(mNotificationTag, nameChannel, IMPORTANCE_MIN);
          channel.enableLights(false);
          channel.enableVibration(false);
          channel.setBlockableSystem(true);
          channel.setGroup(sChannelGroup.getId());
          channel.setBypassDnd(true);
          mNotificationManager.createNotificationChannel(channel);
      }
  
  
      private ApplicationInfo getApplicationInfo(PackageManager pm, String packageName) {
          try {
              return pm.getApplicationInfo(packageName, 0);
          } catch (PackageManager.NameNotFoundException e) {
              return null;
          }
      }
  }
  