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