Loading core/java/android/app/appfunctions/GenericDocumentWrapper.java +76 −19 Original line number Original line Diff line number Diff line Loading @@ -16,10 +16,13 @@ package android.app.appfunctions; package android.app.appfunctions; import android.annotation.Nullable; import android.app.appsearch.GenericDocument; import android.app.appsearch.GenericDocument; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.MathUtils; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.NonNull; import java.util.Objects; import java.util.Objects; Loading @@ -31,24 +34,33 @@ import java.util.Objects; * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * directly or Android shared memory if the data is large. * directly or Android shared memory if the data is large. * * * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled * from the underlying `Parcel` when {@link #getValue()} is called. This optimization * allows the system server to pass through the generic document, without unparcel and parcel it. * * @hide * @hide * @see Parcel#writeBlob(byte[]) * @see Parcel#writeBlob(byte[]) */ */ public final class GenericDocumentWrapper implements Parcelable { public final class GenericDocumentWrapper implements Parcelable { @Nullable @GuardedBy("mLock") private GenericDocument mGenericDocument; @GuardedBy("mLock") @Nullable private Parcel mParcel; private final Object mLock = new Object(); public static final Creator<GenericDocumentWrapper> CREATOR = public static final Creator<GenericDocumentWrapper> CREATOR = new Creator<>() { new Creator<>() { @Override @Override public GenericDocumentWrapper createFromParcel(Parcel in) { public GenericDocumentWrapper createFromParcel(Parcel in) { byte[] dataBlob = Objects.requireNonNull(in.readBlob()); int length = in.readInt(); Parcel unmarshallParcel = Parcel.obtain(); int offset = in.dataPosition(); try { in.setDataPosition(MathUtils.addOrThrow(offset, length)); unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); Parcel p = Parcel.obtain(); return new GenericDocumentWrapper( p.appendFrom(in, offset, length); GenericDocument.createFromParcel(unmarshallParcel)); p.setDataPosition(0); } finally { return new GenericDocumentWrapper(p); unmarshallParcel.recycle(); } } } @Override @Override Loading @@ -56,16 +68,42 @@ public final class GenericDocumentWrapper implements Parcelable { return new GenericDocumentWrapper[size]; return new GenericDocumentWrapper[size]; } } }; }; @NonNull private final GenericDocument mGenericDocument; public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) { public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) { mGenericDocument = Objects.requireNonNull(genericDocument); mGenericDocument = Objects.requireNonNull(genericDocument); mParcel = null; } public GenericDocumentWrapper(@NonNull Parcel parcel) { mGenericDocument = null; mParcel = Objects.requireNonNull(parcel); } } /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */ /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */ @NonNull @NonNull public GenericDocument getValue() { public GenericDocument getValue() { return mGenericDocument; unparcel(); synchronized (mLock) { return Objects.requireNonNull(mGenericDocument); } } private void unparcel() { synchronized (mLock) { if (mGenericDocument != null) { return; } byte[] dataBlob = Objects.requireNonNull(Objects.requireNonNull(mParcel).readBlob()); Parcel unmarshallParcel = Parcel.obtain(); try { unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); mGenericDocument = GenericDocument.createFromParcel(unmarshallParcel); mParcel = null; } finally { unmarshallParcel.recycle(); } } } } @Override @Override Loading @@ -75,13 +113,32 @@ public final class GenericDocumentWrapper implements Parcelable { @Override @Override public void writeToParcel(@NonNull Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) { Parcel parcel = Parcel.obtain(); synchronized (mLock) { if (mGenericDocument != null) { int lengthPos = dest.dataPosition(); // write a placeholder for length dest.writeInt(-1); Parcel tempParcel = Parcel.obtain(); byte[] bytes; try { try { mGenericDocument.writeToParcel(parcel, flags); mGenericDocument.writeToParcel(tempParcel, flags); byte[] bytes = parcel.marshall(); bytes = tempParcel.marshall(); dest.writeBlob(bytes); } finally { } finally { parcel.recycle(); tempParcel.recycle(); } int startPos = dest.dataPosition(); dest.writeBlob(bytes); int endPos = dest.dataPosition(); dest.setDataPosition(lengthPos); // Overwrite the length placeholder dest.writeInt(endPos - startPos); dest.setDataPosition(endPos); } else { Parcel originalParcel = Objects.requireNonNull(mParcel); dest.writeInt(originalParcel.dataSize()); dest.appendFrom(originalParcel, 0, originalParcel.dataSize()); } } } } } } } services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt 0 → 100644 +78 −0 Original line number Original line 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 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.app.appfunctions import android.app.appsearch.GenericDocument import android.os.Parcel import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class GenericDocumentWrapperTest { @Test fun parcelUnparcel() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) val recovered = parcelUnparcel(wrapper) assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) } @Test fun parcelUnparcel_afterGetValue() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) val recovered = parcelUnparcel(wrapper) assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) } @Test fun getValue() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) } private fun parcelUnparcel(obj: GenericDocumentWrapper): GenericDocumentWrapper { val parcel = Parcel.obtain() try { obj.writeToParcel(parcel, 0) parcel.setDataPosition(0) return GenericDocumentWrapper.CREATOR.createFromParcel(parcel) } finally { parcel.recycle() } } } No newline at end of file Loading
core/java/android/app/appfunctions/GenericDocumentWrapper.java +76 −19 Original line number Original line Diff line number Diff line Loading @@ -16,10 +16,13 @@ package android.app.appfunctions; package android.app.appfunctions; import android.annotation.Nullable; import android.app.appsearch.GenericDocument; import android.app.appsearch.GenericDocument; import android.os.Parcel; import android.os.Parcel; import android.os.Parcelable; import android.os.Parcelable; import android.util.MathUtils; import androidx.annotation.GuardedBy; import androidx.annotation.NonNull; import androidx.annotation.NonNull; import java.util.Objects; import java.util.Objects; Loading @@ -31,24 +34,33 @@ import java.util.Objects; * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * <p>{#link {@link Parcel#writeBlob(byte[])}} could take care of whether to pass data via binder * directly or Android shared memory if the data is large. * directly or Android shared memory if the data is large. * * * <p>This class performs lazy unparcelling. The `GenericDocument` is only unparcelled * from the underlying `Parcel` when {@link #getValue()} is called. This optimization * allows the system server to pass through the generic document, without unparcel and parcel it. * * @hide * @hide * @see Parcel#writeBlob(byte[]) * @see Parcel#writeBlob(byte[]) */ */ public final class GenericDocumentWrapper implements Parcelable { public final class GenericDocumentWrapper implements Parcelable { @Nullable @GuardedBy("mLock") private GenericDocument mGenericDocument; @GuardedBy("mLock") @Nullable private Parcel mParcel; private final Object mLock = new Object(); public static final Creator<GenericDocumentWrapper> CREATOR = public static final Creator<GenericDocumentWrapper> CREATOR = new Creator<>() { new Creator<>() { @Override @Override public GenericDocumentWrapper createFromParcel(Parcel in) { public GenericDocumentWrapper createFromParcel(Parcel in) { byte[] dataBlob = Objects.requireNonNull(in.readBlob()); int length = in.readInt(); Parcel unmarshallParcel = Parcel.obtain(); int offset = in.dataPosition(); try { in.setDataPosition(MathUtils.addOrThrow(offset, length)); unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); Parcel p = Parcel.obtain(); return new GenericDocumentWrapper( p.appendFrom(in, offset, length); GenericDocument.createFromParcel(unmarshallParcel)); p.setDataPosition(0); } finally { return new GenericDocumentWrapper(p); unmarshallParcel.recycle(); } } } @Override @Override Loading @@ -56,16 +68,42 @@ public final class GenericDocumentWrapper implements Parcelable { return new GenericDocumentWrapper[size]; return new GenericDocumentWrapper[size]; } } }; }; @NonNull private final GenericDocument mGenericDocument; public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) { public GenericDocumentWrapper(@NonNull GenericDocument genericDocument) { mGenericDocument = Objects.requireNonNull(genericDocument); mGenericDocument = Objects.requireNonNull(genericDocument); mParcel = null; } public GenericDocumentWrapper(@NonNull Parcel parcel) { mGenericDocument = null; mParcel = Objects.requireNonNull(parcel); } } /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */ /** Returns the wrapped {@link android.app.appsearch.GenericDocument} */ @NonNull @NonNull public GenericDocument getValue() { public GenericDocument getValue() { return mGenericDocument; unparcel(); synchronized (mLock) { return Objects.requireNonNull(mGenericDocument); } } private void unparcel() { synchronized (mLock) { if (mGenericDocument != null) { return; } byte[] dataBlob = Objects.requireNonNull(Objects.requireNonNull(mParcel).readBlob()); Parcel unmarshallParcel = Parcel.obtain(); try { unmarshallParcel.unmarshall(dataBlob, 0, dataBlob.length); unmarshallParcel.setDataPosition(0); mGenericDocument = GenericDocument.createFromParcel(unmarshallParcel); mParcel = null; } finally { unmarshallParcel.recycle(); } } } } @Override @Override Loading @@ -75,13 +113,32 @@ public final class GenericDocumentWrapper implements Parcelable { @Override @Override public void writeToParcel(@NonNull Parcel dest, int flags) { public void writeToParcel(@NonNull Parcel dest, int flags) { Parcel parcel = Parcel.obtain(); synchronized (mLock) { if (mGenericDocument != null) { int lengthPos = dest.dataPosition(); // write a placeholder for length dest.writeInt(-1); Parcel tempParcel = Parcel.obtain(); byte[] bytes; try { try { mGenericDocument.writeToParcel(parcel, flags); mGenericDocument.writeToParcel(tempParcel, flags); byte[] bytes = parcel.marshall(); bytes = tempParcel.marshall(); dest.writeBlob(bytes); } finally { } finally { parcel.recycle(); tempParcel.recycle(); } int startPos = dest.dataPosition(); dest.writeBlob(bytes); int endPos = dest.dataPosition(); dest.setDataPosition(lengthPos); // Overwrite the length placeholder dest.writeInt(endPos - startPos); dest.setDataPosition(endPos); } else { Parcel originalParcel = Objects.requireNonNull(mParcel); dest.writeInt(originalParcel.dataSize()); dest.appendFrom(originalParcel, 0, originalParcel.dataSize()); } } } } } } }
services/tests/appfunctions/src/android/app/appfunctions/GenericDocumentWrapperTest.kt 0 → 100644 +78 −0 Original line number Original line 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 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.app.appfunctions import android.app.appsearch.GenericDocument import android.os.Parcel import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.JUnit4 @RunWith(JUnit4::class) class GenericDocumentWrapperTest { @Test fun parcelUnparcel() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) val recovered = parcelUnparcel(wrapper) assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) } @Test fun parcelUnparcel_afterGetValue() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) val recovered = parcelUnparcel(wrapper) assertThat(recovered.value.getPropertyLong("test")).isEqualTo(42) } @Test fun getValue() { val doc = GenericDocument.Builder<GenericDocument.Builder<*>>("", "", "") .setPropertyLong("test", 42) .build() val wrapper = GenericDocumentWrapper(doc) assertThat(wrapper.value.getPropertyLong("test")).isEqualTo(42) } private fun parcelUnparcel(obj: GenericDocumentWrapper): GenericDocumentWrapper { val parcel = Parcel.obtain() try { obj.writeToParcel(parcel, 0) parcel.setDataPosition(0) return GenericDocumentWrapper.CREATOR.createFromParcel(parcel) } finally { parcel.recycle() } } } No newline at end of file