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