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