1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.safetycenter; 18 19 import android.annotation.UserIdInt; 20 import android.os.IBinder; 21 import android.os.RemoteCallbackList; 22 import android.os.RemoteException; 23 import android.safetycenter.IOnSafetyCenterDataChangedListener; 24 import android.safetycenter.SafetyCenterData; 25 import android.safetycenter.SafetyCenterErrorDetails; 26 import android.util.ArrayMap; 27 import android.util.Log; 28 import android.util.SparseArray; 29 30 import androidx.annotation.Nullable; 31 32 import java.io.PrintWriter; 33 import java.util.concurrent.atomic.AtomicReference; 34 35 import javax.annotation.concurrent.NotThreadSafe; 36 37 /** 38 * A class that keeps track of all the registered {@link IOnSafetyCenterDataChangedListener} 39 * per-user. 40 * 41 * <p>This class isn't thread safe. Thread safety must be handled by the caller. 42 */ 43 @NotThreadSafe 44 final class SafetyCenterListeners { 45 46 private static final String TAG = "SafetyCenterListeners"; 47 48 private final SafetyCenterDataFactory mSafetyCenterDataFactory; 49 50 private final SparseArray<RemoteCallbackList<IOnSafetyCenterDataChangedListener>> 51 mSafetyCenterDataChangedListeners = new SparseArray<>(); 52 SafetyCenterListeners(SafetyCenterDataFactory safetyCenterDataFactory)53 SafetyCenterListeners(SafetyCenterDataFactory safetyCenterDataFactory) { 54 mSafetyCenterDataFactory = safetyCenterDataFactory; 55 } 56 57 /** 58 * Delivers a {@link SafetyCenterData} update to a single {@link 59 * IOnSafetyCenterDataChangedListener}. 60 */ deliverDataForListener( IOnSafetyCenterDataChangedListener listener, SafetyCenterData safetyCenterData)61 static void deliverDataForListener( 62 IOnSafetyCenterDataChangedListener listener, SafetyCenterData safetyCenterData) { 63 try { 64 listener.onSafetyCenterDataChanged(safetyCenterData); 65 } catch (RemoteException e) { 66 Log.w(TAG, "Error delivering SafetyCenterData to listener", e); 67 } 68 } 69 70 /** 71 * Delivers a {@link SafetyCenterErrorDetails} update to a single {@link 72 * IOnSafetyCenterDataChangedListener}. 73 */ deliverErrorForListener( IOnSafetyCenterDataChangedListener listener, SafetyCenterErrorDetails safetyCenterErrorDetails)74 private static void deliverErrorForListener( 75 IOnSafetyCenterDataChangedListener listener, 76 SafetyCenterErrorDetails safetyCenterErrorDetails) { 77 try { 78 listener.onError(safetyCenterErrorDetails); 79 } catch (RemoteException e) { 80 Log.w(TAG, "Error delivering SafetyCenterErrorDetails to listener", e); 81 } 82 } 83 84 /** 85 * Delivers a {@link SafetyCenterData} update on all listeners of the given {@link 86 * UserProfileGroup}. 87 */ deliverDataForUserProfileGroup(UserProfileGroup userProfileGroup)88 void deliverDataForUserProfileGroup(UserProfileGroup userProfileGroup) { 89 ArrayMap<String, SafetyCenterData> safetyCenterDataCache = new ArrayMap<>(); 90 int[] relevantUserIds = userProfileGroup.getAllRunningProfilesUserIds(); 91 for (int i = 0; i < relevantUserIds.length; i++) { 92 deliverUpdateForUser( 93 relevantUserIds[i], 94 userProfileGroup, 95 safetyCenterDataCache, 96 /* updateSafetyCenterData= */ true, 97 /* safetyCenterErrorDetails= */ null); 98 } 99 } 100 101 /** 102 * Delivers a given {@link SafetyCenterErrorDetails} in an update on all listeners of the given 103 * {@link UserProfileGroup}. 104 */ deliverErrorForUserProfileGroup( UserProfileGroup userProfileGroup, SafetyCenterErrorDetails safetyCenterErrorDetails)105 void deliverErrorForUserProfileGroup( 106 UserProfileGroup userProfileGroup, SafetyCenterErrorDetails safetyCenterErrorDetails) { 107 ArrayMap<String, SafetyCenterData> safetyCenterDataCache = new ArrayMap<>(); 108 int[] relevantUserIds = userProfileGroup.getAllRunningProfilesUserIds(); 109 for (int i = 0; i < relevantUserIds.length; i++) { 110 deliverUpdateForUser( 111 relevantUserIds[i], 112 userProfileGroup, 113 safetyCenterDataCache, 114 /* updateSafetyCenterData= */ false, 115 safetyCenterErrorDetails); 116 } 117 } 118 119 /** 120 * Adds a {@link IOnSafetyCenterDataChangedListener} for the given {@code packageName} and 121 * {@code userId}. 122 * 123 * <p>Returns the registered {@link IOnSafetyCenterDataChangedListener} if this operation was 124 * successful. Otherwise, returns {@code null}. 125 */ 126 @Nullable addListener( IOnSafetyCenterDataChangedListener listener, String packageName, @UserIdInt int userId)127 IOnSafetyCenterDataChangedListener addListener( 128 IOnSafetyCenterDataChangedListener listener, 129 String packageName, 130 @UserIdInt int userId) { 131 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners = 132 mSafetyCenterDataChangedListeners.get(userId); 133 if (listeners == null) { 134 listeners = new RemoteCallbackList<>(); 135 } 136 OnSafetyCenterDataChangedListenerWrapper listenerWrapper = 137 new OnSafetyCenterDataChangedListenerWrapper(listener, packageName); 138 boolean registered = listeners.register(listenerWrapper); 139 if (!registered) { 140 return null; 141 } 142 mSafetyCenterDataChangedListeners.put(userId, listeners); 143 return listenerWrapper; 144 } 145 146 /** 147 * Removes a {@link IOnSafetyCenterDataChangedListener} for the given {@code userId}. 148 * 149 * <p>Returns whether the callback was unregistered. Returns {@code false} if the callback was 150 * never registered. 151 */ removeListener(IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId)152 boolean removeListener(IOnSafetyCenterDataChangedListener listener, @UserIdInt int userId) { 153 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners = 154 mSafetyCenterDataChangedListeners.get(userId); 155 if (listeners == null) { 156 return false; 157 } 158 boolean unregistered = listeners.unregister(listener); 159 if (listeners.getRegisteredCallbackCount() == 0) { 160 mSafetyCenterDataChangedListeners.remove(userId); 161 } 162 return unregistered; 163 } 164 165 /** Clears all {@link IOnSafetyCenterDataChangedListener}s, for the given user. */ clearForUser(@serIdInt int userId)166 void clearForUser(@UserIdInt int userId) { 167 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners = 168 mSafetyCenterDataChangedListeners.get(userId); 169 if (listeners == null) { 170 return; 171 } 172 listeners.kill(); 173 mSafetyCenterDataChangedListeners.remove(userId); 174 } 175 176 /** Clears all {@link IOnSafetyCenterDataChangedListener}s, for all user ids. */ clear()177 void clear() { 178 for (int i = 0; i < mSafetyCenterDataChangedListeners.size(); i++) { 179 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners = 180 mSafetyCenterDataChangedListeners.valueAt(i); 181 if (listeners == null) { 182 continue; 183 } 184 listeners.kill(); 185 } 186 mSafetyCenterDataChangedListeners.clear(); 187 } 188 deliverUpdateForUser( @serIdInt int userId, UserProfileGroup userProfileGroup, ArrayMap<String, SafetyCenterData> safetyCenterDataCache, boolean updateSafetyCenterData, @Nullable SafetyCenterErrorDetails safetyCenterErrorDetails)189 private void deliverUpdateForUser( 190 @UserIdInt int userId, 191 UserProfileGroup userProfileGroup, 192 ArrayMap<String, SafetyCenterData> safetyCenterDataCache, 193 boolean updateSafetyCenterData, 194 @Nullable SafetyCenterErrorDetails safetyCenterErrorDetails) { 195 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listenersForUserId = 196 mSafetyCenterDataChangedListeners.get(userId); 197 if (listenersForUserId == null) { 198 return; 199 } 200 int i = listenersForUserId.beginBroadcast(); 201 try { 202 while (i > 0) { 203 i--; 204 OnSafetyCenterDataChangedListenerWrapper listenerWrapper = 205 (OnSafetyCenterDataChangedListenerWrapper) 206 listenersForUserId.getBroadcastItem(i); 207 if (updateSafetyCenterData) { 208 SafetyCenterData safetyCenterData = 209 assembleSafetyCenterDataIfAbsent( 210 safetyCenterDataCache, 211 listenerWrapper.getPackageName(), 212 userProfileGroup); 213 214 deliverDataForListener(listenerWrapper, safetyCenterData); 215 } 216 if (safetyCenterErrorDetails != null) { 217 deliverErrorForListener(listenerWrapper, safetyCenterErrorDetails); 218 } 219 } 220 } finally { 221 listenersForUserId.finishBroadcast(); 222 } 223 } 224 assembleSafetyCenterDataIfAbsent( ArrayMap<String, SafetyCenterData> safetyCenterDataCache, String packageName, UserProfileGroup userProfileGroup)225 private SafetyCenterData assembleSafetyCenterDataIfAbsent( 226 ArrayMap<String, SafetyCenterData> safetyCenterDataCache, 227 String packageName, 228 UserProfileGroup userProfileGroup) { 229 SafetyCenterData cachedSafetyCenterData = safetyCenterDataCache.get(packageName); 230 if (cachedSafetyCenterData != null) { 231 return cachedSafetyCenterData; 232 } 233 SafetyCenterData safetyCenterData = 234 mSafetyCenterDataFactory.assembleSafetyCenterData(packageName, userProfileGroup); 235 safetyCenterDataCache.put(packageName, safetyCenterData); 236 return safetyCenterData; 237 } 238 239 /** Dumps state for debugging purposes. */ dump(PrintWriter fout)240 void dump(PrintWriter fout) { 241 int userIdCount = mSafetyCenterDataChangedListeners.size(); 242 fout.println("DATA CHANGED LISTENERS (" + userIdCount + " user IDs)"); 243 for (int i = 0; i < userIdCount; i++) { 244 int userId = mSafetyCenterDataChangedListeners.keyAt(i); 245 RemoteCallbackList<IOnSafetyCenterDataChangedListener> listeners = 246 mSafetyCenterDataChangedListeners.valueAt(i); 247 if (listeners == null) { 248 continue; 249 } 250 int listenerCount = listeners.getRegisteredCallbackCount(); 251 fout.println("\t[" + i + "] user " + userId + " (" + listenerCount + " listeners)"); 252 for (int j = 0; j < listenerCount; j++) { 253 fout.println("\t\t[" + j + "] " + listeners.getRegisteredCallbackItem(j)); 254 } 255 } 256 fout.println(); 257 } 258 259 /** 260 * A wrapper around an {@link IOnSafetyCenterDataChangedListener} to ensure it is only called 261 * when the {@link SafetyCenterData} actually changes. 262 */ 263 private static final class OnSafetyCenterDataChangedListenerWrapper 264 implements IOnSafetyCenterDataChangedListener { 265 266 private final IOnSafetyCenterDataChangedListener mDelegate; 267 private final String mPackageName; 268 269 private final AtomicReference<SafetyCenterData> mLastSafetyCenterData = 270 new AtomicReference<>(); 271 OnSafetyCenterDataChangedListenerWrapper( IOnSafetyCenterDataChangedListener delegate, String packageName)272 OnSafetyCenterDataChangedListenerWrapper( 273 IOnSafetyCenterDataChangedListener delegate, String packageName) { 274 mDelegate = delegate; 275 mPackageName = packageName; 276 } 277 278 @Override onSafetyCenterDataChanged(SafetyCenterData safetyCenterData)279 public void onSafetyCenterDataChanged(SafetyCenterData safetyCenterData) 280 throws RemoteException { 281 if (safetyCenterData.equals(mLastSafetyCenterData.getAndSet(safetyCenterData))) { 282 return; 283 } 284 mDelegate.onSafetyCenterDataChanged(safetyCenterData); 285 } 286 287 @Override onError(SafetyCenterErrorDetails safetyCenterErrorDetails)288 public void onError(SafetyCenterErrorDetails safetyCenterErrorDetails) 289 throws RemoteException { 290 mDelegate.onError(safetyCenterErrorDetails); 291 } 292 293 @Override asBinder()294 public IBinder asBinder() { 295 return mDelegate.asBinder(); 296 } 297 getPackageName()298 public String getPackageName() { 299 return mPackageName; 300 } 301 302 @Override toString()303 public String toString() { 304 return "OnSafetyCenterDataChangedListenerWrapper{" 305 + "mDelegate=" 306 + mDelegate 307 + ", mPackageName='" 308 + mPackageName 309 + '\'' 310 + ", mLastSafetyCenterData=" 311 + mLastSafetyCenterData 312 + '}'; 313 } 314 } 315 } 316