Resolve message/conversation image Uris with the correct user id

Bug: 317503801
Test: atest ExpandableNotificationRowTest
Flag: ACONFIG com.android.systemui.notification_row_user_context DEVELOPMENT
Change-Id: I11c5b39f2d9d8f0788acab43640a6d4abcd5a179
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 2ad7192..ba0cc68 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -405,3 +405,14 @@
    bug: "282007590"
 }
 
+flag {
+    name: "notification_row_user_context"
+    namespace: "systemui"
+    description: "Create a user-specific Context for the ImageResolver in ExpandableNotificationRow"
+        " (based on the NotificationEntry's user)."
+    bug: "317503801"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5eeb066..859ce70 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -40,6 +40,7 @@
 import android.os.Bundle;
 import android.os.SystemProperties;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.util.AttributeSet;
 import android.util.FloatProperty;
 import android.util.IndentingPrintWriter;
@@ -382,7 +383,7 @@
     private boolean mUseIncreasedHeadsUpHeight;
     private float mTranslationWhenRemoved;
     private boolean mWasChildInGroupWhenRemoved;
-    private NotificationInlineImageResolver mImageResolver;
+    private final NotificationInlineImageResolver mImageResolver;
     private BigPictureIconManager mBigPictureIconManager;
     @Nullable
     private OnExpansionChangedListener mExpansionChangedListener;
@@ -1700,13 +1701,43 @@
     }
 
     /**
-     * Constructs an ExpandableNotificationRow.
-     * @param context context passed to image resolver
+     * Constructs an ExpandableNotificationRow. Used by layout inflation.
+     *
+     * @param context passed to image resolver
      * @param attrs attributes used to initialize parent view
      */
     public ExpandableNotificationRow(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mImageResolver = new NotificationInlineImageResolver(context,
+        this(context, attrs, context);
+        if (com.android.systemui.Flags.notificationRowUserContext()) {
+            Log.wtf(TAG, "This constructor shouldn't be called");
+        }
+    }
+
+    /**
+     * Constructs an ExpandableNotificationRow. Used by layout inflation (with a custom {@code
+     * AsyncLayoutFactory} in {@link RowInflaterTask}.
+     *
+     * @param context context context of the view
+     * @param attrs attributes used to initialize parent view
+     * @param entry notification that the row will be associated to (determines the user for the
+     *              ImageResolver)
+     */
+    public ExpandableNotificationRow(Context context, AttributeSet attrs, NotificationEntry entry) {
+        this(context, attrs, userContextForEntry(context, entry));
+    }
+
+    private static Context userContextForEntry(Context base, NotificationEntry entry) {
+        if (base.getUserId() == entry.getSbn().getNormalizedUserId()) {
+            return base;
+        }
+        return base.createContextAsUser(
+                UserHandle.of(entry.getSbn().getNormalizedUserId()), /* flags= */ 0);
+    }
+
+    private ExpandableNotificationRow(Context sysUiContext, AttributeSet attrs,
+            Context userContext) {
+        super(sysUiContext, attrs);
+        mImageResolver = new NotificationInlineImageResolver(userContext,
                 new NotificationInlineImageCache());
         float radius = getResources().getDimension(R.dimen.notification_corner_radius_small);
         mSmallRoundness = radius / getMaxRadius();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
index c620f44..609b15e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java
@@ -31,6 +31,7 @@
 import com.android.internal.widget.ImageResolver;
 import com.android.internal.widget.LocalImageResolver;
 import com.android.internal.widget.MessagingMessage;
+import com.android.systemui.Flags;
 
 import java.util.HashSet;
 import java.util.List;
@@ -66,7 +67,11 @@
      * @param imageCache The implementation of internal cache.
      */
     public NotificationInlineImageResolver(Context context, ImageCache imageCache) {
-        mContext = context.getApplicationContext();
+        if (Flags.notificationRowUserContext()) {
+            mContext = context;
+        } else {
+            mContext = context.getApplicationContext();
+        }
         mImageCache = imageCache;
 
         if (mImageCache != null) {
@@ -76,6 +81,11 @@
         updateMaxImageSizes();
     }
 
+    @VisibleForTesting
+    public Context getContext() {
+        return mContext;
+    }
+
     /**
      * Check if this resolver has its internal cache implementation.
      * @return True if has its internal cache, false otherwise.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
index 8e974c2..9445d56 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java
@@ -17,10 +17,15 @@
 package com.android.systemui.statusbar.notification.row;
 
 import android.content.Context;
+import android.util.AttributeSet;
 import android.util.Log;
 import android.view.View;
 import android.view.ViewGroup;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.asynclayoutinflater.view.AsyncLayoutFactory;
 import androidx.asynclayoutinflater.view.AsyncLayoutInflater;
 
 import com.android.systemui.res.R;
@@ -55,12 +60,40 @@
             mInflateOrigin = new Throwable("inflate requested here");
         }
         mListener = listener;
-        AsyncLayoutInflater inflater = new AsyncLayoutInflater(context);
+        AsyncLayoutInflater inflater = com.android.systemui.Flags.notificationRowUserContext()
+                ? new AsyncLayoutInflater(context, new RowAsyncLayoutInflater(entry))
+                : new AsyncLayoutInflater(context);
         mEntry = entry;
         entry.setInflationTask(this);
         inflater.inflate(R.layout.status_bar_notification_row, parent, this);
     }
 
+    @VisibleForTesting
+    static class RowAsyncLayoutInflater implements AsyncLayoutFactory {
+        private final NotificationEntry mEntry;
+
+        RowAsyncLayoutInflater(NotificationEntry entry) {
+            mEntry = entry;
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(@Nullable View parent, @NonNull String name,
+                @NonNull Context context, @NonNull AttributeSet attrs) {
+            if (name.equals(ExpandableNotificationRow.class.getName())) {
+                return new ExpandableNotificationRow(context, attrs, mEntry);
+            }
+            return null;
+        }
+
+        @Nullable
+        @Override
+        public View onCreateView(@NonNull String name, @NonNull Context context,
+                @NonNull AttributeSet attrs) {
+            return null;
+        }
+    }
+
     @Override
     public void abort() {
         mCancelled = true;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
index e373143..49ba915 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java
@@ -16,10 +16,9 @@
 
 package com.android.systemui.statusbar.notification.row;
 
-import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
-
-import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL;
+import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.PKG;
+import static com.android.systemui.statusbar.notification.row.NotificationTestHelper.USER_HANDLE;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,11 +39,13 @@
 import static org.mockito.Mockito.when;
 
 import android.app.Notification;
-import android.app.NotificationChannel;
+import android.content.Context;
 import android.graphics.Color;
 import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.AnimationDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.UserHandle;
+import android.platform.test.annotations.EnableFlags;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.testing.TestableLooper.RunWithLooper;
@@ -57,6 +58,7 @@
 import com.android.internal.R;
 import com.android.internal.widget.CachingIconView;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
@@ -806,6 +808,27 @@
         assertThat(row.isDrawingAppearAnimation()).isFalse();
     }
 
+    @Test
+    public void imageResolver_sameNotificationUser_usesContext() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
+                USER_HANDLE.getUid(1234), USER_HANDLE);
+
+        assertThat(row.getImageResolver().getContext()).isSameInstanceAs(mContext);
+    }
+
+    @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_NOTIFICATION_ROW_USER_CONTEXT)
+    public void imageResolver_differentNotificationUser_createsUserContext() throws Exception {
+        UserHandle user = new UserHandle(33);
+        Context userContext = new SysuiTestableContext(mContext);
+        mContext.prepareCreateContextAsUser(user, userContext);
+
+        ExpandableNotificationRow row = mNotificationTestHelper.createRow(PKG,
+                user.getUid(1234), user);
+
+        assertThat(row.getImageResolver().getContext()).isSameInstanceAs(userContext);
+    }
+
     private void setDrawableIconsInImageView(CachingIconView icon, Drawable iconDrawable,
             Drawable rightIconDrawable) {
         ImageView iconView = mock(ImageView.class);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index a6bfaa4..c717991 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -569,7 +569,10 @@
             throws Exception {
 
         LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
-                mContext.LAYOUT_INFLATER_SERVICE);
+                Context.LAYOUT_INFLATER_SERVICE);
+        if (com.android.systemui.Flags.notificationRowUserContext()) {
+            inflater.setFactory2(new RowInflaterTask.RowAsyncLayoutInflater(entry));
+        }
         mRow = (ExpandableNotificationRow) inflater.inflate(
                 R.layout.status_bar_notification_row,
                 null /* root */,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
index 2d76027..0358474 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestableContext.java
@@ -14,6 +14,7 @@
 
 package com.android.systemui;
 
+import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,12 +31,15 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.systemui.res.R;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 public class SysuiTestableContext extends TestableContext {
 
     @GuardedBy("mRegisteredReceivers")
     private final Set<BroadcastReceiver> mRegisteredReceivers = new ArraySet<>();
+    private final Map<UserHandle, Context> mContextForUser = new HashMap<>();
 
     public SysuiTestableContext(Context base) {
         super(base);
@@ -153,4 +157,22 @@
         }
         super.unregisterReceiver(receiver);
     }
+
+    /**
+     * Sets a Context object that will be returned as the result of {@link #createContextAsUser}
+     * for a specific {@code user}.
+     */
+    public void prepareCreateContextAsUser(UserHandle user, Context context) {
+        mContextForUser.put(user, context);
+    }
+
+    @Override
+    @NonNull
+    public Context createContextAsUser(UserHandle user, int flags) {
+        Context userContext = mContextForUser.get(user);
+        if (userContext != null) {
+            return userContext;
+        }
+        return super.createContextAsUser(user, flags);
+    }
 }