Loading packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +4 −18 Original line number Diff line number Diff line Loading @@ -24,10 +24,8 @@ import android.app.INotificationManager; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Bundle; Loading @@ -36,12 +34,9 @@ import android.provider.Settings; import android.util.Log; import android.view.ViewGroup; import androidx.preference.PreferenceManager; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.util.List; Loading Loading @@ -128,26 +123,17 @@ public class PeopleSpaceActivity extends Activity { /** Stores the user selected configuration for {@code mAppWidgetId}. */ private void storeWidgetConfiguration(PeopleSpaceTile tile) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences.Editor editor = sp.edit(); if (PeopleSpaceUtils.DEBUG) { Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: " + tile.getId() + " for widget ID: " + mAppWidgetId); } // Ensure updates to app widget can be retrieved from both appWidget Id and tile ID. editor.putString(String.valueOf(mAppWidgetId), tile.getId()); editor.putInt(tile.getId(), mAppWidgetId); editor.apply(); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); Bundle options = new Bundle(); options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, tile); appWidgetManager.updateAppWidgetOptions(mAppWidgetId, options); int[] widgetIds = appWidgetManager.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class)); PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId); int[] widgetIds = new int[mAppWidgetId]; // TODO: Populate new widget with existing conversation notification, if there is any. PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); mPeopleManager); finishActivity(); } Loading packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +182 −49 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.people; import static android.app.Notification.EXTRA_MESSAGES; import android.annotation.Nullable; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; Loading Loading @@ -70,11 +71,13 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; Loading @@ -89,6 +92,13 @@ public class PeopleSpaceUtils { private static final int MIN_HOUR = 1; private static final int ONE_DAY = 1; public static final String OPTIONS_PEOPLE_SPACE_TILE = "options_people_space_tile"; public static final String PACKAGE_NAME = "package_name"; public static final String USER_ID = "user_id"; public static final String SHORTCUT_ID = "shortcut_id"; public static final String EMPTY_STRING = ""; public static final int INVALID_WIDGET_ID = -1; public static final int INVALID_USER_ID = -1; private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); Loading Loading @@ -171,34 +181,18 @@ public class PeopleSpaceUtils { * notification being posted or removed. */ public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { IPeopleManager peopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>(); try { List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(context, notificationManager, peopleManager, launcherApps); for (int appWidgetId : appWidgetIds) { String shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (DEBUG) { Log.d(TAG, "Widget ID: " + appWidgetId + " Shortcut ID: " + shortcutId); } Optional<PeopleSpaceTile> entry = tiles.stream().filter( e -> e.getId().equals(shortcutId)).findFirst(); if (!entry.isPresent() || shortcutId == null) { PeopleSpaceTile tile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, appWidgetId); if (tile == null) { if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); //TODO: Delete app widget id when crash is fixed (b/175486868) //TODO: Delete app widget id when crash is fixed (b/172932636) continue; } // Augment current tile based on stored fields. PeopleSpaceTile tile = augmentTileFromStorage(entry.get(), appWidgetManager, appWidgetId); if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName()); RemoteViews views = createRemoteViews(context, tile, appWidgetId); // Tell the AppWidgetManager to perform an update on the current app widget. Loading @@ -206,35 +200,155 @@ public class PeopleSpaceUtils { widgetIdToTile.put(appWidgetId, tile); } } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversations to set tiles: " + e); } getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds); } /** Augment {@link PeopleSpaceTile} with fields from stored tile. */ @VisibleForTesting static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile, AppWidgetManager appWidgetManager, int appWidgetId) { @Nullable private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, AppWidgetManager appWidgetManager, Context context, int appWidgetId) { try { // Migrate storage for existing users. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (shortcutId == null) { Log.e(TAG, "Cannot restore widget"); return null; } migrateExistingUsersToNewStorage(context, shortcutId, appWidgetId); pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); } // Check if tile is cached. Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (storedTile == null) { PeopleSpaceTile tile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (tile != null) { return tile; } return tile.toBuilder() .setBirthdayText(storedTile.getBirthdayText()) .setNotificationKey(storedTile.getNotificationKey()) .setNotificationContent(storedTile.getNotificationContent()) .setNotificationDataUri(storedTile.getNotificationDataUri()) .build(); // If tile is null, we need to retrieve from persisted storage. if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); if (channel == null) { Log.d(TAG, "Could not retrieve conversation from storage"); return null; } return new PeopleSpaceTile.Builder(channel, launcherApps).build(); } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversation for tile: " + e); return null; } } /** If incoming notification changed tile, store the changes in the tile options. */ public static void storeNotificationChange(StatusBarNotification sbn, /** Best-effort attempts to migrate existing users to the new storage format. */ // TODO: Remove after sufficient time. Temporary migration storage for existing users. private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, int appWidgetId) { try { List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(context, INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)), IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)), context.getSystemService(LauncherApps.class)); Optional<PeopleSpaceTile> entry = tiles.stream().filter( e -> e.getId().equals(shortcutId)).findFirst(); if (entry.isPresent()) { if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); setStorageForTile(context, entry.get(), appWidgetId); } else { Log.e(TAG, "Could not migrate user"); } } catch (Exception e) { Log.e(TAG, "Could not query conversations"); } } /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */ public static void setStorageForTile(Context context, PeopleSpaceTile tile, int appWidgetId) { // Write relevant persisted storage. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName()); widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId()); int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId); widgetEditor.apply(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); editor.putString(String.valueOf(appWidgetId), tile.getId()); String key = PeopleSpaceUtils.getKey(tile.getId(), tile.getPackageName(), userId); // Don't overwrite existing widgets with the same key. Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.add(String.valueOf(appWidgetId)); editor.putStringSet(key, storedWidgetIds); editor.apply(); // Write cached storage. updateAppWidgetOptionsAndView(AppWidgetManager.getInstance(context), context, appWidgetId, tile); } /** Removes stored data when tile is deleted. */ public static void removeStorageForTile(Context context, int widgetId) { SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), Context.MODE_PRIVATE); String packageName = widgetSp.getString(PACKAGE_NAME, null); String shortcutId = widgetSp.getString(SHORTCUT_ID, null); int userId = widgetSp.getInt(USER_ID, -1); // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.remove(String.valueOf(widgetId)); editor.putStringSet(key, storedWidgetIds); editor.remove(String.valueOf(widgetId)); editor.apply(); // Delete all data specifically mapped to widgetId. SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.remove(PACKAGE_NAME); widgetEditor.remove(USER_ID); widgetEditor.remove(SHORTCUT_ID); widgetEditor.apply(); } /** * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the * requested update data. */ public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId, String packageName, int userId) { SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING); int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID); String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING); return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId) && storedUserId == userId; } /** * If incoming notification changed tile, store the changes in the tile options. */ public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager, Context context, StatusBarNotification sbn, NotificationAction notificationAction, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, appWidgetId); if (storedTile == null) { if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); return; Loading Loading @@ -263,7 +377,7 @@ public class PeopleSpaceUtils { .setNotificationDataUri(null) .build(); } updateAppWidgetOptions(appWidgetManager, appWidgetId, storedTile); updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); } private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId, Loading Loading @@ -677,4 +791,23 @@ public class PeopleSpaceUtils { } return lookupKeysWithBirthdaysToday; } /** * Returns the uniquely identifying key for the conversation. * * <p>{@code userId} will always be a number, so we put user ID as the * delimiter between the app-provided strings of shortcut ID and package name. * * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring * a {@code packageName} to always start with a letter. This restriction means we are * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first * case is impossible given the package name restrictions: * <ul> * <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li> * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> * </ul> */ public static String getKey(String shortcutId, String packageName, int userId) { return shortcutId + "/" + userId + "/" + packageName; } } No newline at end of file packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +40 −27 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; import android.app.INotificationManager; import android.app.NotificationChannel; import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; Loading @@ -29,6 +29,7 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.appwidget.IAppWidgetService; Loading @@ -37,7 +38,8 @@ import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import java.util.Objects; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; Loading @@ -51,7 +53,7 @@ public class PeopleSpaceWidgetManager { private final Context mContext; private IAppWidgetService mAppWidgetService; private AppWidgetManager mAppWidgetManager; private INotificationManager mNotificationManager; private IPeopleManager mPeopleManager; @Inject public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) { Loading @@ -59,11 +61,13 @@ public class PeopleSpaceWidgetManager { mContext = context; mAppWidgetService = appWidgetService; mAppWidgetManager = AppWidgetManager.getInstance(context); mNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); } /** Constructor used for testing. */ /** * Constructor used for testing. */ @VisibleForTesting protected PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); Loading @@ -72,16 +76,20 @@ public class PeopleSpaceWidgetManager { ServiceManager.getService(Context.APPWIDGET_SERVICE)); } /** AppWidgetManager setter used for testing. */ /** * AppWidgetManager setter used for testing. */ @VisibleForTesting protected void setAppWidgetManager(IAppWidgetService appWidgetService, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { mAppWidgetService = appWidgetService; mAppWidgetManager = appWidgetManager; mNotificationManager = notificationManager; mPeopleManager = peopleManager; } /** Updates People Space widgets. */ /** * Updates People Space widgets. */ public void updateWidgets() { try { if (DEBUG) Log.d(TAG, "updateWidgets called"); Loading @@ -99,7 +107,7 @@ public class PeopleSpaceWidgetManager { if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); mAppWidgetManager, mPeopleManager); } else { mAppWidgetService .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds, Loading @@ -114,30 +122,38 @@ public class PeopleSpaceWidgetManager { * Check if any existing People tiles match the incoming notification change, and store the * change in the tile if so. */ public void storeNotificationChange(StatusBarNotification sbn, public void updateWidgetWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { if (DEBUG) Log.d(TAG, "storeNotificationChange called"); RemoteViews views = new RemoteViews( mContext.getPackageName(), R.layout.people_space_small_avatar_tile); if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (!showSingleConversation) { return; } try { String sbnShortcutId = sbn.getShortcutId(); if (sbnShortcutId == null) { return; } int[] widgetIds = mAppWidgetService.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { return; } SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); for (int widgetId : widgetIds) { String shortcutId = sp.getString(String.valueOf(widgetId), null); if (!Objects.equals(sbn.getShortcutId(), shortcutId)) { continue; int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(); String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); if (storedWidgetIds.isEmpty()) { return; } for (String widgetIdString : storedWidgetIds) { int widgetId = Integer.parseInt(widgetIdString); if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); PeopleSpaceUtils.storeNotificationChange( PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext, sbn, notificationAction, mAppWidgetManager, widgetId); } } catch (Exception e) { Loading @@ -159,8 +175,7 @@ public class PeopleSpaceWidgetManager { public void onNotificationPosted( StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted"); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.POSTED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); } @Override Loading @@ -169,8 +184,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap ) { if (DEBUG) Log.d(TAG, "onNotificationRemoved"); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override Loading @@ -179,8 +193,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap, int reason) { if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override Loading packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +4 −3 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; import android.app.INotificationManager; import android.app.PendingIntent; import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; Loading Loading @@ -53,8 +53,8 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds, appWidgetManager, INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE))); appWidgetManager, IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE))); return; } // Perform this loop procedure for each App Widget that belongs to this provider Loading Loading @@ -91,6 +91,7 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { for (int widgetId : appWidgetIds) { if (DEBUG) Log.d(TAG, "Widget removed"); mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); PeopleSpaceUtils.removeStorageForTile(context, widgetId); } } Loading packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +0 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +4 −18 Original line number Diff line number Diff line Loading @@ -24,10 +24,8 @@ import android.app.INotificationManager; import android.app.people.IPeopleManager; import android.app.people.PeopleSpaceTile; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.os.Bundle; Loading @@ -36,12 +34,9 @@ import android.provider.Settings; import android.util.Log; import android.view.ViewGroup; import androidx.preference.PreferenceManager; import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.UiEventLoggerImpl; import com.android.systemui.R; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.util.List; Loading Loading @@ -128,26 +123,17 @@ public class PeopleSpaceActivity extends Activity { /** Stores the user selected configuration for {@code mAppWidgetId}. */ private void storeWidgetConfiguration(PeopleSpaceTile tile) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); SharedPreferences.Editor editor = sp.edit(); if (PeopleSpaceUtils.DEBUG) { Log.d(TAG, "Put " + tile.getUserName() + "'s shortcut ID: " + tile.getId() + " for widget ID: " + mAppWidgetId); } // Ensure updates to app widget can be retrieved from both appWidget Id and tile ID. editor.putString(String.valueOf(mAppWidgetId), tile.getId()); editor.putInt(tile.getId(), mAppWidgetId); editor.apply(); AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext); Bundle options = new Bundle(); options.putParcelable(PeopleSpaceUtils.OPTIONS_PEOPLE_SPACE_TILE, tile); appWidgetManager.updateAppWidgetOptions(mAppWidgetId, options); int[] widgetIds = appWidgetManager.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class)); PeopleSpaceUtils.setStorageForTile(mContext, tile, mAppWidgetId); int[] widgetIds = new int[mAppWidgetId]; // TODO: Populate new widget with existing conversation notification, if there is any. PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); mPeopleManager); finishActivity(); } Loading
packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +182 −49 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.people; import static android.app.Notification.EXTRA_MESSAGES; import android.annotation.Nullable; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; Loading Loading @@ -70,11 +71,13 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; Loading @@ -89,6 +92,13 @@ public class PeopleSpaceUtils { private static final int MIN_HOUR = 1; private static final int ONE_DAY = 1; public static final String OPTIONS_PEOPLE_SPACE_TILE = "options_people_space_tile"; public static final String PACKAGE_NAME = "package_name"; public static final String USER_ID = "user_id"; public static final String SHORTCUT_ID = "shortcut_id"; public static final String EMPTY_STRING = ""; public static final int INVALID_WIDGET_ID = -1; public static final int INVALID_USER_ID = -1; private static final Pattern DOUBLE_EXCLAMATION_PATTERN = Pattern.compile("[!][!]+"); private static final Pattern DOUBLE_QUESTION_PATTERN = Pattern.compile("[?][?]+"); Loading Loading @@ -171,34 +181,18 @@ public class PeopleSpaceUtils { * notification being posted or removed. */ public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { IPeopleManager peopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { Map<Integer, PeopleSpaceTile> widgetIdToTile = new HashMap<>(); try { List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(context, notificationManager, peopleManager, launcherApps); for (int appWidgetId : appWidgetIds) { String shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (DEBUG) { Log.d(TAG, "Widget ID: " + appWidgetId + " Shortcut ID: " + shortcutId); } Optional<PeopleSpaceTile> entry = tiles.stream().filter( e -> e.getId().equals(shortcutId)).findFirst(); if (!entry.isPresent() || shortcutId == null) { PeopleSpaceTile tile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, appWidgetId); if (tile == null) { if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); //TODO: Delete app widget id when crash is fixed (b/175486868) //TODO: Delete app widget id when crash is fixed (b/172932636) continue; } // Augment current tile based on stored fields. PeopleSpaceTile tile = augmentTileFromStorage(entry.get(), appWidgetManager, appWidgetId); if (DEBUG) Log.d(TAG, "Widget: " + appWidgetId + ", " + tile.getUserName()); RemoteViews views = createRemoteViews(context, tile, appWidgetId); // Tell the AppWidgetManager to perform an update on the current app widget. Loading @@ -206,35 +200,155 @@ public class PeopleSpaceUtils { widgetIdToTile.put(appWidgetId, tile); } } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversations to set tiles: " + e); } getBirthdaysOnBackgroundThread(context, appWidgetManager, widgetIdToTile, appWidgetIds); } /** Augment {@link PeopleSpaceTile} with fields from stored tile. */ @VisibleForTesting static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile, AppWidgetManager appWidgetManager, int appWidgetId) { @Nullable private static PeopleSpaceTile getPeopleSpaceTile(IPeopleManager peopleManager, AppWidgetManager appWidgetManager, Context context, int appWidgetId) { try { // Migrate storage for existing users. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); String pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); int userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); String shortcutId = widgetSp.getString(SHORTCUT_ID, EMPTY_STRING); if (pkg.isEmpty() || shortcutId.isEmpty() || userId == INVALID_WIDGET_ID) { SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); shortcutId = sp.getString(String.valueOf(appWidgetId), null); if (shortcutId == null) { Log.e(TAG, "Cannot restore widget"); return null; } migrateExistingUsersToNewStorage(context, shortcutId, appWidgetId); pkg = widgetSp.getString(PACKAGE_NAME, EMPTY_STRING); userId = widgetSp.getInt(USER_ID, INVALID_USER_ID); } // Check if tile is cached. Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (storedTile == null) { PeopleSpaceTile tile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (tile != null) { return tile; } return tile.toBuilder() .setBirthdayText(storedTile.getBirthdayText()) .setNotificationKey(storedTile.getNotificationKey()) .setNotificationContent(storedTile.getNotificationContent()) .setNotificationDataUri(storedTile.getNotificationDataUri()) .build(); // If tile is null, we need to retrieve from persisted storage. if (DEBUG) Log.d(TAG, "Retrieving from storage after reboots"); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); ConversationChannel channel = peopleManager.getConversation(pkg, userId, shortcutId); if (channel == null) { Log.d(TAG, "Could not retrieve conversation from storage"); return null; } return new PeopleSpaceTile.Builder(channel, launcherApps).build(); } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversation for tile: " + e); return null; } } /** If incoming notification changed tile, store the changes in the tile options. */ public static void storeNotificationChange(StatusBarNotification sbn, /** Best-effort attempts to migrate existing users to the new storage format. */ // TODO: Remove after sufficient time. Temporary migration storage for existing users. private static void migrateExistingUsersToNewStorage(Context context, String shortcutId, int appWidgetId) { try { List<PeopleSpaceTile> tiles = PeopleSpaceUtils.getTiles(context, INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)), IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)), context.getSystemService(LauncherApps.class)); Optional<PeopleSpaceTile> entry = tiles.stream().filter( e -> e.getId().equals(shortcutId)).findFirst(); if (entry.isPresent()) { if (DEBUG) Log.d(TAG, "Migrate storage for " + entry.get().getUserName()); setStorageForTile(context, entry.get(), appWidgetId); } else { Log.e(TAG, "Could not migrate user"); } } catch (Exception e) { Log.e(TAG, "Could not query conversations"); } } /** Sets all relevant storage for {@code appWidgetId} association to {@code tile}. */ public static void setStorageForTile(Context context, PeopleSpaceTile tile, int appWidgetId) { // Write relevant persisted storage. SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.putString(PeopleSpaceUtils.PACKAGE_NAME, tile.getPackageName()); widgetEditor.putString(PeopleSpaceUtils.SHORTCUT_ID, tile.getId()); int userId = UserHandle.getUserHandleForUid(tile.getUid()).getIdentifier(); widgetEditor.putInt(PeopleSpaceUtils.USER_ID, userId); widgetEditor.apply(); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); editor.putString(String.valueOf(appWidgetId), tile.getId()); String key = PeopleSpaceUtils.getKey(tile.getId(), tile.getPackageName(), userId); // Don't overwrite existing widgets with the same key. Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.add(String.valueOf(appWidgetId)); editor.putStringSet(key, storedWidgetIds); editor.apply(); // Write cached storage. updateAppWidgetOptionsAndView(AppWidgetManager.getInstance(context), context, appWidgetId, tile); } /** Removes stored data when tile is deleted. */ public static void removeStorageForTile(Context context, int widgetId) { SharedPreferences widgetSp = context.getSharedPreferences(String.valueOf(widgetId), Context.MODE_PRIVATE); String packageName = widgetSp.getString(PACKAGE_NAME, null); String shortcutId = widgetSp.getString(SHORTCUT_ID, null); int userId = widgetSp.getInt(USER_ID, -1); // Delete widgetId mapping to key. SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences.Editor editor = sp.edit(); String key = PeopleSpaceUtils.getKey(shortcutId, packageName, userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); storedWidgetIds.remove(String.valueOf(widgetId)); editor.putStringSet(key, storedWidgetIds); editor.remove(String.valueOf(widgetId)); editor.apply(); // Delete all data specifically mapped to widgetId. SharedPreferences.Editor widgetEditor = widgetSp.edit(); widgetEditor.remove(PACKAGE_NAME); widgetEditor.remove(USER_ID); widgetEditor.remove(SHORTCUT_ID); widgetEditor.apply(); } /** * Returns whether the data mapped to app widget specified by {@code appWidgetId} matches the * requested update data. */ public static boolean isCorrectAppWidget(Context context, int appWidgetId, String shortcutId, String packageName, int userId) { SharedPreferences sp = context.getSharedPreferences(String.valueOf(appWidgetId), Context.MODE_PRIVATE); String storedPackage = sp.getString(PACKAGE_NAME, EMPTY_STRING); int storedUserId = sp.getInt(USER_ID, INVALID_USER_ID); String storedShortcutId = sp.getString(SHORTCUT_ID, EMPTY_STRING); return storedPackage.equals(packageName) && storedShortcutId.equals(shortcutId) && storedUserId == userId; } /** * If incoming notification changed tile, store the changes in the tile options. */ public static void updateWidgetWithNotificationChanged(IPeopleManager peopleManager, Context context, StatusBarNotification sbn, NotificationAction notificationAction, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); PeopleSpaceTile storedTile = getPeopleSpaceTile(peopleManager, appWidgetManager, context, appWidgetId); if (storedTile == null) { if (DEBUG) Log.d(TAG, "Could not find stored tile to add notification to"); return; Loading Loading @@ -263,7 +377,7 @@ public class PeopleSpaceUtils { .setNotificationDataUri(null) .build(); } updateAppWidgetOptions(appWidgetManager, appWidgetId, storedTile); updateAppWidgetOptionsAndView(appWidgetManager, context, appWidgetId, storedTile); } private static void updateAppWidgetOptions(AppWidgetManager appWidgetManager, int appWidgetId, Loading Loading @@ -677,4 +791,23 @@ public class PeopleSpaceUtils { } return lookupKeysWithBirthdaysToday; } /** * Returns the uniquely identifying key for the conversation. * * <p>{@code userId} will always be a number, so we put user ID as the * delimiter between the app-provided strings of shortcut ID and package name. * * <p>There aren't restrictions on shortcut ID characters, but there are restrictions requiring * a {@code packageName} to always start with a letter. This restriction means we are * guaranteed to avoid cases like "a/b/0/0/package.name" having two potential keys, as the first * case is impossible given the package name restrictions: * <ul> * <li>"a/b" + "/" + 0 + "/" + "0/packageName"</li> * <li>"a/b/0" + "/" + 0 + "/" + "packageName"</li> * </ul> */ public static String getKey(String shortcutId, String packageName, int userId) { return shortcutId + "/" + userId + "/" + packageName; } } No newline at end of file
packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +40 −27 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; import android.app.INotificationManager; import android.app.NotificationChannel; import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.Context; Loading @@ -29,6 +29,7 @@ import android.provider.Settings; import android.service.notification.NotificationListenerService; import android.service.notification.StatusBarNotification; import android.util.Log; import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.appwidget.IAppWidgetService; Loading @@ -37,7 +38,8 @@ import com.android.systemui.people.PeopleSpaceUtils; import com.android.systemui.statusbar.NotificationListener; import com.android.systemui.statusbar.NotificationListener.NotificationHandler; import java.util.Objects; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; Loading @@ -51,7 +53,7 @@ public class PeopleSpaceWidgetManager { private final Context mContext; private IAppWidgetService mAppWidgetService; private AppWidgetManager mAppWidgetManager; private INotificationManager mNotificationManager; private IPeopleManager mPeopleManager; @Inject public PeopleSpaceWidgetManager(Context context, IAppWidgetService appWidgetService) { Loading @@ -59,11 +61,13 @@ public class PeopleSpaceWidgetManager { mContext = context; mAppWidgetService = appWidgetService; mAppWidgetManager = AppWidgetManager.getInstance(context); mNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); mPeopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); } /** Constructor used for testing. */ /** * Constructor used for testing. */ @VisibleForTesting protected PeopleSpaceWidgetManager(Context context) { if (DEBUG) Log.d(TAG, "constructor"); Loading @@ -72,16 +76,20 @@ public class PeopleSpaceWidgetManager { ServiceManager.getService(Context.APPWIDGET_SERVICE)); } /** AppWidgetManager setter used for testing. */ /** * AppWidgetManager setter used for testing. */ @VisibleForTesting protected void setAppWidgetManager(IAppWidgetService appWidgetService, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { AppWidgetManager appWidgetManager, IPeopleManager peopleManager) { mAppWidgetService = appWidgetService; mAppWidgetManager = appWidgetManager; mNotificationManager = notificationManager; mPeopleManager = peopleManager; } /** Updates People Space widgets. */ /** * Updates People Space widgets. */ public void updateWidgets() { try { if (DEBUG) Log.d(TAG, "updateWidgets called"); Loading @@ -99,7 +107,7 @@ public class PeopleSpaceWidgetManager { if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); mAppWidgetManager, mPeopleManager); } else { mAppWidgetService .notifyAppWidgetViewDataChanged(mContext.getOpPackageName(), widgetIds, Loading @@ -114,30 +122,38 @@ public class PeopleSpaceWidgetManager { * Check if any existing People tiles match the incoming notification change, and store the * change in the tile if so. */ public void storeNotificationChange(StatusBarNotification sbn, public void updateWidgetWithNotificationChanged(StatusBarNotification sbn, PeopleSpaceUtils.NotificationAction notificationAction) { if (DEBUG) Log.d(TAG, "storeNotificationChange called"); RemoteViews views = new RemoteViews( mContext.getPackageName(), R.layout.people_space_small_avatar_tile); if (DEBUG) Log.d(TAG, "updateWidgetWithNotificationChanged called"); boolean showSingleConversation = Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (!showSingleConversation) { return; } try { String sbnShortcutId = sbn.getShortcutId(); if (sbnShortcutId == null) { return; } int[] widgetIds = mAppWidgetService.getAppWidgetIds( new ComponentName(mContext, PeopleSpaceWidgetProvider.class) ); if (widgetIds.length == 0) { return; } SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(mContext); for (int widgetId : widgetIds) { String shortcutId = sp.getString(String.valueOf(widgetId), null); if (!Objects.equals(sbn.getShortcutId(), shortcutId)) { continue; int userId = UserHandle.getUserHandleForUid(sbn.getUid()).getIdentifier(); String key = PeopleSpaceUtils.getKey(sbnShortcutId, sbn.getPackageName(), userId); Set<String> storedWidgetIds = new HashSet<>(sp.getStringSet(key, new HashSet<>())); if (storedWidgetIds.isEmpty()) { return; } for (String widgetIdString : storedWidgetIds) { int widgetId = Integer.parseInt(widgetIdString); if (DEBUG) Log.d(TAG, "Storing notification change, key:" + sbn.getKey()); PeopleSpaceUtils.storeNotificationChange( PeopleSpaceUtils.updateWidgetWithNotificationChanged(mPeopleManager, mContext, sbn, notificationAction, mAppWidgetManager, widgetId); } } catch (Exception e) { Loading @@ -159,8 +175,7 @@ public class PeopleSpaceWidgetManager { public void onNotificationPosted( StatusBarNotification sbn, NotificationListenerService.RankingMap rankingMap) { if (DEBUG) Log.d(TAG, "onNotificationPosted"); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.POSTED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.POSTED); } @Override Loading @@ -169,8 +184,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap ) { if (DEBUG) Log.d(TAG, "onNotificationRemoved"); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override Loading @@ -179,8 +193,7 @@ public class PeopleSpaceWidgetManager { NotificationListenerService.RankingMap rankingMap, int reason) { if (DEBUG) Log.d(TAG, "onNotificationRemoved with reason " + reason); storeNotificationChange(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); updateWidgets(); updateWidgetWithNotificationChanged(sbn, PeopleSpaceUtils.NotificationAction.REMOVED); } @Override Loading
packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetProvider.java +4 −3 Original line number Diff line number Diff line Loading @@ -16,8 +16,8 @@ package com.android.systemui.people.widget; import android.app.INotificationManager; import android.app.PendingIntent; import android.app.people.IPeopleManager; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; Loading Loading @@ -53,8 +53,8 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { Settings.Global.PEOPLE_SPACE_CONVERSATION_TYPE, 0) == 0; if (showSingleConversation) { PeopleSpaceUtils.updateSingleConversationWidgets(context, appWidgetIds, appWidgetManager, INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE))); appWidgetManager, IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE))); return; } // Perform this loop procedure for each App Widget that belongs to this provider Loading Loading @@ -91,6 +91,7 @@ public class PeopleSpaceWidgetProvider extends AppWidgetProvider { for (int widgetId : appWidgetIds) { if (DEBUG) Log.d(TAG, "Widget removed"); mUiEventLogger.log(PeopleSpaceUtils.PeopleSpaceWidgetEvent.PEOPLE_SPACE_WIDGET_DELETED); PeopleSpaceUtils.removeStorageForTile(context, widgetId); } } Loading
packages/SystemUI/tests/src/com/android/systemui/people/PeopleSpaceUtilsTest.java +0 −28 File changed.Preview size limit exceeded, changes collapsed. Show changes