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