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