Loading packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt +67 −20 Original line number Diff line number Diff line Loading @@ -18,16 +18,23 @@ package com.android.settingslib.metadata import android.content.Intent import android.os.Bundle import android.os.Parcel import android.os.Parcelable /** Returns if the two bundles are equal (`null` values are ignored). */ @Suppress("DEPRECATION") fun Bundle?.contentEquals(other: Bundle?): Boolean { infix fun Bundle?.contentEquals(other: Bundle?): Boolean { if (this == null) return other == null if (other == null) return false if (keySet() != other.keySet()) return false fun Any?.valueEquals(other: Any?) = // entry could have null value, so compare all keys val keys = keySet() + other.keySet() return keys.all { key -> get(key).valueEquals(other.get(key)) } } private fun Any?.valueEquals(other: Any?) = when (this) { is Bundle -> other is Bundle && this.contentEquals(other) is Intent -> other is Intent && this.filterEquals(other) is Bundle -> other is Bundle && contentEquals(other) is Intent -> other is Intent && filterEquals(other) && extras contentEquals other.extras is BooleanArray -> other is BooleanArray && this contentEquals other is ByteArray -> other is ByteArray && this contentEquals other is CharArray -> other is CharArray && this contentEquals other Loading @@ -39,8 +46,48 @@ fun Bundle?.contentEquals(other: Bundle?): Boolean { is Array<*> -> other is Array<*> && this contentDeepEquals other else -> this == other } for (key in keySet()) { if (!get(key).valueEquals(other.get(key))) return false /** Marshall a [Parcelable] to byte array. */ fun Parcelable.marshallParcel(): ByteArray = useParcel { parcel -> writeToParcel(parcel, 0) return@useParcel parcel.marshall() } /** * Unmarshall a byte array to [Bundle]. * * Proper [ClassLoader] should be provided if needed (e.g. [Parcelable]) when read entry. */ fun ByteArray.unmarshallBundle(): Bundle = unmarshallParcel(Bundle.CREATOR) /** Unmarshall a byte array to [Parcelable]. */ inline fun <reified T> ByteArray.unmarshallParcel(creator: Parcelable.Creator<T>): T = useParcel { parcel -> parcel.unmarshall(this, 0, size) parcel.setDataPosition(0) return@useParcel creator.createFromParcel(parcel) } /** Unmarshall a byte array to [Parcelable] with given class loader. */ inline fun <reified T> ByteArray.unmarshallParcel( creator: Parcelable.ClassLoaderCreator<T>, classLoader: ClassLoader, ): T = useParcel { parcel -> parcel.unmarshall(this, 0, size) parcel.setDataPosition(0) return@useParcel creator.createFromParcel(parcel, classLoader) } /** * Obtains a [Parcel] and performs an action. * * The parcel is ensured to be recycled when exception is thrown. */ fun <R> useParcel(block: (Parcel) -> R): R { val parcel = Parcel.obtain() try { return block(parcel) } finally { parcel.recycle() } return true } packages/SettingsLib/Metadata/tests/Android.bp 0 → 100644 +23 −0 Original line number Diff line number Diff line package { default_applicable_licenses: ["frameworks_base_license"], } android_app { name: "SettingsLibMetadataShell", platform_apis: true, } android_robolectric_test { name: "SettingsLibMetadataTest", srcs: ["src/**/*.kt"], static_libs: [ "SettingsLibMetadata", "androidx.test.ext.junit", "kotlin-parcelize-runtime", "truth", ], kotlin_plugins: ["kotlin-parcelize-compiler-plugin"], instrumentation_for: "SettingsLibMetadataShell", coverage_libs: ["SettingsLibMetadata"], upstream: true, } packages/SettingsLib/Metadata/tests/AndroidManifest.xml 0 → 100644 +2 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <manifest package="com.android.settingslib.metadata.test" /> packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/BundlesTest.kt 0 → 100644 +68 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.settingslib.metadata import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.os.Process import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import kotlinx.parcelize.Parcelize import kotlinx.parcelize.parcelableCreator import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BundlesTest { @Test fun bundle_marshall_unmarshall() { val userHandle = Process.myUserHandle() val bundle = Bundle().apply { putString("nullString", null) putString("string", "string") putByteArray("bytes", byteArrayOf(1)) putInt("int", 1) putLong("long", 2) putBoolean("boolean", true) putDouble("double", 3.0) putFloat("float", 4f) putParcelable(Intent.EXTRA_USER, userHandle) putParcelable("foo", Foo("foo")) putParcelable("bar", Bar("bar")) } val result = bundle.marshallParcel().unmarshallBundle() result.classLoader = Foo::class.java.classLoader @Suppress("DEPRECATION") val foo = result.getParcelable<Foo>("foo")!! assertThat(foo.name).isEqualTo("foo") result.classLoader = Bar::class.java.classLoader assertThat(bundle contentEquals result).isTrue() } @Test fun marshallParcel_unmarshallParcel() { val parcelable = Foo("foo") val result = parcelable.marshallParcel().unmarshallParcel(parcelableCreator<Foo>()) assertThat(result).isEqualTo(parcelable) } @Parcelize data class Foo(val name: String) : Parcelable @Parcelize data class Bar(val name: String) : Parcelable } Loading
packages/SettingsLib/Metadata/src/com/android/settingslib/metadata/Bundles.kt +67 −20 Original line number Diff line number Diff line Loading @@ -18,16 +18,23 @@ package com.android.settingslib.metadata import android.content.Intent import android.os.Bundle import android.os.Parcel import android.os.Parcelable /** Returns if the two bundles are equal (`null` values are ignored). */ @Suppress("DEPRECATION") fun Bundle?.contentEquals(other: Bundle?): Boolean { infix fun Bundle?.contentEquals(other: Bundle?): Boolean { if (this == null) return other == null if (other == null) return false if (keySet() != other.keySet()) return false fun Any?.valueEquals(other: Any?) = // entry could have null value, so compare all keys val keys = keySet() + other.keySet() return keys.all { key -> get(key).valueEquals(other.get(key)) } } private fun Any?.valueEquals(other: Any?) = when (this) { is Bundle -> other is Bundle && this.contentEquals(other) is Intent -> other is Intent && this.filterEquals(other) is Bundle -> other is Bundle && contentEquals(other) is Intent -> other is Intent && filterEquals(other) && extras contentEquals other.extras is BooleanArray -> other is BooleanArray && this contentEquals other is ByteArray -> other is ByteArray && this contentEquals other is CharArray -> other is CharArray && this contentEquals other Loading @@ -39,8 +46,48 @@ fun Bundle?.contentEquals(other: Bundle?): Boolean { is Array<*> -> other is Array<*> && this contentDeepEquals other else -> this == other } for (key in keySet()) { if (!get(key).valueEquals(other.get(key))) return false /** Marshall a [Parcelable] to byte array. */ fun Parcelable.marshallParcel(): ByteArray = useParcel { parcel -> writeToParcel(parcel, 0) return@useParcel parcel.marshall() } /** * Unmarshall a byte array to [Bundle]. * * Proper [ClassLoader] should be provided if needed (e.g. [Parcelable]) when read entry. */ fun ByteArray.unmarshallBundle(): Bundle = unmarshallParcel(Bundle.CREATOR) /** Unmarshall a byte array to [Parcelable]. */ inline fun <reified T> ByteArray.unmarshallParcel(creator: Parcelable.Creator<T>): T = useParcel { parcel -> parcel.unmarshall(this, 0, size) parcel.setDataPosition(0) return@useParcel creator.createFromParcel(parcel) } /** Unmarshall a byte array to [Parcelable] with given class loader. */ inline fun <reified T> ByteArray.unmarshallParcel( creator: Parcelable.ClassLoaderCreator<T>, classLoader: ClassLoader, ): T = useParcel { parcel -> parcel.unmarshall(this, 0, size) parcel.setDataPosition(0) return@useParcel creator.createFromParcel(parcel, classLoader) } /** * Obtains a [Parcel] and performs an action. * * The parcel is ensured to be recycled when exception is thrown. */ fun <R> useParcel(block: (Parcel) -> R): R { val parcel = Parcel.obtain() try { return block(parcel) } finally { parcel.recycle() } return true }
packages/SettingsLib/Metadata/tests/Android.bp 0 → 100644 +23 −0 Original line number Diff line number Diff line package { default_applicable_licenses: ["frameworks_base_license"], } android_app { name: "SettingsLibMetadataShell", platform_apis: true, } android_robolectric_test { name: "SettingsLibMetadataTest", srcs: ["src/**/*.kt"], static_libs: [ "SettingsLibMetadata", "androidx.test.ext.junit", "kotlin-parcelize-runtime", "truth", ], kotlin_plugins: ["kotlin-parcelize-compiler-plugin"], instrumentation_for: "SettingsLibMetadataShell", coverage_libs: ["SettingsLibMetadata"], upstream: true, }
packages/SettingsLib/Metadata/tests/AndroidManifest.xml 0 → 100644 +2 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <manifest package="com.android.settingslib.metadata.test" />
packages/SettingsLib/Metadata/tests/src/com/android/settingslib/metadata/BundlesTest.kt 0 → 100644 +68 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.settingslib.metadata import android.content.Intent import android.os.Bundle import android.os.Parcelable import android.os.Process import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat import kotlinx.parcelize.Parcelize import kotlinx.parcelize.parcelableCreator import org.junit.Test import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class BundlesTest { @Test fun bundle_marshall_unmarshall() { val userHandle = Process.myUserHandle() val bundle = Bundle().apply { putString("nullString", null) putString("string", "string") putByteArray("bytes", byteArrayOf(1)) putInt("int", 1) putLong("long", 2) putBoolean("boolean", true) putDouble("double", 3.0) putFloat("float", 4f) putParcelable(Intent.EXTRA_USER, userHandle) putParcelable("foo", Foo("foo")) putParcelable("bar", Bar("bar")) } val result = bundle.marshallParcel().unmarshallBundle() result.classLoader = Foo::class.java.classLoader @Suppress("DEPRECATION") val foo = result.getParcelable<Foo>("foo")!! assertThat(foo.name).isEqualTo("foo") result.classLoader = Bar::class.java.classLoader assertThat(bundle contentEquals result).isTrue() } @Test fun marshallParcel_unmarshallParcel() { val parcelable = Foo("foo") val result = parcelable.marshallParcel().unmarshallParcel(parcelableCreator<Foo>()) assertThat(result).isEqualTo(parcelable) } @Parcelize data class Foo(val name: String) : Parcelable @Parcelize data class Bar(val name: String) : Parcelable }