From cdc15d7020154ae05ba9f19172f5b0a191d29ce9 Mon Sep 17 00:00:00 2001 From: Anna Zappone Date: Mon, 12 Jul 2021 20:06:59 +0100 Subject: [PATCH] Prevent any SecurityExceptions on Uri resolution By deferring resolution of the Uri to Launcher, we risk a SecurityException. This is extremely important to avoid as if we the SecurityException is thrown, we can no longer make any updates to the widget. Bug: 192986188 Test: locally with Signal Change-Id: I5421ff5494b405ab9d48afd678fd72dc570a655b --- .../systemui/people/PeopleTileViewHelper.java | 54 +++++++++++++++++-- .../widget/PeopleSpaceWidgetManager.java | 17 +++--- .../widget/PeopleSpaceWidgetManagerTest.java | 6 ++- 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java index 7ab4b6f200ed9..cabfb73b31dc9 100644 --- a/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java +++ b/packages/SystemUI/src/com/android/systemui/people/PeopleTileViewHelper.java @@ -47,6 +47,7 @@ import android.content.Intent; import android.graphics.Bitmap; import android.graphics.ColorMatrix; import android.graphics.ColorMatrixColorFilter; +import android.graphics.ImageDecoder; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.graphics.text.LineBreaker; @@ -59,6 +60,7 @@ import android.text.TextUtils; import android.util.IconDrawableFactory; import android.util.Log; import android.util.Pair; +import android.util.Size; import android.util.SizeF; import android.util.TypedValue; import android.view.Gravity; @@ -79,6 +81,7 @@ import com.android.systemui.people.widget.LaunchConversationActivity; import com.android.systemui.people.widget.PeopleSpaceWidgetProvider; import com.android.systemui.people.widget.PeopleTileKey; +import java.io.IOException; import java.text.NumberFormat; import java.time.Duration; import java.util.ArrayList; @@ -677,15 +680,24 @@ public class PeopleTileViewHelper { RemoteViews views = setViewForContentLayout(new RemoteViews(mContext.getPackageName(), getLayoutForNotificationContent())); CharSequence sender = mTile.getNotificationSender(); - Uri image = mTile.getNotificationDataUri(); - if (image != null) { - // TODO: Use NotificationInlineImageCache - views.setImageViewUri(R.id.image, image); + Uri imageUri = mTile.getNotificationDataUri(); + if (imageUri != null) { String newImageDescription = mContext.getString( R.string.new_notification_image_content_description, mTile.getUserName()); views.setContentDescription(R.id.image, newImageDescription); views.setViewVisibility(R.id.image, View.VISIBLE); views.setViewVisibility(R.id.text_content, View.GONE); + try { + Drawable drawable = resolveImage(imageUri, mContext); + Bitmap bitmap = convertDrawableToBitmap(drawable); + views.setImageViewBitmap(R.id.image, bitmap); + } catch (IOException e) { + Log.e(TAG, "Could not decode image: " + e); + // If we couldn't load the image, show text that we have a new image. + views.setTextViewText(R.id.text_content, newImageDescription); + views.setViewVisibility(R.id.text_content, View.VISIBLE); + views.setViewVisibility(R.id.image, View.GONE); + } } else { setMaxLines(views, !TextUtils.isEmpty(sender)); CharSequence content = mTile.getNotificationContent(); @@ -720,6 +732,40 @@ public class PeopleTileViewHelper { return views; } + private Drawable resolveImage(Uri uri, Context context) throws IOException { + final ImageDecoder.Source source = + ImageDecoder.createSource(context.getContentResolver(), uri); + final Drawable drawable = + ImageDecoder.decodeDrawable(source, (decoder, info, s) -> { + onHeaderDecoded(decoder, info, s); + }); + return drawable; + } + + private static int getPowerOfTwoForSampleRatio(double ratio) { + final int k = Integer.highestOneBit((int) Math.floor(ratio)); + return Math.max(1, k); + } + + private void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info, + ImageDecoder.Source source) { + int widthInPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mWidth, + mContext.getResources().getDisplayMetrics()); + int heightInPx = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mHeight, + mContext.getResources().getDisplayMetrics()); + int maxIconSizeInPx = Math.max(widthInPx, heightInPx); + int minDimen = (int) (1.5 * Math.min(widthInPx, heightInPx)); + if (minDimen < maxIconSizeInPx) { + maxIconSizeInPx = minDimen; + } + final Size size = info.getSize(); + final int originalSize = Math.max(size.getHeight(), size.getWidth()); + final double ratio = (originalSize > maxIconSizeInPx) + ? originalSize * 1f / maxIconSizeInPx + : 1.0; + decoder.setTargetSampleSize(getPowerOfTwoForSampleRatio(ratio)); + } + private void setContentDescriptionForNotificationTextContent(RemoteViews views, CharSequence content, CharSequence sender) { String newTextDescriptionWithNotificationContent = mContext.getString( diff --git a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java index 3320fbda6fe5f..985903435b9a3 100644 --- a/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java +++ b/packages/SystemUI/src/com/android/systemui/people/widget/PeopleSpaceWidgetManager.java @@ -375,7 +375,7 @@ public class PeopleSpaceWidgetManager { widgetSp.getInt(USER_ID, INVALID_USER_ID), widgetSp.getString(PACKAGE_NAME, EMPTY_STRING)); - return getTileFromPersistentStorage(key, appWidgetId); + return getTileFromPersistentStorage(key, appWidgetId, /* supplementFromStorage= */ true); } /** @@ -383,7 +383,8 @@ public class PeopleSpaceWidgetManager { * If a {@link PeopleTileKey} is not provided, fetch one from {@link SharedPreferences}. */ @Nullable - public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId) throws + public PeopleSpaceTile getTileFromPersistentStorage(PeopleTileKey key, int appWidgetId, + boolean supplementFromStorage) throws PackageManager.NameNotFoundException { if (!PeopleTileKey.isValid(key)) { Log.e(TAG, "PeopleTileKey invalid: " + key.toString()); @@ -412,7 +413,8 @@ public class PeopleSpaceWidgetManager { // Supplement with our storage. String contactUri = mSharedPrefs.getString(String.valueOf(appWidgetId), null); - if (contactUri != null && storedTile.build().getContactUri() == null) { + if (supplementFromStorage && contactUri != null + && storedTile.build().getContactUri() == null) { if (DEBUG) Log.d(TAG, "Restore contact uri from storage: " + contactUri); storedTile.setContactUri(Uri.parse(contactUri)); } @@ -811,7 +813,8 @@ public class PeopleSpaceWidgetManager { if (DEBUG) Log.d(TAG, "addNewWidget called with key for appWidgetId: " + appWidgetId); PeopleSpaceTile tile = null; try { - tile = getTileFromPersistentStorage(key, appWidgetId); + tile = getTileFromPersistentStorage(key, appWidgetId, /* supplementFromStorage= */ + false); } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Cannot add widget since app was uninstalled"); return; @@ -850,7 +853,9 @@ public class PeopleSpaceWidgetManager { } catch (Exception e) { Log.w(TAG, "Exception caching shortcut:" + e); } - updateAppWidgetOptionsAndView(appWidgetId, tile); + PeopleSpaceTile finalTile = tile; + mBgExecutor.execute( + () -> updateAppWidgetOptionsAndView(appWidgetId, finalTile)); } /** Registers a conversation listener for {@code appWidgetId} if not already registered. */ @@ -1071,7 +1076,7 @@ public class PeopleSpaceWidgetManager { return; } for (int appWidgetId : appWidgetIds) { - if (DEBUG) Log.d(TAG, "Updating widget from broadcast, widget id: " + appWidgetId); + if (DEBUG) Log.d(TAG, "Updating widget from broadcast, widget id: " + appWidgetId); PeopleSpaceTile existingTile = null; PeopleSpaceTile updatedTile = null; try { diff --git a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java index 05bef4c2aeb79..24c189a853277 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/people/widget/PeopleSpaceWidgetManagerTest.java @@ -1125,7 +1125,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A)); when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(channel); PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A); - PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT); + PeopleSpaceTile tile = mManager + .getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT, true); assertThat(tile.getId()).isEqualTo(key.getShortcutId()); } @@ -1133,7 +1134,8 @@ public class PeopleSpaceWidgetManagerTest extends SysuiTestCase { public void testGetPeopleTileFromPersistentStorageNoConversation() throws Exception { when(mIPeopleManager.getConversation(TEST_PACKAGE_A, 0, SHORTCUT_ID)).thenReturn(null); PeopleTileKey key = new PeopleTileKey(SHORTCUT_ID, 0, TEST_PACKAGE_A); - PeopleSpaceTile tile = mManager.getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT); + PeopleSpaceTile tile = mManager + .getTileFromPersistentStorage(key, WIDGET_ID_WITH_SHORTCUT, false); assertThat(tile).isNull(); }