summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/com/android/internal/widget/LocalImageResolver.java80
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt72
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt5
-rw-r--r--packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolver.java18
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java28
5 files changed, 114 insertions, 89 deletions
diff --git a/core/java/com/android/internal/widget/LocalImageResolver.java b/core/java/com/android/internal/widget/LocalImageResolver.java
index b4e108faee2d..3f205c785258 100644
--- a/core/java/com/android/internal/widget/LocalImageResolver.java
+++ b/core/java/com/android/internal/widget/LocalImageResolver.java
@@ -16,69 +16,61 @@
package com.android.internal.widget;
-import android.annotation.Nullable;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-import android.graphics.drawable.BitmapDrawable;
+import android.graphics.ImageDecoder;
+import android.graphics.drawable.AnimatedImageDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.util.Log;
+import android.util.Size;
import java.io.IOException;
-import java.io.InputStream;
-/**
- * A class to extract Bitmaps from a MessagingStyle message.
- */
+/** A class to extract Drawables from a MessagingStyle/ConversationStyle message. */
public class LocalImageResolver {
private static final String TAG = LocalImageResolver.class.getSimpleName();
private static final int MAX_SAFE_ICON_SIZE_PX = 480;
- @Nullable
public static Drawable resolveImage(Uri uri, Context context) throws IOException {
- BitmapFactory.Options onlyBoundsOptions = getBoundsOptionsForImage(uri, context);
- if ((onlyBoundsOptions.outWidth == -1) || (onlyBoundsOptions.outHeight == -1)) {
- return null;
- }
-
- int originalSize =
- (onlyBoundsOptions.outHeight > onlyBoundsOptions.outWidth)
- ? onlyBoundsOptions.outHeight
- : onlyBoundsOptions.outWidth;
-
- double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
- ? (originalSize / MAX_SAFE_ICON_SIZE_PX)
- : 1.0;
-
- BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
- bitmapOptions.inSampleSize = getPowerOfTwoForSampleRatio(ratio);
- InputStream input = context.getContentResolver().openInputStream(uri);
- Bitmap bitmap = BitmapFactory.decodeStream(input, null, bitmapOptions);
- input.close();
- return new BitmapDrawable(context.getResources(), bitmap);
+ final ImageDecoder.Source source =
+ ImageDecoder.createSource(context.getContentResolver(), uri);
+ final Drawable drawable =
+ ImageDecoder.decodeDrawable(source, LocalImageResolver::onHeaderDecoded);
+ return drawable;
}
- private static BitmapFactory.Options getBoundsOptionsForImage(Uri uri, Context context)
+ public static Drawable resolveImage(Uri uri, Context context, int maxWidth, int maxHeight)
throws IOException {
- BitmapFactory.Options onlyBoundsOptions = new BitmapFactory.Options();
- try (InputStream input = context.getContentResolver().openInputStream(uri)) {
- if (input == null) {
- throw new IllegalArgumentException();
+ final ImageDecoder.Source source =
+ ImageDecoder.createSource(context.getContentResolver(), uri);
+ return ImageDecoder.decodeDrawable(source, (decoder, info, unused) -> {
+ final Size size = info.getSize();
+ if (size.getWidth() > size.getHeight()) {
+ if (size.getWidth() > maxWidth) {
+ final int targetHeight = size.getHeight() * maxWidth / size.getWidth();
+ decoder.setTargetSize(maxWidth, targetHeight);
+ }
+ } else {
+ if (size.getHeight() > maxHeight) {
+ final int targetWidth = size.getWidth() * maxHeight / size.getHeight();
+ decoder.setTargetSize(targetWidth, maxHeight);
+ }
}
- onlyBoundsOptions.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(input, null, onlyBoundsOptions);
- } catch (IllegalArgumentException iae) {
- onlyBoundsOptions.outWidth = -1;
- onlyBoundsOptions.outHeight = -1;
- Log.e(TAG, "error loading image", iae);
- }
- return onlyBoundsOptions;
+ });
}
private static int getPowerOfTwoForSampleRatio(double ratio) {
- int k = Integer.highestOneBit((int) Math.floor(ratio));
+ final int k = Integer.highestOneBit((int) Math.floor(ratio));
return Math.max(1, k);
}
+
+ private static void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+ ImageDecoder.Source source) {
+ final Size size = info.getSize();
+ final int originalSize = Math.max(size.getHeight(), size.getWidth());
+ final double ratio = (originalSize > MAX_SAFE_ICON_SIZE_PX)
+ ? originalSize * 1f / MAX_SAFE_ICON_SIZE_PX
+ : 1.0;
+ decoder.setTargetSampleSize(getPowerOfTwoForSampleRatio(ratio));
+ }
}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
index 44b9bd26aa38..d1ab7ea55d57 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/ConversationNotifications.kt
@@ -19,18 +19,25 @@ package com.android.systemui.statusbar.notification
import android.app.Notification
import android.content.Context
import android.content.pm.LauncherApps
+import android.graphics.drawable.AnimatedImageDrawable
import android.os.Handler
import android.service.notification.NotificationListenerService.Ranking
import android.service.notification.NotificationListenerService.RankingMap
import com.android.internal.statusbar.NotificationVisibility
import com.android.internal.widget.ConversationLayout
+import com.android.internal.widget.MessagingImageMessage
+import com.android.internal.widget.MessagingLayout
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.statusbar.StatusBarStateController
import com.android.systemui.statusbar.notification.collection.NotificationEntry
import com.android.systemui.statusbar.notification.collection.legacy.NotificationGroupManagerLegacy
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.NotificationContentView
import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.policy.HeadsUpManager
+import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener
+import com.android.systemui.util.children
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@@ -58,6 +65,71 @@ class ConversationNotificationProcessor @Inject constructor(
}
/**
+ * Tracks state related to animated images inside of notifications. Ex: starting and stopping
+ * animations to conserve CPU and memory.
+ */
+@SysUISingleton
+class AnimatedImageNotificationManager @Inject constructor(
+ private val notificationEntryManager: NotificationEntryManager,
+ private val headsUpManager: HeadsUpManager,
+ private val statusBarStateController: StatusBarStateController
+) {
+
+ private var isStatusBarExpanded = false
+
+ /** Begins listening to state changes and updating animations accordingly. */
+ fun bind() {
+ headsUpManager.addListener(object : OnHeadsUpChangedListener {
+ override fun onHeadsUpStateChanged(entry: NotificationEntry, isHeadsUp: Boolean) {
+ entry.row?.let { row ->
+ updateAnimatedImageDrawables(row, animating = isHeadsUp || isStatusBarExpanded)
+ }
+ }
+ })
+ statusBarStateController.addCallback(object : StatusBarStateController.StateListener {
+ override fun onExpandedChanged(isExpanded: Boolean) {
+ isStatusBarExpanded = isExpanded
+ notificationEntryManager.activeNotificationsForCurrentUser.forEach { entry ->
+ entry.row?.let { row ->
+ updateAnimatedImageDrawables(row, animating = isExpanded || row.isHeadsUp)
+ }
+ }
+ }
+ })
+ notificationEntryManager.addNotificationEntryListener(object : NotificationEntryListener {
+ override fun onEntryInflated(entry: NotificationEntry) {
+ entry.row?.let { row ->
+ updateAnimatedImageDrawables(
+ row,
+ animating = isStatusBarExpanded || row.isHeadsUp)
+ }
+ }
+ override fun onEntryReinflated(entry: NotificationEntry) = onEntryInflated(entry)
+ })
+ }
+
+ private fun updateAnimatedImageDrawables(row: ExpandableNotificationRow, animating: Boolean) =
+ (row.layouts?.asSequence() ?: emptySequence())
+ .flatMap { layout -> layout.allViews.asSequence() }
+ .flatMap { view ->
+ (view as? ConversationLayout)?.messagingGroups?.asSequence()
+ ?: (view as? MessagingLayout)?.messagingGroups?.asSequence()
+ ?: emptySequence()
+ }
+ .flatMap { messagingGroup -> messagingGroup.messageContainer.children }
+ .mapNotNull { view ->
+ (view as? MessagingImageMessage)
+ ?.let { imageMessage ->
+ imageMessage.drawable as? AnimatedImageDrawable
+ }
+ }
+ .forEach { animatedImageDrawable ->
+ if (animating) animatedImageDrawable.start()
+ else animatedImageDrawable.stop()
+ }
+}
+
+/**
* Tracks state related to conversation notifications, and updates the UI of existing notifications
* when necessary.
*/
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
index 8f352ad55041..54ce4ede9770 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/init/NotificationsControllerImpl.kt
@@ -22,6 +22,7 @@ import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.Snoo
import com.android.systemui.statusbar.FeatureFlags
import com.android.systemui.statusbar.NotificationListener
import com.android.systemui.statusbar.NotificationPresenter
+import com.android.systemui.statusbar.notification.AnimatedImageNotificationManager
import com.android.systemui.statusbar.notification.NotificationActivityStarter
import com.android.systemui.statusbar.notification.NotificationClicker
import com.android.systemui.statusbar.notification.NotificationEntryManager
@@ -71,7 +72,8 @@ class NotificationsControllerImpl @Inject constructor(
private val headsUpManager: HeadsUpManager,
private val headsUpController: HeadsUpController,
private val headsUpViewBinder: HeadsUpViewBinder,
- private val clickerBuilder: NotificationClicker.Builder
+ private val clickerBuilder: NotificationClicker.Builder,
+ private val animatedImageNotificationManager: AnimatedImageNotificationManager
) : NotificationsController {
override fun initialize(
@@ -100,6 +102,7 @@ class NotificationsControllerImpl @Inject constructor(
bindRowCallback)
headsUpViewBinder.setPresenter(presenter)
notifBindPipelineInitializer.initialize()
+ animatedImageNotificationManager.bind()
if (featureFlags.isNewNotifPipelineEnabled) {
newNotifPipeline.get().initialize(
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 7bd192d850c1..44ccb68cce4a 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
@@ -19,10 +19,7 @@ package com.android.systemui.statusbar.notification.row;
import android.app.ActivityManager;
import android.app.Notification;
import android.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.Parcelable;
@@ -81,7 +78,7 @@ public class NotificationInlineImageResolver implements ImageResolver {
* @return True if has its internal cache, false otherwise.
*/
public boolean hasCache() {
- return mImageCache != null && !ActivityManager.isLowRamDeviceStatic();
+ return mImageCache != null && !isLowRam();
}
private boolean isLowRam() {
@@ -110,11 +107,6 @@ public class NotificationInlineImageResolver implements ImageResolver {
: R.dimen.notification_custom_view_max_image_height);
}
- @VisibleForTesting
- protected BitmapDrawable resolveImageInternal(Uri uri) throws IOException {
- return (BitmapDrawable) LocalImageResolver.resolveImage(uri, mContext);
- }
-
/**
* To resolve image from specified uri directly. If the resulting image is larger than the
* maximum allowed size, scale it down.
@@ -123,13 +115,7 @@ public class NotificationInlineImageResolver implements ImageResolver {
* @throws IOException Throws if failed at resolving the image.
*/
Drawable resolveImage(Uri uri) throws IOException {
- BitmapDrawable image = resolveImageInternal(uri);
- if (image == null || image.getBitmap() == null) {
- throw new IOException("resolveImageInternal returned null for uri: " + uri);
- }
- Bitmap bitmap = image.getBitmap();
- image.setBitmap(Icon.scaleDownIfNecessary(bitmap, mMaxImageWidth, mMaxImageHeight));
- return image;
+ return LocalImageResolver.resolveImage(uri, mContext, mMaxImageWidth, mMaxImageHeight);
}
@Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
index 7f48cd1313fe..edf2b4c30ce4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationInlineImageResolverTest.java
@@ -69,32 +69,4 @@ public class NotificationInlineImageResolverTest extends SysuiTestCase {
assertEquals("Height matches new config", mResolver.mMaxImageHeight, 20);
assertEquals("Width matches new config", mResolver.mMaxImageWidth, 15);
}
-
- @Test
- public void resolveImage_sizeTooBig() throws IOException {
- doReturn(mBitmapDrawable).when(mResolver).resolveImageInternal(mUri);
- mResolver.mMaxImageHeight = 5;
- mResolver.mMaxImageWidth = 5;
-
- // original bitmap size is 10x10
- BitmapDrawable resolved = (BitmapDrawable) mResolver.resolveImage(mUri);
- Bitmap resolvedBitmap = resolved.getBitmap();
- assertEquals("Bitmap width reduced", 5, resolvedBitmap.getWidth());
- assertEquals("Bitmap height reduced", 5, resolvedBitmap.getHeight());
- assertNotSame("Bitmap replaced", resolvedBitmap, mBitmap);
- }
-
- @Test
- public void resolveImage_sizeOK() throws IOException {
- doReturn(mBitmapDrawable).when(mResolver).resolveImageInternal(mUri);
- mResolver.mMaxImageWidth = 15;
- mResolver.mMaxImageHeight = 15;
-
- // original bitmap size is 10x10
- BitmapDrawable resolved = (BitmapDrawable) mResolver.resolveImage(mUri);
- Bitmap resolvedBitmap = resolved.getBitmap();
- assertEquals("Bitmap width unchanged", 10, resolvedBitmap.getWidth());
- assertEquals("Bitmap height unchanged", 10, resolvedBitmap.getHeight());
- assertSame("Bitmap not replaced", resolvedBitmap, mBitmap);
- }
}