Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ef0692a authored by Willie Koomson's avatar Willie Koomson
Browse files

Include bitmap memory estimate in AppWidgetServiceImpl dumpsys output

This change adds a new field "view_bitmap_memory" to the
AppWidgetServiceImpl dumpsys output, which holds the estimate of bitmap
memory used by a widget held in memory by the service.

To support this, a new method RemoteViews.estimateTotalBitmapMemoryUsage
is added, which includes memory from both bitmap and Icon actions.

Also updates RemoteViews.estimateMemoryUsage to return a long. The
int sum can be overflowed to beat the memory check in
AppWidgetServiceImpl.

Bug: 377356756
Test: RemoteViewsTest#estimateMemoryUsage_*
Flag: EXEMPT bugfix

Change-Id: Id37d88bad93d154826e132dc243e24daf27d3905
parent 2fb05f5a
Loading
Loading
Loading
Loading
+96 −5
Original line number Diff line number Diff line
@@ -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);

        /**
@@ -849,6 +855,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
@@ -1312,6 +1341,19 @@ public class RemoteViews implements Parcelable, Filter {
            mItems.visitUris(visitor);
        }

        @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)
@@ -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. */
@@ -4139,6 +4188,11 @@ public class RemoteViews implements Parcelable, Filter {
            mNestedViews.visitUris(visitor);
        }

        @Override
        public void visitIcons(@NonNull Consumer<Icon> visitor) {
            mNestedViews.visitIcons(visitor);
        }

        @Override
        public boolean canWriteToProto() {
            return true;
@@ -6393,14 +6447,42 @@ 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.
     *
@@ -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);
            }
        }
    }

    /**
+1 −0
Original line number Diff line number Diff line
@@ -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
+131 −0
Original line number Diff line number Diff line
@@ -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() {
+7 −1
Original line number Diff line number Diff line
@@ -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());
        }
    }

+1 −1
Original line number Diff line number Diff line
@@ -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
Loading