Loading core/java/android/app/people/PeopleSpaceTile.java +58 −12 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.StatusBarNotification; /** * The People Space tile contains all relevant information to render a tile in People Space: namely Loading @@ -48,7 +47,10 @@ public class PeopleSpaceTile implements Parcelable { private long mLastInteractionTimestamp; private boolean mIsImportantConversation; private boolean mIsHiddenConversation; private StatusBarNotification mNotification; private String mNotificationKey; // TODO: add mNotificationTimestamp private CharSequence mNotificationContent; private Uri mNotificationDataUri; private Intent mIntent; // TODO: add a List of the Status objects once created Loading @@ -62,7 +64,9 @@ public class PeopleSpaceTile implements Parcelable { mLastInteractionTimestamp = b.mLastInteractionTimestamp; mIsImportantConversation = b.mIsImportantConversation; mIsHiddenConversation = b.mIsHiddenConversation; mNotification = b.mNotification; mNotificationKey = b.mNotificationKey; mNotificationContent = b.mNotificationContent; mNotificationDataUri = b.mNotificationDataUri; mIntent = b.mIntent; } Loading Loading @@ -112,10 +116,18 @@ public class PeopleSpaceTile implements Parcelable { /** * If a notification is currently active that maps to the relevant shortcut ID, provides the * {@link StatusBarNotification} associated. * associated notification's key. */ public StatusBarNotification getNotification() { return mNotification; public String getNotificationKey() { return mNotificationKey; } public CharSequence getNotificationContent() { return mNotificationContent; } public Uri getNotificationDataUri() { return mNotificationDataUri; } /** Loading @@ -129,6 +141,22 @@ public class PeopleSpaceTile implements Parcelable { return mIntent; } /** Converts a {@link PeopleSpaceTile} into a {@link PeopleSpaceTile.Builder}. */ public PeopleSpaceTile.Builder toBuilder() { PeopleSpaceTile.Builder builder = new PeopleSpaceTile.Builder(mId, mUserName.toString(), mUserIcon, mIntent); builder.setContactUri(mContactUri); builder.setUid(mUid); builder.setPackageName(mPackageName); builder.setLastInteractionTimestamp(mLastInteractionTimestamp); builder.setIsImportantConversation(mIsImportantConversation); builder.setIsHiddenConversation(mIsHiddenConversation); builder.setNotificationKey(mNotificationKey); builder.setNotificationContent(mNotificationContent); builder.setNotificationDataUri(mNotificationDataUri); return builder; } /** Builder to create a {@link PeopleSpaceTile}. */ public static class Builder { private String mId; Loading @@ -140,7 +168,9 @@ public class PeopleSpaceTile implements Parcelable { private long mLastInteractionTimestamp; private boolean mIsImportantConversation; private boolean mIsHiddenConversation; private StatusBarNotification mNotification; private String mNotificationKey; private CharSequence mNotificationContent; private Uri mNotificationDataUri; private Intent mIntent; /** Builder for use only if a shortcut is not available for the tile. */ Loading Loading @@ -214,9 +244,21 @@ public class PeopleSpaceTile implements Parcelable { return this; } /** Sets the associated notification. */ public Builder setNotification(StatusBarNotification notification) { mNotification = notification; /** Sets the associated notification's key. */ public Builder setNotificationKey(String notificationKey) { mNotificationKey = notificationKey; return this; } /** Sets the associated notification's content. */ public Builder setNotificationContent(CharSequence notificationContent) { mNotificationContent = notificationContent; return this; } /** Sets the associated notification's data URI. */ public Builder setNotificationDataUri(Uri notificationDataUri) { mNotificationDataUri = notificationDataUri; return this; } Loading @@ -242,7 +284,9 @@ public class PeopleSpaceTile implements Parcelable { mLastInteractionTimestamp = in.readLong(); mIsImportantConversation = in.readBoolean(); mIsHiddenConversation = in.readBoolean(); mNotification = in.readParcelable(StatusBarNotification.class.getClassLoader()); mNotificationKey = in.readString(); mNotificationContent = in.readCharSequence(); mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader()); mIntent = in.readParcelable(Intent.class.getClassLoader()); } Loading @@ -259,9 +303,11 @@ public class PeopleSpaceTile implements Parcelable { dest.writeInt(mUid); dest.writeString(mPackageName); dest.writeLong(mLastInteractionTimestamp); dest.writeParcelable(mNotification, flags); dest.writeBoolean(mIsImportantConversation); dest.writeBoolean(mIsHiddenConversation); dest.writeString(mNotificationKey); dest.writeCharSequence(mNotificationContent); dest.writeParcelable(mNotificationDataUri, flags); dest.writeParcelable(mIntent, flags); } Loading core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +24 −10 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; Loading @@ -35,8 +34,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading Loading @@ -186,17 +183,34 @@ public class PeopleSpaceTileTest { } @Test public void testNotification() { Notification notification = new Notification.Builder(mContext, "test").build(); StatusBarNotification sbn = new StatusBarNotification("pkg" /* pkg */, "pkg" /* opPkg */, 1 /* id */, "" /* tag */, 0 /* uid */, 0 /* initialPid */, 0 /* score */, notification, UserHandle.CURRENT, 0 /* postTime */); public void testNotificationKey() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotification(sbn) .setNotificationKey("test") .build(); assertThat(tile.getNotification()).isEqualTo(sbn); assertThat(tile.getNotificationKey()).isEqualTo("test"); } @Test public void testNotificationContent() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotificationContent("test") .build(); assertThat(tile.getNotificationContent()).isEqualTo("test"); } @Test public void testNotificationDataUri() { PeopleSpaceTile tile = new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotificationDataUri(Uri.parse("test")) .build(); assertThat(tile.getNotificationDataUri()).isEqualTo(Uri.parse("test")); } @Test Loading packages/SystemUI/res/layout/people_space_small_avatar_tile.xml +18 −9 Original line number Diff line number Diff line Loading @@ -82,16 +82,25 @@ android:ellipsize="end" /> </LinearLayout> </LinearLayout> <LinearLayout android:background="@drawable/people_space_content_background" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/content" android:paddingVertical="3dp" android:paddingHorizontal="12dp" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" android:background="@drawable/people_space_content_background" android:textSize="14sp" android:layout_width="match_parent" android:layout_height="match_parent" android:maxLines="2" android:ellipsize="end" /> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone"/> </LinearLayout> </LinearLayout> </LinearLayout> No newline at end of file packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +4 −0 Original line number Diff line number Diff line Loading @@ -136,8 +136,12 @@ public class PeopleSpaceActivity extends Activity { editor.putString(String.valueOf(mAppWidgetId), tile.getId()); editor.commit(); 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)); // TODO: Populate new widget with existing conversation notification, if there is any. PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); finishActivity(); Loading packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +181 −51 Original line number Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.systemui.people; import static android.app.Notification.EXTRA_MESSAGES; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; Loading @@ -26,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; Loading @@ -34,20 +36,28 @@ import android.graphics.drawable.Drawable; import android.icu.text.MeasureFormat; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import androidx.preference.PreferenceManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.time.Duration; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Map; Loading @@ -63,6 +73,13 @@ public class PeopleSpaceUtils { private static final int DAYS_IN_A_WEEK = 7; 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"; /** Represents whether {@link StatusBarNotification} was posted or removed. */ public enum NotificationAction { POSTED, REMOVED } /** Returns a list of map entries corresponding to user's conversations. */ public static List<Map.Entry<Long, PeopleSpaceTile>> getTiles( Loading Loading @@ -93,73 +110,40 @@ public class PeopleSpaceUtils { /** Updates {@code appWidgetIds} with their associated conversation stored. */ public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { PackageManager mPackageManager = context.getPackageManager(); IPeopleManager mPeopleManager = IPeopleManager.Stub.asInterface( IPeopleManager peopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); LauncherApps mLauncherApps = context.getSystemService(LauncherApps.class); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); Intent activityIntent = new Intent(context, LaunchConversationActivity.class); activityIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); try { List<Map.Entry<Long, PeopleSpaceTile>> shortcutInfos = PeopleSpaceUtils.getTiles( context, notificationManager, mPeopleManager, mLauncherApps); List<Map.Entry<Long, 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, "Set widget: " + appWidgetId + " with shortcut ID: " + shortcutId); } Optional<Map.Entry<Long, PeopleSpaceTile>> entry = shortcutInfos.stream().filter( Optional<Map.Entry<Long, PeopleSpaceTile>> entry = tiles.stream().filter( e -> e.getValue().getId().equals(shortcutId)).findFirst(); if (!entry.isPresent() || shortcutId == null) { if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); //TODO: Delete app widget id when crash is fixed (b/175486868) continue; } PeopleSpaceTile tile = entry.get().getValue(); RemoteViews views = new RemoteViews(context.getPackageName(), getLayout(tile)); PeopleSpaceTile tile = augmentTileFromStorage(entry.get().getValue(), appWidgetManager, appWidgetId); String status = PeopleSpaceUtils.getLastInteractionString(context, entry.get().getKey()); views.setTextViewText(R.id.status, status); views.setTextViewText(R.id.name, tile.getUserName().toString()); RemoteViews views = createRemoteViews(context, tile, entry.get().getKey(), appWidgetId); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)); views.setImageViewBitmap( R.id.package_icon, PeopleSpaceUtils.convertDrawableToBitmap( mPackageManager.getApplicationIcon(tile.getPackageName()) ) ); views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); // Tell the AppWidgetManager to perform an update on the current app widget. appWidgetManager.updateAppWidget(appWidgetId, views); } } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversations to set tiles"); } Log.e(TAG, "Exception updating single conversation widgets: " + e); } /** Returns the layout ID for the {@code tile}. */ private static int getLayout(PeopleSpaceTile tile) { return tile.getNotification() == null ? R.layout.people_space_large_avatar_tile : R.layout.people_space_small_avatar_tile; } /** Returns a list sorted by ascending last interaction time from {@code stream}. */ Loading @@ -172,6 +156,152 @@ public class PeopleSpaceUtils { .collect(Collectors.toList()); } /** Augment {@link PeopleSpaceTile} with fields from stored tile. */ @VisibleForTesting static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (storedTile == null) { return tile; } return tile.toBuilder() .setNotificationKey(storedTile.getNotificationKey()) .setNotificationContent(storedTile.getNotificationContent()) .setNotificationDataUri(storedTile.getNotificationDataUri()) .build(); } /** If incoming notification changed tile, store the changes in the tile options. */ public static void storeNotificationChange(StatusBarNotification sbn, NotificationAction notificationAction, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn); if (message == null) { if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping."); return; } storedTile = storedTile .toBuilder() .setNotificationKey(sbn.getKey()) .setNotificationContent(message.getText()) .setNotificationDataUri(message.getDataUri()) .build(); } else { if (DEBUG) { Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); } storedTile = storedTile .toBuilder() .setNotificationKey(null) .setNotificationContent(null) .setNotificationDataUri(null) .build(); } Bundle newOptions = new Bundle(); newOptions.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, storedTile); appWidgetManager.updateAppWidgetOptions(appWidgetId, newOptions); } private static RemoteViews createRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { // TODO: If key is null or if text and data uri are null. if (tile.getNotificationKey() == null) { return createLastInteractionRemoteViews(context, tile, lastInteraction, appWidgetId); } return createNotificationRemoteViews(context, tile, lastInteraction, appWidgetId); } private static RemoteViews createLastInteractionRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { RemoteViews views = new RemoteViews( context.getPackageName(), R.layout.people_space_large_avatar_tile); String status = PeopleSpaceUtils.getLastInteractionString( context, lastInteraction); views.setTextViewText(R.id.status, status); views = setCommonRemoteViewsFields(context, views, tile, appWidgetId); return views; } private static RemoteViews createNotificationRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { RemoteViews views = new RemoteViews( context.getPackageName(), R.layout.people_space_small_avatar_tile); Uri image = tile.getNotificationDataUri(); if (image != null) { //TODO: Use NotificationInlineImageCache views.setImageViewUri(R.id.image, image); views.setViewVisibility(R.id.image, View.VISIBLE); views.setViewVisibility(R.id.content, View.GONE); } else { views.setTextViewText(R.id.content, tile.getNotificationContent()); views.setViewVisibility(R.id.content, View.VISIBLE); views.setViewVisibility(R.id.image, View.GONE); } views = setCommonRemoteViewsFields(context, views, tile, appWidgetId); return views; } private static RemoteViews setCommonRemoteViewsFields( Context context, RemoteViews views, PeopleSpaceTile tile, int appWidgetId) throws Exception { views.setTextViewText(R.id.name, tile.getUserName().toString()); views.setImageViewBitmap( R.id.package_icon, PeopleSpaceUtils.convertDrawableToBitmap( context.getPackageManager().getApplicationIcon(tile.getPackageName()) ) ); views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); Intent activityIntent = new Intent(context, LaunchConversationActivity.class); activityIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)); return views; } /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */ public static Notification.MessagingStyle.Message getLastMessagingStyleMessage( StatusBarNotification sbn) { Notification notification = sbn.getNotification(); if (notification == null) { return null; } if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle()) && notification.extras != null) { final Parcelable[] messages = notification.extras.getParcelableArray(EXTRA_MESSAGES); if (!ArrayUtils.isEmpty(messages)) { List<Notification.MessagingStyle.Message> sortedMessages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); sortedMessages.sort(Collections.reverseOrder( Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp))); return sortedMessages.get(0); } } return null; } /** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */ private static Long getLastInteraction(IPeopleManager peopleManager, PeopleSpaceTile tile) { Loading Loading
core/java/android/app/people/PeopleSpaceTile.java +58 −12 Original line number Diff line number Diff line Loading @@ -28,7 +28,6 @@ import android.graphics.drawable.Icon; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; import android.service.notification.StatusBarNotification; /** * The People Space tile contains all relevant information to render a tile in People Space: namely Loading @@ -48,7 +47,10 @@ public class PeopleSpaceTile implements Parcelable { private long mLastInteractionTimestamp; private boolean mIsImportantConversation; private boolean mIsHiddenConversation; private StatusBarNotification mNotification; private String mNotificationKey; // TODO: add mNotificationTimestamp private CharSequence mNotificationContent; private Uri mNotificationDataUri; private Intent mIntent; // TODO: add a List of the Status objects once created Loading @@ -62,7 +64,9 @@ public class PeopleSpaceTile implements Parcelable { mLastInteractionTimestamp = b.mLastInteractionTimestamp; mIsImportantConversation = b.mIsImportantConversation; mIsHiddenConversation = b.mIsHiddenConversation; mNotification = b.mNotification; mNotificationKey = b.mNotificationKey; mNotificationContent = b.mNotificationContent; mNotificationDataUri = b.mNotificationDataUri; mIntent = b.mIntent; } Loading Loading @@ -112,10 +116,18 @@ public class PeopleSpaceTile implements Parcelable { /** * If a notification is currently active that maps to the relevant shortcut ID, provides the * {@link StatusBarNotification} associated. * associated notification's key. */ public StatusBarNotification getNotification() { return mNotification; public String getNotificationKey() { return mNotificationKey; } public CharSequence getNotificationContent() { return mNotificationContent; } public Uri getNotificationDataUri() { return mNotificationDataUri; } /** Loading @@ -129,6 +141,22 @@ public class PeopleSpaceTile implements Parcelable { return mIntent; } /** Converts a {@link PeopleSpaceTile} into a {@link PeopleSpaceTile.Builder}. */ public PeopleSpaceTile.Builder toBuilder() { PeopleSpaceTile.Builder builder = new PeopleSpaceTile.Builder(mId, mUserName.toString(), mUserIcon, mIntent); builder.setContactUri(mContactUri); builder.setUid(mUid); builder.setPackageName(mPackageName); builder.setLastInteractionTimestamp(mLastInteractionTimestamp); builder.setIsImportantConversation(mIsImportantConversation); builder.setIsHiddenConversation(mIsHiddenConversation); builder.setNotificationKey(mNotificationKey); builder.setNotificationContent(mNotificationContent); builder.setNotificationDataUri(mNotificationDataUri); return builder; } /** Builder to create a {@link PeopleSpaceTile}. */ public static class Builder { private String mId; Loading @@ -140,7 +168,9 @@ public class PeopleSpaceTile implements Parcelable { private long mLastInteractionTimestamp; private boolean mIsImportantConversation; private boolean mIsHiddenConversation; private StatusBarNotification mNotification; private String mNotificationKey; private CharSequence mNotificationContent; private Uri mNotificationDataUri; private Intent mIntent; /** Builder for use only if a shortcut is not available for the tile. */ Loading Loading @@ -214,9 +244,21 @@ public class PeopleSpaceTile implements Parcelable { return this; } /** Sets the associated notification. */ public Builder setNotification(StatusBarNotification notification) { mNotification = notification; /** Sets the associated notification's key. */ public Builder setNotificationKey(String notificationKey) { mNotificationKey = notificationKey; return this; } /** Sets the associated notification's content. */ public Builder setNotificationContent(CharSequence notificationContent) { mNotificationContent = notificationContent; return this; } /** Sets the associated notification's data URI. */ public Builder setNotificationDataUri(Uri notificationDataUri) { mNotificationDataUri = notificationDataUri; return this; } Loading @@ -242,7 +284,9 @@ public class PeopleSpaceTile implements Parcelable { mLastInteractionTimestamp = in.readLong(); mIsImportantConversation = in.readBoolean(); mIsHiddenConversation = in.readBoolean(); mNotification = in.readParcelable(StatusBarNotification.class.getClassLoader()); mNotificationKey = in.readString(); mNotificationContent = in.readCharSequence(); mNotificationDataUri = in.readParcelable(Uri.class.getClassLoader()); mIntent = in.readParcelable(Intent.class.getClassLoader()); } Loading @@ -259,9 +303,11 @@ public class PeopleSpaceTile implements Parcelable { dest.writeInt(mUid); dest.writeString(mPackageName); dest.writeLong(mLastInteractionTimestamp); dest.writeParcelable(mNotification, flags); dest.writeBoolean(mIsImportantConversation); dest.writeBoolean(mIsHiddenConversation); dest.writeString(mNotificationKey); dest.writeCharSequence(mNotificationContent); dest.writeParcelable(mNotificationDataUri, flags); dest.writeParcelable(mIntent, flags); } Loading
core/tests/coretests/src/android/app/people/PeopleSpaceTileTest.java +24 −10 Original line number Diff line number Diff line Loading @@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; import android.app.Notification; import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; Loading @@ -35,8 +34,6 @@ import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.net.Uri; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; Loading Loading @@ -186,17 +183,34 @@ public class PeopleSpaceTileTest { } @Test public void testNotification() { Notification notification = new Notification.Builder(mContext, "test").build(); StatusBarNotification sbn = new StatusBarNotification("pkg" /* pkg */, "pkg" /* opPkg */, 1 /* id */, "" /* tag */, 0 /* uid */, 0 /* initialPid */, 0 /* score */, notification, UserHandle.CURRENT, 0 /* postTime */); public void testNotificationKey() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotification(sbn) .setNotificationKey("test") .build(); assertThat(tile.getNotification()).isEqualTo(sbn); assertThat(tile.getNotificationKey()).isEqualTo("test"); } @Test public void testNotificationContent() { PeopleSpaceTile tile = new PeopleSpaceTile .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotificationContent("test") .build(); assertThat(tile.getNotificationContent()).isEqualTo("test"); } @Test public void testNotificationDataUri() { PeopleSpaceTile tile = new PeopleSpaceTile.Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps) .setNotificationDataUri(Uri.parse("test")) .build(); assertThat(tile.getNotificationDataUri()).isEqualTo(Uri.parse("test")); } @Test Loading
packages/SystemUI/res/layout/people_space_small_avatar_tile.xml +18 −9 Original line number Diff line number Diff line Loading @@ -82,16 +82,25 @@ android:ellipsize="end" /> </LinearLayout> </LinearLayout> <LinearLayout android:background="@drawable/people_space_content_background" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/content" android:paddingVertical="3dp" android:paddingHorizontal="12dp" android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem" android:background="@drawable/people_space_content_background" android:textSize="14sp" android:layout_width="match_parent" android:layout_height="match_parent" android:maxLines="2" android:ellipsize="end" /> <ImageView android:id="@+id/image" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone"/> </LinearLayout> </LinearLayout> </LinearLayout> No newline at end of file
packages/SystemUI/src/com/android/systemui/people/PeopleSpaceActivity.java +4 −0 Original line number Diff line number Diff line Loading @@ -136,8 +136,12 @@ public class PeopleSpaceActivity extends Activity { editor.putString(String.valueOf(mAppWidgetId), tile.getId()); editor.commit(); 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)); // TODO: Populate new widget with existing conversation notification, if there is any. PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager, mNotificationManager); finishActivity(); Loading
packages/SystemUI/src/com/android/systemui/people/PeopleSpaceUtils.java +181 −51 Original line number Diff line number Diff line Loading @@ -16,7 +16,10 @@ package com.android.systemui.people; import static android.app.Notification.EXTRA_MESSAGES; import android.app.INotificationManager; import android.app.Notification; import android.app.PendingIntent; import android.app.people.ConversationChannel; import android.app.people.IPeopleManager; Loading @@ -26,7 +29,6 @@ import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; Loading @@ -34,20 +36,28 @@ import android.graphics.drawable.Drawable; import android.icu.text.MeasureFormat; import android.icu.util.Measure; import android.icu.util.MeasureUnit; import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.os.ServiceManager; import android.os.UserHandle; import android.preference.PreferenceManager; import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; import android.service.notification.StatusBarNotification; import android.util.Log; import android.view.View; import android.widget.RemoteViews; import androidx.preference.PreferenceManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.ArrayUtils; import com.android.systemui.R; import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import java.time.Duration; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Locale; import java.util.Map; Loading @@ -63,6 +73,13 @@ public class PeopleSpaceUtils { private static final int DAYS_IN_A_WEEK = 7; 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"; /** Represents whether {@link StatusBarNotification} was posted or removed. */ public enum NotificationAction { POSTED, REMOVED } /** Returns a list of map entries corresponding to user's conversations. */ public static List<Map.Entry<Long, PeopleSpaceTile>> getTiles( Loading Loading @@ -93,73 +110,40 @@ public class PeopleSpaceUtils { /** Updates {@code appWidgetIds} with their associated conversation stored. */ public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds, AppWidgetManager appWidgetManager, INotificationManager notificationManager) { PackageManager mPackageManager = context.getPackageManager(); IPeopleManager mPeopleManager = IPeopleManager.Stub.asInterface( IPeopleManager peopleManager = IPeopleManager.Stub.asInterface( ServiceManager.getService(Context.PEOPLE_SERVICE)); LauncherApps mLauncherApps = context.getSystemService(LauncherApps.class); LauncherApps launcherApps = context.getSystemService(LauncherApps.class); SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); Intent activityIntent = new Intent(context, LaunchConversationActivity.class); activityIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); try { List<Map.Entry<Long, PeopleSpaceTile>> shortcutInfos = PeopleSpaceUtils.getTiles( context, notificationManager, mPeopleManager, mLauncherApps); List<Map.Entry<Long, 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, "Set widget: " + appWidgetId + " with shortcut ID: " + shortcutId); } Optional<Map.Entry<Long, PeopleSpaceTile>> entry = shortcutInfos.stream().filter( Optional<Map.Entry<Long, PeopleSpaceTile>> entry = tiles.stream().filter( e -> e.getValue().getId().equals(shortcutId)).findFirst(); if (!entry.isPresent() || shortcutId == null) { if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID"); //TODO: Delete app widget id when crash is fixed (b/175486868) continue; } PeopleSpaceTile tile = entry.get().getValue(); RemoteViews views = new RemoteViews(context.getPackageName(), getLayout(tile)); PeopleSpaceTile tile = augmentTileFromStorage(entry.get().getValue(), appWidgetManager, appWidgetId); String status = PeopleSpaceUtils.getLastInteractionString(context, entry.get().getKey()); views.setTextViewText(R.id.status, status); views.setTextViewText(R.id.name, tile.getUserName().toString()); RemoteViews views = createRemoteViews(context, tile, entry.get().getKey(), appWidgetId); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)); views.setImageViewBitmap( R.id.package_icon, PeopleSpaceUtils.convertDrawableToBitmap( mPackageManager.getApplicationIcon(tile.getPackageName()) ) ); views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); // Tell the AppWidgetManager to perform an update on the current app widget. appWidgetManager.updateAppWidget(appWidgetId, views); } } catch (Exception e) { Log.e(TAG, "Failed to retrieve conversations to set tiles"); } Log.e(TAG, "Exception updating single conversation widgets: " + e); } /** Returns the layout ID for the {@code tile}. */ private static int getLayout(PeopleSpaceTile tile) { return tile.getNotification() == null ? R.layout.people_space_large_avatar_tile : R.layout.people_space_small_avatar_tile; } /** Returns a list sorted by ascending last interaction time from {@code stream}. */ Loading @@ -172,6 +156,152 @@ public class PeopleSpaceUtils { .collect(Collectors.toList()); } /** Augment {@link PeopleSpaceTile} with fields from stored tile. */ @VisibleForTesting static PeopleSpaceTile augmentTileFromStorage(PeopleSpaceTile tile, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (storedTile == null) { return tile; } return tile.toBuilder() .setNotificationKey(storedTile.getNotificationKey()) .setNotificationContent(storedTile.getNotificationContent()) .setNotificationDataUri(storedTile.getNotificationDataUri()) .build(); } /** If incoming notification changed tile, store the changes in the tile options. */ public static void storeNotificationChange(StatusBarNotification sbn, NotificationAction notificationAction, AppWidgetManager appWidgetManager, int appWidgetId) { Bundle options = appWidgetManager.getAppWidgetOptions(appWidgetId); PeopleSpaceTile storedTile = options.getParcelable(OPTIONS_PEOPLE_SPACE_TILE); if (notificationAction == PeopleSpaceUtils.NotificationAction.POSTED) { if (DEBUG) Log.i(TAG, "Adding notification to storage, appWidgetId: " + appWidgetId); Notification.MessagingStyle.Message message = getLastMessagingStyleMessage(sbn); if (message == null) { if (DEBUG) Log.i(TAG, "Notification doesn't have content, skipping."); return; } storedTile = storedTile .toBuilder() .setNotificationKey(sbn.getKey()) .setNotificationContent(message.getText()) .setNotificationDataUri(message.getDataUri()) .build(); } else { if (DEBUG) { Log.i(TAG, "Removing notification from storage, appWidgetId: " + appWidgetId); } storedTile = storedTile .toBuilder() .setNotificationKey(null) .setNotificationContent(null) .setNotificationDataUri(null) .build(); } Bundle newOptions = new Bundle(); newOptions.putParcelable(OPTIONS_PEOPLE_SPACE_TILE, storedTile); appWidgetManager.updateAppWidgetOptions(appWidgetId, newOptions); } private static RemoteViews createRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { // TODO: If key is null or if text and data uri are null. if (tile.getNotificationKey() == null) { return createLastInteractionRemoteViews(context, tile, lastInteraction, appWidgetId); } return createNotificationRemoteViews(context, tile, lastInteraction, appWidgetId); } private static RemoteViews createLastInteractionRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { RemoteViews views = new RemoteViews( context.getPackageName(), R.layout.people_space_large_avatar_tile); String status = PeopleSpaceUtils.getLastInteractionString( context, lastInteraction); views.setTextViewText(R.id.status, status); views = setCommonRemoteViewsFields(context, views, tile, appWidgetId); return views; } private static RemoteViews createNotificationRemoteViews(Context context, PeopleSpaceTile tile, long lastInteraction, int appWidgetId) throws Exception { RemoteViews views = new RemoteViews( context.getPackageName(), R.layout.people_space_small_avatar_tile); Uri image = tile.getNotificationDataUri(); if (image != null) { //TODO: Use NotificationInlineImageCache views.setImageViewUri(R.id.image, image); views.setViewVisibility(R.id.image, View.VISIBLE); views.setViewVisibility(R.id.content, View.GONE); } else { views.setTextViewText(R.id.content, tile.getNotificationContent()); views.setViewVisibility(R.id.content, View.VISIBLE); views.setViewVisibility(R.id.image, View.GONE); } views = setCommonRemoteViewsFields(context, views, tile, appWidgetId); return views; } private static RemoteViews setCommonRemoteViewsFields( Context context, RemoteViews views, PeopleSpaceTile tile, int appWidgetId) throws Exception { views.setTextViewText(R.id.name, tile.getUserName().toString()); views.setImageViewBitmap( R.id.package_icon, PeopleSpaceUtils.convertDrawableToBitmap( context.getPackageManager().getApplicationIcon(tile.getPackageName()) ) ); views.setImageViewIcon(R.id.person_icon, tile.getUserIcon()); Intent activityIntent = new Intent(context, LaunchConversationActivity.class); activityIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_TILE_ID, tile.getId()); activityIntent.putExtra( PeopleSpaceWidgetProvider.EXTRA_PACKAGE_NAME, tile.getPackageName()); activityIntent.putExtra(PeopleSpaceWidgetProvider.EXTRA_UID, tile.getUid()); views.setOnClickPendingIntent(R.id.item, PendingIntent.getActivity( context, appWidgetId, activityIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE)); return views; } /** Gets the most recent {@link Notification.MessagingStyle.Message} from the notification. */ public static Notification.MessagingStyle.Message getLastMessagingStyleMessage( StatusBarNotification sbn) { Notification notification = sbn.getNotification(); if (notification == null) { return null; } if (Notification.MessagingStyle.class.equals(notification.getNotificationStyle()) && notification.extras != null) { final Parcelable[] messages = notification.extras.getParcelableArray(EXTRA_MESSAGES); if (!ArrayUtils.isEmpty(messages)) { List<Notification.MessagingStyle.Message> sortedMessages = Notification.MessagingStyle.Message.getMessagesFromBundleArray(messages); sortedMessages.sort(Collections.reverseOrder( Comparator.comparing(Notification.MessagingStyle.Message::getTimestamp))); return sortedMessages.get(0); } } return null; } /** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */ private static Long getLastInteraction(IPeopleManager peopleManager, PeopleSpaceTile tile) { Loading