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.annotation.Nullable;
18 import android.app.AppOpsManager;
19 import android.os.Handler;
20 import android.os.UserHandle;
21 import android.service.notification.StatusBarNotification;
22 import android.util.ArraySet;
23 import android.util.SparseArray;
24 
25 import com.android.internal.messages.nano.SystemMessageProto;
26 import com.android.systemui.appops.AppOpsController;
27 import com.android.systemui.dagger.qualifiers.Main;
28 import com.android.systemui.statusbar.notification.NotificationEntryManager;
29 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
30 import com.android.systemui.util.Assert;
31 
32 import java.util.Set;
33 
34 import javax.inject.Inject;
35 import javax.inject.Singleton;
36 
37 /**
38  * Tracks state of foreground services and notifications related to foreground services per user.
39  */
40 @Singleton
41 public class ForegroundServiceController {
42     public static final int[] APP_OPS = new int[] {AppOpsManager.OP_CAMERA,
43             AppOpsManager.OP_SYSTEM_ALERT_WINDOW,
44             AppOpsManager.OP_RECORD_AUDIO,
45             AppOpsManager.OP_COARSE_LOCATION,
46             AppOpsManager.OP_FINE_LOCATION};
47 
48     private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
49     private final Object mMutex = new Object();
50     private final NotificationEntryManager mEntryManager;
51     private final Handler mMainHandler;
52 
53     @Inject
ForegroundServiceController(NotificationEntryManager entryManager, AppOpsController appOpsController, @Main Handler mainHandler)54     public ForegroundServiceController(NotificationEntryManager entryManager,
55             AppOpsController appOpsController, @Main Handler mainHandler) {
56         mEntryManager = entryManager;
57         mMainHandler = mainHandler;
58         appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
59             mMainHandler.post(() -> {
60                 onAppOpChanged(code, uid, packageName, active);
61             });
62         });
63     }
64 
65     /**
66      * @return true if this user has services missing notifications and therefore needs a
67      * disclosure notification for running a foreground service.
68      */
isDisclosureNeededForUser(int userId)69     public boolean isDisclosureNeededForUser(int userId) {
70         synchronized (mMutex) {
71             final ForegroundServicesUserState services = mUserServices.get(userId);
72             if (services == null) return false;
73             return services.isDisclosureNeeded();
74         }
75     }
76 
77     /**
78      * @return true if this user/pkg has a missing or custom layout notification and therefore needs
79      * a disclosure notification showing the user which appsOps the app is using.
80      */
isSystemAlertWarningNeeded(int userId, String pkg)81     public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
82         synchronized (mMutex) {
83             final ForegroundServicesUserState services = mUserServices.get(userId);
84             if (services == null) return false;
85             return services.getStandardLayoutKeys(pkg) == null;
86         }
87     }
88 
89     /**
90      * Returns the keys for notifications from this package using the standard template,
91      * if they exist.
92      */
93     @Nullable
getStandardLayoutKeys(int userId, String pkg)94     public ArraySet<String> getStandardLayoutKeys(int userId, String pkg) {
95         synchronized (mMutex) {
96             final ForegroundServicesUserState services = mUserServices.get(userId);
97             if (services == null) return null;
98             return services.getStandardLayoutKeys(pkg);
99         }
100     }
101 
102     /**
103      * Gets active app ops for this user and package
104      */
105     @Nullable
getAppOps(int userId, String pkg)106     public ArraySet<Integer> getAppOps(int userId, String pkg) {
107         synchronized (mMutex) {
108             final ForegroundServicesUserState services = mUserServices.get(userId);
109             if (services == null) {
110                 return null;
111             }
112             return services.getFeatures(pkg);
113         }
114     }
115 
116     /**
117      * Records active app ops and updates the app op for the pending or visible notifications
118      * with the given parameters.
119      * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
120      * have a notification to tag.
121      * @param appOpCode code for appOp to add/remove
122      * @param uid of user the notification is sent to
123      * @param packageName package that created the notification
124      * @param active whether the appOpCode is active or not
125      */
onAppOpChanged(int appOpCode, int uid, String packageName, boolean active)126     void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
127         Assert.isMainThread();
128 
129         int userId = UserHandle.getUserId(uid);
130         // Record active app ops
131         synchronized (mMutex) {
132             ForegroundServicesUserState userServices = mUserServices.get(userId);
133             if (userServices == null) {
134                 userServices = new ForegroundServicesUserState();
135                 mUserServices.put(userId, userServices);
136             }
137             if (active) {
138                 userServices.addOp(packageName, appOpCode);
139             } else {
140                 userServices.removeOp(packageName, appOpCode);
141             }
142         }
143 
144         // TODO: (b/145659174) remove when moving to NewNotifPipeline. Replaced by
145         //  AppOpsCoordinator
146         // Update appOps if there are associated pending or visible notifications
147         final Set<String> notificationKeys = getStandardLayoutKeys(userId, packageName);
148         if (notificationKeys != null) {
149             boolean changed = false;
150             for (String key : notificationKeys) {
151                 final NotificationEntry entry = mEntryManager.getPendingOrActiveNotif(key);
152                 if (entry != null
153                         && uid == entry.getSbn().getUid()
154                         && packageName.equals(entry.getSbn().getPackageName())) {
155                     synchronized (entry.mActiveAppOps) {
156                         if (active) {
157                             changed |= entry.mActiveAppOps.add(appOpCode);
158                         } else {
159                             changed |= entry.mActiveAppOps.remove(appOpCode);
160                         }
161                     }
162                 }
163             }
164             if (changed) {
165                 mEntryManager.updateNotifications("appOpChanged pkg=" + packageName);
166             }
167         }
168     }
169 
170     /**
171      * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
172      * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
173      * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
174      * If {@code createIfNotFound} is false, no update is performed.
175      *
176      * @return false if no user state was found and none was created; true otherwise.
177      */
updateUserState(int userId, UserStateUpdateCallback updateCallback, boolean createIfNotFound)178     boolean updateUserState(int userId,
179             UserStateUpdateCallback updateCallback,
180             boolean createIfNotFound) {
181         synchronized (mMutex) {
182             ForegroundServicesUserState userState = mUserServices.get(userId);
183             if (userState == null) {
184                 if (createIfNotFound) {
185                     userState = new ForegroundServicesUserState();
186                     mUserServices.put(userId, userState);
187                 } else {
188                     return false;
189                 }
190             }
191             return updateCallback.updateUserState(userState);
192         }
193     }
194 
195     /**
196      * @return true if {@code sbn} is the system-provided disclosure notification containing the
197      * list of running foreground services.
198      */
isDisclosureNotification(StatusBarNotification sbn)199     public boolean isDisclosureNotification(StatusBarNotification sbn) {
200         return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
201                 && sbn.getTag() == null
202                 && sbn.getPackageName().equals("android");
203     }
204 
205     /**
206      * @return true if sbn is one of the window manager "drawing over other apps" notifications
207      */
isSystemAlertNotification(StatusBarNotification sbn)208     public boolean isSystemAlertNotification(StatusBarNotification sbn) {
209         return sbn.getPackageName().equals("android")
210                 && sbn.getTag() != null
211                 && sbn.getTag().contains("AlertWindowNotification");
212     }
213 
214     /**
215      * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
216      * to perform the update.
217      */
218     interface UserStateUpdateCallback {
219         /**
220          * Perform update operations on the provided {@code userState}.
221          *
222          * @return true if the update succeeded.
223          */
updateUserState(ForegroundServicesUserState userState)224         boolean updateUserState(ForegroundServicesUserState userState);
225 
226         /** Called if the state was not found and was not created. */
userStateNotFound(int userId)227         default void userStateNotFound(int userId) {
228         }
229     }
230 }
231