summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Willie Koomson <wvk@google.com> 2024-07-23 23:23:31 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-07-23 23:23:31 +0000
commitdaa08602ea43202fb9e109eff4e593b31aa0b1d6 (patch)
tree6b13b857d5ec2d45c1601e053763b1e437901fc0
parent9928e34fa49b3807833b77595efaa923a959eb27 (diff)
parente0c3f6e6d22c04bf4dbfe0c08907956000de54bc (diff)
Merge "Write Icon to proto" into main
-rw-r--r--core/java/android/widget/RemoteViews.java2
-rw-r--r--core/java/android/widget/RemoteViewsSerializers.java177
-rw-r--r--core/proto/android/widget/remoteviews.proto18
-rw-r--r--core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt94
4 files changed, 290 insertions, 1 deletions
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 0dadbe374aa4..eb3581717637 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -8360,7 +8360,7 @@ public class RemoteViews implements Parcelable, Filter {
}
}
- private interface PendingResources<T> {
+ interface PendingResources<T> {
T create(Context context, Resources appResources, HierarchyRootData rootData, int depth)
throws Exception;
}
diff --git a/core/java/android/widget/RemoteViewsSerializers.java b/core/java/android/widget/RemoteViewsSerializers.java
new file mode 100644
index 000000000000..600fea4a0bb8
--- /dev/null
+++ b/core/java/android/widget/RemoteViewsSerializers.java
@@ -0,0 +1,177 @@
+/*
+ * 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 required 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.
+ */
+package android.widget;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BlendMode;
+import android.graphics.drawable.Icon;
+import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.proto.ProtoInputStream;
+import android.util.proto.ProtoOutputStream;
+import android.util.proto.ProtoUtils;
+
+import androidx.annotation.NonNull;
+
+import java.io.ByteArrayOutputStream;
+import java.util.function.Function;
+
+/**
+ * This class provides serialization for certain types used within RemoteViews.
+ *
+ * @hide
+ */
+public class RemoteViewsSerializers {
+ private static final String TAG = "RemoteViews";
+
+ /**
+ * Write Icon to proto.
+ */
+ public static void writeIconToProto(@NonNull ProtoOutputStream out,
+ @NonNull Resources appResources, @NonNull Icon icon) {
+ if (icon.getTintList() != null) {
+ final long token = out.start(RemoteViewsProto.Icon.TINT_LIST);
+ icon.getTintList().writeToProto(out);
+ out.end(token);
+ }
+ out.write(RemoteViewsProto.Icon.BLEND_MODE, BlendMode.toValue(icon.getTintBlendMode()));
+ switch (icon.getType()) {
+ case Icon.TYPE_BITMAP:
+ final ByteArrayOutputStream bitmapBytes = new ByteArrayOutputStream();
+ icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bitmapBytes);
+ out.write(RemoteViewsProto.Icon.BITMAP, bitmapBytes.toByteArray());
+ break;
+ case Icon.TYPE_ADAPTIVE_BITMAP:
+ final ByteArrayOutputStream adaptiveBitmapBytes = new ByteArrayOutputStream();
+ icon.getBitmap().compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100,
+ adaptiveBitmapBytes);
+ out.write(RemoteViewsProto.Icon.ADAPTIVE_BITMAP, adaptiveBitmapBytes.toByteArray());
+ break;
+ case Icon.TYPE_RESOURCE:
+ out.write(RemoteViewsProto.Icon.RESOURCE,
+ appResources.getResourceName(icon.getResId()));
+ break;
+ case Icon.TYPE_DATA:
+ out.write(RemoteViewsProto.Icon.DATA, icon.getDataBytes());
+ break;
+ case Icon.TYPE_URI:
+ out.write(RemoteViewsProto.Icon.URI, icon.getUriString());
+ break;
+ case Icon.TYPE_URI_ADAPTIVE_BITMAP:
+ out.write(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP, icon.getUriString());
+ break;
+ default:
+ Log.e(TAG, "Tried to serialize unknown Icon type " + icon.getType());
+ }
+ }
+
+ /**
+ * Create Icon from proto.
+ */
+ @NonNull
+ public static Function<Resources, Icon> createIconFromProto(@NonNull ProtoInputStream in)
+ throws Exception {
+ final LongSparseArray<Object> values = new LongSparseArray<>();
+ while (in.nextField() != ProtoInputStream.NO_MORE_FIELDS) {
+ switch (in.getFieldNumber()) {
+ case (int) RemoteViewsProto.Icon.BLEND_MODE:
+ values.put(RemoteViewsProto.Icon.BLEND_MODE,
+ in.readInt(RemoteViewsProto.Icon.BLEND_MODE));
+ break;
+ case (int) RemoteViewsProto.Icon.TINT_LIST:
+ final long tintListToken = in.start(RemoteViewsProto.Icon.TINT_LIST);
+ values.put(RemoteViewsProto.Icon.TINT_LIST, ColorStateList.createFromProto(in));
+ in.end(tintListToken);
+ break;
+ case (int) RemoteViewsProto.Icon.BITMAP:
+ byte[] bitmapData = in.readBytes(RemoteViewsProto.Icon.BITMAP);
+ values.put(RemoteViewsProto.Icon.BITMAP,
+ BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length));
+ break;
+ case (int) RemoteViewsProto.Icon.ADAPTIVE_BITMAP:
+ final byte[] bitmapAdaptiveData = in.readBytes(
+ RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+ values.put(RemoteViewsProto.Icon.ADAPTIVE_BITMAP,
+ BitmapFactory.decodeByteArray(bitmapAdaptiveData, 0,
+ bitmapAdaptiveData.length));
+ break;
+ case (int) RemoteViewsProto.Icon.RESOURCE:
+ values.put(RemoteViewsProto.Icon.RESOURCE,
+ in.readString(RemoteViewsProto.Icon.RESOURCE));
+ break;
+ case (int) RemoteViewsProto.Icon.DATA:
+ values.put(RemoteViewsProto.Icon.DATA,
+ in.readBytes(RemoteViewsProto.Icon.DATA));
+ break;
+ case (int) RemoteViewsProto.Icon.URI:
+ values.put(RemoteViewsProto.Icon.URI, in.readString(RemoteViewsProto.Icon.URI));
+ break;
+ case (int) RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP:
+ values.put(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP,
+ in.readString(RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP));
+ break;
+ default:
+ Log.w(TAG, "Unhandled field while reading Icon proto!\n"
+ + ProtoUtils.currentFieldToString(in));
+ }
+ }
+
+ return (resources) -> {
+ final int blendMode = (int) values.get(RemoteViewsProto.Icon.BLEND_MODE, -1);
+ final ColorStateList tintList = (ColorStateList) values.get(
+ RemoteViewsProto.Icon.TINT_LIST);
+ final Bitmap bitmap = (Bitmap) values.get(RemoteViewsProto.Icon.BITMAP);
+ final Bitmap bitmapAdaptive = (Bitmap) values.get(
+ RemoteViewsProto.Icon.ADAPTIVE_BITMAP);
+ final String resName = (String) values.get(RemoteViewsProto.Icon.RESOURCE);
+ final int resource = resName != null ? resources.getIdentifier(resName, /* defType= */
+ null,
+ /* defPackage= */ null) : -1;
+ final byte[] data = (byte[]) values.get(RemoteViewsProto.Icon.DATA);
+ final String uri = (String) values.get(RemoteViewsProto.Icon.URI);
+ final String uriAdaptive = (String) values.get(
+ RemoteViewsProto.Icon.URI_ADAPTIVE_BITMAP);
+ Icon icon;
+ if (bitmap != null) {
+ icon = Icon.createWithBitmap(bitmap);
+ } else if (bitmapAdaptive != null) {
+ icon = Icon.createWithAdaptiveBitmap(bitmapAdaptive);
+ } else if (resource != -1) {
+ icon = Icon.createWithResource(resources, resource);
+ } else if (data != null) {
+ icon = Icon.createWithData(data, 0, data.length);
+ } else if (uri != null) {
+ icon = Icon.createWithContentUri(uri);
+ } else if (uriAdaptive != null) {
+ icon = Icon.createWithAdaptiveBitmapContentUri(uriAdaptive);
+ } else {
+ // Either this Icon has no data or is of an unknown type.
+ return null;
+ }
+
+ if (tintList != null) {
+ icon.setTintList(tintList);
+ }
+ if (blendMode != -1) {
+ icon.setTintBlendMode(BlendMode.fromValue(blendMode));
+ }
+ return icon;
+ };
+ }
+}
diff --git a/core/proto/android/widget/remoteviews.proto b/core/proto/android/widget/remoteviews.proto
index f08ea1b6f092..37d1c5b03ee5 100644
--- a/core/proto/android/widget/remoteviews.proto
+++ b/core/proto/android/widget/remoteviews.proto
@@ -21,6 +21,7 @@ option java_multiple_files = true;
package android.widget;
import "frameworks/base/core/proto/android/privacy.proto";
+import "frameworks/base/core/proto/android/content/res/color_state_list.proto";
/**
* An android.widget.RemoteViews object. This is used by RemoteViews.createPreviewFromProto
@@ -71,6 +72,23 @@ message RemoteViewsProto {
optional int32 view_type_count = 4;
optional bool attached = 5;
}
+
+ /**
+ * An android.graphics.drawable Icon.
+ */
+ message Icon {
+ option (android.msg_privacy).dest = DEST_AUTOMATIC;
+ optional int32 blend_mode = 1;
+ optional android.content.res.ColorStateListProto tint_list = 2;
+ oneof icon {
+ bytes bitmap = 3;
+ string resource = 4;
+ bytes data = 5;
+ string uri = 6;
+ string uri_adaptive_bitmap = 7;
+ bytes adaptive_bitmap = 8;
+ };
+ }
}
diff --git a/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
new file mode 100644
index 000000000000..44d10d32606c
--- /dev/null
+++ b/core/tests/coretests/src/android/widget/RemoteViewsSerializersTest.kt
@@ -0,0 +1,94 @@
+/*
+ * 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 required 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
+ */
+
+package android.widget
+
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Bitmap
+import android.graphics.BlendMode
+import android.graphics.Color
+import android.graphics.drawable.BitmapDrawable
+import android.graphics.drawable.Icon
+import android.util.proto.ProtoInputStream
+import android.util.proto.ProtoOutputStream
+import android.widget.RemoteViewsSerializers.createIconFromProto
+import android.widget.RemoteViewsSerializers.writeIconToProto
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.frameworks.coretests.R
+import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayOutputStream
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RemoteViewsSerializersTest {
+ private val context = ApplicationProvider.getApplicationContext<Context>()
+
+ /**
+ * Based on android.graphics.drawable.IconTest#testParcel
+ */
+ @Test
+ fun testWriteIconToProto() {
+ val bitmap = (context.getDrawable(R.drawable.landscape) as BitmapDrawable).bitmap
+ val bitmapData = ByteArrayOutputStream().let {
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, it)
+ it.toByteArray()
+ }
+
+ for (icon in listOf(
+ Icon.createWithBitmap(bitmap),
+ Icon.createWithAdaptiveBitmap(bitmap),
+ Icon.createWithData(bitmapData, 0, bitmapData.size),
+ Icon.createWithResource(context, R.drawable.landscape),
+ Icon.createWithContentUri("content://com.example.myapp/my_icon"),
+ Icon.createWithAdaptiveBitmapContentUri("content://com.example.myapp/my_icon"),
+ )) {
+ icon.tintList = ColorStateList.valueOf(Color.RED)
+ icon.tintBlendMode = BlendMode.SRC_OVER
+ val bytes = ProtoOutputStream().let {
+ writeIconToProto(it, context.resources, icon)
+ it.bytes
+ }
+
+ val copy = ProtoInputStream(bytes).let {
+ createIconFromProto(it).apply(context.resources)
+ }
+ assertThat(copy.type).isEqualTo(icon.type)
+ assertThat(copy.tintBlendMode).isEqualTo(icon.tintBlendMode)
+ assertThat(equalColorStateLists(copy.tintList, icon.tintList)).isTrue()
+
+ when (icon.type) {
+ Icon.TYPE_DATA, Icon.TYPE_URI, Icon.TYPE_URI_ADAPTIVE_BITMAP,
+ Icon.TYPE_RESOURCE -> {
+ assertThat(copy.sameAs(icon)).isTrue()
+ }
+
+ Icon.TYPE_BITMAP, Icon.TYPE_ADAPTIVE_BITMAP -> {
+ assertThat(copy.bitmap.sameAs(icon.bitmap)).isTrue()
+ }
+ }
+ }
+ }
+}
+
+fun equalColorStateLists(a: ColorStateList?, b: ColorStateList?): Boolean {
+ if (a == null && b == null) return true
+ return a != null && b != null &&
+ a.colors.contentEquals(b.colors) &&
+ a.states.foldIndexed(true) { i, acc, it -> acc && it.contentEquals(b.states[i])}
+}