blob: 3714c4ea7e2c1cd8c08ae451a596d4cccbbf7c0a [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;
21import android.service.notification.StatusBarNotification;
22import android.util.ArrayMap;
23import android.util.ArraySet;
24import android.util.Log;
25import android.util.SparseArray;
26
27import com.android.internal.annotations.VisibleForTesting;
28import 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 {
Dan Sandler9830b5a2017-10-26 23:27:57 -040037
38 // 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
44 private final SparseArray<UserServices> mUserServices = new SparseArray<>();
45 private final Object mMutex = new Object();
46
47 public ForegroundServiceControllerImpl(Context context) {
48 }
49
50 @Override
51 public boolean isDungeonNeededForUser(int userId) {
52 synchronized (mMutex) {
53 final UserServices services = mUserServices.get(userId);
54 if (services == null) return false;
55 return services.isDungeonNeeded();
56 }
57 }
58
59 @Override
60 public void addNotification(StatusBarNotification sbn, int importance) {
61 updateNotification(sbn, importance);
62 }
63
64 @Override
65 public boolean removeNotification(StatusBarNotification sbn) {
66 synchronized (mMutex) {
67 final UserServices userServices = mUserServices.get(sbn.getUserId());
68 if (userServices == null) {
69 if (DBG) {
70 Log.w(TAG, String.format(
71 "user %d with no known notifications got removeNotification for %s",
72 sbn.getUserId(), sbn));
73 }
74 return false;
75 }
76 if (isDungeonNotification(sbn)) {
77 // if you remove the dungeon entirely, we take that to mean there are
78 // no running services
Dan Sandler9830b5a2017-10-26 23:27:57 -040079 userServices.setRunningServices(null, 0);
Dan Sandlerf5aafb92017-05-28 12:18:53 -040080 return true;
81 } else {
82 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
83 return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
84 }
85 }
86 }
87
88 @Override
89 public void updateNotification(StatusBarNotification sbn, int newImportance) {
90 synchronized (mMutex) {
91 UserServices userServices = mUserServices.get(sbn.getUserId());
92 if (userServices == null) {
93 userServices = new UserServices();
94 mUserServices.put(sbn.getUserId(), userServices);
95 }
96
97 if (isDungeonNotification(sbn)) {
98 final Bundle extras = sbn.getNotification().extras;
99 if (extras != null) {
100 final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
Dan Sandler9830b5a2017-10-26 23:27:57 -0400101 userServices.setRunningServices(svcs, sbn.getNotification().when);
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400102 }
103 } else {
104 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
105 if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
106 && newImportance > NotificationManager.IMPORTANCE_MIN) {
107 userServices.addNotification(sbn.getPackageName(), sbn.getKey());
108 }
109 }
110 }
111 }
112
113 @Override
114 public boolean isDungeonNotification(StatusBarNotification sbn) {
115 return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
116 && sbn.getTag() == null
117 && sbn.getPackageName().equals("android");
118 }
119
120 /**
121 * Struct to track relevant packages and notifications for a userid's foreground services.
122 */
123 private static class UserServices {
124 private String[] mRunning = null;
Dan Sandler9830b5a2017-10-26 23:27:57 -0400125 private long mServiceStartTime = 0;
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400126 private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
Dan Sandler9830b5a2017-10-26 23:27:57 -0400127 public void setRunningServices(String[] pkgs, long serviceStartTime) {
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400128 mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
Dan Sandler9830b5a2017-10-26 23:27:57 -0400129 mServiceStartTime = serviceStartTime;
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400130 }
131 public void addNotification(String pkg, String key) {
132 if (mNotifications.get(pkg) == null) {
133 mNotifications.put(pkg, new ArraySet<String>());
134 }
135 mNotifications.get(pkg).add(key);
136 }
137 public boolean removeNotification(String pkg, String key) {
138 final boolean found;
139 final ArraySet<String> keys = mNotifications.get(pkg);
140 if (keys == null) {
141 found = false;
142 } else {
143 found = keys.remove(key);
144 if (keys.size() == 0) {
145 mNotifications.remove(pkg);
146 }
147 }
148 return found;
149 }
150 public boolean isDungeonNeeded() {
Dan Sandler9830b5a2017-10-26 23:27:57 -0400151 if (mRunning != null
152 && System.currentTimeMillis() - mServiceStartTime >= FG_SERVICE_GRACE_MILLIS) {
153
Dan Sandlerf5aafb92017-05-28 12:18:53 -0400154 for (String pkg : mRunning) {
155 final ArraySet<String> set = mNotifications.get(pkg);
156 if (set == null || set.size() == 0) {
157 return true;
158 }
159 }
160 }
161 return false;
162 }
163 }
164}