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