mirror of
https://github.com/Divested-Mobile/DivestOS-Build.git
synced 2024-10-01 01:35:54 -04:00
264 lines
11 KiB
Diff
264 lines
11 KiB
Diff
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||
|
From: Valentin Iftime <valiiftime@google.com>
|
||
|
Date: Wed, 15 Feb 2023 20:39:44 +0100
|
||
|
Subject: [PATCH] Wait for preloading images to complete before inflating
|
||
|
notifications
|
||
|
|
||
|
NotificationContentInflater waits on SysUiBg thread for images to load, with a timeout
|
||
|
of 1000ms.
|
||
|
|
||
|
Test: 1. Build a test app that posts MessagingStyle notifications with a huge image (8k+) set as data Uri.
|
||
|
2. SystemUi should not ANR
|
||
|
3. adb logcat | grep NotificationInlineImageCache - shows timeout/cancellation logs
|
||
|
|
||
|
Bug: 252766417
|
||
|
Bug: 223859644
|
||
|
|
||
|
(cherry picked from commit 195043f40e46ddcd2fe534a9dac344792d39d91c)
|
||
|
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:733089e71ca4b98417586e593a1fb0e50f3a5c61)
|
||
|
Merged-In: I341db60223214cf2282b5c0270e343e1ce95fa01
|
||
|
Change-Id: I341db60223214cf2282b5c0270e343e1ce95fa01
|
||
|
---
|
||
|
.../row/NotificationContentInflater.java | 22 ++++++-
|
||
|
.../row/NotificationInlineImageCache.java | 24 ++++++--
|
||
|
.../row/NotificationInlineImageResolver.java | 57 ++++++++++++++++++-
|
||
|
3 files changed, 92 insertions(+), 11 deletions(-)
|
||
|
|
||
|
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
|
||
|
index a612a1721c41..29aaf5201412 100644
|
||
|
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
|
||
|
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java
|
||
|
@@ -475,6 +475,7 @@ public class NotificationContentInflater {
|
||
|
CancellationSignal cancellationSignal = new CancellationSignal();
|
||
|
cancellationSignal.setOnCancelListener(
|
||
|
() -> runningInflations.values().forEach(CancellationSignal::cancel));
|
||
|
+
|
||
|
return cancellationSignal;
|
||
|
}
|
||
|
|
||
|
@@ -751,6 +752,7 @@ public class NotificationContentInflater {
|
||
|
public static class AsyncInflationTask extends AsyncTask<Void, Void, InflationProgress>
|
||
|
implements InflationCallback, InflationTask {
|
||
|
|
||
|
+ private static final long IMG_PRELOAD_TIMEOUT_MS = 1000L;
|
||
|
private final StatusBarNotification mSbn;
|
||
|
private final Context mContext;
|
||
|
private final boolean mInflateSynchronously;
|
||
|
@@ -817,9 +819,15 @@ public class NotificationContentInflater {
|
||
|
InflationProgress inflationProgress = createRemoteViews(mReInflateFlags,
|
||
|
recoveredBuilder, mIsLowPriority, mIsChildInGroup, mUsesIncreasedHeight,
|
||
|
mUsesIncreasedHeadsUpHeight, packageContext);
|
||
|
- return inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
|
||
|
+
|
||
|
+ InflationProgress result = inflateSmartReplyViews(inflationProgress, mReInflateFlags, mRow.getEntry(),
|
||
|
mRow.getContext(), packageContext, mRow.getHeadsUpManager(),
|
||
|
mRow.getExistingSmartRepliesAndActions());
|
||
|
+
|
||
|
+ // wait for image resolver to finish preloading
|
||
|
+ mRow.getImageResolver().waitForPreloadedImages(IMG_PRELOAD_TIMEOUT_MS);
|
||
|
+
|
||
|
+ return result;
|
||
|
} catch (Exception e) {
|
||
|
mError = e;
|
||
|
return null;
|
||
|
@@ -842,8 +850,13 @@ public class NotificationContentInflater {
|
||
|
final String ident = sbn.getPackageName() + "/0x"
|
||
|
+ Integer.toHexString(sbn.getId());
|
||
|
Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
|
||
|
- mCallback.handleInflationException(sbn,
|
||
|
- new InflationException("Couldn't inflate contentViews" + e));
|
||
|
+ if (mCallback != null) {
|
||
|
+ mCallback.handleInflationException(sbn,
|
||
|
+ new InflationException("Couldn't inflate contentViews" + e));
|
||
|
+ }
|
||
|
+
|
||
|
+ // Cancel any image loading tasks, not useful any more
|
||
|
+ mRow.getImageResolver().cancelRunningTasks();
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
@@ -877,6 +890,9 @@ public class NotificationContentInflater {
|
||
|
// Notify the resolver that the inflation task has finished,
|
||
|
// try to purge unnecessary cached entries.
|
||
|
mRow.getImageResolver().purgeCache();
|
||
|
+
|
||
|
+ // Cancel any image loading tasks that have not completed at this point
|
||
|
+ mRow.getImageResolver().cancelRunningTasks();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
|
||
|
index 4b0e2ffd5d7f..6fdc8a3dce0b 100644
|
||
|
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
|
||
|
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageCache.java
|
||
|
@@ -21,10 +21,12 @@ import android.net.Uri;
|
||
|
import android.os.AsyncTask;
|
||
|
import android.util.Log;
|
||
|
|
||
|
-import java.io.IOException;
|
||
|
import java.util.Set;
|
||
|
+import java.util.concurrent.CancellationException;
|
||
|
import java.util.concurrent.ConcurrentHashMap;
|
||
|
import java.util.concurrent.ExecutionException;
|
||
|
+import java.util.concurrent.TimeUnit;
|
||
|
+import java.util.concurrent.TimeoutException;
|
||
|
|
||
|
/**
|
||
|
* A cache for inline images of image messages.
|
||
|
@@ -57,12 +59,13 @@ public class NotificationInlineImageCache implements NotificationInlineImageReso
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
- public Drawable get(Uri uri) {
|
||
|
+ public Drawable get(Uri uri, long timeoutMs) {
|
||
|
Drawable result = null;
|
||
|
try {
|
||
|
- result = mCache.get(uri).get();
|
||
|
- } catch (InterruptedException | ExecutionException ex) {
|
||
|
- Log.d(TAG, "get: Failed get image from " + uri);
|
||
|
+ result = mCache.get(uri).get(timeoutMs, TimeUnit.MILLISECONDS);
|
||
|
+ } catch (InterruptedException | ExecutionException
|
||
|
+ | TimeoutException | CancellationException ex) {
|
||
|
+ Log.d(TAG, "get: Failed get image from " + uri + " " + ex);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
@@ -73,6 +76,15 @@ public class NotificationInlineImageCache implements NotificationInlineImageReso
|
||
|
mCache.entrySet().removeIf(entry -> !wantedSet.contains(entry.getKey()));
|
||
|
}
|
||
|
|
||
|
+ @Override
|
||
|
+ public void cancelRunningTasks() {
|
||
|
+ mCache.forEach((key, value) -> {
|
||
|
+ if (value.getStatus() != AsyncTask.Status.FINISHED) {
|
||
|
+ value.cancel(true);
|
||
|
+ }
|
||
|
+ });
|
||
|
+ }
|
||
|
+
|
||
|
private static class PreloadImageTask extends AsyncTask<Uri, Void, Drawable> {
|
||
|
private final NotificationInlineImageResolver mResolver;
|
||
|
|
||
|
@@ -87,7 +99,7 @@ public class NotificationInlineImageCache implements NotificationInlineImageReso
|
||
|
|
||
|
try {
|
||
|
drawable = mResolver.resolveImage(target);
|
||
|
- } catch (IOException | SecurityException ex) {
|
||
|
+ } catch (Exception ex) {
|
||
|
Log.d(TAG, "PreloadImageTask: Resolve failed from " + target, ex);
|
||
|
}
|
||
|
|
||
|
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 a3e13053d169..466be072afdb 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
|
||
|
@@ -23,6 +23,7 @@ import android.graphics.drawable.Drawable;
|
||
|
import android.net.Uri;
|
||
|
import android.os.Bundle;
|
||
|
import android.os.Parcelable;
|
||
|
+import android.os.SystemClock;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import com.android.internal.widget.ImageResolver;
|
||
|
@@ -40,6 +41,9 @@ import java.util.Set;
|
||
|
public class NotificationInlineImageResolver implements ImageResolver {
|
||
|
private static final String TAG = NotificationInlineImageResolver.class.getSimpleName();
|
||
|
|
||
|
+ // Timeout for loading images from ImageCache when calling from UI thread
|
||
|
+ private static final long MAX_UI_THREAD_TIMEOUT_MS = 100L;
|
||
|
+
|
||
|
private final Context mContext;
|
||
|
private final ImageCache mImageCache;
|
||
|
private Set<Uri> mWantedUriSet;
|
||
|
@@ -76,17 +80,37 @@ public class NotificationInlineImageResolver implements ImageResolver {
|
||
|
return LocalImageResolver.resolveImage(uri, mContext);
|
||
|
}
|
||
|
|
||
|
+ /**
|
||
|
+ * Loads an image from the Uri.
|
||
|
+ * This method is synchronous and is usually called from the Main thread.
|
||
|
+ * It will time-out after MAX_UI_THREAD_TIMEOUT_MS.
|
||
|
+ *
|
||
|
+ * @param uri Uri of the target image.
|
||
|
+ * @return drawable of the image, null if loading failed/timeout
|
||
|
+ */
|
||
|
@Override
|
||
|
public Drawable loadImage(Uri uri) {
|
||
|
Drawable result = null;
|
||
|
try {
|
||
|
- result = hasCache() ? mImageCache.get(uri) : resolveImage(uri);
|
||
|
+ if (hasCache()) {
|
||
|
+ result = loadImageFromCache(uri, MAX_UI_THREAD_TIMEOUT_MS);
|
||
|
+ } else {
|
||
|
+ result = resolveImage(uri);
|
||
|
+ }
|
||
|
} catch (IOException | SecurityException ex) {
|
||
|
Log.d(TAG, "loadImage: Can't load image from " + uri, ex);
|
||
|
}
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
+ private Drawable loadImageFromCache(Uri uri, long timeoutMs) {
|
||
|
+ // if the uri isn't currently cached, try caching it first
|
||
|
+ if (!mImageCache.hasEntry(uri)) {
|
||
|
+ mImageCache.preload((uri));
|
||
|
+ }
|
||
|
+ return mImageCache.get(uri, timeoutMs);
|
||
|
+ }
|
||
|
+
|
||
|
/**
|
||
|
* Resolve the message list from specified notification and
|
||
|
* refresh internal cache according to the result.
|
||
|
@@ -158,6 +182,30 @@ public class NotificationInlineImageResolver implements ImageResolver {
|
||
|
return mWantedUriSet;
|
||
|
}
|
||
|
|
||
|
+ /**
|
||
|
+ * Wait for a maximum timeout for images to finish preloading
|
||
|
+ * @param timeoutMs total timeout time
|
||
|
+ */
|
||
|
+ void waitForPreloadedImages(long timeoutMs) {
|
||
|
+ if (!hasCache()) {
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ Set<Uri> preloadedUris = getWantedUriSet();
|
||
|
+ if (preloadedUris != null) {
|
||
|
+ // Decrement remaining timeout after each image check
|
||
|
+ long endTimeMs = SystemClock.elapsedRealtime() + timeoutMs;
|
||
|
+ preloadedUris.forEach(
|
||
|
+ uri -> loadImageFromCache(uri, endTimeMs - SystemClock.elapsedRealtime()));
|
||
|
+ }
|
||
|
+ }
|
||
|
+
|
||
|
+ void cancelRunningTasks() {
|
||
|
+ if (!hasCache()) {
|
||
|
+ return;
|
||
|
+ }
|
||
|
+ mImageCache.cancelRunningTasks();
|
||
|
+ }
|
||
|
+
|
||
|
/**
|
||
|
* A interface for internal cache implementation of this resolver.
|
||
|
*/
|
||
|
@@ -167,7 +215,7 @@ public class NotificationInlineImageResolver implements ImageResolver {
|
||
|
* @param uri The uri of the image.
|
||
|
* @return Drawable of the image.
|
||
|
*/
|
||
|
- Drawable get(Uri uri);
|
||
|
+ Drawable get(Uri uri, long timeoutMs);
|
||
|
|
||
|
/**
|
||
|
* Set the image resolver that actually resolves image from specified uri.
|
||
|
@@ -192,6 +240,11 @@ public class NotificationInlineImageResolver implements ImageResolver {
|
||
|
* Purge unnecessary entries in the cache.
|
||
|
*/
|
||
|
void purge();
|
||
|
+
|
||
|
+ /**
|
||
|
+ * Cancel all unfinished image loading tasks
|
||
|
+ */
|
||
|
+ void cancelRunningTasks();
|
||
|
}
|
||
|
|
||
|
}
|