blob: 1fa925e5ad1f0eba7b5253223208022a3e7422b1 [file] [log] [blame]
Dan Sandlerf5aafb92017-05-28 12:18:53 -04001/*
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
15package com.android.systemui;
16
17import android.app.Notification;
18import android.app.NotificationManager;
19import android.content.Context;
20import android.os.Bundle;
Julia Reynoldsfc640012018-02-21 12:25:27 -050021import android.os.UserHandle;
Dan Sandlerf5aafb92017-05-28 12:18:53 -040022import android.service.notification.StatusBarNotification;
23import android.util.ArrayMap;
24import android.util.ArraySet;
25import android.util.Log;
Julia Reynoldsaf78a792018-03-06 14:27:00 -050026import android.util.Slog;
Dan Sandlerf5aafb92017-05-28 12:18:53 -040027import android.util.SparseArray;
28
Dan Sandlerf5aafb92017-05-28 12:18:53 -040029import com.android.internal.messages.nano.SystemMessageProto;
30
31import java.util.Arrays;
32
33/**
34 * Foreground service controller, a/k/a Dianne's Dungeon.
35 */
36public class ForegroundServiceControllerImpl
37 implements ForegroundServiceController {
Julia Reynoldsfc640012018-02-21 12:25:27 -050038
Dan Sandler9830b5a2017-10-26 23:27:57 -040039 // shelf life of foreground services before they go bad
40 public static final long FG_SERVICE_GRACE_MILLIS = 5000;
41
Dan Sandlerf5aafb92017-05-28 12:18:53 -040042 private static final String TAG = "FgServiceController";
43 private static final boolean DBG = false;
44
Julia Reynoldsfc640012018-02-21 12:25:27 -050045 private final Context mContext;
Dan Sandlerf5aafb92017-05-28 12:18:53 -040046 private final SparseArray<UserServices> mUserServices = new SparseArray<>();
47 private final Object mMutex = new Object();
48
49 public ForegroundServiceControllerImpl(Context context) {
Julia Reynoldsfc640012018-02-21 12:25:27 -050050 mContext = context;
Dan Sandlerf5aafb92017-05-28 12:18:53 -040051 }
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
Julia Reynoldsfc640012018-02-21 12:25:27 -050063 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
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400109 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
Dan Sandler9830b5a2017-10-26 23:27:57 -0400128 userServices.setRunningServices(null, 0);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400129 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);
Dan Sandler9830b5a2017-10-26 23:27:57 -0400150 userServices.setRunningServices(svcs, sbn.getNotification().when);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400151 }
152 } else {
153 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
Julia Reynoldsfc640012018-02-21 12:25:27 -0500154 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 }
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400164 }
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
Julia Reynoldsfc640012018-02-21 12:25:27 -0500176 @Override
177 public boolean isSystemAlertNotification(StatusBarNotification sbn) {
Julia Reynoldsaf78a792018-03-06 14:27:00 -0500178 return sbn.getPackageName().equals("android")
179 && sbn.getTag() != null
180 && sbn.getTag().contains("AlertWindowNotification");
Julia Reynoldsfc640012018-02-21 12:25:27 -0500181 }
182
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400183 /**
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;
Dan Sandler9830b5a2017-10-26 23:27:57 -0400188 private long mServiceStartTime = 0;
Julia Reynoldsfc640012018-02-21 12:25:27 -0500189 // 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
Dan Sandler9830b5a2017-10-26 23:27:57 -0400197 public void setRunningServices(String[] pkgs, long serviceStartTime) {
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400198 mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
Dan Sandler9830b5a2017-10-26 23:27:57 -0400199 mServiceStartTime = serviceStartTime;
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400200 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500201
202 public void addOp(String pkg, int op) {
203 if (mAppOps.get(pkg) == null) {
204 mAppOps.put(pkg, new ArraySet<>(3));
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400205 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500206 mAppOps.get(pkg).add(op);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400207 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500208
209 public boolean removeOp(String pkg, int op) {
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400210 final boolean found;
Julia Reynoldsfc640012018-02-21 12:25:27 -0500211 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);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400258 if (keys == null) {
259 found = false;
260 } else {
261 found = keys.remove(key);
262 if (keys.size() == 0) {
Julia Reynoldsfc640012018-02-21 12:25:27 -0500263 map.remove(pkg);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400264 }
265 }
266 return found;
267 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500268
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400269 public boolean isDungeonNeeded() {
Dan Sandler9830b5a2017-10-26 23:27:57 -0400270 if (mRunning != null
271 && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
272
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400273 for (String pkg : mRunning) {
Julia Reynoldsfc640012018-02-21 12:25:27 -0500274 final ArraySet<String> set = mImportantNotifications.get(pkg);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400275 if (set == null || set.size() == 0) {
276 return true;
277 }
278 }
279 }
280 return false;
281 }
Julia Reynoldsfc640012018-02-21 12:25:27 -0500282
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 }
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400304 }
305}