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
This commit is contained in:
Anna Zappone
2021-07-12 20:06:59 +01:00
parent 50c03ea7ed
commit cdc15d7020
3 changed files with 65 additions and 12 deletions

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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();
}