diff options
6 files changed, 238 insertions, 8 deletions
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 7c75d7b30037..2deb685f9411 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -160,6 +160,7 @@ import java.util.Set;  import java.util.concurrent.CompletableFuture;  import java.util.concurrent.Executor;  import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong;  import java.util.function.Consumer;  import java.util.function.Function;  import java.util.function.Predicate; @@ -720,6 +721,11 @@ public class RemoteViews implements Parcelable, Filter {              // Nothing to visit by default.          } +        /** See {@link RemoteViews#visitIcons(Consumer)}. **/ +        public void visitIcons(@NonNull Consumer<Icon> visitor) { +            // Nothing to visit by default. +        } +          public abstract void writeToParcel(Parcel dest, int flags);          /** @@ -850,6 +856,29 @@ public class RemoteViews implements Parcelable, Filter {      }      /** +     * Note all {@link Icon} that are referenced internally. +     * @hide +     */ +    public void visitIcons(@NonNull Consumer<Icon> visitor) { +        if (mActions != null) { +            for (int i = 0; i < mActions.size(); i++) { +                mActions.get(i).visitIcons(visitor); +            } +        } +        if (mSizedRemoteViews != null) { +            for (int i = 0; i < mSizedRemoteViews.size(); i++) { +                mSizedRemoteViews.get(i).visitIcons(visitor); +            } +        } +        if (mLandscape != null) { +            mLandscape.visitIcons(visitor); +        } +        if (mPortrait != null) { +            mPortrait.visitIcons(visitor); +        } +    } + +    /**       * @hide       * @return True if there is a change       */ @@ -1313,6 +1342,19 @@ public class RemoteViews implements Parcelable, Filter {          }          @Override +        public void visitIcons(Consumer<Icon> visitor) { +            if (mItems == null) { +                RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId); +                if (cachedItems != null) { +                    cachedItems.visitIcons(visitor); +                } +                return; +            } + +            mItems.visitIcons(visitor); +        } + +        @Override          public boolean canWriteToProto() {              // Skip actions that do not contain items (intent only actions)              return mItems != null; @@ -2386,7 +2428,7 @@ public class RemoteViews implements Parcelable, Filter {          @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)          ArrayList<Bitmap> mBitmaps;          SparseIntArray mBitmapHashes; -        int mBitmapMemory = -1; +        long mBitmapMemory = -1;          public BitmapCache() {              mBitmaps = new ArrayList<>(); @@ -2450,7 +2492,7 @@ public class RemoteViews implements Parcelable, Filter {              }          } -        public int getBitmapMemory() { +        public long getBitmapMemory() {              if (mBitmapMemory < 0) {                  mBitmapMemory = 0;                  int count = mBitmaps.size(); @@ -2736,6 +2778,13 @@ public class RemoteViews implements Parcelable, Filter {                  // TODO(b/281044385): Should we do anything about type BUNDLE?              }          } + +        @Override +        public void visitIcons(@NonNull Consumer<Icon> visitor) { +            if (mType == ICON && getParameterValue(null) instanceof Icon icon) { +                visitor.accept(icon); +            } +        }      }      /** Class for the reflection actions. */ @@ -4140,6 +4189,11 @@ public class RemoteViews implements Parcelable, Filter {          }          @Override +        public void visitIcons(@NonNull Consumer<Icon> visitor) { +            mNestedViews.visitIcons(visitor); +        } + +        @Override          public boolean canWriteToProto() {              return true;          } @@ -6393,15 +6447,43 @@ public class RemoteViews implements Parcelable, Filter {      }      /** -     * Returns an estimate of the bitmap heap memory usage for this RemoteViews. +     * Returns an estimate of the bitmap heap memory usage by setBitmap and setImageViewBitmap in +     * this RemoteViews. +     * +     * @hide       */ -    /** @hide */      @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) -    public int estimateMemoryUsage() { +    public long estimateMemoryUsage() {          return mBitmapCache.getBitmapMemory();      }      /** +     * Returns an estimate of bitmap heap memory usage by setIcon and setImageViewIcon in this +     * RemoteViews. Note that this function will count duplicate Icons in its estimate. +     * +     * @hide +     */ +    public long estimateIconMemoryUsage() { +        AtomicLong total = new AtomicLong(0); +        visitIcons(icon -> { +            if (icon.getType() == Icon.TYPE_BITMAP || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { +                total.addAndGet(icon.getBitmap().getAllocationByteCount()); +            } +        }); +        return total.get(); +    } + +    /** +     * Returns an estimate of the bitmap heap memory usage for all Icon and Bitmap actions in this +     * RemoteViews. +     * +     * @hide +     */ +    public long estimateTotalBitmapMemoryUsage() { +        return estimateMemoryUsage() + estimateIconMemoryUsage(); +    } + +    /**       * Add an action to be executed on the remote side when apply is called.       *       * @param a The action to add @@ -9763,6 +9845,15 @@ public class RemoteViews implements Parcelable, Filter {                  view.visitUris(visitor);              }          } + +        /** +         * See {@link RemoteViews#visitIcons(Consumer)}. +         */ +        private void visitIcons(@NonNull Consumer<Icon> visitor) { +            for (RemoteViews view : mViews) { +                view.visitIcons(visitor); +            } +        }      }      /** diff --git a/core/proto/android/service/appwidget.proto b/core/proto/android/service/appwidget.proto index fb907196bfc7..5dc90e02faf5 100644 --- a/core/proto/android/service/appwidget.proto +++ b/core/proto/android/service/appwidget.proto @@ -39,6 +39,7 @@ message WidgetProto {      optional int32 maxWidth = 8;      optional int32 maxHeight = 9;      optional bool restoreCompleted = 10; +    optional int32 views_bitmap_memory = 11;  }  // represents a set of widget previews for a particular provider diff --git a/core/tests/coretests/src/android/widget/RemoteViewsTest.java b/core/tests/coretests/src/android/widget/RemoteViewsTest.java index 2880ecf835c4..8b0d3158e9e7 100644 --- a/core/tests/coretests/src/android/widget/RemoteViewsTest.java +++ b/core/tests/coretests/src/android/widget/RemoteViewsTest.java @@ -36,6 +36,7 @@ import android.app.PendingIntent;  import android.appwidget.AppWidgetHostView;  import android.content.Context;  import android.content.Intent; +import android.graphics.Bitmap;  import android.graphics.drawable.Icon;  import android.net.Uri;  import android.os.AsyncTask; @@ -934,6 +935,136 @@ public class RemoteViewsTest {          assertEquals(testText, replacedTextView.getText());      } +    @Test +    public void estimateMemoryUsage() { +        Bitmap b1 = Bitmap.createBitmap(1024, 768, Bitmap.Config.ARGB_8888); +        int b1Memory = b1.getAllocationByteCount(); +        Bitmap b2 = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); +        int b2Memory = b2.getAllocationByteCount(); +        Bitmap b3 = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888); +        int b3Memory = b3.getAllocationByteCount(); + +        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); +        assertEquals(0, rv.estimateMemoryUsage()); +        assertEquals(0, rv.estimateIconMemoryUsage()); +        assertEquals(0, rv.estimateTotalBitmapMemoryUsage()); + +        rv.setBitmap(R.id.view, "", b1); +        rv.setImageViewBitmap(R.id.view, b1); // second instance of b1 is cached +        rv.setBitmap(R.id.view, "", b2); +        assertEquals(b1Memory + b2Memory, rv.estimateMemoryUsage()); +        assertEquals(0, rv.estimateIconMemoryUsage()); +        assertEquals(b1Memory + b2Memory, rv.estimateTotalBitmapMemoryUsage()); + +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b2)); +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b3)); +        rv.setImageViewIcon(R.id.view, Icon.createWithBitmap(b3)); +        assertEquals(b1Memory + b2Memory, rv.estimateMemoryUsage()); +        assertEquals(b2Memory + (2 * b3Memory), rv.estimateIconMemoryUsage()); +        assertEquals(b1Memory + (2 * b2Memory) + (2 * b3Memory), +                rv.estimateTotalBitmapMemoryUsage()); +    } + +    @Test +    public void estimateMemoryUsage_landscapePortrait() { +        Bitmap b1 = Bitmap.createBitmap(1024, 768, Bitmap.Config.ARGB_8888); +        int b1Memory = b1.getAllocationByteCount(); +        Bitmap b2 = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); +        int b2Memory = b2.getAllocationByteCount(); +        Bitmap b3 = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888); +        int b3Memory = b3.getAllocationByteCount(); +        Bitmap b4 = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888); +        int b4Memory = b4.getAllocationByteCount(); + +        // Landscape and portrait using same bitmaps get counted twice. +        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.setBitmap(R.id.view, "", b1); +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b2)); +        RemoteViews landscapePortraitViews = new RemoteViews(rv, rv); +        assertEquals(b1Memory, landscapePortraitViews.estimateMemoryUsage()); +        assertEquals(2 * b2Memory, landscapePortraitViews.estimateIconMemoryUsage()); +        assertEquals(b1Memory + (2 * b2Memory), +                landscapePortraitViews.estimateTotalBitmapMemoryUsage()); + +        final RemoteViews rv2 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.setBitmap(R.id.view, "", b3); +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b4)); +        landscapePortraitViews = new RemoteViews(rv, rv2); +        assertEquals(b1Memory + b3Memory, landscapePortraitViews.estimateMemoryUsage()); +        assertEquals(b2Memory + b4Memory, landscapePortraitViews.estimateIconMemoryUsage()); +        assertEquals(b1Memory + b2Memory + b3Memory + b4Memory, +                landscapePortraitViews.estimateTotalBitmapMemoryUsage()); +    } + +    @Test +    public void estimateMemoryUsage_sizedViews() { +        Bitmap b1 = Bitmap.createBitmap(1024, 768, Bitmap.Config.ARGB_8888); +        int b1Memory = b1.getAllocationByteCount(); +        Bitmap b2 = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); +        int b2Memory = b2.getAllocationByteCount(); +        Bitmap b3 = Bitmap.createBitmap(800, 600, Bitmap.Config.ARGB_8888); +        int b3Memory = b3.getAllocationByteCount(); +        Bitmap b4 = Bitmap.createBitmap(320, 240, Bitmap.Config.ARGB_8888); +        int b4Memory = b4.getAllocationByteCount(); + +        // Sized views using same bitmaps do not get counted twice. +        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.setBitmap(R.id.view, "", b1); +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b2)); +        RemoteViews sizedViews = new RemoteViews( +                Map.of(new SizeF(0f, 0f), rv, new SizeF(1f, 1f), rv)); +        assertEquals(b1Memory, sizedViews.estimateMemoryUsage()); +        assertEquals(2 * b2Memory, sizedViews.estimateIconMemoryUsage()); +        assertEquals(b1Memory + (2 * b2Memory), sizedViews.estimateTotalBitmapMemoryUsage()); + +        final RemoteViews rv2 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.setBitmap(R.id.view, "", b3); +        rv.setIcon(R.id.view, "", Icon.createWithBitmap(b4)); +        sizedViews = new RemoteViews(Map.of(new SizeF(0f, 0f), rv, new SizeF(1f, 1f), rv2)); +        assertEquals(b1Memory + b3Memory, sizedViews.estimateMemoryUsage()); +        assertEquals(b2Memory + b4Memory, sizedViews.estimateIconMemoryUsage()); +        assertEquals(b1Memory + b2Memory + b3Memory + b4Memory, +                sizedViews.estimateTotalBitmapMemoryUsage()); +    } + +    @Test +    public void estimateMemoryUsage_nestedViews() { +        Bitmap b1 = Bitmap.createBitmap(1024, 768, Bitmap.Config.ARGB_8888); +        int b1Memory = b1.getAllocationByteCount(); +        Bitmap b2 = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); +        int b2Memory = b2.getAllocationByteCount(); + +        final RemoteViews rv1 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv1.setBitmap(R.id.view, "", b1); +        final RemoteViews rv2 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv2.setIcon(R.id.view, "", Icon.createWithBitmap(b2)); +        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.addView(R.id.view, rv1); +        rv.addView(R.id.view, rv2); +        assertEquals(b1Memory, rv.estimateMemoryUsage()); +        assertEquals(b2Memory, rv.estimateIconMemoryUsage()); +        assertEquals(b1Memory + b2Memory, rv.estimateTotalBitmapMemoryUsage()); +    } + +    @Test +    public void estimateMemoryUsage_remoteCollectionItems() { +        Bitmap b1 = Bitmap.createBitmap(1024, 768, Bitmap.Config.ARGB_8888); +        int b1Memory = b1.getAllocationByteCount(); +        Bitmap b2 = Bitmap.createBitmap(640, 480, Bitmap.Config.ARGB_8888); +        int b2Memory = b2.getAllocationByteCount(); + +        final RemoteViews rv1 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv1.setBitmap(R.id.view, "", b1); +        final RemoteViews rv2 = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv2.setIcon(R.id.view, "", Icon.createWithBitmap(b2)); +        final RemoteViews rv = new RemoteViews(mPackage, R.layout.remote_views_test); +        rv.setRemoteAdapter(R.id.view, new RemoteViews.RemoteCollectionItems.Builder().addItem(0L, +                rv1).addItem(1L, rv2).build()); +        assertEquals(b1Memory, rv.estimateMemoryUsage()); +        assertEquals(b2Memory, rv.estimateIconMemoryUsage()); +        assertEquals(b1Memory + b2Memory, rv.estimateTotalBitmapMemoryUsage()); +    } +      private static LayoutInflater.Factory2 createLayoutInflaterFactory(String viewTypeToReplace,              View replacementView) {          return new LayoutInflater.Factory2() { diff --git a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java index cffdfbd36532..536d53c07f11 100644 --- a/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java +++ b/services/appwidget/java/com/android/server/appwidget/AppWidgetServiceImpl.java @@ -1095,6 +1095,10 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku              proto.write(WidgetProto.MAX_HEIGHT,                  widget.options.getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT, 0));          } +        if (widget.views != null) { +            proto.write(WidgetProto.VIEWS_BITMAP_MEMORY, +                    widget.views.estimateTotalBitmapMemoryUsage()); +        }          proto.end(token);      } @@ -2846,7 +2850,7 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku                  // For a full update we replace the RemoteViews completely.                  widget.views = views;              } -            int memoryUsage; +            long memoryUsage;              if ((UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID) &&                      (widget.views != null) &&                      ((memoryUsage = widget.views.estimateMemoryUsage()) > mMaxWidgetBitmapMemory)) { @@ -3503,6 +3507,8 @@ class AppWidgetServiceImpl extends IAppWidgetService.Stub implements WidgetBacku          }          if (widget.views != null) {              pw.print("    views="); pw.println(widget.views); +            pw.print("    views_bitmap_memory="); +            pw.println(widget.views.estimateTotalBitmapMemoryUsage());          }      } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 05aa4134cbd5..c16b7886181d 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -8683,7 +8683,7 @@ public class NotificationManagerService extends SystemService {          if (contentView == null) {              return false;          } -        final int contentViewSize = contentView.estimateMemoryUsage(); +        final long contentViewSize = contentView.estimateMemoryUsage();          if (contentViewSize > mWarnRemoteViewsSizeBytes                  && contentViewSize < mStripRemoteViewsSizeBytes) {              Slog.w(TAG, "RemoteViews too large on pkg: " + pkg + " tag: " + tag + " id: " + id diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index e43b28bb9404..44d9786e81a4 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -11977,7 +11977,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {      @Test      @DisableFlags(android.app.Flags.FLAG_REMOVE_REMOTE_VIEWS)      public void testRemoveLargeRemoteViews() throws Exception { -        int removeSize = mContext.getResources().getInteger( +        // Cast to long to mock RemoteViews.estimateMemoryUsage which returns long. +        long removeSize = mContext.getResources().getInteger(                  com.android.internal.R.integer.config_notificationStripRemoteViewSizeBytes);          RemoteViews rv = mock(RemoteViews.class);  |