• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
ForegroundServiceControllerImpl(Context context)49     public ForegroundServiceControllerImpl(Context context) {
50         mContext = context;
51     }
52 
53     @Override
isDungeonNeededForUser(int userId)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
isSystemAlertWarningNeeded(int userId, String pkg)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
getStandardLayoutKey(int userId, String pkg)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
getAppOps(int userId, String pkg)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
onAppOpChanged(int code, int uid, String packageName, boolean active)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
addNotification(StatusBarNotification sbn, int importance)109     public void addNotification(StatusBarNotification sbn, int importance) {
110         updateNotification(sbn, importance);
111     }
112 
113     @Override
removeNotification(StatusBarNotification sbn)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
updateNotification(StatusBarNotification sbn, int newImportance)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
isDungeonNotification(StatusBarNotification sbn)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
isSystemAlertNotification(StatusBarNotification sbn)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 
setRunningServices(String[] pkgs, long serviceStartTime)197         public void setRunningServices(String[] pkgs, long serviceStartTime) {
198             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
199             mServiceStartTime = serviceStartTime;
200         }
201 
addOp(String pkg, int op)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 
removeOp(String pkg, int op)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 
addImportantNotification(String pkg, String key)223         public void addImportantNotification(String pkg, String key) {
224             addNotification(mImportantNotifications, pkg, key);
225         }
226 
removeImportantNotification(String pkg, String key)227         public boolean removeImportantNotification(String pkg, String key) {
228             return removeNotification(mImportantNotifications, pkg, key);
229         }
230 
addStandardLayoutNotification(String pkg, String key)231         public void addStandardLayoutNotification(String pkg, String key) {
232             addNotification(mStandardLayoutNotifications, pkg, key);
233         }
234 
removeStandardLayoutNotification(String pkg, String key)235         public boolean removeStandardLayoutNotification(String pkg, String key) {
236             return removeNotification(mStandardLayoutNotifications, pkg, key);
237         }
238 
removeNotification(String pkg, String key)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 
addNotification(ArrayMap<String, ArraySet<String>> map, String pkg, String key)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 
removeNotification(ArrayMap<String, ArraySet<String>> map, String pkg, String key)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 
isDungeonNeeded()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 
getFeatures(String pkg)283         public ArraySet<Integer> getFeatures(String pkg) {
284             return mAppOps.get(pkg);
285         }
286 
getStandardLayoutKey(String pkg)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
toString()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