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