Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ebb1c97b authored by Flavio Fiszman's avatar Flavio Fiszman
Browse files

Show notification content in single tile widget.

Test: manual, PeopleSpaceUtilsTest and PeopleSpaceWidgetManagerTest
Change-Id: I2538874672dfc0a0860682e06e3522c17d00b29d
parent 3961654d
Loading
Loading
Loading
Loading
+58 −12
Original line number Original line Diff line number Diff line
@@ -28,7 +28,6 @@ import android.graphics.drawable.Icon;
import android.net.Uri;
import android.net.Uri;
import android.os.Parcel;
import android.os.Parcel;
import android.os.Parcelable;
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
 * The People Space tile contains all relevant information to render a tile in People Space: namely
@@ -48,7 +47,10 @@ public class PeopleSpaceTile implements Parcelable {
    private long mLastInteractionTimestamp;
    private long mLastInteractionTimestamp;
    private boolean mIsImportantConversation;
    private boolean mIsImportantConversation;
    private boolean mIsHiddenConversation;
    private boolean mIsHiddenConversation;
    private StatusBarNotification mNotification;
    private String mNotificationKey;
    // TODO: add mNotificationTimestamp
    private CharSequence mNotificationContent;
    private Uri mNotificationDataUri;
    private Intent mIntent;
    private Intent mIntent;
    // TODO: add a List of the Status objects once created
    // TODO: add a List of the Status objects once created


@@ -62,7 +64,9 @@ public class PeopleSpaceTile implements Parcelable {
        mLastInteractionTimestamp = b.mLastInteractionTimestamp;
        mLastInteractionTimestamp = b.mLastInteractionTimestamp;
        mIsImportantConversation = b.mIsImportantConversation;
        mIsImportantConversation = b.mIsImportantConversation;
        mIsHiddenConversation = b.mIsHiddenConversation;
        mIsHiddenConversation = b.mIsHiddenConversation;
        mNotification = b.mNotification;
        mNotificationKey = b.mNotificationKey;
        mNotificationContent = b.mNotificationContent;
        mNotificationDataUri = b.mNotificationDataUri;
        mIntent = b.mIntent;
        mIntent = b.mIntent;
    }
    }


@@ -112,10 +116,18 @@ public class PeopleSpaceTile implements Parcelable {


    /**
    /**
     * If a notification is currently active that maps to the relevant shortcut ID, provides the
     * 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() {
    public String getNotificationKey() {
        return mNotification;
        return mNotificationKey;
    }

    public CharSequence getNotificationContent() {
        return mNotificationContent;
    }

    public Uri getNotificationDataUri() {
        return mNotificationDataUri;
    }
    }


    /**
    /**
@@ -129,6 +141,22 @@ public class PeopleSpaceTile implements Parcelable {
        return mIntent;
        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}. */
    /** Builder to create a {@link PeopleSpaceTile}. */
    public static class Builder {
    public static class Builder {
        private String mId;
        private String mId;
@@ -140,7 +168,9 @@ public class PeopleSpaceTile implements Parcelable {
        private long mLastInteractionTimestamp;
        private long mLastInteractionTimestamp;
        private boolean mIsImportantConversation;
        private boolean mIsImportantConversation;
        private boolean mIsHiddenConversation;
        private boolean mIsHiddenConversation;
        private StatusBarNotification mNotification;
        private String mNotificationKey;
        private CharSequence mNotificationContent;
        private Uri mNotificationDataUri;
        private Intent mIntent;
        private Intent mIntent;


        /** Builder for use only if a shortcut is not available for the tile. */
        /** Builder for use only if a shortcut is not available for the tile. */
@@ -214,9 +244,21 @@ public class PeopleSpaceTile implements Parcelable {
            return this;
            return this;
        }
        }


        /** Sets the associated notification. */
        /** Sets the associated notification's key. */
        public Builder setNotification(StatusBarNotification notification) {
        public Builder setNotificationKey(String notificationKey) {
            mNotification = notification;
            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;
            return this;
        }
        }


@@ -242,7 +284,9 @@ public class PeopleSpaceTile implements Parcelable {
        mLastInteractionTimestamp = in.readLong();
        mLastInteractionTimestamp = in.readLong();
        mIsImportantConversation = in.readBoolean();
        mIsImportantConversation = in.readBoolean();
        mIsHiddenConversation = 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());
        mIntent = in.readParcelable(Intent.class.getClassLoader());
    }
    }


@@ -259,9 +303,11 @@ public class PeopleSpaceTile implements Parcelable {
        dest.writeInt(mUid);
        dest.writeInt(mUid);
        dest.writeString(mPackageName);
        dest.writeString(mPackageName);
        dest.writeLong(mLastInteractionTimestamp);
        dest.writeLong(mLastInteractionTimestamp);
        dest.writeParcelable(mNotification, flags);
        dest.writeBoolean(mIsImportantConversation);
        dest.writeBoolean(mIsImportantConversation);
        dest.writeBoolean(mIsHiddenConversation);
        dest.writeBoolean(mIsHiddenConversation);
        dest.writeString(mNotificationKey);
        dest.writeCharSequence(mNotificationContent);
        dest.writeParcelable(mNotificationDataUri, flags);
        dest.writeParcelable(mIntent, flags);
        dest.writeParcelable(mIntent, flags);
    }
    }


+24 −10
Original line number Original line Diff line number Diff line
@@ -25,7 +25,6 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.when;


import android.app.Notification;
import android.content.Context;
import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps;
@@ -35,8 +34,6 @@ import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Icon;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.net.Uri;
import android.os.UserHandle;
import android.service.notification.StatusBarNotification;


import androidx.test.InstrumentationRegistry;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import androidx.test.filters.SmallTest;
@@ -186,17 +183,34 @@ public class PeopleSpaceTileTest {
    }
    }


    @Test
    @Test
    public void testNotification() {
    public void testNotificationKey() {
        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 */);
        PeopleSpaceTile tile = new PeopleSpaceTile
        PeopleSpaceTile tile = new PeopleSpaceTile
                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
                .Builder(new ShortcutInfo.Builder(mContext, "123").build(), mLauncherApps)
                .setNotification(sbn)
                .setNotificationKey("test")
                .build();
                .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
    @Test
+18 −9
Original line number Original line Diff line number Diff line
@@ -82,16 +82,25 @@
                    android:ellipsize="end" />
                    android:ellipsize="end" />
            </LinearLayout>
            </LinearLayout>
        </LinearLayout>
        </LinearLayout>
        <LinearLayout
            android:background="@drawable/people_space_content_background"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <TextView
            <TextView
                android:id="@+id/content"
                android:id="@+id/content"
                android:paddingVertical="3dp"
                android:paddingVertical="3dp"
                android:paddingHorizontal="12dp"
                android:paddingHorizontal="12dp"
                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
                android:textAppearance="@*android:style/TextAppearance.DeviceDefault.ListItem"
            android:background="@drawable/people_space_content_background"
                android:textSize="14sp"
                android:textSize="14sp"
                android:layout_width="match_parent"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_height="match_parent"
                android:maxLines="2"
                android:maxLines="2"
                android:ellipsize="end" />
                android:ellipsize="end" />
            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:visibility="gone"/>
        </LinearLayout>
    </LinearLayout>
    </LinearLayout>
</LinearLayout>
</LinearLayout>
 No newline at end of file
+4 −0
Original line number Original line Diff line number Diff line
@@ -136,8 +136,12 @@ public class PeopleSpaceActivity extends Activity {
        editor.putString(String.valueOf(mAppWidgetId), tile.getId());
        editor.putString(String.valueOf(mAppWidgetId), tile.getId());
        editor.commit();
        editor.commit();
        AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
        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(
        int[] widgetIds = appWidgetManager.getAppWidgetIds(
                new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
                new ComponentName(mContext, PeopleSpaceWidgetProvider.class));
        // TODO: Populate new widget with existing conversation notification, if there is any.
        PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
        PeopleSpaceUtils.updateSingleConversationWidgets(mContext, widgetIds, mAppWidgetManager,
                mNotificationManager);
                mNotificationManager);
        finishActivity();
        finishActivity();
+181 −51
Original line number Original line Diff line number Diff line
@@ -16,7 +16,10 @@


package com.android.systemui.people;
package com.android.systemui.people;


import static android.app.Notification.EXTRA_MESSAGES;

import android.app.INotificationManager;
import android.app.INotificationManager;
import android.app.Notification;
import android.app.PendingIntent;
import android.app.PendingIntent;
import android.app.people.ConversationChannel;
import android.app.people.ConversationChannel;
import android.app.people.IPeopleManager;
import android.app.people.IPeopleManager;
@@ -26,7 +29,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Canvas;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.BitmapDrawable;
@@ -34,20 +36,28 @@ import android.graphics.drawable.Drawable;
import android.icu.text.MeasureFormat;
import android.icu.text.MeasureFormat;
import android.icu.util.Measure;
import android.icu.util.Measure;
import android.icu.util.MeasureUnit;
import android.icu.util.MeasureUnit;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
import android.os.ServiceManager;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserHandle;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.provider.Settings;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.StatusBarNotification;
import android.util.Log;
import android.util.Log;
import android.view.View;
import android.widget.RemoteViews;
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.R;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.LaunchConversationActivity;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;
import com.android.systemui.people.widget.PeopleSpaceWidgetProvider;


import java.time.Duration;
import java.time.Duration;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.List;
import java.util.Locale;
import java.util.Locale;
import java.util.Map;
import java.util.Map;
@@ -63,6 +73,13 @@ public class PeopleSpaceUtils {
    private static final int DAYS_IN_A_WEEK = 7;
    private static final int DAYS_IN_A_WEEK = 7;
    private static final int MIN_HOUR = 1;
    private static final int MIN_HOUR = 1;
    private static final int ONE_DAY = 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. */
    /** Returns a list of map entries corresponding to user's conversations. */
    public static List<Map.Entry<Long, PeopleSpaceTile>> getTiles(
    public static List<Map.Entry<Long, PeopleSpaceTile>> getTiles(
@@ -93,73 +110,40 @@ public class PeopleSpaceUtils {
    /** Updates {@code appWidgetIds} with their associated conversation stored. */
    /** Updates {@code appWidgetIds} with their associated conversation stored. */
    public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds,
    public static void updateSingleConversationWidgets(Context context, int[] appWidgetIds,
            AppWidgetManager appWidgetManager, INotificationManager notificationManager) {
            AppWidgetManager appWidgetManager, INotificationManager notificationManager) {
        PackageManager mPackageManager = context.getPackageManager();
        IPeopleManager peopleManager = IPeopleManager.Stub.asInterface(
        IPeopleManager mPeopleManager = IPeopleManager.Stub.asInterface(
                ServiceManager.getService(Context.PEOPLE_SERVICE));
                ServiceManager.getService(Context.PEOPLE_SERVICE));
        LauncherApps mLauncherApps = context.getSystemService(LauncherApps.class);
        LauncherApps launcherApps = context.getSystemService(LauncherApps.class);
        SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
        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 {
        try {
            List<Map.Entry<Long, PeopleSpaceTile>> shortcutInfos =
            List<Map.Entry<Long, PeopleSpaceTile>> tiles =
                    PeopleSpaceUtils.getTiles(
                    PeopleSpaceUtils.getTiles(context, notificationManager,
                            context, notificationManager,
                            peopleManager, launcherApps);
                            mPeopleManager, mLauncherApps);

            for (int appWidgetId : appWidgetIds) {
            for (int appWidgetId : appWidgetIds) {
                String shortcutId = sp.getString(String.valueOf(appWidgetId), null);
                String shortcutId = sp.getString(String.valueOf(appWidgetId), null);
                if (DEBUG) {
                if (DEBUG) {
                    Log.d(TAG, "Set widget: " + appWidgetId + " with shortcut ID: " + shortcutId);
                    Log.d(TAG, "Set widget: " + appWidgetId + " with shortcut ID: " + shortcutId);
                }
                }

                Optional<Map.Entry<Long, PeopleSpaceTile>> entry = tiles.stream().filter(
                Optional<Map.Entry<Long, PeopleSpaceTile>> entry = shortcutInfos.stream().filter(
                        e -> e.getValue().getId().equals(shortcutId)).findFirst();
                        e -> e.getValue().getId().equals(shortcutId)).findFirst();
                if (!entry.isPresent() || shortcutId == null) {
                if (!entry.isPresent() || shortcutId == null) {
                    if (DEBUG) Log.d(TAG, "Matching conversation not found for shortcut ID");
                    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/175486868)
                    continue;
                    continue;
                }
                }
                PeopleSpaceTile tile = entry.get().getValue();
                PeopleSpaceTile tile =
                RemoteViews views = new RemoteViews(context.getPackageName(),
                        augmentTileFromStorage(entry.get().getValue(), appWidgetManager,
                        getLayout(tile));
                                appWidgetId);


                String status = PeopleSpaceUtils.getLastInteractionString(context,
                RemoteViews views = createRemoteViews(context, tile, entry.get().getKey(),
                        entry.get().getKey());
                        appWidgetId);
                views.setTextViewText(R.id.status, status);
                views.setTextViewText(R.id.name, tile.getUserName().toString());


                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.
                // Tell the AppWidgetManager to perform an update on the current app widget.
                appWidgetManager.updateAppWidget(appWidgetId, views);
                appWidgetManager.updateAppWidget(appWidgetId, views);
            }
            }
        } catch (Exception e) {
        } 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}. */
    /** Returns a list sorted by ascending last interaction time from {@code stream}. */
@@ -172,6 +156,152 @@ public class PeopleSpaceUtils {
                .collect(Collectors.toList());
                .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}. */
    /** Returns the last interaction time with the user specified by {@code PeopleSpaceTile}. */
    private static Long getLastInteraction(IPeopleManager peopleManager,
    private static Long getLastInteraction(IPeopleManager peopleManager,
            PeopleSpaceTile tile) {
            PeopleSpaceTile tile) {
Loading