• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.systemui.people.widget;
18 
19 import static android.Manifest.permission.READ_CONTACTS;
20 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
21 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
22 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
23 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
24 import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_HOME_SCREEN;
25 import static android.appwidget.AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD;
26 import static android.appwidget.flags.Flags.generatedPreviews;
27 import static android.content.Intent.ACTION_BOOT_COMPLETED;
28 import static android.content.Intent.ACTION_PACKAGE_ADDED;
29 import static android.content.Intent.ACTION_PACKAGE_REMOVED;
30 import static android.service.notification.ZenPolicy.CONVERSATION_SENDERS_ANYONE;
31 
32 import static com.android.systemui.people.NotificationHelper.getContactUri;
33 import static com.android.systemui.people.NotificationHelper.getHighestPriorityNotification;
34 import static com.android.systemui.people.NotificationHelper.shouldFilterOut;
35 import static com.android.systemui.people.NotificationHelper.shouldMatchNotificationByUri;
36 import static com.android.systemui.people.PeopleBackupFollowUpJob.SHARED_FOLLOW_UP;
37 import static com.android.systemui.people.PeopleSpaceUtils.EMPTY_STRING;
38 import static com.android.systemui.people.PeopleSpaceUtils.INVALID_USER_ID;
39 import static com.android.systemui.people.PeopleSpaceUtils.PACKAGE_NAME;
40 import static com.android.systemui.people.PeopleSpaceUtils.SHORTCUT_ID;
41 import static com.android.systemui.people.PeopleSpaceUtils.USER_ID;
42 import static com.android.systemui.people.PeopleSpaceUtils.augmentTileFromNotification;
43 import static com.android.systemui.people.PeopleSpaceUtils.getMessagesCount;
44 import static com.android.systemui.people.PeopleSpaceUtils.getNotificationsByUri;
45 import static com.android.systemui.people.PeopleSpaceUtils.removeNotificationFields;
46 import static com.android.systemui.people.widget.PeopleBackupHelper.getEntryType;
47 
48 import android.annotation.NonNull;
49 import android.annotation.Nullable;
50 import android.app.INotificationManager;
51 import android.app.NotificationChannel;
52 import android.app.NotificationManager;
53 import android.app.PendingIntent;
54 import android.app.Person;
55 import android.app.backup.BackupManager;
56 import android.app.job.JobScheduler;
57 import android.app.people.ConversationChannel;
58 import android.app.people.IPeopleManager;
59 import android.app.people.PeopleManager;
60 import android.app.people.PeopleSpaceTile;
61 import android.appwidget.AppWidgetManager;
62 import android.appwidget.AppWidgetProviderInfo;
63 import android.content.BroadcastReceiver;
64 import android.content.ComponentName;
65 import android.content.Context;
66 import android.content.Intent;
67 import android.content.IntentFilter;
68 import android.content.SharedPreferences;
69 import android.content.pm.LauncherApps;
70 import android.content.pm.PackageManager;
71 import android.content.pm.ShortcutInfo;
72 import android.graphics.drawable.Icon;
73 import android.net.Uri;
74 import android.os.Bundle;
75 import android.os.RemoteException;
76 import android.os.ServiceManager;
77 import android.os.Trace;
78 import android.os.UserHandle;
79 import android.os.UserManager;
80 import android.preference.PreferenceManager;
81 import android.service.notification.ConversationChannelWrapper;
82 import android.service.notification.NotificationListenerService;
83 import android.service.notification.StatusBarNotification;
84 import android.service.notification.ZenModeConfig;
85 import android.text.TextUtils;
86 import android.util.Log;
87 import android.util.SparseBooleanArray;
88 import android.widget.RemoteViews;
89 
90 import com.android.internal.annotations.GuardedBy;
91 import com.android.internal.annotations.VisibleForTesting;
92 import com.android.internal.logging.UiEventLogger;
93 import com.android.internal.logging.UiEventLoggerImpl;
94 import com.android.keyguard.KeyguardUpdateMonitor;
95 import com.android.keyguard.KeyguardUpdateMonitorCallback;
96 import com.android.systemui.Dumpable;
97 import com.android.systemui.broadcast.BroadcastDispatcher;
98 import com.android.systemui.dagger.SysUISingleton;
99 import com.android.systemui.dagger.qualifiers.Background;
100 import com.android.systemui.dump.DumpManager;
101 import com.android.systemui.people.NotificationHelper;
102 import com.android.systemui.people.PeopleBackupFollowUpJob;
103 import com.android.systemui.people.PeopleSpaceUtils;
104 import com.android.systemui.people.PeopleTileViewHelper;
105 import com.android.systemui.people.SharedPreferencesHelper;
106 import com.android.systemui.res.R;
107 import com.android.systemui.settings.UserTracker;
108 import com.android.systemui.statusbar.NotificationListener;
109 import com.android.systemui.statusbar.NotificationListener.NotificationHandler;
110 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
111 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
112 import com.android.wm.shell.bubbles.Bubbles;
113 
114 import java.io.PrintWriter;
115 import java.util.ArrayList;
116 import java.util.Arrays;
117 import java.util.Collection;
118 import java.util.Collections;
119 import java.util.HashMap;
120 import java.util.HashSet;
121 import java.util.List;
122 import java.util.Map;
123 import java.util.Objects;
124 import java.util.Optional;
125 import java.util.Set;
126 import java.util.concurrent.Executor;
127 import java.util.function.Function;
128 import java.util.stream.Collectors;
129 import java.util.stream.Stream;
130 
131 import javax.inject.Inject;
132 
133 /** Manager for People Space widget. */
134 @SysUISingleton
135 public class PeopleSpaceWidgetManager implements Dumpable {
136 
137     private static final String TAG = "PeopleSpaceWidgetMgr";
138     private static final boolean DEBUG = PeopleSpaceUtils.DEBUG;
139 
140     private final Object mLock = new Object();
141     private final Context mContext;
142     private LauncherApps mLauncherApps;
143     private Optional<AppWidgetManager> mAppWidgetManagerOptional;
144     private IPeopleManager mIPeopleManager;
145     private SharedPreferences mSharedPrefs;
146     private PeopleManager mPeopleManager;
147     private CommonNotifCollection mNotifCollection;
148     private PackageManager mPackageManager;
149     private INotificationManager mINotificationManager;
150     private Optional<Bubbles> mBubblesOptional;
151     private UserManager mUserManager;
152     private PeopleSpaceWidgetManager mManager;
153     private BackupManager mBackupManager;
154     public UiEventLogger mUiEventLogger = new UiEventLoggerImpl();
155     private NotificationManager mNotificationManager;
156     private BroadcastDispatcher mBroadcastDispatcher;
157     private Executor mBgExecutor;
158     @GuardedBy("mLock")
159     public static Map<PeopleTileKey, TileConversationListener>
160             mListeners = new HashMap<>();
161 
162     @GuardedBy("mLock")
163     // Map of notification key mapped to widget IDs previously updated by the contact Uri field.
164     // This is required because on notification removal, the contact Uri field is stripped and we
165     // only have the notification key to determine which widget IDs should be updated.
166     private Map<String, Set<String>> mNotificationKeyToWidgetIdsMatchedByUri = new HashMap<>();
167     private boolean mRegisteredReceivers;
168 
169     @GuardedBy("mLock")
170     public static Map<Integer, PeopleSpaceTile> mTiles = new HashMap<>();
171 
172     @NonNull private final UserTracker mUserTracker;
173     @NonNull private final SparseBooleanArray mUpdatedPreviews = new SparseBooleanArray();
174     @NonNull private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback =
175             new KeyguardUpdateMonitorCallback() {
176                 @Override
177                 public void onUserUnlocked() {
178                     if (DEBUG) {
179                         Log.d(TAG, "onUserUnlocked " + mUserTracker.getUserId());
180                     }
181                     updateGeneratedPreviewForUser(mUserTracker.getUserHandle());
182                 }
183             };
184 
185     @Inject
PeopleSpaceWidgetManager(Context context, Optional<AppWidgetManager> appWidgetManagerOptional, LauncherApps launcherApps, CommonNotifCollection notifCollection, PackageManager packageManager, Optional<Bubbles> bubblesOptional, UserManager userManager, NotificationManager notificationManager, BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor, DumpManager dumpManager, @NonNull UserTracker userTracker, @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor)186     public PeopleSpaceWidgetManager(Context context,
187             Optional<AppWidgetManager> appWidgetManagerOptional,
188             LauncherApps launcherApps, CommonNotifCollection notifCollection,
189             PackageManager packageManager, Optional<Bubbles> bubblesOptional,
190             UserManager userManager, NotificationManager notificationManager,
191             BroadcastDispatcher broadcastDispatcher, @Background Executor bgExecutor,
192             DumpManager dumpManager, @NonNull UserTracker userTracker,
193             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor) {
194         if (DEBUG) Log.d(TAG, "constructor");
195         mContext = context;
196         mAppWidgetManagerOptional = appWidgetManagerOptional;
197         mIPeopleManager = IPeopleManager.Stub.asInterface(
198                 ServiceManager.getService(Context.PEOPLE_SERVICE));
199         mLauncherApps = launcherApps;
200         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(mContext);
201         mPeopleManager = context.getSystemService(PeopleManager.class);
202         mNotifCollection = notifCollection;
203         mPackageManager = packageManager;
204         mINotificationManager = INotificationManager.Stub.asInterface(
205                 ServiceManager.getService(Context.NOTIFICATION_SERVICE));
206         mBubblesOptional = bubblesOptional;
207         mUserManager = userManager;
208         mBackupManager = new BackupManager(context);
209         mNotificationManager = notificationManager;
210         mManager = this;
211         mBroadcastDispatcher = broadcastDispatcher;
212         mBgExecutor = bgExecutor;
213         dumpManager.registerNormalDumpable(TAG, this);
214         mUserTracker = userTracker;
215         keyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback);
216     }
217 
218     /** Initializes {@PeopleSpaceWidgetManager}. */
init()219     public void init() {
220         synchronized (mLock) {
221             if (!mRegisteredReceivers) {
222                 if (DEBUG) Log.d(TAG, "Register receivers");
223                 IntentFilter filter = new IntentFilter();
224                 filter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
225                 filter.addAction(ACTION_BOOT_COMPLETED);
226                 filter.addAction(Intent.ACTION_LOCALE_CHANGED);
227                 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
228                 filter.addAction(Intent.ACTION_PACKAGES_SUSPENDED);
229                 filter.addAction(Intent.ACTION_PACKAGES_UNSUSPENDED);
230                 filter.addAction(Intent.ACTION_MANAGED_PROFILE_AVAILABLE);
231                 filter.addAction(Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE);
232                 filter.addAction(Intent.ACTION_USER_UNLOCKED);
233                 mBroadcastDispatcher.registerReceiver(mBaseBroadcastReceiver, filter,
234 
235                         null /* executor */, UserHandle.ALL);
236                 IntentFilter perAppFilter = new IntentFilter(ACTION_PACKAGE_REMOVED);
237                 perAppFilter.addAction(ACTION_PACKAGE_ADDED);
238                 perAppFilter.addDataScheme("package");
239                 // BroadcastDispatcher doesn't allow data schemes.
240                 mContext.registerReceiver(mBaseBroadcastReceiver, perAppFilter);
241                 IntentFilter bootComplete = new IntentFilter(ACTION_BOOT_COMPLETED);
242                 bootComplete.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
243                 // BroadcastDispatcher doesn't allow priority.
244                 mContext.registerReceiver(mBaseBroadcastReceiver, bootComplete);
245                 mRegisteredReceivers = true;
246             }
247         }
248     }
249 
250     /** Listener for the shortcut data changes. */
251     public class TileConversationListener implements PeopleManager.ConversationListener {
252 
253         @Override
onConversationUpdate(@onNull ConversationChannel conversation)254         public void onConversationUpdate(@NonNull ConversationChannel conversation) {
255             if (DEBUG) {
256                 Log.d(TAG,
257                         "Received updated conversation: "
258                                 + conversation.getShortcutInfo().getLabel());
259             }
260             mBgExecutor.execute(() ->
261                     updateWidgetsWithConversationChanged(conversation));
262         }
263     }
264 
265     /**
266      * PeopleSpaceWidgetManager setter used for testing.
267      */
268     @VisibleForTesting
PeopleSpaceWidgetManager(Context context, Optional<AppWidgetManager> appWidgetManager, IPeopleManager iPeopleManager, PeopleManager peopleManager, LauncherApps launcherApps, CommonNotifCollection notifCollection, PackageManager packageManager, Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager, INotificationManager iNotificationManager, NotificationManager notificationManager, @Background Executor executor, UserTracker userTracker)269     PeopleSpaceWidgetManager(Context context,
270             Optional<AppWidgetManager> appWidgetManager, IPeopleManager iPeopleManager,
271             PeopleManager peopleManager, LauncherApps launcherApps,
272             CommonNotifCollection notifCollection, PackageManager packageManager,
273             Optional<Bubbles> bubblesOptional, UserManager userManager, BackupManager backupManager,
274             INotificationManager iNotificationManager, NotificationManager notificationManager,
275             @Background Executor executor, UserTracker userTracker) {
276         mContext = context;
277         mAppWidgetManagerOptional = appWidgetManager;
278         mIPeopleManager = iPeopleManager;
279         mPeopleManager = peopleManager;
280         mLauncherApps = launcherApps;
281         mNotifCollection = notifCollection;
282         mPackageManager = packageManager;
283         mBubblesOptional = bubblesOptional;
284         mUserManager = userManager;
285         mBackupManager = backupManager;
286         mINotificationManager = iNotificationManager;
287         mNotificationManager = notificationManager;
288         mManager = this;
289         mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
290         mBgExecutor = executor;
291         mUserTracker = userTracker;
292     }
293 
294     /**
295      * Updates People Space widgets.
296      */
updateWidgets(int[] widgetIds)297     public void updateWidgets(int[] widgetIds) {
298         mBgExecutor.execute(() -> updateWidgetsInBackground(widgetIds));
299     }
300 
updateWidgetsInBackground(int[] widgetIds)301     private void updateWidgetsInBackground(int[] widgetIds) {
302         try {
303             if (DEBUG) Log.d(TAG, "updateWidgets called");
304             if (widgetIds.length == 0) {
305                 if (DEBUG) Log.d(TAG, "no widgets to update");
306                 return;
307             }
308             synchronized (mLock) {
309                 updateSingleConversationWidgets(widgetIds);
310             }
311         } catch (Exception e) {
312             Log.e(TAG, "failed to update widgets", e);
313         }
314     }
315 
316     /**
317      * Updates {@code appWidgetIds} with their associated conversation stored, handling a
318      * notification being posted or removed.
319      */
updateSingleConversationWidgets(int[] appWidgetIds)320     public void updateSingleConversationWidgets(int[] appWidgetIds) {
321         Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>();
322         for (int appWidgetId : appWidgetIds) {
323             if (DEBUG) Log.d(TAG, "Updating widget: " + appWidgetId);
324             PeopleSpaceTile tile = getTileForExistingWidget(appWidgetId);
325             if (tile == null) {
326                 Log.e(TAG, "Matching conversation not found for widget " + appWidgetId);
327             }
328             updateAppWidgetOptionsAndView(appWidgetId, tile);
329             widgetIdToTile.put(appWidgetId, tile);
330             if (tile != null) {
331                 registerConversationListenerIfNeeded(appWidgetId,
332                         new PeopleTileKey(tile));
333             }
334         }
335         PeopleSpaceUtils.getDataFromContactsOnBackgroundThread(
336                 mContext, mManager, widgetIdToTile, appWidgetIds);
337     }
338 
339     /** Updates the current widget view with provided {@link PeopleSpaceTile}. */
updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options)340     private void updateAppWidgetViews(int appWidgetId, PeopleSpaceTile tile, Bundle options) {
341         if (mAppWidgetManagerOptional.isEmpty()) {
342             return;
343         }
344 
345         PeopleTileKey key = getKeyFromStorageByWidgetId(appWidgetId);
346         if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + " for: " + key.toString());
347 
348         if (!PeopleTileKey.isValid(key)) {
349             Log.e(TAG, "Invalid tile key updating widget " + appWidgetId);
350             return;
351         }
352         RemoteViews views = PeopleTileViewHelper.createRemoteViews(mContext, tile, appWidgetId,
353                 options, key);
354 
355         // Tell the AppWidgetManager to perform an update on the current app widget.
356         if (DEBUG) Log.d(TAG, "Calling update widget for widgetId: " + appWidgetId);
357         mAppWidgetManagerOptional.get().updateAppWidget(appWidgetId, views);
358     }
359 
360     /** Updates tile in app widget options and the current view. */
updateAppWidgetOptionsAndViewOptional(int appWidgetId, Optional<PeopleSpaceTile> tile)361     public void updateAppWidgetOptionsAndViewOptional(int appWidgetId,
362             Optional<PeopleSpaceTile> tile) {
363         if (tile.isPresent()) {
364             updateAppWidgetOptionsAndView(appWidgetId, tile.get());
365         }
366     }
367 
368     /** Updates tile in app widget options and the current view. */
updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile)369     public void updateAppWidgetOptionsAndView(int appWidgetId, PeopleSpaceTile tile) {
370         if (mAppWidgetManagerOptional.isEmpty()) {
371             return;
372         }
373 
374         if (tile == null) {
375             Log.w(TAG, "Storing null tile for widget " + appWidgetId);
376         }
377         synchronized (mTiles) {
378             mTiles.put(appWidgetId, tile);
379         }
380         Bundle options = mAppWidgetManagerOptional.get().getAppWidgetOptions(appWidgetId);
381         updateAppWidgetViews(appWidgetId, tile, options);
382     }
383 
384     /**
385      * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}.
386      * Widget already exists, so fetch {@link PeopleTileKey} from {@link SharedPreferences}.
387      */
388     @Nullable
getTileForExistingWidget(int appWidgetId)389     public PeopleSpaceTile getTileForExistingWidget(int appWidgetId) {
390         try {
391             return getTileForExistingWidgetThrowing(appWidgetId);
392         } catch (Exception e) {
393             Log.e(TAG, "failed to retrieve tile for existing widget " + appWidgetId, e);
394             return null;
395         }
396     }
397 
398     @Nullable
getTileForExistingWidgetThrowing(int appWidgetId)399     private PeopleSpaceTile getTileForExistingWidgetThrowing(int appWidgetId) throws
400             PackageManager.NameNotFoundException {
401         // First, check if tile is cached in memory.
402         PeopleSpaceTile tile;
403         synchronized (mTiles) {
404             tile = mTiles.get(appWidgetId);
405         }
406         if (tile != null) {
407             if (DEBUG) Log.d(TAG, "People Tile is cached for widget: " + appWidgetId);
408             return tile;
409         }
410 
411         // If tile is null, we need to retrieve from persistent storage.
412         if (DEBUG) Log.d(TAG, "Fetching key from sharedPreferences: " + appWidgetId);
413         SharedPreferences widgetSp = mContext.getSharedPreferences(
414                 String.valueOf(appWidgetId),
415                 Context.MODE_PRIVATE);
416         PeopleTileKey key = new PeopleTileKey(
417                 widgetSp.getString(SHORTCUT_ID, EMPTY_STRING),
418                 widgetSp.getInt(USER_ID, INVALID_USER_ID),
419                 widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
420 
421         return getTileFromPersistentStorage(key, appWidgetId, /* supplementFromStorage= */ true);
422     }
423 
424     /**
425      * Returns a {@link PeopleSpaceTile} based on the {@code appWidgetId}.
426      * If a {@link PeopleTileKey} is not provided, fetch one from {@link SharedPreferences}.
427      */
428     @Nullable
getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId, boolean supplementFromStorage)429     public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId,
430             boolean supplementFromStorage) throws
431             PackageManager.NameNotFoundException {
432         if (!PeopleTileKey.isValid(key)) {
433             Log.e(TAG, "Invalid tile key finding tile for existing widget " + appWidgetId);
434             return null;
435         }
436 
437         if (mIPeopleManager == null || mLauncherApps == null) {
438             Log.d(TAG, "System services are null");
439             return null;
440         }
441         try {
442             if (DEBUG) Log.d(TAG, "Retrieving Tile from storage: " + key.toString());
443             ConversationChannel channel = mIPeopleManager.getConversation(
444                     key.getPackageName(), key.getUserId(), key.getShortcutId());
445             if (channel == null) {
446                 if (DEBUG) Log.d(TAG, "Could not retrieve conversation from storage");
447                 return null;
448             }
449 
450             // Get tile from shortcut & conversation storage.
451             PeopleSpaceTile.Builder storedTile = new PeopleSpaceTile.Builder(channel,
452                     mLauncherApps);
453             if (storedTile == null) {
454                 return storedTile.build();
455             }
456 
457             // Supplement with our storage.
458             String contactUri = mSharedPrefs.getString(String.valueOf(appWidgetId), null);
459             if (supplementFromStorage && contactUri != null
460                     && storedTile.build().getContactUri() == null) {
461                 if (DEBUG) Log.d(TAG, "Restore contact uri from storage: " + contactUri);
462                 storedTile.setContactUri(Uri.parse(contactUri));
463             }
464 
465             // Add current state.
466             return getTileWithCurrentState(storedTile.build(), ACTION_BOOT_COMPLETED);
467         } catch (RemoteException e) {
468             Log.e(TAG, "getTileFromPersistentStorage failing for widget " + appWidgetId, e);
469             return null;
470         }
471     }
472 
473     /**
474      * Check if any existing People tiles match the incoming notification change, and store the
475      * change in the tile if so.
476      */
updateWidgetsWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction)477     public void updateWidgetsWithNotificationChanged(StatusBarNotification sbn,
478             PeopleSpaceUtils.NotificationAction notificationAction) {
479         if (DEBUG) {
480             if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) {
481                 Log.d(TAG, "Notification posted, key: " + sbn.getKey());
482             } else {
483                 Log.d(TAG, "Notification removed, key: " + sbn.getKey());
484             }
485         }
486         if (DEBUG) Log.d(TAG, "Fetching notifications");
487         Collection<NotificationEntry> notifications = mNotifCollection.getAllNotifs();
488         mBgExecutor.execute(
489                 () -> updateWidgetsWithNotificationChangedInBackground(
490                         sbn, notificationAction, notifications));
491     }
492 
updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction action, Collection<NotificationEntry> notifications)493     private void updateWidgetsWithNotificationChangedInBackground(StatusBarNotification sbn,
494             PeopleSpaceUtils.NotificationAction action,
495             Collection<NotificationEntry> notifications) {
496         if (mAppWidgetManagerOptional.isEmpty()) {
497             return;
498         }
499 
500         try {
501             PeopleTileKey key = new PeopleTileKey(
502                     sbn.getShortcutId(), sbn.getUser().getIdentifier(), sbn.getPackageName());
503             if (!PeopleTileKey.isValid(key)) {
504                 if (DEBUG) Log.d(TAG, "Sbn doesn't contain valid PeopleTileKey: " + key.toString());
505                 return;
506             }
507             int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
508                     new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
509             );
510             if (widgetIds.length == 0) {
511                 Log.d(TAG, "No app widget ids returned");
512                 return;
513             }
514             synchronized (mLock) {
515                 Set<String> tilesUpdated = getMatchingKeyWidgetIds(key);
516                 Set<String> tilesUpdatedByUri = getMatchingUriWidgetIds(sbn, action);
517                 if (DEBUG) {
518                     Log.d(TAG, "Widgets by key to be updated:" + tilesUpdated.toString());
519                     Log.d(TAG, "Widgets by URI to be updated:" + tilesUpdatedByUri.toString());
520                 }
521                 tilesUpdated.addAll(tilesUpdatedByUri);
522                 updateWidgetIdsBasedOnNotifications(tilesUpdated, notifications);
523             }
524         } catch (Exception e) {
525             Log.e(TAG, "updateWidgetsWithNotificationChangedInBackground failing", e);
526         }
527     }
528 
529     /** Updates {@code widgetIdsToUpdate} with {@code action}. */
updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate, Collection<NotificationEntry> ungroupedNotifications)530     private void updateWidgetIdsBasedOnNotifications(Set<String> widgetIdsToUpdate,
531             Collection<NotificationEntry> ungroupedNotifications) {
532         if (widgetIdsToUpdate.isEmpty()) {
533             if (DEBUG) Log.d(TAG, "No widgets to update, returning.");
534             return;
535         }
536         try {
537             Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
538                     groupConversationNotifications(ungroupedNotifications);
539 
540             widgetIdsToUpdate
541                     .stream()
542                     .map(Integer::parseInt)
543                     .collect(Collectors.toMap(
544                             Function.identity(),
545                             id -> getAugmentedTileForExistingWidget(id, groupedNotifications)))
546                     .forEach((id, tile) -> updateAppWidgetOptionsAndViewOptional(id, tile));
547         } catch (Exception e) {
548             Log.e(TAG, "updateWidgetIdsBasedOnNotifications failing", e);
549         }
550     }
551 
552     /**
553      * Augments {@code tile} based on notifications returned from {@code notificationEntryManager}.
554      */
augmentTileFromNotificationEntryManager(PeopleSpaceTile tile, Optional<Integer> appWidgetId)555     public PeopleSpaceTile augmentTileFromNotificationEntryManager(PeopleSpaceTile tile,
556             Optional<Integer> appWidgetId) {
557         PeopleTileKey key = new PeopleTileKey(tile);
558         if (DEBUG) {
559             Log.d(TAG,
560                     "Augmenting tile from NotificationEntryManager widget: " + key.toString());
561         }
562         Map<PeopleTileKey, Set<NotificationEntry>> notifications =
563                 groupConversationNotifications(mNotifCollection.getAllNotifs());
564         String contactUri = null;
565         if (tile.getContactUri() != null) {
566             contactUri = tile.getContactUri().toString();
567         }
568         return augmentTileFromNotifications(tile, key, contactUri, notifications, appWidgetId);
569     }
570 
571     /** Groups active and pending notifications grouped by {@link PeopleTileKey}. */
groupConversationNotifications( Collection<NotificationEntry> notifications )572     public Map<PeopleTileKey, Set<NotificationEntry>> groupConversationNotifications(
573             Collection<NotificationEntry> notifications
574     ) {
575         if (DEBUG) Log.d(TAG, "Number of total notifications: " + notifications.size());
576         Map<PeopleTileKey, Set<NotificationEntry>> groupedNotifications =
577                 notifications
578                         .stream()
579                         .filter(entry -> NotificationHelper.isValid(entry)
580                                 && NotificationHelper.isMissedCallOrHasContent(entry)
581                                 && !shouldFilterOut(mBubblesOptional, entry))
582                         .collect(Collectors.groupingBy(
583                                 PeopleTileKey::new,
584                                 Collectors.mapping(Function.identity(), Collectors.toSet())));
585         if (DEBUG) {
586             Log.d(TAG, "Number of grouped conversation notifications keys: "
587                     + groupedNotifications.keySet().size());
588         }
589         return groupedNotifications;
590     }
591 
592     /** Augments {@code tile} based on {@code notifications}, matching {@code contactUri}. */
augmentTileFromNotifications(PeopleSpaceTile tile, PeopleTileKey key, String contactUri, Map<PeopleTileKey, Set<NotificationEntry>> notifications, Optional<Integer> appWidgetId)593     public PeopleSpaceTile augmentTileFromNotifications(PeopleSpaceTile tile, PeopleTileKey key,
594             String contactUri,
595             Map<PeopleTileKey, Set<NotificationEntry>> notifications,
596             Optional<Integer> appWidgetId) {
597         if (DEBUG) Log.d(TAG, "Augmenting tile from notifications. Tile key: " + key.toString());
598         boolean hasReadContactsPermission = mPackageManager.checkPermission(READ_CONTACTS,
599                 tile.getPackageName()) == PackageManager.PERMISSION_GRANTED;
600 
601         List<NotificationEntry> notificationsByUri = new ArrayList<>();
602         if (hasReadContactsPermission) {
603             notificationsByUri = getNotificationsByUri(mPackageManager, contactUri, notifications);
604             if (!notificationsByUri.isEmpty()) {
605                 if (DEBUG) {
606                     Log.d(TAG, "Number of notifications matched by contact URI: "
607                             + notificationsByUri.size());
608                 }
609             }
610         }
611 
612         Set<NotificationEntry> allNotifications = notifications.get(key);
613         if (allNotifications == null) {
614             allNotifications = new HashSet<>();
615         }
616         if (allNotifications.isEmpty() && notificationsByUri.isEmpty()) {
617             if (DEBUG) Log.d(TAG, "No existing notifications for tile: " + key.toString());
618             return removeNotificationFields(tile);
619         }
620 
621         // Merge notifications matched by key and by contact URI.
622         allNotifications.addAll(notificationsByUri);
623         if (DEBUG) Log.d(TAG, "Total notifications matching tile: " + allNotifications.size());
624 
625         int messagesCount = getMessagesCount(allNotifications);
626         NotificationEntry highestPriority = getHighestPriorityNotification(allNotifications);
627 
628         if (DEBUG) Log.d(TAG, "Augmenting tile from notification, key: " + key.toString());
629         return augmentTileFromNotification(mContext, tile, key, highestPriority, messagesCount,
630                 appWidgetId, mBackupManager);
631     }
632 
633     /** Returns an augmented tile for an existing widget. */
634     @Nullable
getAugmentedTileForExistingWidget(int widgetId, Map<PeopleTileKey, Set<NotificationEntry>> notifications)635     public Optional<PeopleSpaceTile> getAugmentedTileForExistingWidget(int widgetId,
636             Map<PeopleTileKey, Set<NotificationEntry>> notifications) {
637         if (DEBUG) Log.d(TAG, "Augmenting tile for existing widget: " + widgetId);
638         PeopleSpaceTile tile = getTileForExistingWidget(widgetId);
639         if (tile == null) {
640             Log.w(TAG, "Null tile for existing widget " + widgetId + ", skipping update.");
641             return Optional.empty();
642         }
643         String contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
644         // Should never be null, but using ofNullable for extra safety.
645         PeopleTileKey key = new PeopleTileKey(tile);
646         if (DEBUG) Log.d(TAG, "Existing widget: " + widgetId + ". Tile key: " + key.toString());
647         return Optional.ofNullable(
648                 augmentTileFromNotifications(tile, key, contactUriString, notifications,
649                         Optional.of(widgetId)));
650     }
651 
652     /** Returns stored widgets for the conversation specified. */
getMatchingKeyWidgetIds(PeopleTileKey key)653     public Set<String> getMatchingKeyWidgetIds(PeopleTileKey key) {
654         if (!PeopleTileKey.isValid(key)) {
655             return new HashSet<>();
656         }
657         return new HashSet<>(mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
658     }
659 
660     /**
661      * Updates in-memory map of tiles with matched Uris, dependent on the {@code action}.
662      *
663      * <p>If the notification was added, adds the notification based on the contact Uri within
664      * {@code sbn}.
665      * <p>If the notification was removed, removes the notification based on the in-memory map of
666      * widgets previously updated by Uri (since the contact Uri is stripped from the {@code sbn}).
667      */
668     @Nullable
getMatchingUriWidgetIds(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction action)669     private Set<String> getMatchingUriWidgetIds(StatusBarNotification sbn,
670             PeopleSpaceUtils.NotificationAction action) {
671         if (action.equals(PeopleSpaceUtils.NotificationAction.POSTED)) {
672             Set<String> widgetIdsUpdatedByUri = fetchMatchingUriWidgetIds(sbn);
673             if (widgetIdsUpdatedByUri != null && !widgetIdsUpdatedByUri.isEmpty()) {
674                 mNotificationKeyToWidgetIdsMatchedByUri.put(sbn.getKey(), widgetIdsUpdatedByUri);
675                 return widgetIdsUpdatedByUri;
676             }
677         } else {
678             // Remove the notification on any widgets where the notification was added
679             // purely based on the Uri.
680             Set<String> widgetsPreviouslyUpdatedByUri =
681                     mNotificationKeyToWidgetIdsMatchedByUri.remove(sbn.getKey());
682             if (widgetsPreviouslyUpdatedByUri != null && !widgetsPreviouslyUpdatedByUri.isEmpty()) {
683                 return widgetsPreviouslyUpdatedByUri;
684             }
685         }
686         return new HashSet<>();
687     }
688 
689     /** Fetches widget Ids that match the contact URI in {@code sbn}. */
690     @Nullable
fetchMatchingUriWidgetIds(StatusBarNotification sbn)691     private Set<String> fetchMatchingUriWidgetIds(StatusBarNotification sbn) {
692         // Check if it's a missed call notification
693         if (!shouldMatchNotificationByUri(sbn)) {
694             if (DEBUG) Log.d(TAG, "Should not supplement conversation");
695             return null;
696         }
697 
698         // Try to get the Contact Uri from the Missed Call notification directly.
699         String contactUri = getContactUri(sbn);
700         if (contactUri == null) {
701             if (DEBUG) Log.d(TAG, "No contact uri");
702             return null;
703         }
704 
705         // Supplement any tiles with the same Uri.
706         Set<String> storedWidgetIdsByUri =
707                 new HashSet<>(mSharedPrefs.getStringSet(contactUri, new HashSet<>()));
708         if (storedWidgetIdsByUri.isEmpty()) {
709             if (DEBUG) Log.d(TAG, "No tiles for contact");
710             return null;
711         }
712         return storedWidgetIdsByUri;
713     }
714 
715     /**
716      * Update the tiles associated with the incoming conversation update.
717      */
updateWidgetsWithConversationChanged(ConversationChannel conversation)718     public void updateWidgetsWithConversationChanged(ConversationChannel conversation) {
719         ShortcutInfo info = conversation.getShortcutInfo();
720         synchronized (mLock) {
721             PeopleTileKey key = new PeopleTileKey(
722                     info.getId(), info.getUserId(), info.getPackage());
723             Set<String> storedWidgetIds = getMatchingKeyWidgetIds(key);
724             for (String widgetIdString : storedWidgetIds) {
725                 if (DEBUG) {
726                     Log.d(TAG,
727                             "Conversation update for widget " + widgetIdString + " , "
728                                     + info.getLabel());
729                 }
730                 updateStorageAndViewWithConversationData(conversation,
731                         Integer.parseInt(widgetIdString));
732             }
733         }
734     }
735 
736     /**
737      * Update {@code appWidgetId} with the new data provided by {@code conversation}.
738      */
updateStorageAndViewWithConversationData(ConversationChannel conversation, int appWidgetId)739     private void updateStorageAndViewWithConversationData(ConversationChannel conversation,
740             int appWidgetId) {
741         PeopleSpaceTile storedTile = getTileForExistingWidget(appWidgetId);
742         if (storedTile == null) {
743             if (DEBUG) Log.d(TAG, "Could not find stored tile to add conversation to");
744             return;
745         }
746         PeopleSpaceTile.Builder updatedTile = storedTile.toBuilder();
747         ShortcutInfo info = conversation.getShortcutInfo();
748         Uri uri = null;
749         if (info.getPersons() != null && info.getPersons().length > 0) {
750             Person person = info.getPersons()[0];
751             uri = person.getUri() == null ? null : Uri.parse(person.getUri());
752         }
753         CharSequence label = info.getLabel();
754         if (label != null) {
755             updatedTile.setUserName(label);
756         }
757         Icon icon = PeopleSpaceTile.convertDrawableToIcon(mLauncherApps.getShortcutIconDrawable(
758                 info, 0));
759         if (icon != null) {
760             updatedTile.setUserIcon(icon);
761         }
762         if (DEBUG) Log.d(TAG, "Statuses: " + conversation.getStatuses());
763         NotificationChannel channel = conversation.getNotificationChannel();
764         if (channel != null) {
765             if (DEBUG) Log.d(TAG, "Important:" + channel.isImportantConversation());
766             updatedTile.setIsImportantConversation(channel.isImportantConversation());
767         }
768         updatedTile
769                 .setContactUri(uri)
770                 .setStatuses(conversation.getStatuses())
771                 .setLastInteractionTimestamp(conversation.getLastEventTimestamp());
772         updateAppWidgetOptionsAndView(appWidgetId, updatedTile.build());
773     }
774 
775     /**
776      * Attaches the manager to the pipeline, making it ready to receive events. Should only be
777      * called once.
778      */
attach(NotificationListener listenerService)779     public void attach(NotificationListener listenerService) {
780         if (DEBUG) Log.d(TAG, "attach");
781         listenerService.addNotificationHandler(mListener);
782     }
783 
784     private final NotificationHandler mListener = new NotificationHandler() {
785         @Override
786         public void onNotificationPosted(
787                 StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) {
788             updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED);
789         }
790 
791         @Override
792         public void onNotificationRemoved(
793                 StatusBarNotification sbn,
794                 NotificationListenerService.RankingMap rankingMap
795         ) {
796             updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
797         }
798 
799         @Override
800         public void onNotificationRemoved(
801                 StatusBarNotification sbn,
802                 NotificationListenerService.RankingMap rankingMap,
803                 int reason) {
804             updateWidgetsWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED);
805         }
806 
807         @Override
808         public void onNotificationRankingUpdate(
809                 NotificationListenerService.RankingMap rankingMap) {
810         }
811 
812         @Override
813         public void onNotificationsInitialized() {
814             if (DEBUG) Log.d(TAG, "onNotificationsInitialized");
815         }
816 
817         @Override
818         public void onNotificationChannelModified(
819                 String pkgName,
820                 UserHandle user,
821                 NotificationChannel channel,
822                 int modificationType) {
823             if (mAppWidgetManagerOptional.isEmpty()) {
824                 return;
825             }
826 
827             if (channel.isConversation()) {
828                 mBgExecutor.execute(() -> {
829                     if (mUserManager.isUserUnlocked(user)) {
830                         updateWidgets(mAppWidgetManagerOptional.get().getAppWidgetIds(
831                                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class)
832                         ));
833                     }
834                 });
835             }
836         }
837     };
838 
839     /**
840      * Checks if this widget has been added externally, and this the first time we are learning
841      * about the widget. If so, the widget adder should have populated options with PeopleTileKey
842      * arguments.
843      */
onAppWidgetOptionsChanged(int appWidgetId, Bundle newOptions)844     public void onAppWidgetOptionsChanged(int appWidgetId, Bundle newOptions) {
845         // Check if this widget has been added externally, and this the first time we are
846         // learning about the widget. If so, the widget adder should have populated options with
847         // PeopleTileKey arguments.
848         if (DEBUG) Log.d(TAG, "onAppWidgetOptionsChanged called for widget: " + appWidgetId);
849         if (mAppWidgetManagerOptional.isEmpty()) {
850             return;
851         }
852 
853         PeopleTileKey optionsKey = AppWidgetOptionsHelper.getPeopleTileKeyFromBundle(newOptions);
854         if (PeopleTileKey.isValid(optionsKey)) {
855             if (DEBUG) {
856                 Log.d(TAG, "PeopleTileKey was present in Options, shortcutId: "
857                         + optionsKey.getShortcutId());
858             }
859             AppWidgetOptionsHelper.removePeopleTileKey(mAppWidgetManagerOptional.get(),
860                     appWidgetId);
861             addNewWidget(appWidgetId, optionsKey);
862         }
863         // Update views for new widget dimensions.
864         updateWidgets(new int[]{appWidgetId});
865     }
866 
867     /** Adds a widget based on {@code key} mapped to {@code appWidgetId}. */
addNewWidget(int appWidgetId, PeopleTileKey key)868     public void addNewWidget(int appWidgetId, PeopleTileKey key) {
869         if (DEBUG) Log.d(TAG, "addNewWidget called with key for appWidgetId: " + appWidgetId);
870         PeopleSpaceTile tile = null;
871         try {
872             tile = getTileFromPersistentStorage(key, appWidgetId,  /* supplementFromStorage= */
873                     false);
874         } catch (PackageManager.NameNotFoundException e) {
875             Log.e(TAG, "Cannot add widget " + appWidgetId + " since app was uninstalled");
876             return;
877         }
878         if (tile == null) {
879             return;
880         }
881         tile = augmentTileFromNotificationEntryManager(tile, Optional.of(appWidgetId));
882 
883         PeopleTileKey existingKeyIfStored;
884         synchronized (mLock) {
885             existingKeyIfStored = getKeyFromStorageByWidgetId(appWidgetId);
886         }
887         // Delete previous storage if the widget already existed and is just reconfigured.
888         if (PeopleTileKey.isValid(existingKeyIfStored)) {
889             if (DEBUG) Log.d(TAG, "Remove previous storage for widget: " + appWidgetId);
890             deleteWidgets(new int[]{appWidgetId});
891         } else {
892             // Widget newly added.
893             mUiEventLogger.log(
894                     PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_ADDED);
895         }
896 
897         synchronized (mLock) {
898             if (DEBUG) Log.d(TAG, "Add storage for : " + key.toString());
899             PeopleSpaceUtils.setSharedPreferencesStorageForTile(mContext, key, appWidgetId,
900                     tile.getContactUri(), mBackupManager);
901         }
902         if (DEBUG) Log.d(TAG, "Ensure listener is registered for widget: " + appWidgetId);
903         registerConversationListenerIfNeeded(appWidgetId, key);
904         try {
905             if (DEBUG) Log.d(TAG, "Caching shortcut for PeopleTile: " + key.toString());
906             mLauncherApps.cacheShortcuts(tile.getPackageName(),
907                     Collections.singletonList(tile.getId()),
908                     tile.getUserHandle(), LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
909         } catch (Exception e) {
910             Log.w(TAG, "failed to cache shortcut for widget " + appWidgetId, e);
911         }
912         PeopleSpaceTile finalTile = tile;
913         mBgExecutor.execute(
914                 () -> updateAppWidgetOptionsAndView(appWidgetId, finalTile));
915     }
916 
917     /** Registers a conversation listener for {@code appWidgetId} if not already registered. */
registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key)918     public void registerConversationListenerIfNeeded(int widgetId, PeopleTileKey key) {
919         // Retrieve storage needed for registration.
920         if (!PeopleTileKey.isValid(key)) {
921             Log.w(TAG, "Invalid tile key registering listener for widget " + widgetId);
922             return;
923         }
924         TileConversationListener newListener = new TileConversationListener();
925         synchronized (mListeners) {
926             if (mListeners.containsKey(key)) {
927                 if (DEBUG) Log.d(TAG, "Already registered listener");
928                 return;
929             }
930             if (DEBUG) Log.d(TAG, "Register listener for " + widgetId + " with " + key.toString());
931             mListeners.put(key, newListener);
932         }
933         mPeopleManager.registerConversationListener(key.getPackageName(),
934                 key.getUserId(),
935                 key.getShortcutId(), newListener,
936                 mContext.getMainExecutor());
937     }
938 
939     /**
940      * Attempts to get a key from storage for {@code widgetId}, returning null if an invalid key is
941      * found.
942      */
getKeyFromStorageByWidgetId(int widgetId)943     private PeopleTileKey getKeyFromStorageByWidgetId(int widgetId) {
944         SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId),
945                 Context.MODE_PRIVATE);
946         PeopleTileKey key = new PeopleTileKey(
947                 widgetSp.getString(SHORTCUT_ID, EMPTY_STRING),
948                 widgetSp.getInt(USER_ID, INVALID_USER_ID),
949                 widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
950         return key;
951     }
952 
953     /** Deletes all storage, listeners, and caching for {@code appWidgetIds}. */
deleteWidgets(int[] appWidgetIds)954     public void deleteWidgets(int[] appWidgetIds) {
955         for (int widgetId : appWidgetIds) {
956             if (DEBUG) Log.d(TAG, "Widget removed: " + widgetId);
957             mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED);
958             // Retrieve storage needed for widget deletion.
959             PeopleTileKey key;
960             Set<String> storedWidgetIdsForKey;
961             String contactUriString;
962             synchronized (mLock) {
963                 SharedPreferences widgetSp = mContext.getSharedPreferences(String.valueOf(widgetId),
964                         Context.MODE_PRIVATE);
965                 key = new PeopleTileKey(
966                         widgetSp.getString(SHORTCUT_ID, null),
967                         widgetSp.getInt(USER_ID, INVALID_USER_ID),
968                         widgetSp.getString(PACKAGE_NAME, null));
969                 if (!PeopleTileKey.isValid(key)) {
970                     Log.e(TAG, "Invalid tile key trying to remove widget " + widgetId);
971                     return;
972                 }
973                 storedWidgetIdsForKey = new HashSet<>(
974                         mSharedPrefs.getStringSet(key.toString(), new HashSet<>()));
975                 contactUriString = mSharedPrefs.getString(String.valueOf(widgetId), null);
976             }
977             synchronized (mLock) {
978                 PeopleSpaceUtils.removeSharedPreferencesStorageForTile(mContext, key, widgetId,
979                         contactUriString);
980             }
981             // If another tile with the conversation is still stored, we need to keep the listener.
982             if (DEBUG) Log.d(TAG, "Stored widget IDs: " + storedWidgetIdsForKey.toString());
983             if (storedWidgetIdsForKey.contains(String.valueOf(widgetId))
984                     && storedWidgetIdsForKey.size() == 1) {
985                 if (DEBUG) Log.d(TAG, "Remove caching and listener");
986                 unregisterConversationListener(key, widgetId);
987                 uncacheConversationShortcut(key);
988             }
989         }
990     }
991 
992     /** Unregisters the conversation listener for {@code appWidgetId}. */
unregisterConversationListener(PeopleTileKey key, int appWidgetId)993     private void unregisterConversationListener(PeopleTileKey key, int appWidgetId) {
994         TileConversationListener registeredListener;
995         synchronized (mListeners) {
996             registeredListener = mListeners.get(key);
997             if (registeredListener == null) {
998                 if (DEBUG) Log.d(TAG, "Cannot find listener to unregister");
999                 return;
1000             }
1001             if (DEBUG) {
1002                 Log.d(TAG, "Unregister listener for " + appWidgetId + " with " + key.toString());
1003             }
1004             mListeners.remove(key);
1005         }
1006         mPeopleManager.unregisterConversationListener(registeredListener);
1007     }
1008 
1009     /** Uncaches the conversation shortcut. */
uncacheConversationShortcut(PeopleTileKey key)1010     private void uncacheConversationShortcut(PeopleTileKey key) {
1011         try {
1012             if (DEBUG) Log.d(TAG, "Uncaching shortcut for PeopleTile: " + key.getShortcutId());
1013             mLauncherApps.uncacheShortcuts(key.getPackageName(),
1014                     Collections.singletonList(key.getShortcutId()),
1015                     UserHandle.of(key.getUserId()),
1016                     LauncherApps.FLAG_CACHE_PEOPLE_TILE_SHORTCUTS);
1017         } catch (Exception e) {
1018             Log.d(TAG, "failed to uncache shortcut", e);
1019         }
1020     }
1021 
1022     /**
1023      * Builds a request to pin a People Tile app widget, with a preview and storing necessary
1024      * information as the callback.
1025      */
requestPinAppWidget(ShortcutInfo shortcutInfo, Bundle options)1026     public boolean requestPinAppWidget(ShortcutInfo shortcutInfo, Bundle options) {
1027         if (DEBUG) Log.d(TAG, "Requesting pin widget, shortcutId: " + shortcutInfo.getId());
1028 
1029         if (mAppWidgetManagerOptional.isEmpty()) {
1030             return false;
1031         }
1032 
1033         RemoteViews widgetPreview = getPreview(shortcutInfo.getId(),
1034                 shortcutInfo.getUserHandle(), shortcutInfo.getPackage(), options);
1035         if (widgetPreview == null) {
1036             Log.w(TAG, "Skipping pinning widget: no tile for shortcutId: " + shortcutInfo.getId());
1037             return false;
1038         }
1039         Bundle extras = new Bundle();
1040         extras.putParcelable(AppWidgetManager.EXTRA_APPWIDGET_PREVIEW, widgetPreview);
1041 
1042         PendingIntent successCallback =
1043                 PeopleSpaceWidgetPinnedReceiver.getPendingIntent(mContext, shortcutInfo);
1044 
1045         ComponentName componentName = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
1046         return mAppWidgetManagerOptional.get().requestPinAppWidget(componentName, extras,
1047                 successCallback);
1048     }
1049 
1050     /** Returns a list of map entries corresponding to user's priority conversations. */
1051     @NonNull
getPriorityTiles()1052     public List<PeopleSpaceTile> getPriorityTiles()
1053             throws Exception {
1054         List<ConversationChannelWrapper> conversations =
1055                 mINotificationManager.getConversations(true).getList();
1056         // Add priority conversations to tiles list.
1057         Stream<ShortcutInfo> priorityConversations = conversations.stream()
1058                 .filter(c -> c.getNotificationChannel() != null
1059                         && c.getNotificationChannel().isImportantConversation())
1060                 .map(c -> c.getShortcutInfo());
1061         List<PeopleSpaceTile> priorityTiles = PeopleSpaceUtils.getSortedTiles(mIPeopleManager,
1062                 mLauncherApps, mUserManager,
1063                 priorityConversations);
1064         return priorityTiles;
1065     }
1066 
1067     /** Returns a list of map entries corresponding to user's recent conversations. */
1068     @NonNull
getRecentTiles()1069     public List<PeopleSpaceTile> getRecentTiles()
1070             throws Exception {
1071         if (DEBUG) Log.d(TAG, "Add recent conversations");
1072         List<ConversationChannelWrapper> conversations =
1073                 mINotificationManager.getConversations(false).getList();
1074         Stream<ShortcutInfo> nonPriorityConversations = conversations.stream()
1075                 .filter(c -> c.getNotificationChannel() == null
1076                         || !c.getNotificationChannel().isImportantConversation())
1077                 .map(c -> c.getShortcutInfo());
1078 
1079         List<ConversationChannel> recentConversationsList =
1080                 mIPeopleManager.getRecentConversations().getList();
1081         Stream<ShortcutInfo> recentConversations = recentConversationsList
1082                 .stream()
1083                 .map(c -> c.getShortcutInfo());
1084 
1085         Stream<ShortcutInfo> mergedStream = Stream.concat(nonPriorityConversations,
1086                 recentConversations);
1087         List<PeopleSpaceTile> recentTiles =
1088                 PeopleSpaceUtils.getSortedTiles(mIPeopleManager, mLauncherApps, mUserManager,
1089                         mergedStream);
1090         return recentTiles;
1091     }
1092 
1093     /**
1094      * Returns a {@link RemoteViews} preview of a Conversation's People Tile. Returns null if one
1095      * is not available.
1096      */
getPreview(String shortcutId, UserHandle userHandle, String packageName, Bundle options)1097     public RemoteViews getPreview(String shortcutId, UserHandle userHandle, String packageName,
1098             Bundle options) {
1099         PeopleSpaceTile tile;
1100         ConversationChannel channel;
1101         try {
1102             channel = mIPeopleManager.getConversation(
1103                     packageName, userHandle.getIdentifier(), shortcutId);
1104             tile = PeopleSpaceUtils.getTile(channel, mLauncherApps);
1105         } catch (Exception e) {
1106             Log.w(TAG, "failed to get conversation or tile", e);
1107             return null;
1108         }
1109         if (tile == null) {
1110             if (DEBUG) Log.i(TAG, "No tile was returned");
1111             return null;
1112         }
1113 
1114         PeopleSpaceTile augmentedTile = augmentTileFromNotificationEntryManager(tile,
1115                 Optional.empty());
1116 
1117         if (DEBUG) Log.i(TAG, "Returning tile preview for shortcutId: " + shortcutId);
1118         return PeopleTileViewHelper.createRemoteViews(mContext, augmentedTile, 0, options,
1119                 new PeopleTileKey(augmentedTile));
1120     }
1121 
1122     protected final BroadcastReceiver mBaseBroadcastReceiver = new BroadcastReceiver() {
1123 
1124         @Override
1125         public void onReceive(Context context, Intent intent) {
1126             if (DEBUG) Log.d(TAG, "Update widgets from: " + intent.getAction());
1127             mBgExecutor.execute(() -> updateWidgetsFromBroadcastInBackground(intent.getAction()));
1128         }
1129     };
1130 
1131     /** Updates any app widget to the current state, triggered by a broadcast update. */
1132     @VisibleForTesting
updateWidgetsFromBroadcastInBackground(String entryPoint)1133     void updateWidgetsFromBroadcastInBackground(String entryPoint) {
1134         if (mAppWidgetManagerOptional.isEmpty()) {
1135             return;
1136         }
1137 
1138         int[] appWidgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
1139                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
1140         if (appWidgetIds == null) {
1141             return;
1142         }
1143         for (int appWidgetId : appWidgetIds) {
1144             if (DEBUG) Log.d(TAG, "Updating widget from broadcast, widget id: " + appWidgetId);
1145             PeopleSpaceTile existingTile = null;
1146             PeopleSpaceTile updatedTile = null;
1147             try {
1148                 synchronized (mLock) {
1149                     existingTile = getTileForExistingWidgetThrowing(appWidgetId);
1150                     if (existingTile == null) {
1151                         Log.e(TAG, "Matching conversation not found for widget "
1152                                 + appWidgetId);
1153                         continue;
1154                     }
1155                     updatedTile = getTileWithCurrentState(existingTile, entryPoint);
1156                     updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
1157                 }
1158             } catch (PackageManager.NameNotFoundException e) {
1159                 // Delete data for uninstalled widgets.
1160                 Log.e(TAG, "Package no longer found for widget " + appWidgetId, e);
1161                 JobScheduler jobScheduler = mContext.getSystemService(JobScheduler.class);
1162                 if (jobScheduler != null
1163                         && jobScheduler.getPendingJob(PeopleBackupFollowUpJob.JOB_ID) != null) {
1164                     if (DEBUG) {
1165                         Log.d(TAG, "Device was recently restored, wait before deleting storage.");
1166                     }
1167                     continue;
1168                 }
1169                 synchronized (mLock) {
1170                     updateAppWidgetOptionsAndView(appWidgetId, updatedTile);
1171                 }
1172                 deleteWidgets(new int[]{appWidgetId});
1173             }
1174         }
1175     }
1176 
1177     /** Checks the current state of {@code tile} dependencies, modifying fields as necessary. */
1178     @Nullable
getTileWithCurrentState(PeopleSpaceTile tile, String entryPoint)1179     private PeopleSpaceTile getTileWithCurrentState(PeopleSpaceTile tile,
1180             String entryPoint) throws
1181             PackageManager.NameNotFoundException {
1182         PeopleSpaceTile.Builder updatedTile = tile.toBuilder();
1183         switch (entryPoint) {
1184             case NotificationManager
1185                     .ACTION_INTERRUPTION_FILTER_CHANGED:
1186                 updatedTile.setNotificationPolicyState(getNotificationPolicyState());
1187                 break;
1188             case Intent.ACTION_PACKAGES_SUSPENDED:
1189             case Intent.ACTION_PACKAGES_UNSUSPENDED:
1190                 updatedTile.setIsPackageSuspended(getPackageSuspended(tile));
1191                 break;
1192             case Intent.ACTION_MANAGED_PROFILE_AVAILABLE:
1193             case Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE:
1194             case Intent.ACTION_USER_UNLOCKED:
1195                 updatedTile.setIsUserQuieted(getUserQuieted(tile));
1196                 break;
1197             case Intent.ACTION_LOCALE_CHANGED:
1198                 break;
1199             case ACTION_BOOT_COMPLETED:
1200             default:
1201                 updatedTile.setIsUserQuieted(getUserQuieted(tile)).setIsPackageSuspended(
1202                         getPackageSuspended(tile)).setNotificationPolicyState(
1203                         getNotificationPolicyState());
1204         }
1205         return updatedTile.build();
1206     }
1207 
getPackageSuspended(PeopleSpaceTile tile)1208     private boolean getPackageSuspended(PeopleSpaceTile tile) throws
1209             PackageManager.NameNotFoundException {
1210         boolean packageSuspended = !TextUtils.isEmpty(tile.getPackageName())
1211                 && mPackageManager.isPackageSuspended(tile.getPackageName());
1212         if (DEBUG) Log.d(TAG, "Package suspended: " + packageSuspended);
1213         // isPackageSuspended() only throws an exception if the app has been uninstalled, and the
1214         // app data has also been cleared. We want to empty the layout when the app is uninstalled
1215         // regardless of app data clearing, which getApplicationInfoAsUser() handles.
1216         mPackageManager.getApplicationInfoAsUser(
1217                 tile.getPackageName(), PackageManager.GET_META_DATA,
1218                 PeopleSpaceUtils.getUserId(tile));
1219         return packageSuspended;
1220     }
1221 
getUserQuieted(PeopleSpaceTile tile)1222     private boolean getUserQuieted(PeopleSpaceTile tile) {
1223         boolean workProfileQuieted =
1224                 tile.getUserHandle() != null && mUserManager.isQuietModeEnabled(
1225                         tile.getUserHandle());
1226         if (DEBUG) Log.d(TAG, "Work profile quiet: " + workProfileQuieted);
1227         return workProfileQuieted;
1228     }
1229 
getNotificationPolicyState()1230     private int getNotificationPolicyState() {
1231         NotificationManager.Policy policy = mNotificationManager.getNotificationPolicy();
1232         boolean suppressVisualEffects =
1233                 NotificationManager.Policy.areAllVisualEffectsSuppressed(
1234                         policy.suppressedVisualEffects);
1235         int notificationPolicyState = 0;
1236         // If the user sees notifications in DND, we do not need to evaluate the current DND
1237         // state, just always show notifications.
1238         if (!suppressVisualEffects) {
1239             if (DEBUG) Log.d(TAG, "Visual effects not suppressed.");
1240             return PeopleSpaceTile.SHOW_CONVERSATIONS;
1241         }
1242         switch (mNotificationManager.getCurrentInterruptionFilter()) {
1243             case INTERRUPTION_FILTER_ALL:
1244                 if (DEBUG) Log.d(TAG, "All interruptions allowed");
1245                 return PeopleSpaceTile.SHOW_CONVERSATIONS;
1246             case INTERRUPTION_FILTER_PRIORITY:
1247                 if (policy.allowConversations()) {
1248                     if (policy.priorityConversationSenders == CONVERSATION_SENDERS_ANYONE) {
1249                         if (DEBUG) Log.d(TAG, "All conversations allowed");
1250                         // We only show conversations, so we can show everything.
1251                         return PeopleSpaceTile.SHOW_CONVERSATIONS;
1252                     } else if (policy.priorityConversationSenders
1253                             == NotificationManager.Policy.CONVERSATION_SENDERS_IMPORTANT) {
1254                         if (DEBUG) Log.d(TAG, "Important conversations allowed");
1255                         notificationPolicyState |= PeopleSpaceTile.SHOW_IMPORTANT_CONVERSATIONS;
1256                     }
1257                 }
1258                 if (policy.allowMessages()) {
1259                     switch (policy.allowMessagesFrom()) {
1260                         case ZenModeConfig.SOURCE_CONTACT:
1261                             if (DEBUG) Log.d(TAG, "All contacts allowed");
1262                             notificationPolicyState |= PeopleSpaceTile.SHOW_CONTACTS;
1263                             return notificationPolicyState;
1264                         case ZenModeConfig.SOURCE_STAR:
1265                             if (DEBUG) Log.d(TAG, "Starred contacts allowed");
1266                             notificationPolicyState |= PeopleSpaceTile.SHOW_STARRED_CONTACTS;
1267                             return notificationPolicyState;
1268                         case ZenModeConfig.SOURCE_ANYONE:
1269                         default:
1270                             if (DEBUG) Log.d(TAG, "All messages allowed");
1271                             return PeopleSpaceTile.SHOW_CONVERSATIONS;
1272                     }
1273                 }
1274                 if (notificationPolicyState != 0) {
1275                     if (DEBUG) Log.d(TAG, "Return block state: " + notificationPolicyState);
1276                     return notificationPolicyState;
1277                 }
1278                 // If only alarms or nothing can bypass DND, the tile shouldn't show conversations.
1279             case INTERRUPTION_FILTER_NONE:
1280             case INTERRUPTION_FILTER_ALARMS:
1281             default:
1282                 if (DEBUG) Log.d(TAG, "Block conversations");
1283                 return PeopleSpaceTile.BLOCK_CONVERSATIONS;
1284         }
1285     }
1286 
1287     /**
1288      * Modifies widgets storage after a restore operation, since widget ids get remapped on restore.
1289      * This is guaranteed to run after the PeopleBackupHelper restore operation.
1290      */
remapWidgets(int[] oldWidgetIds, int[] newWidgetIds)1291     public void remapWidgets(int[] oldWidgetIds, int[] newWidgetIds) {
1292         if (DEBUG) {
1293             Log.d(TAG, "Remapping widgets, old: " + Arrays.toString(oldWidgetIds) + ". new: "
1294                     + Arrays.toString(newWidgetIds));
1295         }
1296 
1297         Map<String, String> widgets = new HashMap<>();
1298         for (int i = 0; i < oldWidgetIds.length; i++) {
1299             widgets.put(String.valueOf(oldWidgetIds[i]), String.valueOf(newWidgetIds[i]));
1300         }
1301 
1302         remapWidgetFiles(widgets);
1303         remapSharedFile(widgets);
1304         remapFollowupFile(widgets);
1305 
1306         if (mAppWidgetManagerOptional.isEmpty()) {
1307             return;
1308         }
1309 
1310         int[] widgetIds = mAppWidgetManagerOptional.get().getAppWidgetIds(
1311                 new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
1312         Bundle b = new Bundle();
1313         b.putBoolean(AppWidgetManager.OPTION_APPWIDGET_RESTORE_COMPLETED, true);
1314         for (int id : widgetIds) {
1315             if (DEBUG) Log.d(TAG, "Setting widget as restored, widget id:" + id);
1316             mAppWidgetManagerOptional.get().updateAppWidgetOptions(id, b);
1317         }
1318 
1319         updateWidgets(widgetIds);
1320     }
1321 
1322     /** Remaps widget ids in widget specific files. */
remapWidgetFiles(Map<String, String> widgets)1323     public void remapWidgetFiles(Map<String, String> widgets) {
1324         if (DEBUG) Log.d(TAG, "Remapping widget files");
1325         Map<String, PeopleTileKey> remapped = new HashMap<>();
1326         for (Map.Entry<String, String> entry : widgets.entrySet()) {
1327             String from = String.valueOf(entry.getKey());
1328             String to = String.valueOf(entry.getValue());
1329             if (Objects.equals(from, to)) {
1330                 continue;
1331             }
1332 
1333             SharedPreferences src = mContext.getSharedPreferences(from, Context.MODE_PRIVATE);
1334             PeopleTileKey key = SharedPreferencesHelper.getPeopleTileKey(src);
1335             if (PeopleTileKey.isValid(key)) {
1336                 if (DEBUG) {
1337                     Log.d(TAG, "Moving PeopleTileKey: " + key.toString() + " from file: "
1338                             + from + ", to file: " + to);
1339                 }
1340                 remapped.put(to, key);
1341                 SharedPreferencesHelper.clear(src);
1342             } else {
1343                 if (DEBUG) Log.d(TAG, "Widget file has invalid key: " + key);
1344             }
1345         }
1346         for (Map.Entry<String, PeopleTileKey> entry : remapped.entrySet()) {
1347             SharedPreferences dest = mContext.getSharedPreferences(
1348                     entry.getKey(), Context.MODE_PRIVATE);
1349             SharedPreferencesHelper.setPeopleTileKey(dest, entry.getValue());
1350         }
1351     }
1352 
1353     /** Remaps widget ids in default shared storage. */
remapSharedFile(Map<String, String> widgets)1354     public void remapSharedFile(Map<String, String> widgets) {
1355         if (DEBUG) Log.d(TAG, "Remapping shared file");
1356         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
1357         SharedPreferences.Editor editor = sp.edit();
1358         Map<String, ?> all = sp.getAll();
1359         for (Map.Entry<String, ?> entry : all.entrySet()) {
1360             String key = entry.getKey();
1361             PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
1362             if (DEBUG) Log.d(TAG, "Remapping key:" + key);
1363             switch (keyType) {
1364                 case WIDGET_ID:
1365                     String newId = widgets.get(key);
1366                     if (TextUtils.isEmpty(newId)) {
1367                         Log.w(TAG, "Key is widget id without matching new id, skipping: " + key);
1368                         break;
1369                     }
1370                     if (DEBUG) Log.d(TAG, "Key is widget id: " + key + ", replace with: " + newId);
1371                     try {
1372                         editor.putString(newId, (String) entry.getValue());
1373                     } catch (Exception e) {
1374                         Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
1375                     }
1376                     editor.remove(key);
1377                     break;
1378                 case PEOPLE_TILE_KEY:
1379                 case CONTACT_URI:
1380                     Set<String> oldWidgetIds;
1381                     try {
1382                         oldWidgetIds = (Set<String>) entry.getValue();
1383                     } catch (Exception e) {
1384                         Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
1385                         editor.remove(key);
1386                         break;
1387                     }
1388                     Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
1389                     if (DEBUG) {
1390                         Log.d(TAG, "Key is PeopleTileKey or contact URI: " + key
1391                                 + ", replace values with new ids: " + newWidgets);
1392                     }
1393                     editor.putStringSet(key, newWidgets);
1394                     break;
1395                 case UNKNOWN:
1396                     Log.e(TAG, "Key not identified:" + key);
1397             }
1398         }
1399         editor.apply();
1400     }
1401 
1402     /** Remaps widget ids in follow-up job file. */
remapFollowupFile(Map<String, String> widgets)1403     public void remapFollowupFile(Map<String, String> widgets) {
1404         if (DEBUG) Log.d(TAG, "Remapping follow up file");
1405         SharedPreferences followUp = mContext.getSharedPreferences(
1406                 SHARED_FOLLOW_UP, Context.MODE_PRIVATE);
1407         SharedPreferences.Editor followUpEditor = followUp.edit();
1408         Map<String, ?> followUpAll = followUp.getAll();
1409         for (Map.Entry<String, ?> entry : followUpAll.entrySet()) {
1410             String key = entry.getKey();
1411             Set<String> oldWidgetIds;
1412             try {
1413                 oldWidgetIds = (Set<String>) entry.getValue();
1414             } catch (Exception e) {
1415                 Log.e(TAG, "malformed entry value: " + entry.getValue(), e);
1416                 followUpEditor.remove(key);
1417                 continue;
1418             }
1419             Set<String> newWidgets = getNewWidgets(oldWidgetIds, widgets);
1420             if (DEBUG) {
1421                 Log.d(TAG, "Follow up key: " + key + ", replace with new ids: " + newWidgets);
1422             }
1423             followUpEditor.putStringSet(key, newWidgets);
1424         }
1425         followUpEditor.apply();
1426     }
1427 
getNewWidgets(Set<String> oldWidgets, Map<String, String> widgetsMapping)1428     private Set<String> getNewWidgets(Set<String> oldWidgets, Map<String, String> widgetsMapping) {
1429         return oldWidgets
1430                 .stream()
1431                 .map(widgetsMapping::get)
1432                 .filter(id -> !TextUtils.isEmpty(id))
1433                 .collect(Collectors.toSet());
1434     }
1435 
1436     @Override
dump(@onNull PrintWriter pw, @NonNull String[] args)1437     public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
1438         Trace.traceBegin(Trace.TRACE_TAG_APP, TAG + ".dump");
1439         SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext);
1440         Map<String, ?> all = sp.getAll();
1441         pw.println("People widget list:");
1442         for (Map.Entry<String, ?> entry : all.entrySet()) {
1443             String key = entry.getKey();
1444             PeopleBackupHelper.SharedFileEntryType keyType = getEntryType(entry);
1445             switch (keyType) {
1446                 case WIDGET_ID:
1447                     SharedPreferences widgetSp = mContext.getSharedPreferences(key,
1448                             Context.MODE_PRIVATE);
1449                     pw.print("People widget (valid) [");
1450                     pw.print(key);
1451                     pw.print("] shortcut id: \"");
1452                     pw.print(widgetSp.getString(SHORTCUT_ID, EMPTY_STRING));
1453                     pw.print("\", user id: ");
1454                     pw.print(widgetSp.getInt(USER_ID, INVALID_USER_ID));
1455                     pw.print(", package: ");
1456                     pw.println(widgetSp.getString(PACKAGE_NAME, EMPTY_STRING));
1457                     break;
1458                 case PEOPLE_TILE_KEY:
1459                 case CONTACT_URI:
1460                     pw.print("Extra data [");
1461                     pw.print(key);
1462                     pw.print(" : ");
1463                     pw.print((Set<String>) entry.getValue());
1464                     pw.println("]");
1465                     break;
1466             }
1467         }
1468 
1469         Trace.traceEnd(Trace.TRACE_TAG_APP);
1470     }
1471 
1472     @VisibleForTesting
updateGeneratedPreviewForUser(UserHandle user)1473     void updateGeneratedPreviewForUser(UserHandle user) {
1474         if (!generatedPreviews() || mUpdatedPreviews.get(user.getIdentifier())
1475                 || !mUserManager.isUserUnlocked(user) || mAppWidgetManagerOptional.isEmpty()) {
1476             return;
1477         }
1478 
1479         // The widget provider may be disabled on SystemUI implementers, e.g. TvSystemUI.
1480         ComponentName provider = new ComponentName(mContext, PeopleSpaceWidgetProvider.class);
1481         List<AppWidgetProviderInfo> infos =
1482                 mAppWidgetManagerOptional.get().getInstalledProvidersForPackage(
1483                         mContext.getPackageName(), user);
1484         if (infos.stream().noneMatch(info -> info.provider.equals(provider))) {
1485             return;
1486         }
1487 
1488         if (DEBUG) {
1489             Log.d(TAG, "Updating People Space widget preview for user " + user.getIdentifier());
1490         }
1491         boolean success = mAppWidgetManagerOptional.get().setWidgetPreview(
1492                 provider, WIDGET_CATEGORY_HOME_SCREEN | WIDGET_CATEGORY_KEYGUARD,
1493                 new RemoteViews(mContext.getPackageName(),
1494                         R.layout.people_space_placeholder_layout));
1495         if (DEBUG && !success) {
1496             Log.d(TAG, "Failed to update generated preview for user " + user.getIdentifier());
1497         }
1498         mUpdatedPreviews.put(user.getIdentifier(), success);
1499     }
1500 }
1501