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

Commit b2c9d20b authored by Willie Koomson's avatar Willie Koomson Committed by Android (Google) Code Review
Browse files

Merge changes I4a0bd0e9,Ia1619e6d into main

* changes:
  Write ColorStateList to proto
  Write RemoteViews caches to proto
parents fcf7f7b6 de35e9db
Loading
Loading
Loading
Loading
+62 −0
Original line number Diff line number Diff line
@@ -32,6 +32,9 @@ import android.util.MathUtils;
import android.util.SparseArray;
import android.util.StateSet;
import android.util.Xml;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoUtils;

import com.android.internal.R;
import com.android.internal.graphics.ColorUtils;
@@ -44,7 +47,9 @@ import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 *
@@ -793,4 +798,61 @@ public class ColorStateList extends ComplexColor implements Parcelable {
            return new ColorStateList(stateSpecs, colors);
        }
    };

    /** @hide */
    public void writeToProto(ProtoOutputStream out) {
        for (int[] states : mStateSpecs) {
            long specToken = out.start(ColorStateListProto.STATE_SPECS);
            for (int state : states) {
                out.write(ColorStateListProto.StateSpec.STATE, state);
            }
            out.end(specToken);
        }
        for (int color : mColors) {
            out.write(ColorStateListProto.COLORS, color);
        }
    }

    /** @hide */
    public static ColorStateList createFromProto(ProtoInputStream in)
            throws Exception {
        List<int[]> stateSpecs = new ArrayList<>();
        List<Integer> colors = new ArrayList<>();


        while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
            switch (in.getFieldNumber()) {
                case (int) ColorStateListProto.COLORS:
                    colors.add(in.readInt(ColorStateListProto.COLORS));
                    break;
                case (int) ColorStateListProto.STATE_SPECS:
                    final long stateToken = in.start(ColorStateListProto.STATE_SPECS);
                    List<Integer> states = new ArrayList<>();
                    while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
                        switch (in.getFieldNumber()) {
                            case (int) ColorStateListProto.StateSpec.STATE:
                                states.add(in.readInt(ColorStateListProto.StateSpec.STATE));
                                break;
                            default:
                                Log.w(TAG, "Unhandled field while reading Icon proto!\n"
                                        + ProtoUtils.currentFieldToString(in));
                        }
                    }
                    int[] statesArray = new int[states.size()];
                    Arrays.setAll(statesArray, states::get);
                    stateSpecs.add(statesArray);
                    in.end(stateToken);
                    break;
                default:
                    Log.w(TAG, "Unhandled field while reading Icon proto!\n"
                            + ProtoUtils.currentFieldToString(in));
            }
        }

        int[][] stateSpecsArray = new int[stateSpecs.size()][];
        Arrays.setAll(stateSpecsArray, stateSpecs::get);
        int[] colorsArray = new int[colors.size()];
        Arrays.setAll(colorsArray, colors::get);
        return new ColorStateList(stateSpecsArray, colorsArray);
    }
}
+251 −2
Original line number Diff line number Diff line
@@ -62,6 +62,7 @@ import android.content.res.TypedArray;
import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlendMode;
import android.graphics.Outline;
import android.graphics.PorterDuff;
@@ -90,6 +91,7 @@ import android.util.DisplayMetrics;
import android.util.IntArray;
import android.util.Log;
import android.util.LongArray;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.SizeF;
import android.util.SparseArray;
@@ -98,6 +100,7 @@ import android.util.TypedValue;
import android.util.TypedValue.ComplexDimensionUnit;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;
import android.util.proto.ProtoStream;
import android.util.proto.ProtoUtils;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
@@ -1266,10 +1269,15 @@ public class RemoteViews implements Parcelable, Filter {
                int intentId = in.readInt();
                String intentUri = in.readString8();
                RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData);
                addMapping(intentId, intentUri, items);
            }
        }

        void addMapping(int intentId, String intentUri, RemoteCollectionItems items) {
            mIdToUriMapping.put(intentId, intentUri);
            mUriToCollectionMapping.put(intentUri, items);
        }
        }


        void setHierarchyDataForId(int intentId, HierarchyRootData data) {
            String uri = mIdToUriMapping.get(intentId);
@@ -1465,6 +1473,87 @@ public class RemoteViews implements Parcelable, Filter {
                mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true);
            }
        }

        public void writeToProto(Context context, ProtoOutputStream out) {
            final long token = out.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE);
            for (int i = 0; i < mIdToUriMapping.size(); i++) {
                final long entryToken = out.start(RemoteViewsProto.RemoteCollectionCache.ENTRIES);
                out.write(RemoteViewsProto.RemoteCollectionCache.Entry.ID,
                        mIdToUriMapping.keyAt(i));
                String intentUri = mIdToUriMapping.valueAt(i);
                out.write(RemoteViewsProto.RemoteCollectionCache.Entry.URI, intentUri);
                final long itemsToken = out.start(
                        RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS);
                mUriToCollectionMapping.get(intentUri).writeToProto(context, out, /* attached= */
                        true);
                out.end(itemsToken);
                out.end(entryToken);
            }
            out.end(token);
        }
    }

    private PendingResources<RemoteCollectionCache> populateRemoteCollectionCacheFromProto(
            ProtoInputStream in) throws Exception {
        final ArrayList<LongSparseArray<Object>> entries = new ArrayList<>();
        final long token = in.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE);
        while (in.nextField() != NO_MORE_FIELDS) {
            switch (in.getFieldNumber()) {
                case (int) RemoteViewsProto.RemoteCollectionCache.ENTRIES:
                    final LongSparseArray<Object> entry = new LongSparseArray<>();
                    final long entryToken = in.start(
                            RemoteViewsProto.RemoteCollectionCache.ENTRIES);
                    while (in.nextField() != NO_MORE_FIELDS) {
                        switch (in.getFieldNumber()) {
                            case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ID:
                                entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ID,
                                        in.readInt(
                                                RemoteViewsProto.RemoteCollectionCache.Entry.ID));
                                break;
                            case (int) RemoteViewsProto.RemoteCollectionCache.Entry.URI:
                                entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.URI,
                                        in.readString(
                                                RemoteViewsProto.RemoteCollectionCache.Entry.URI));
                                break;
                            case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS:
                                final long itemsToken = in.start(
                                        RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS);
                                entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS,
                                        RemoteCollectionItems.createFromProto(in));
                                in.end(itemsToken);
                                break;
                            default:
                                Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
                                        + ProtoUtils.currentFieldToString(in));
                        }
                    }
                    in.end(entryToken);
                    checkContainsKeys(entry,
                            new long[]{RemoteViewsProto.RemoteCollectionCache.Entry.ID,
                                    RemoteViewsProto.RemoteCollectionCache.Entry.URI,
                                    RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS});
                    entries.add(entry);
                    break;
                default:
                    Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
                            + ProtoUtils.currentFieldToString(in));
            }
        }
        in.end(token);

        return (context, resources, rootData, depth) -> {
            for (LongSparseArray<Object> entry : entries) {
                int id = (int) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.ID);
                String uri = (String) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.URI);
                // Depth resets to 0 for RemoteCollectionItems
                RemoteCollectionItems items = ((PendingResources<RemoteCollectionItems>) entry.get(
                        RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS)).create(context,
                        resources, rootData, depth);
                rootData.mRemoteCollectionCache.addMapping(id, uri, items);
            }
            // Redundant return, but type signature requires we return something.
            return rootData.mRemoteCollectionCache;
        };
    }

    private class SetRemoteViewsAdapterIntent extends Action {
@@ -2080,6 +2169,15 @@ public class RemoteViews implements Parcelable, Filter {
            dest.writeTypedList(mBitmaps, flags);
        }

        public void writeBitmapsToProto(ProtoOutputStream out) {
            for (int i = 0; i < mBitmaps.size(); i++) {
                final Bitmap bitmap = mBitmaps.get(i);
                final ByteArrayOutputStream bytes = new ByteArrayOutputStream();
                bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bytes);
                out.write(RemoteViewsProto.BITMAP_CACHE, bytes.toByteArray());
            }
        }

        public int getBitmapMemory() {
            if (mBitmapMemory < 0) {
                mBitmapMemory = 0;
@@ -7522,6 +7620,127 @@ public class RemoteViews implements Parcelable, Filter {
            dest.restoreAllowSquashing(prevAllowSquashing);
        }

        /** @hide */
        public void writeToProto(Context context, ProtoOutputStream out) {
            writeToProto(context, out, /* attached= */ false);
        }

        private void writeToProto(Context context, ProtoOutputStream out, boolean attached) {
            for (long id : mIds) {
                out.write(RemoteViewsProto.RemoteCollectionItems.IDS, id);
            }

            boolean restoreRoot = false;
            out.write(RemoteViewsProto.RemoteCollectionItems.ATTACHED, attached);
            if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) {
                restoreRoot = true;
                mViews[0].mIsRoot = true;
            }
            for (RemoteViews view : mViews) {
                final long viewsToken = out.start(RemoteViewsProto.RemoteCollectionItems.VIEWS);
                view.writePreviewToProto(context, out);
                out.end(viewsToken);
            }
            if (restoreRoot) mViews[0].mIsRoot = false;
            out.write(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, mHasStableIds);
            out.write(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, mViewTypeCount);
        }

        /**
         * Overload used for testing unattached RemoteCollectionItems serialization.
         *
         * @hide
         */
        public static RemoteCollectionItems createFromProto(Context context, ProtoInputStream in)
                throws Exception {
            return createFromProto(in).create(context, context.getResources(), /* rootData= */
                    null, 0);
        }

        /** @hide */
        public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in)
                throws Exception {
            final LongSparseArray<Object> values = new LongSparseArray<>();
            values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>());
            values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS,
                    new ArrayList<PendingResources<RemoteViews>>());
            while (in.nextField() != NO_MORE_FIELDS) {
                switch (in.getFieldNumber()) {
                    case (int) RemoteViewsProto.RemoteCollectionItems.IDS:
                        ((ArrayList<Long>) values.get(
                                RemoteViewsProto.RemoteCollectionItems.IDS)).add(
                                in.readLong(RemoteViewsProto.RemoteCollectionItems.IDS));
                        break;
                    case (int) RemoteViewsProto.RemoteCollectionItems.VIEWS:
                        final long viewsToken = in.start(
                                RemoteViewsProto.RemoteCollectionItems.VIEWS);
                        ((ArrayList<PendingResources<RemoteViews>>) values.get(
                                RemoteViewsProto.RemoteCollectionItems.VIEWS)).add(
                                RemoteViews.createFromProto(in));
                        in.end(viewsToken);
                        break;
                    case (int) RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS:
                        values.put(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS,
                                in.readBoolean(
                                        RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS));
                        break;
                    case (int) RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT:
                        values.put(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT,
                                in.readInt(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT));
                        break;
                    case (int) RemoteViewsProto.RemoteCollectionItems.ATTACHED:
                        values.put(RemoteViewsProto.RemoteCollectionItems.ATTACHED,
                                in.readBoolean(RemoteViewsProto.RemoteCollectionItems.ATTACHED));
                        break;
                    default:
                        Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n"
                                + ProtoUtils.currentFieldToString(in));
                }
            }

            checkContainsKeys(values,
                    new long[]{RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT});
            return (context, resources, rootData, depth) -> {
                List<Long> idList = (List<Long>) values.get(
                        RemoteViewsProto.RemoteCollectionItems.IDS);
                long[] ids = new long[idList.size()];
                for (int i = 0; i < idList.size(); i++) {
                    ids[i] = idList.get(i);
                }
                boolean attached = (boolean) values.get(
                        RemoteViewsProto.RemoteCollectionItems.ATTACHED, false);
                List<PendingResources<RemoteViews>> pendingViews =
                        (List<PendingResources<RemoteViews>>) values.get(
                                RemoteViewsProto.RemoteCollectionItems.VIEWS);
                RemoteViews[] views = new RemoteViews[pendingViews.size()];

                if (attached && rootData == null) {
                    throw new IllegalStateException("Cannot create a RemoteCollectionItems from "
                            + "proto that was attached without providing HierarchyRootData");
                }

                int firstChildIndex = 0;
                if (!attached && pendingViews.size() > 0) {
                    // If written as unattached, get HierarchyRootData from first view
                    views[0] = pendingViews.get(0).create(context, resources, /* rootData= */ null,
                            /* depth= */ 0);
                    rootData = views[0].getHierarchyRootData();
                    firstChildIndex = 1;
                }
                for (int i = firstChildIndex; i < views.length; i++) {
                    // Depth is reset to 0 for RemoteCollectionItems item views, see Parcel
                    // constructor.
                    views[i] = pendingViews.get(i).create(context, resources, rootData,
                            /* depth= */ 0);
                }
                return new RemoteCollectionItems(ids, views,
                        (boolean) values.get(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS,
                                false),
                        (int) values.get(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT,
                                0));
            };
        }

        /**
         * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
         * should be considered meaningful across collection updates.
@@ -7907,6 +8126,10 @@ public class RemoteViews implements Parcelable, Filter {
        if (mViewId != 0 && mViewId != -1) {
            out.write(RemoteViewsProto.VIEW_ID, appResources.getResourceName(mViewId));
        }
        if (mIsRoot) {
            mBitmapCache.writeBitmapsToProto(out);
            mCollectionCache.writeToProto(context, out);
        }
        out.write(RemoteViewsProto.IS_ROOT, mIsRoot);
        out.write(RemoteViewsProto.APPLY_FLAGS, mApplyFlags);
        out.write(RemoteViewsProto.HAS_DRAW_INSTRUCTIONS, mHasDrawInstructions);
@@ -7968,6 +8191,7 @@ public class RemoteViews implements Parcelable, Filter {
            final List<PendingResources<RemoteViews>> mSizedRemoteViews = new ArrayList<>();
            PendingResources<RemoteViews> mLandscapeViews = null;
            PendingResources<RemoteViews> mPortraitViews = null;
            PendingResources<RemoteCollectionCache> mPopulateRemoteCollectionCache = null;
            boolean mIsRoot = false;
            boolean mHasDrawInstructions = false;
        };
@@ -8018,6 +8242,18 @@ public class RemoteViews implements Parcelable, Filter {
                        ref.mPortraitViews = createFromProto(in);
                        in.end(portraitToken);
                        break;
                    case (int) RemoteViewsProto.BITMAP_CACHE:
                        byte[] src = in.readBytes(RemoteViewsProto.BITMAP_CACHE);
                        Bitmap bitmap = BitmapFactory.decodeByteArray(src, 0, src.length);
                        ref.mRv.mBitmapCache.getBitmapId(bitmap);
                        break;
                    case (int) RemoteViewsProto.REMOTE_COLLECTION_CACHE:
                        final long collectionToken = in.start(
                                RemoteViewsProto.REMOTE_COLLECTION_CACHE);
                        ref.mPopulateRemoteCollectionCache =
                                ref.mRv.populateRemoteCollectionCacheFromProto(in);
                        in.end(collectionToken);
                        break;
                    case (int) RemoteViewsProto.IS_ROOT:
                        ref.mIsRoot = in.readBoolean(RemoteViewsProto.IS_ROOT);
                        break;
@@ -8087,6 +8323,9 @@ public class RemoteViews implements Parcelable, Filter {
                    rv.setLightBackgroundLayoutId(lightBackgroundLayoutId);
                }
            }
            if (ref.mPopulateRemoteCollectionCache != null) {
                ref.mPopulateRemoteCollectionCache.create(context, resources, rootData, depth);
            }
            if (ref.mProviderInstanceId != -1) {
                rv.mProviderInstanceId = ref.mProviderInstanceId;
            }
@@ -8139,6 +8378,16 @@ public class RemoteViews implements Parcelable, Filter {
        }
    }

    private static void checkContainsKeys(LongSparseArray<?> array, long[] requiredFields) {
        for (long requiredField : requiredFields) {
            if (array.indexOfKey(requiredField) < 0) {
                throw new IllegalArgumentException(
                        "RemoteViews proto missing field: " + ProtoStream.getFieldIdString(
                                requiredField));
            }
        }
    }

    private static SizeF createSizeFFromProto(ProtoInputStream in) throws Exception {
        float width = 0;
        float height = 0;
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless optional by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

syntax = "proto2";

option java_multiple_files = true;

package android.content.res;

import "frameworks/base/core/proto/android/privacy.proto";

/**
 * An android.content.res.ColorStateList object.
 */
message ColorStateListProto {
    option (android.msg_privacy).dest = DEST_AUTOMATIC;
    repeated StateSpec state_specs = 1;
    repeated int32 colors = 2 [packed = true];

    message StateSpec {
        repeated int32 state = 1 [packed = true];
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -51,6 +51,26 @@ message RemoteViewsProto {
    optional RemoteViewsProto landscape_remoteviews = 11;
    optional bool is_root = 12;
    optional bool has_draw_instructions = 13;
    repeated bytes bitmap_cache = 14;
    optional RemoteCollectionCache remote_collection_cache = 15;

    message RemoteCollectionCache {
        message Entry {
            optional int64 id = 1;
            optional string uri = 2;
            optional RemoteCollectionItems items = 3;
        }

        repeated Entry entries = 1;
    }

    message RemoteCollectionItems {
        repeated int64 ids = 1 [packed = true];
        repeated RemoteViewsProto views = 2;
        optional bool has_stable_ids = 3;
        optional int32 view_type_count = 4;
        optional bool attached = 5;
    }
}


+27 −0
Original line number Diff line number Diff line
@@ -19,6 +19,8 @@ package android.graphics;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.test.AndroidTestCase;
import android.util.proto.ProtoInputStream;
import android.util.proto.ProtoOutputStream;

import androidx.test.filters.SmallTest;

@@ -48,6 +50,15 @@ public class ColorStateListTest extends AndroidTestCase {
        assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
    }

    @SmallTest
    public void testStateIsInList_proto() throws Exception {
        ColorStateList colorStateList = recreateFromProto(
                mResources.getColorStateList(R.color.color1));
        int[] focusedState = {android.R.attr.state_focused};
        int focusColor = colorStateList.getColorForState(focusedState, R.color.failColor);
        assertEquals(mResources.getColor(R.color.testcolor1), focusColor);
    }

    @SmallTest
    public void testEmptyState() throws Exception {
        ColorStateList colorStateList = mResources.getColorStateList(R.color.color1);
@@ -56,6 +67,15 @@ public class ColorStateListTest extends AndroidTestCase {
        assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
    }

    @SmallTest
    public void testEmptyState_proto() throws Exception {
        ColorStateList colorStateList = recreateFromProto(
                mResources.getColorStateList(R.color.color1));
        int[] emptyState = {};
        int defaultColor = colorStateList.getColorForState(emptyState, mFailureColor);
        assertEquals(mResources.getColor(R.color.testcolor2), defaultColor);
    }

    @SmallTest
    public void testGetColor() throws Exception {
        int defaultColor = mResources.getColor(R.color.color1);
@@ -73,4 +93,11 @@ public class ColorStateListTest extends AndroidTestCase {
        int defaultColor = mResources.getColor(R.color.color_with_lstar);
        assertEquals(mResources.getColor(R.color.testcolor3), defaultColor);
    }

    private ColorStateList recreateFromProto(ColorStateList colorStateList) throws Exception {
        ProtoOutputStream out = new ProtoOutputStream();
        colorStateList.writeToProto(out);
        ProtoInputStream in = new ProtoInputStream(out.getBytes());
        return ColorStateList.createFromProto(in);
    }
}