Loading core/java/android/widget/RemoteViews.java +33 −0 Original line number Diff line number Diff line Loading @@ -413,6 +413,13 @@ public class RemoteViews implements Parcelable, Filter { /** Class cookies of the Parcel this instance was read from. */ private Map<Class, Object> mClassCookies; /** * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance * used by this class. */ @Nullable private LayoutInflater.Factory2 mLayoutInflaterFactory2; private static final InteractionHandler DEFAULT_INTERACTION_HANDLER = (view, pendingIntent, response) -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); Loading @@ -431,6 +438,29 @@ public class RemoteViews implements Parcelable, Filter { mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); } /** * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used * by this class instance. It has to be set before the views are inflated to have any effect. * * The factory callbacks will be called on the background thread so the implementation needs * to be thread safe. * * @hide */ public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) { mLayoutInflaterFactory2 = factory; } /** * Returns currently set {@link LayoutInflater.Factory2}. * * @hide */ @Nullable public LayoutInflater.Factory2 getLayoutInflaterFactory() { return mLayoutInflaterFactory2; } /** * Reduces all images and ensures that they are all below the given sizes. * Loading Loading @@ -5659,6 +5689,9 @@ public class RemoteViews implements Parcelable, Filter { // we don't add a filter to the static version returned by getSystemService. inflater = inflater.cloneInContext(inflationContext); inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this); if (mLayoutInflaterFactory2 != null) { inflater.setFactory2(mLayoutInflaterFactory2); } View v = inflater.inflate(rv.getLayoutId(), parent, false); if (mViewId != View.NO_ID) { v.setId(mViewId); Loading core/tests/coretests/src/android/widget/RemoteViewsTest.java +72 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; Loading @@ -39,11 +40,15 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; import android.os.Parcel; import android.util.AttributeSet; import android.util.SizeF; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading Loading @@ -827,4 +832,71 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon3S.getUri())); verify(visitor, times(1)).accept(eq(icon4S.getUri())); } @Test public void layoutInflaterFactory_nothingSet_returnsNull() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); assertNull(rv.getLayoutInflaterFactory()); } @Test public void layoutInflaterFactory_replacesImageView_viewReplaced() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); final View replacement = new FrameLayout(mContext); replacement.setId(1337); LayoutInflater.Factory2 factory = createLayoutInflaterFactory("ImageView", replacement); rv.setLayoutInflaterFactory(factory); // Now inflate the views. View inflated = rv.apply(mContext, mContainer); assertEquals(factory, rv.getLayoutInflaterFactory()); View replacedFrameLayout = inflated.findViewById(1337); assertNotNull(replacedFrameLayout); assertEquals(replacement, replacedFrameLayout); // ImageView should be fully replaced. assertNull(inflated.findViewById(R.id.image)); } @Test public void layoutInflaterFactory_replacesImageView_settersStillFunctional() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); final TextView replacement = new TextView(mContext); replacement.setId(R.id.text); final String testText = "testText"; rv.setLayoutInflaterFactory(createLayoutInflaterFactory("TextView", replacement)); rv.setTextViewText(R.id.text, testText); // Now inflate the views. View inflated = rv.apply(mContext, mContainer); TextView replacedTextView = inflated.findViewById(R.id.text); assertSame(replacement, replacedTextView); assertEquals(testText, replacedTextView.getText()); } private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace, View replacementView) { return new LayoutInflater.Factory2() { @Nullable @Override public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (viewTypeToReplace.equals(name)) { return replacementView; } return null; } @Nullable @Override public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { return null; } }; } } services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.RemoteViews; Loading Loading @@ -95,7 +96,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // Types that we can't really produce. No methods receiving these parameters will be invoked. private static final ImmutableSet<Class<?>> UNUSABLE_TYPES = ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class, PrintWriter.class, Resources.Theme.class, View.class); PrintWriter.class, Resources.Theme.class, View.class, LayoutInflater.Factory2.class); // Maximum number of times we allow generating the same class recursively. // E.g. new RemoteViews.addView(new RemoteViews()) but stop there. Loading Loading
core/java/android/widget/RemoteViews.java +33 −0 Original line number Diff line number Diff line Loading @@ -413,6 +413,13 @@ public class RemoteViews implements Parcelable, Filter { /** Class cookies of the Parcel this instance was read from. */ private Map<Class, Object> mClassCookies; /** * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance * used by this class. */ @Nullable private LayoutInflater.Factory2 mLayoutInflaterFactory2; private static final InteractionHandler DEFAULT_INTERACTION_HANDLER = (view, pendingIntent, response) -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); Loading @@ -431,6 +438,29 @@ public class RemoteViews implements Parcelable, Filter { mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); } /** * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used * by this class instance. It has to be set before the views are inflated to have any effect. * * The factory callbacks will be called on the background thread so the implementation needs * to be thread safe. * * @hide */ public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) { mLayoutInflaterFactory2 = factory; } /** * Returns currently set {@link LayoutInflater.Factory2}. * * @hide */ @Nullable public LayoutInflater.Factory2 getLayoutInflaterFactory() { return mLayoutInflaterFactory2; } /** * Reduces all images and ensures that they are all below the given sizes. * Loading Loading @@ -5659,6 +5689,9 @@ public class RemoteViews implements Parcelable, Filter { // we don't add a filter to the static version returned by getSystemService. inflater = inflater.cloneInContext(inflationContext); inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this); if (mLayoutInflaterFactory2 != null) { inflater.setFactory2(mLayoutInflaterFactory2); } View v = inflater.inflate(rv.getLayoutId(), parent, false); if (mViewId != View.NO_ID) { v.setId(mViewId); Loading
core/tests/coretests/src/android/widget/RemoteViewsTest.java +72 −0 Original line number Diff line number Diff line Loading @@ -22,6 +22,7 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.spy; Loading @@ -39,11 +40,15 @@ import android.os.AsyncTask; import android.os.Binder; import android.os.Looper; import android.os.Parcel; import android.util.AttributeSet; import android.util.SizeF; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; Loading Loading @@ -827,4 +832,71 @@ public class RemoteViewsTest { verify(visitor, times(1)).accept(eq(icon3S.getUri())); verify(visitor, times(1)).accept(eq(icon4S.getUri())); } @Test public void layoutInflaterFactory_nothingSet_returnsNull() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); assertNull(rv.getLayoutInflaterFactory()); } @Test public void layoutInflaterFactory_replacesImageView_viewReplaced() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); final View replacement = new FrameLayout(mContext); replacement.setId(1337); LayoutInflater.Factory2 factory = createLayoutInflaterFactory("ImageView", replacement); rv.setLayoutInflaterFactory(factory); // Now inflate the views. View inflated = rv.apply(mContext, mContainer); assertEquals(factory, rv.getLayoutInflaterFactory()); View replacedFrameLayout = inflated.findViewById(1337); assertNotNull(replacedFrameLayout); assertEquals(replacement, replacedFrameLayout); // ImageView should be fully replaced. assertNull(inflated.findViewById(R.id.image)); } @Test public void layoutInflaterFactory_replacesImageView_settersStillFunctional() { final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); final TextView replacement = new TextView(mContext); replacement.setId(R.id.text); final String testText = "testText"; rv.setLayoutInflaterFactory(createLayoutInflaterFactory("TextView", replacement)); rv.setTextViewText(R.id.text, testText); // Now inflate the views. View inflated = rv.apply(mContext, mContainer); TextView replacedTextView = inflated.findViewById(R.id.text); assertSame(replacement, replacedTextView); assertEquals(testText, replacedTextView.getText()); } private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace, View replacementView) { return new LayoutInflater.Factory2() { @Nullable @Override public View onCreateView(@Nullable View parent, @NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { if (viewTypeToReplace.equals(name)) { return replacementView; } return null; } @Nullable @Override public View onCreateView(@NonNull String name, @NonNull Context context, @NonNull AttributeSet attrs) { return null; } }; } }
services/tests/uiservicestests/src/com/android/server/notification/NotificationVisitUrisTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -34,6 +34,7 @@ import android.os.Bundle; import android.os.IBinder; import android.os.Parcel; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.RemoteViews; Loading Loading @@ -95,7 +96,8 @@ public class NotificationVisitUrisTest extends UiServiceTestCase { // Types that we can't really produce. No methods receiving these parameters will be invoked. private static final ImmutableSet<Class<?>> UNUSABLE_TYPES = ImmutableSet.of(Consumer.class, IBinder.class, MediaSession.Token.class, Parcel.class, PrintWriter.class, Resources.Theme.class, View.class); PrintWriter.class, Resources.Theme.class, View.class, LayoutInflater.Factory2.class); // Maximum number of times we allow generating the same class recursively. // E.g. new RemoteViews.addView(new RemoteViews()) but stop there. Loading