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.os.UserHandle;
     22 import android.service.notification.StatusBarNotification;
     23 import android.util.ArrayMap;
     24 import android.util.ArraySet;
     25 import android.util.Log;
     26 import android.util.Slog;
     27 import android.util.SparseArray;
     28 
     29 import com.android.internal.messages.nano.SystemMessageProto;
     30 
     31 import java.util.Arrays;
     32 
     33 /**
     34  * Foreground service controller, a/k/a Dianne's Dungeon.
     35  */
     36 public class ForegroundServiceControllerImpl
     37         implements ForegroundServiceController {
     38 
     39     // shelf life of foreground services before they go bad
     40     public static final long FG_SERVICE_GRACE_MILLIS = 5000;
     41 
     42     private static final String TAG = "FgServiceController";
     43     private static final boolean DBG = false;
     44 
     45     private final Context mContext;
     46     private final SparseArray<UserServices> mUserServices = new SparseArray<>();
     47     private final Object mMutex = new Object();
     48 
     49     public ForegroundServiceControllerImpl(Context context) {
     50         mContext = context;
     51     }
     52 
     53     @Override
     54     public boolean isDungeonNeededForUser(int userId) {
     55         synchronized (mMutex) {
     56             final UserServices services = mUserServices.get(userId);
     57             if (services == null) return false;
     58             return services.isDungeonNeeded();
     59         }
     60     }
     61 
     62     @Override
     63     public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
     64         synchronized (mMutex) {
     65             final UserServices services = mUserServices.get(userId);
     66             if (services == null) return false;
     67             return services.getStandardLayoutKey(pkg) == null;
     68         }
     69     }
     70 
     71     @Override
     72     public String getStandardLayoutKey(int userId, String pkg) {
     73         synchronized (mMutex) {
     74             final UserServices services = mUserServices.get(userId);
     75             if (services == null) return null;
     76             return services.getStandardLayoutKey(pkg);
     77         }
     78     }
     79 
     80     @Override
     81     public ArraySet<Integer> getAppOps(int userId, String pkg) {
     82         synchronized (mMutex) {
     83             final UserServices services = mUserServices.get(userId);
     84             if (services == null) {
     85                 return null;
     86             }
     87             return services.getFeatures(pkg);
     88         }
     89     }
     90 
     91     @Override
     92     public void onAppOpChanged(int code, int uid, String packageName, boolean active) {
     93         int userId = UserHandle.getUserId(uid);
     94         synchronized (mMutex) {
     95             UserServices userServices = mUserServices.get(userId);
     96             if (userServices == null) {
     97                 userServices = new UserServices();
     98                 mUserServices.put(userId, userServices);
     99             }
    100             if (active) {
    101                 userServices.addOp(packageName, code);
    102             } else {
    103                 userServices.removeOp(packageName, code);
    104             }
    105         }
    106     }
    107 
    108     @Override
    109     public void addNotification(StatusBarNotification sbn, int importance) {
    110         updateNotification(sbn, importance);
    111     }
    112 
    113     @Override
    114     public boolean removeNotification(StatusBarNotification sbn) {
    115         synchronized (mMutex) {
    116             final UserServices userServices = mUserServices.get(sbn.getUserId());
    117             if (userServices == null) {
    118                 if (DBG) {
    119                     Log.w(TAG, String.format(
    120                             "user %d with no known notifications got removeNotification for %s",
    121                             sbn.getUserId(), sbn));
    122                 }
    123                 return false;
    124             }
    125             if (isDungeonNotification(sbn)) {
    126                 // if you remove the dungeon entirely, we take that to mean there are
    127                 // no running services
    128                 userServices.setRunningServices(null, 0);
    129                 return true;
    130             } else {
    131                 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
    132                 return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
    133             }
    134         }
    135     }
    136 
    137     @Override
    138     public void updateNotification(StatusBarNotification sbn, int newImportance) {
    139         synchronized (mMutex) {
    140             UserServices userServices = mUserServices.get(sbn.getUserId());
    141             if (userServices == null) {
    142                 userServices = new UserServices();
    143                 mUserServices.put(sbn.getUserId(), userServices);
    144             }
    145 
    146             if (isDungeonNotification(sbn)) {
    147                 final Bundle extras = sbn.getNotification().extras;
    148                 if (extras != null) {
    149                     final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
    150                     userServices.setRunningServices(svcs, sbn.getNotification().when);
    151                 }
    152             } else {
    153                 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
    154                 if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)) {
    155                     if (newImportance > NotificationManager.IMPORTANCE_MIN) {
    156                         userServices.addImportantNotification(sbn.getPackageName(), sbn.getKey());
    157                     }
    158                     final Notification.Builder builder = Notification.Builder.recoverBuilder(
    159                             mContext, sbn.getNotification());
    160                     if (builder.usesStandardHeader()) {
    161                         userServices.addStandardLayoutNotification(
    162                                 sbn.getPackageName(), sbn.getKey());
    163                     }
    164                 }
    165             }
    166         }
    167     }
    168 
    169     @Override
    170     public boolean isDungeonNotification(StatusBarNotification sbn) {
    171         return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
    172                 && sbn.getTag() == null
    173                 && sbn.getPackageName().equals("android");
    174     }
    175 
    176     @Override
    177     public boolean isSystemAlertNotification(StatusBarNotification sbn) {
    178         return sbn.getPackageName().equals("android")
    179                 && sbn.getTag() != null
    180                 && sbn.getTag().contains("AlertWindowNotification");
    181     }
    182 
    183     /**
    184      * Struct to track relevant packages and notifications for a userid's foreground services.
    185      */
    186     private static class UserServices {
    187         private String[] mRunning = null;
    188         private long mServiceStartTime = 0;
    189         // package -> sufficiently important posted notification keys
    190         private ArrayMap<String, ArraySet<String>> mImportantNotifications = new ArrayMap<>(1);
    191         // package -> standard layout posted notification keys
    192         private ArrayMap<String, ArraySet<String>> mStandardLayoutNotifications = new ArrayMap<>(1);
    193 
    194         // package -> app ops
    195         private ArrayMap<String, ArraySet<Integer>> mAppOps = new ArrayMap<>(1);
    196 
    197         public void setRunningServices(String[] pkgs, long serviceStartTime) {
    198             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
    199             mServiceStartTime = serviceStartTime;
    200         }
    201 
    202         public void addOp(String pkg, int op) {
    203             if (mAppOps.get(pkg) == null) {
    204                 mAppOps.put(pkg, new ArraySet<>(3));
    205             }
    206             mAppOps.get(pkg).add(op);
    207         }
    208 
    209         public boolean removeOp(String pkg, int op) {
    210             final boolean found;
    211             final ArraySet<Integer> keys = mAppOps.get(pkg);
    212             if (keys == null) {
    213                 found = false;
    214             } else {
    215                 found = keys.remove(op);
    216                 if (keys.size() == 0) {
    217                     mAppOps.remove(pkg);
    218                 }
    219             }
    220             return found;
    221         }
    222 
    223         public void addImportantNotification(String pkg, String key) {
    224             addNotification(mImportantNotifications, pkg, key);
    225         }
    226 
    227         public boolean removeImportantNotification(String pkg, String key) {
    228             return removeNotification(mImportantNotifications, pkg, key);
    229         }
    230 
    231         public void addStandardLayoutNotification(String pkg, String key) {
    232             addNotification(mStandardLayoutNotifications, pkg, key);
    233         }
    234 
    235         public boolean removeStandardLayoutNotification(String pkg, String key) {
    236             return removeNotification(mStandardLayoutNotifications, pkg, key);
    237         }
    238 
    239         public boolean removeNotification(String pkg, String key) {
    240             boolean removed = false;
    241             removed |= removeImportantNotification(pkg, key);
    242             removed |= removeStandardLayoutNotification(pkg, key);
    243             return removed;
    244         }
    245 
    246         public void addNotification(ArrayMap<String, ArraySet<String>> map, String pkg,
    247                 String key) {
    248             if (map.get(pkg) == null) {
    249                 map.put(pkg, new ArraySet<>());
    250             }
    251             map.get(pkg).add(key);
    252         }
    253 
    254         public boolean removeNotification(ArrayMap<String, ArraySet<String>> map,
    255                 String pkg, String key) {
    256             final boolean found;
    257             final ArraySet<String> keys = map.get(pkg);
    258             if (keys == null) {
    259                 found = false;
    260             } else {
    261                 found = keys.remove(key);
    262                 if (keys.size() == 0) {
    263                     map.remove(pkg);
    264                 }
    265             }
    266             return found;
    267         }
    268 
    269         public boolean isDungeonNeeded() {
    270             if (mRunning != null
    271                 && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
    272 
    273                 for (String pkg : mRunning) {
    274                     final ArraySet<String> set = mImportantNotifications.get(pkg);
    275                     if (set == null || set.size() == 0) {
    276                         return true;
    277                     }
    278                 }
    279             }
    280             return false;
    281         }
    282 
    283         public ArraySet<Integer> getFeatures(String pkg) {
    284             return mAppOps.get(pkg);
    285         }
    286 
    287         public String getStandardLayoutKey(String pkg) {
    288             final ArraySet<String> set = mStandardLayoutNotifications.get(pkg);
    289             if (set == null || set.size() == 0) {
    290                 return null;
    291             }
    292             return set.valueAt(0);
    293         }
    294 
    295         @Override
    296         public String toString() {
    297             return "UserServices{" +
    298                     "mRunning=" + Arrays.toString(mRunning) +
    299                     ", mServiceStartTime=" + mServiceStartTime +
    300                     ", mImportantNotifications=" + mImportantNotifications +
    301                     ", mStandardLayoutNotifications=" + mStandardLayoutNotifications +
    302                     '}';
    303         }
    304     }
    305 }
    306