• 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.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.SysUISingleton;
28 import com.android.systemui.dagger.qualifiers.Main;
29 import com.android.systemui.util.Assert;
30 
31 import javax.inject.Inject;
32 
33 /**
34  * Tracks state of foreground services and notifications related to foreground services per user.
35  */
36 @SysUISingleton
37 public class ForegroundServiceController {
38     public static final int[] APP_OPS = new int[] {AppOpsManager.OP_SYSTEM_ALERT_WINDOW};
39 
40     private final SparseArray<ForegroundServicesUserState> mUserServices = new SparseArray<>();
41     private final Object mMutex = new Object();
42     private final Handler mMainHandler;
43 
44     @Inject
ForegroundServiceController(AppOpsController appOpsController, @Main Handler mainHandler)45     public ForegroundServiceController(AppOpsController appOpsController,
46             @Main Handler mainHandler) {
47         mMainHandler = mainHandler;
48         appOpsController.addCallback(APP_OPS, (code, uid, packageName, active) -> {
49             mMainHandler.post(() -> {
50                 onAppOpChanged(code, uid, packageName, active);
51             });
52         });
53     }
54 
55     /**
56      * @return true if this user has services missing notifications and therefore needs a
57      * disclosure notification for running a foreground service.
58      */
isDisclosureNeededForUser(int userId)59     public boolean isDisclosureNeededForUser(int userId) {
60         synchronized (mMutex) {
61             final ForegroundServicesUserState services = mUserServices.get(userId);
62             if (services == null) return false;
63             return services.isDisclosureNeeded();
64         }
65     }
66 
67     /**
68      * @return true if this user/pkg has a missing or custom layout notification and therefore needs
69      * a disclosure notification showing the user which appsOps the app is using.
70      */
isSystemAlertWarningNeeded(int userId, String pkg)71     public boolean isSystemAlertWarningNeeded(int userId, String pkg) {
72         synchronized (mMutex) {
73             final ForegroundServicesUserState services = mUserServices.get(userId);
74             if (services == null) return false;
75             return services.getStandardLayoutKeys(pkg) == null;
76         }
77     }
78 
79     /**
80      * Gets active app ops for this user and package
81      */
82     @Nullable
getAppOps(int userId, String pkg)83     public ArraySet<Integer> getAppOps(int userId, String pkg) {
84         synchronized (mMutex) {
85             final ForegroundServicesUserState services = mUserServices.get(userId);
86             if (services == null) {
87                 return null;
88             }
89             return services.getFeatures(pkg);
90         }
91     }
92 
93     /**
94      * Records active app ops and updates the app op for the pending or visible notifications
95      * with the given parameters.
96      * App Ops are stored in FSC in addition to NotificationEntry in case they change before we
97      * have a notification to tag.
98      * @param appOpCode code for appOp to add/remove
99      * @param uid of user the notification is sent to
100      * @param packageName package that created the notification
101      * @param active whether the appOpCode is active or not
102      */
onAppOpChanged(int appOpCode, int uid, String packageName, boolean active)103     void onAppOpChanged(int appOpCode, int uid, String packageName, boolean active) {
104         Assert.isMainThread();
105 
106         int userId = UserHandle.getUserId(uid);
107         // Record active app ops
108         synchronized (mMutex) {
109             ForegroundServicesUserState userServices = mUserServices.get(userId);
110             if (userServices == null) {
111                 userServices = new ForegroundServicesUserState();
112                 mUserServices.put(userId, userServices);
113             }
114             if (active) {
115                 userServices.addOp(packageName, appOpCode);
116             } else {
117                 userServices.removeOp(packageName, appOpCode);
118             }
119         }
120     }
121 
122     /**
123      * Looks up the {@link ForegroundServicesUserState} for the given {@code userId}, then performs
124      * the given {@link UserStateUpdateCallback} on it.  If no state exists for the user ID, creates
125      * a new one if {@code createIfNotFound} is true, then performs the update on the new state.
126      * If {@code createIfNotFound} is false, no update is performed.
127      *
128      * @return false if no user state was found and none was created; true otherwise.
129      */
updateUserState(int userId, UserStateUpdateCallback updateCallback, boolean createIfNotFound)130     boolean updateUserState(int userId,
131             UserStateUpdateCallback updateCallback,
132             boolean createIfNotFound) {
133         synchronized (mMutex) {
134             ForegroundServicesUserState userState = mUserServices.get(userId);
135             if (userState == null) {
136                 if (createIfNotFound) {
137                     userState = new ForegroundServicesUserState();
138                     mUserServices.put(userId, userState);
139                 } else {
140                     return false;
141                 }
142             }
143             return updateCallback.updateUserState(userState);
144         }
145     }
146 
147     /**
148      * @return true if {@code sbn} is the system-provided disclosure notification containing the
149      * list of running foreground services.
150      */
isDisclosureNotification(StatusBarNotification sbn)151     public boolean isDisclosureNotification(StatusBarNotification sbn) {
152         return sbn.getId() == SystemMessageProto.SystemMessage.NOTE_FOREGROUND_SERVICES
153                 && sbn.getTag() == null
154                 && sbn.getPackageName().equals("android");
155     }
156 
157     /**
158      * @return true if sbn is one of the window manager "drawing over other apps" notifications
159      */
isSystemAlertNotification(StatusBarNotification sbn)160     public boolean isSystemAlertNotification(StatusBarNotification sbn) {
161         return sbn.getPackageName().equals("android")
162                 && sbn.getTag() != null
163                 && sbn.getTag().contains("AlertWindowNotification");
164     }
165 
166     /**
167      * Callback provided to {@link #updateUserState(int, UserStateUpdateCallback, boolean)}
168      * to perform the update.
169      */
170     interface UserStateUpdateCallback {
171         /**
172          * Perform update operations on the provided {@code userState}.
173          *
174          * @return true if the update succeeded.
175          */
updateUserState(ForegroundServicesUserState userState)176         boolean updateUserState(ForegroundServicesUserState userState);
177 
178         /** Called if the state was not found and was not created. */
userStateNotFound(int userId)179         default void userStateNotFound(int userId) {
180         }
181     }
182 }
183