• 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.service.notification.StatusBarNotification;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 import android.util.Log;
25 import android.util.SparseArray;
26 
27 import com.android.internal.annotations.VisibleForTesting;
28 import com.android.internal.messages.nano.SystemMessageProto;
29 
30 import java.util.Arrays;
31 
32 /**
33  * Foreground service controller, a/k/a Dianne's Dungeon.
34  */
35 public class ForegroundServiceControllerImpl
36         implements ForegroundServiceController {
37     private static final String TAG = "FgServiceController";
38     private static final boolean DBG = false;
39 
40     private final SparseArray<UserServices> mUserServices = new SparseArray<>();
41     private final Object mMutex = new Object();
42 
ForegroundServiceControllerImpl(Context context)43     public ForegroundServiceControllerImpl(Context context) {
44     }
45 
46     @Override
isDungeonNeededForUser(int userId)47     public boolean isDungeonNeededForUser(int userId) {
48         synchronized (mMutex) {
49             final UserServices services = mUserServices.get(userId);
50             if (services == null) return false;
51             return services.isDungeonNeeded();
52         }
53     }
54 
55     @Override
addNotification(StatusBarNotification sbn, int importance)56     public void addNotification(StatusBarNotification sbn, int importance) {
57         updateNotification(sbn, importance);
58     }
59 
60     @Override
removeNotification(StatusBarNotification sbn)61     public boolean removeNotification(StatusBarNotification sbn) {
62         synchronized (mMutex) {
63             final UserServices userServices = mUserServices.get(sbn.getUserId());
64             if (userServices == null) {
65                 if (DBG) {
66                     Log.w(TAG, String.format(
67                             "user %d with no known notifications got removeNotification for %s",
68                             sbn.getUserId(), sbn));
69                 }
70                 return false;
71             }
72             if (isDungeonNotification(sbn)) {
73                 // if you remove the dungeon entirely, we take that to mean there are
74                 // no running services
75                 userServices.setRunningServices(null);
76                 return true;
77             } else {
78                 // this is safe to call on any notification, not just FLAG_FOREGROUND_SERVICE
79                 return userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
80             }
81         }
82     }
83 
84     @Override
updateNotification(StatusBarNotification sbn, int newImportance)85     public void updateNotification(StatusBarNotification sbn, int newImportance) {
86         synchronized (mMutex) {
87             UserServices userServices = mUserServices.get(sbn.getUserId());
88             if (userServices == null) {
89                 userServices = new UserServices();
90                 mUserServices.put(sbn.getUserId(), userServices);
91             }
92 
93             if (isDungeonNotification(sbn)) {
94                 final Bundle extras = sbn.getNotification().extras;
95                 if (extras != null) {
96                     final String[] svcs = extras.getStringArray(Notification.EXTRA_FOREGROUND_APPS);
97                     userServices.setRunningServices(svcs); // null ok
98                 }
99             } else {
100                 userServices.removeNotification(sbn.getPackageName(), sbn.getKey());
101                 if (0 != (sbn.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE)
102                         && newImportance > NotificationManager.IMPORTANCE_MIN) {
103                     userServices.addNotification(sbn.getPackageName(), sbn.getKey());
104                 }
105             }
106         }
107     }
108 
109     @Override
isDungeonNotification(StatusBarNotification sbn)110     public boolean isDungeonNotification(StatusBarNotification sbn) {
111         return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
112                 && sbn.getTag() == null
113                 && sbn.getPackageName().equals("android");
114     }
115 
116     /**
117      * Struct to track relevant packages and notifications for a userid's foreground services.
118      */
119     private static class UserServices {
120         private String[] mRunning = null;
121         private ArrayMap<String, ArraySet<String>> mNotifications = new ArrayMap<>(1);
setRunningServices(String[] pkgs)122         public void setRunningServices(String[] pkgs) {
123             mRunning = pkgs != null ? Arrays.copyOf(pkgs, pkgs.length) : null;
124         }
addNotification(String pkg, String key)125         public void addNotification(String pkg, String key) {
126             if (mNotifications.get(pkg) == null) {
127                 mNotifications.put(pkg, new ArraySet<String>());
128             }
129             mNotifications.get(pkg).add(key);
130         }
removeNotification(String pkg, String key)131         public boolean removeNotification(String pkg, String key) {
132             final boolean found;
133             final ArraySet<String> keys = mNotifications.get(pkg);
134             if (keys == null) {
135                 found = false;
136             } else {
137                 found = keys.remove(key);
138                 if (keys.size() == 0) {
139                     mNotifications.remove(pkg);
140                 }
141             }
142             return found;
143         }
isDungeonNeeded()144         public boolean isDungeonNeeded() {
145             if (mRunning != null) {
146                 for (String pkg : mRunning) {
147                     final ArraySet<String> set = mNotifications.get(pkg);
148                     if (set == null || set.size() == 0) {
149                         return true;
150                     }
151                 }
152             }
153             return false;
154         }
155     }
156 }
157