Home | History | Annotate | Download | only in systemui
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
      5  * except in compliance with the License. You may obtain a copy of the License at
      6  *
      7  *      http://www.apache.org/licenses/LICENSE-2.0
      8  *
      9  * Unless required by applicable law or agreed to in writing, software distributed under the
     10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     11  * KIND, either express or implied. See the License for the specific language governing
     12  * permissions and limitations under the License.
     13  */
     14 
     15 package com.android.systemui;
     16 
     17 import android.app.Notification;
     18 import android.app.NotificationManager;
     19 import android.content.Context;
     20 import android.os.Bundle;
     21 import android.service.notification.StatusBarNotification;
     22 import android.util.ArrayMap;
     23 import android.util.ArraySet;
     24 import android.util.Log;
     25 import android.util.SparseArray;
     26 
     27 import com.android.internal.annotations.VisibleForTesting;
     28 import com.android.internal.messages.nano.SystemMessageProto;
     29 
     30 import java.util.Arrays;
     31 
     32 /**
     33  * Foreground service controller, a/k/a Dianne's Dungeon.
     34  */
     35 public class ForegroundServiceControllerImpl
     36         implements ForegroundServiceController {
     37     private static final String TAG = "FgServiceController";
     38     private static final boolean DBG = false;
     39 
     40     private final SparseArray<UserServices> mUserServices = new SparseArray<>();
     41     private final Object mMutex = new Object();
     42 
     43     public ForegroundServiceControllerImpl(Context context) {
     44     }
     45 
     46     @Override
     47     public boolean isDungeonNeededForUser(int userId) {
     48         synchronized (mMutex) {
     49             final UserServices services = mUserServices.get(userId);
     50             if (services == null) return false;
     51             return services.isDungeonNeeded();
     52         }
     53     }
     54 
     55     @Override
     56     public void addNotification(StatusBarNotification sbn, int importance) {
     57         updateNotification(sbn, importance);
     58     }
     59 
     60     @Override
     61     public boolean removeNotification(StatusBarNotification sbn) {
     62         synchronized (mMutex) {
     63             final UserServices userServices = mUserServices.get(sbn.getUserId());
     64             if (userServices == null) {
     65                 if (DBG) {
     66                     Log.w(TAG, String.format(
     67                             "user %d with no known notifications got removeNotification for %s",
     68                             sbn.getUserId(), sbn));
     69                 }
     70                 return false;
     71             }
     72             if (isDungeonNotification(sbn)) {
     73                 // if you remove the dungeon entirely, we take that to mean there are
     74                 // no running services
     75                 userServices.setRunningServices(null);
     76                 return true;
     77             } else {
     78                 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
     79                 return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
     80             }
     81         }
     82     }
     83 
     84     @Override
     85     public void updateNotification(StatusBarNotification sbn, int newImportance) {
     86         synchronized (mMutex) {
     87             UserServices userServices = mUserServices.get(sbn.getUserId());
     88             if (userServices == null) {
     89                 userServices = new UserServices();
     90                 mUserServices.put(sbn.getUserId(), userServices);
     91             }
     92 
     93             if (isDungeonNotification(sbn)) {
     94                 final Bundle extras = sbn.getNotification().extras;
     95                 if (extras != null) {
     96                     final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
     97                     userServices.setRunningServices(svcs); // null ok
     98                 }
     99             } else {
    100                 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
    101                 if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
    102                         && newImportance > NotificationManager.IMPORTANCE_MIN) {
    103                     userServices.addNotification(sbn.getPackageName(), sbn.getKey());
    104                 }
    105             }
    106         }
    107     }
    108 
    109     @Override
    110     public boolean isDungeonNotification(StatusBarNotification sbn) {
    111         return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
    112                 && sbn.getTag() == null
    113                 && sbn.getPackageName().equals("android");
    114     }
    115 
    116     /**
    117      * Struct to track relevant packages and notifications for a userid's foreground services.
    118      */
    119     private static class UserServices {
    120         private String[] mRunning = null;
    121         private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
    122         public void setRunningServices(String[] pkgs) {
    123             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
    124         }
    125         public void addNotification(String pkg, String key) {
    126             if (mNotifications.get(pkg) == null) {
    127                 mNotifications.put(pkg, new ArraySet<String>());
    128             }
    129             mNotifications.get(pkg).add(key);
    130         }
    131         public boolean removeNotification(String pkg, String key) {
    132             final boolean found;
    133             final ArraySet<String> keys = mNotifications.get(pkg);
    134             if (keys == null) {
    135                 found = false;
    136             } else {
    137                 found = keys.remove(key);
    138                 if (keys.size() == 0) {
    139                     mNotifications.remove(pkg);
    140                 }
    141             }
    142             return found;
    143         }
    144         public boolean isDungeonNeeded() {
    145             if (mRunning != null) {
    146                 for (String pkg : mRunning) {
    147                     final ArraySet<String> set = mNotifications.get(pkg);
    148                     if (set == null || set.size() == 0) {
    149                         return true;
    150                     }
    151                 }
    152             }
    153             return false;
    154         }
    155     }
    156 }
    157