Loading core/java/android/app/usage/ParcelableUsageEventList.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; parcelable ParcelableUsageEventList; core/java/android/app/usage/ParcelableUsageEventList.java 0 → 100644 +266 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; import android.annotation.NonNull; import android.app.usage.UsageEvents.Event; import android.content.res.Configuration; import android.os.BadParcelableException; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; /** * This is a copied version of BaseParceledListSlice with specific * {@link UsageEvents.Event} instance that used to transfer the large * list of {@link UsageEvents.Event} objects across an IPC. Splits * into multiple transactions if needed. * * @see BasedParceledListSlice * * @hide */ public final class ParcelableUsageEventList implements Parcelable { private static final String TAG = "ParcelableUsageEventList"; private static final boolean DEBUG = false; private static final boolean DEBUG_ALL = false; private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); private List<Event> mList; public ParcelableUsageEventList(List<Event> list) { mList = list; } private ParcelableUsageEventList(Parcel in) { final int N = in.readInt(); mList = new ArrayList<>(); if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); if (N <= 0) { return; } int i = 0; while (i < N) { if (in.readInt() == 0) { break; } mList.add(readEventFromParcel(in)); if (DEBUG_ALL) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1)); i++; } if (DEBUG) { Log.d(TAG, "Read " + mList.size() + " inline UsageEvents" + ", total N=" + N + " UsageEvents"); } if (i >= N) { return; } final IBinder retriever = in.readStrongBinder(); while (i < N) { if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInt(i); try { retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); reply.readException(); int count = 0; while (i < N && reply.readInt() != 0) { mList.add(readEventFromParcel(reply)); if (DEBUG_ALL) { Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1)); } i++; count++; } if (DEBUG) Log.d(TAG, "Read extra @" + count + " of " + N); } catch (RemoteException e) { throw new BadParcelableException( "Failure retrieving array; only received " + i + " of " + N, e); } finally { reply.recycle(); data.recycle(); } } if (DEBUG) Log.d(TAG, "Finish reading total " + i + " UsageEvents"); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { final int N = mList.size(); final int callFlags = flags; dest.writeInt(N); if (DEBUG) Log.d(TAG, "Writing " + N + " items"); if (N > 0) { int i = 0; while (i < N && dest.dataSize() < MAX_IPC_SIZE) { dest.writeInt(1); final Event event = mList.get(i); writeEventToParcel(event, dest, callFlags); if (DEBUG_ALL) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); i++; } if (i < N) { dest.writeInt(0); Binder retriever = new Binder() { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code != FIRST_CALL_TRANSACTION) { return super.onTransact(code, data, reply, flags); } else if (mList == null) { throw new IllegalArgumentException("Attempt to transfer null list, " + "did transfer finish?"); } int i = data.readInt(); if (DEBUG) { Log.d(TAG, "Writing more @" + i + " of " + N + " to " + Binder.getCallingPid() + ", sender=" + this); } try { reply.writeNoException(); int count = 0; while (i < N && reply.dataSize() < MAX_IPC_SIZE) { reply.writeInt(1); final Event event = mList.get(i); writeEventToParcel(event, reply, callFlags); if (DEBUG_ALL) { Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); } i++; count++; } if (i < N) { if (DEBUG) { Log.d(TAG, "Breaking @" + i + " of " + N + "(count = " + count + ")"); } reply.writeInt(0); } else { if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference"); mList = null; } } catch (RuntimeException e) { if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference"); mList = null; throw e; } return true; } }; if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); dest.writeStrongBinder(retriever); } } } public List<Event> getList() { return mList; } public static final Parcelable.Creator<ParcelableUsageEventList> CREATOR = new Parcelable.Creator<ParcelableUsageEventList>() { public ParcelableUsageEventList createFromParcel(Parcel in) { return new ParcelableUsageEventList(in); } @Override public ParcelableUsageEventList[] newArray(int size) { return new ParcelableUsageEventList[size]; } }; private Event readEventFromParcel(Parcel in) { final Event event = new Event(); event.mPackage = in.readString(); event.mClass = in.readString(); event.mInstanceId = in.readInt(); event.mTaskRootPackage = in.readString(); event.mTaskRootClass = in.readString(); event.mEventType = in.readInt(); event.mTimeStamp = in.readLong(); // Fill out the event-dependant fields. event.mConfiguration = null; event.mShortcutId = null; event.mAction = null; event.mContentType = null; event.mContentAnnotations = null; event.mNotificationChannelId = null; event.mLocusId = null; switch (event.mEventType) { case Event.CONFIGURATION_CHANGE -> { event.mConfiguration = Configuration.CREATOR.createFromParcel(in); } case Event.SHORTCUT_INVOCATION -> event.mShortcutId = in.readString(); case Event.CHOOSER_ACTION -> { event.mAction = in.readString(); event.mContentType = in.readString(); event.mContentAnnotations = in.readStringArray(); } case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt(); case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString(); case Event.LOCUS_ID_SET -> event.mLocusId = in.readString(); } event.mFlags = in.readInt(); return event; } private void writeEventToParcel(@NonNull Event event, @NonNull Parcel dest, int flags) { dest.writeString(event.mPackage); dest.writeString(event.mClass); dest.writeInt(event.mInstanceId); dest.writeString(event.mTaskRootPackage); dest.writeString(event.mTaskRootClass); dest.writeInt(event.mEventType); dest.writeLong(event.mTimeStamp); switch (event.mEventType) { case Event.CONFIGURATION_CHANGE -> event.mConfiguration.writeToParcel(dest, flags); case Event.SHORTCUT_INVOCATION -> dest.writeString(event.mShortcutId); case Event.CHOOSER_ACTION -> { dest.writeString(event.mAction); dest.writeString(event.mContentType); dest.writeStringArray(event.mContentAnnotations); } case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason); case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId); case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId); } dest.writeInt(event.mFlags); } } core/java/android/app/usage/UsageEvents.java +42 −3 Original line number Diff line number Diff line Loading @@ -714,7 +714,7 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage private Parcel mParcel = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mEventCount; private int mEventCount; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mIndex = 0; Loading @@ -735,6 +735,23 @@ public final class UsageEvents implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UsageEvents(Parcel in) { if (Flags.useParceledList()) { readUsageEventsFromParcelWithParceledList(in); } else { readUsageEventsFromParcelWithBlob(in); } mIncludeTaskRoots = true; } private void readUsageEventsFromParcelWithParceledList(Parcel in) { mIndex = in.readInt(); mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(), ParcelableUsageEventList.class).getList(); mEventCount = mEventsToWrite.size(); } private void readUsageEventsFromParcelWithBlob(Parcel in) { byte[] bytes = in.readBlob(); Parcel data = Parcel.obtain(); data.unmarshall(bytes, 0, bytes.length); Loading @@ -752,7 +769,6 @@ public final class UsageEvents implements Parcelable { mParcel.setDataSize(mParcel.dataPosition()); mParcel.setDataPosition(positionInParcel); } mIncludeTaskRoots = true; } /** Loading Loading @@ -810,6 +826,10 @@ public final class UsageEvents implements Parcelable { return false; } if (Flags.useParceledList()) { return getNextEventFromParceledList(eventOut); } if (mParcel != null) { readEventFromParcel(mParcel, eventOut); } else { Loading @@ -824,6 +844,12 @@ public final class UsageEvents implements Parcelable { return true; } private boolean getNextEventFromParceledList(Event eventOut) { eventOut.copyFrom(mEventsToWrite.get(mIndex)); mIndex++; return true; } /** * Resets the collection so that it can be iterated over from the beginning. * Loading Loading @@ -968,7 +994,7 @@ public final class UsageEvents implements Parcelable { case Event.CHOOSER_ACTION: eventOut.mAction = p.readString(); eventOut.mContentType = p.readString(); eventOut.mContentAnnotations = p.createStringArray(); eventOut.mContentAnnotations = p.readStringArray(); break; case Event.STANDBY_BUCKET_CHANGED: eventOut.mBucketAndReason = p.readInt(); Loading @@ -990,6 +1016,19 @@ public final class UsageEvents implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (Flags.useParceledList()) { writeUsageEventsToParcelWithParceledList(dest, flags); } else { writeUsageEventsToParcelWithBlob(dest, flags); } } private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) { dest.writeInt(mIndex); dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags); } private void writeUsageEventsToParcelWithBlob(Parcel dest, int flags) { Parcel data = Parcel.obtain(); data.writeInt(mEventCount); data.writeInt(mIndex); Loading core/java/android/app/usage/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -21,3 +21,10 @@ flag { is_fixed_read_only: true bug: "299336442" } flag { name: "use_parceled_list" namespace: "backstage_power" description: "Flag for parcelable usage event list" bug: "301254110" } core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import android.app.usage.UsageEvents.Event; import android.content.res.Configuration; import android.os.Parcel; import android.test.suitebuilder.annotation.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; @RunWith(AndroidJUnit4.class) @LargeTest public class ParcelableUsageEventListTest { private static final int SMALL_TEST_EVENT_COUNT = 100; private static final int LARGE_TEST_EVENT_COUNT = 10000; private Random mRandom = new Random(); @Test public void testSmallList() throws Exception { testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT); } @Test public void testLargeList() throws Exception { testParcelableUsageEventList(LARGE_TEST_EVENT_COUNT); } private void testParcelableUsageEventList(int eventCount) throws Exception { List<Event> smallList = new ArrayList<>(); for (int i = 0; i < eventCount; i++) { smallList.add(generateUsageEvent()); } ParcelableUsageEventList slice; Parcel parcel = Parcel.obtain(); try { parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0); parcel.setDataPosition(0); slice = parcel.readParcelable(getClass().getClassLoader(), ParcelableUsageEventList.class); } finally { parcel.recycle(); } assertNotNull(slice); assertNotNull(slice.getList()); assertEquals(eventCount, slice.getList().size()); for (int i = 0; i < eventCount; i++) { compareUsageEvent(smallList.get(i), slice.getList().get(i)); } } private Event generateUsageEvent() { final Event event = new Event(); event.mEventType = mRandom.nextInt(Event.MAX_EVENT_TYPE + 1); event.mPackage = anyString(); event.mClass = anyString(); event.mTimeStamp = anyLong(); event.mInstanceId = anyInt(); event.mTimeStamp = anyLong(); switch (event.mEventType) { case Event.CONFIGURATION_CHANGE: event.mConfiguration = new Configuration(); event.mConfiguration.seq = anyInt(); event.mConfiguration.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES; event.mConfiguration.smallestScreenWidthDp = 100; event.mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; event.mConfiguration.windowConfiguration.setRotation(ROTATION_90); break; case Event.SHORTCUT_INVOCATION: event.mShortcutId = anyString(); break; case Event.CHOOSER_ACTION: event.mAction = anyString(); event.mContentType = anyString(); event.mContentAnnotations = new String[mRandom.nextInt(10)]; for (int i = 0; i < event.mContentAnnotations.length; i++) { event.mContentAnnotations[i] = anyString(); } break; case Event.STANDBY_BUCKET_CHANGED: event.mBucketAndReason = anyInt(); break; case Event.NOTIFICATION_INTERRUPTION: event.mNotificationChannelId = anyString(); break; case Event.LOCUS_ID_SET: event.mLocusId = anyString(); break; } event.mFlags = anyInt(); return event; } private static void compareUsageEvent(Event ue1, Event ue2) { assertEquals(ue1.mPackage, ue2.mPackage); assertEquals(ue1.mClass, ue2.mClass); assertEquals(ue1.mTaskRootPackage, ue2.mTaskRootPackage); assertEquals(ue1.mTaskRootClass, ue2.mTaskRootClass); assertEquals(ue1.mInstanceId, ue2.mInstanceId); assertEquals(ue1.mEventType, ue2.mEventType); assertEquals(ue1.mTimeStamp, ue2.mTimeStamp); switch (ue1.mEventType) { case Event.CONFIGURATION_CHANGE: assertEquals(ue1.mConfiguration, ue2.mConfiguration); break; case Event.SHORTCUT_INVOCATION: assertEquals(ue1.mShortcutId, ue2.mShortcutId); break; case Event.CHOOSER_ACTION: assertEquals(ue1.mAction, ue2.mAction); assertEquals(ue1.mContentType, ue2.mContentType); assertTrue(Arrays.equals(ue1.mContentAnnotations, ue2.mContentAnnotations)); break; case Event.STANDBY_BUCKET_CHANGED: assertEquals(ue1.mBucketAndReason, ue2.mBucketAndReason); break; case Event.NOTIFICATION_INTERRUPTION: assertEquals(ue1.mNotificationChannelId, ue1.mNotificationChannelId); break; case Event.LOCUS_ID_SET: assertEquals(ue1.mLocusId, ue2.mLocusId); break; } assertEquals(ue1.mFlags, ue2.mFlags); } } Loading
core/java/android/app/usage/ParcelableUsageEventList.aidl 0 → 100644 +19 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; parcelable ParcelableUsageEventList;
core/java/android/app/usage/ParcelableUsageEventList.java 0 → 100644 +266 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; import android.annotation.NonNull; import android.app.usage.UsageEvents.Event; import android.content.res.Configuration; import android.os.BadParcelableException; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; import android.util.Log; import java.util.ArrayList; import java.util.List; /** * This is a copied version of BaseParceledListSlice with specific * {@link UsageEvents.Event} instance that used to transfer the large * list of {@link UsageEvents.Event} objects across an IPC. Splits * into multiple transactions if needed. * * @see BasedParceledListSlice * * @hide */ public final class ParcelableUsageEventList implements Parcelable { private static final String TAG = "ParcelableUsageEventList"; private static final boolean DEBUG = false; private static final boolean DEBUG_ALL = false; private static final int MAX_IPC_SIZE = IBinder.getSuggestedMaxIpcSizeBytes(); private List<Event> mList; public ParcelableUsageEventList(List<Event> list) { mList = list; } private ParcelableUsageEventList(Parcel in) { final int N = in.readInt(); mList = new ArrayList<>(); if (DEBUG) Log.d(TAG, "Retrieving " + N + " items"); if (N <= 0) { return; } int i = 0; while (i < N) { if (in.readInt() == 0) { break; } mList.add(readEventFromParcel(in)); if (DEBUG_ALL) Log.d(TAG, "Read inline #" + i + ": " + mList.get(mList.size() - 1)); i++; } if (DEBUG) { Log.d(TAG, "Read " + mList.size() + " inline UsageEvents" + ", total N=" + N + " UsageEvents"); } if (i >= N) { return; } final IBinder retriever = in.readStrongBinder(); while (i < N) { if (DEBUG) Log.d(TAG, "Reading more @" + i + " of " + N + ": retriever=" + retriever); Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInt(i); try { retriever.transact(IBinder.FIRST_CALL_TRANSACTION, data, reply, 0); reply.readException(); int count = 0; while (i < N && reply.readInt() != 0) { mList.add(readEventFromParcel(reply)); if (DEBUG_ALL) { Log.d(TAG, "Read extra #" + i + ": " + mList.get(mList.size() - 1)); } i++; count++; } if (DEBUG) Log.d(TAG, "Read extra @" + count + " of " + N); } catch (RemoteException e) { throw new BadParcelableException( "Failure retrieving array; only received " + i + " of " + N, e); } finally { reply.recycle(); data.recycle(); } } if (DEBUG) Log.d(TAG, "Finish reading total " + i + " UsageEvents"); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { final int N = mList.size(); final int callFlags = flags; dest.writeInt(N); if (DEBUG) Log.d(TAG, "Writing " + N + " items"); if (N > 0) { int i = 0; while (i < N && dest.dataSize() < MAX_IPC_SIZE) { dest.writeInt(1); final Event event = mList.get(i); writeEventToParcel(event, dest, callFlags); if (DEBUG_ALL) Log.d(TAG, "Wrote inline #" + i + ": " + mList.get(i)); i++; } if (i < N) { dest.writeInt(0); Binder retriever = new Binder() { @Override protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException { if (code != FIRST_CALL_TRANSACTION) { return super.onTransact(code, data, reply, flags); } else if (mList == null) { throw new IllegalArgumentException("Attempt to transfer null list, " + "did transfer finish?"); } int i = data.readInt(); if (DEBUG) { Log.d(TAG, "Writing more @" + i + " of " + N + " to " + Binder.getCallingPid() + ", sender=" + this); } try { reply.writeNoException(); int count = 0; while (i < N && reply.dataSize() < MAX_IPC_SIZE) { reply.writeInt(1); final Event event = mList.get(i); writeEventToParcel(event, reply, callFlags); if (DEBUG_ALL) { Log.d(TAG, "Wrote extra #" + i + ": " + mList.get(i)); } i++; count++; } if (i < N) { if (DEBUG) { Log.d(TAG, "Breaking @" + i + " of " + N + "(count = " + count + ")"); } reply.writeInt(0); } else { if (DEBUG) Log.d(TAG, "Transfer done, clearing mList reference"); mList = null; } } catch (RuntimeException e) { if (DEBUG) Log.d(TAG, "Transfer failed, clearing mList reference"); mList = null; throw e; } return true; } }; if (DEBUG) Log.d(TAG, "Breaking @" + i + " of " + N + ": retriever=" + retriever); dest.writeStrongBinder(retriever); } } } public List<Event> getList() { return mList; } public static final Parcelable.Creator<ParcelableUsageEventList> CREATOR = new Parcelable.Creator<ParcelableUsageEventList>() { public ParcelableUsageEventList createFromParcel(Parcel in) { return new ParcelableUsageEventList(in); } @Override public ParcelableUsageEventList[] newArray(int size) { return new ParcelableUsageEventList[size]; } }; private Event readEventFromParcel(Parcel in) { final Event event = new Event(); event.mPackage = in.readString(); event.mClass = in.readString(); event.mInstanceId = in.readInt(); event.mTaskRootPackage = in.readString(); event.mTaskRootClass = in.readString(); event.mEventType = in.readInt(); event.mTimeStamp = in.readLong(); // Fill out the event-dependant fields. event.mConfiguration = null; event.mShortcutId = null; event.mAction = null; event.mContentType = null; event.mContentAnnotations = null; event.mNotificationChannelId = null; event.mLocusId = null; switch (event.mEventType) { case Event.CONFIGURATION_CHANGE -> { event.mConfiguration = Configuration.CREATOR.createFromParcel(in); } case Event.SHORTCUT_INVOCATION -> event.mShortcutId = in.readString(); case Event.CHOOSER_ACTION -> { event.mAction = in.readString(); event.mContentType = in.readString(); event.mContentAnnotations = in.readStringArray(); } case Event.STANDBY_BUCKET_CHANGED -> event.mBucketAndReason = in.readInt(); case Event.NOTIFICATION_INTERRUPTION -> event.mNotificationChannelId = in.readString(); case Event.LOCUS_ID_SET -> event.mLocusId = in.readString(); } event.mFlags = in.readInt(); return event; } private void writeEventToParcel(@NonNull Event event, @NonNull Parcel dest, int flags) { dest.writeString(event.mPackage); dest.writeString(event.mClass); dest.writeInt(event.mInstanceId); dest.writeString(event.mTaskRootPackage); dest.writeString(event.mTaskRootClass); dest.writeInt(event.mEventType); dest.writeLong(event.mTimeStamp); switch (event.mEventType) { case Event.CONFIGURATION_CHANGE -> event.mConfiguration.writeToParcel(dest, flags); case Event.SHORTCUT_INVOCATION -> dest.writeString(event.mShortcutId); case Event.CHOOSER_ACTION -> { dest.writeString(event.mAction); dest.writeString(event.mContentType); dest.writeStringArray(event.mContentAnnotations); } case Event.STANDBY_BUCKET_CHANGED -> dest.writeInt(event.mBucketAndReason); case Event.NOTIFICATION_INTERRUPTION -> dest.writeString(event.mNotificationChannelId); case Event.LOCUS_ID_SET -> dest.writeString(event.mLocusId); } dest.writeInt(event.mFlags); } }
core/java/android/app/usage/UsageEvents.java +42 −3 Original line number Diff line number Diff line Loading @@ -714,7 +714,7 @@ public final class UsageEvents implements Parcelable { @UnsupportedAppUsage private Parcel mParcel = null; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private final int mEventCount; private int mEventCount; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) private int mIndex = 0; Loading @@ -735,6 +735,23 @@ public final class UsageEvents implements Parcelable { */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) public UsageEvents(Parcel in) { if (Flags.useParceledList()) { readUsageEventsFromParcelWithParceledList(in); } else { readUsageEventsFromParcelWithBlob(in); } mIncludeTaskRoots = true; } private void readUsageEventsFromParcelWithParceledList(Parcel in) { mIndex = in.readInt(); mEventsToWrite = in.readParcelable(UsageEvents.class.getClassLoader(), ParcelableUsageEventList.class).getList(); mEventCount = mEventsToWrite.size(); } private void readUsageEventsFromParcelWithBlob(Parcel in) { byte[] bytes = in.readBlob(); Parcel data = Parcel.obtain(); data.unmarshall(bytes, 0, bytes.length); Loading @@ -752,7 +769,6 @@ public final class UsageEvents implements Parcelable { mParcel.setDataSize(mParcel.dataPosition()); mParcel.setDataPosition(positionInParcel); } mIncludeTaskRoots = true; } /** Loading Loading @@ -810,6 +826,10 @@ public final class UsageEvents implements Parcelable { return false; } if (Flags.useParceledList()) { return getNextEventFromParceledList(eventOut); } if (mParcel != null) { readEventFromParcel(mParcel, eventOut); } else { Loading @@ -824,6 +844,12 @@ public final class UsageEvents implements Parcelable { return true; } private boolean getNextEventFromParceledList(Event eventOut) { eventOut.copyFrom(mEventsToWrite.get(mIndex)); mIndex++; return true; } /** * Resets the collection so that it can be iterated over from the beginning. * Loading Loading @@ -968,7 +994,7 @@ public final class UsageEvents implements Parcelable { case Event.CHOOSER_ACTION: eventOut.mAction = p.readString(); eventOut.mContentType = p.readString(); eventOut.mContentAnnotations = p.createStringArray(); eventOut.mContentAnnotations = p.readStringArray(); break; case Event.STANDBY_BUCKET_CHANGED: eventOut.mBucketAndReason = p.readInt(); Loading @@ -990,6 +1016,19 @@ public final class UsageEvents implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { if (Flags.useParceledList()) { writeUsageEventsToParcelWithParceledList(dest, flags); } else { writeUsageEventsToParcelWithBlob(dest, flags); } } private void writeUsageEventsToParcelWithParceledList(Parcel dest, int flags) { dest.writeInt(mIndex); dest.writeParcelable(new ParcelableUsageEventList(mEventsToWrite), flags); } private void writeUsageEventsToParcelWithBlob(Parcel dest, int flags) { Parcel data = Parcel.obtain(); data.writeInt(mEventCount); data.writeInt(mIndex); Loading
core/java/android/app/usage/flags.aconfig +7 −0 Original line number Diff line number Diff line Loading @@ -21,3 +21,10 @@ flag { is_fixed_read_only: true bug: "299336442" } flag { name: "use_parceled_list" namespace: "backstage_power" description: "Flag for parcelable usage event list" bug: "301254110" }
core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java 0 → 100644 +164 −0 Original line number Diff line number Diff line /* * Copyright (C) 2023 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.usage; import static android.view.Surface.ROTATION_90; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import android.app.usage.UsageEvents.Event; import android.content.res.Configuration; import android.os.Parcel; import android.test.suitebuilder.annotation.LargeTest; import androidx.test.runner.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; @RunWith(AndroidJUnit4.class) @LargeTest public class ParcelableUsageEventListTest { private static final int SMALL_TEST_EVENT_COUNT = 100; private static final int LARGE_TEST_EVENT_COUNT = 10000; private Random mRandom = new Random(); @Test public void testSmallList() throws Exception { testParcelableUsageEventList(SMALL_TEST_EVENT_COUNT); } @Test public void testLargeList() throws Exception { testParcelableUsageEventList(LARGE_TEST_EVENT_COUNT); } private void testParcelableUsageEventList(int eventCount) throws Exception { List<Event> smallList = new ArrayList<>(); for (int i = 0; i < eventCount; i++) { smallList.add(generateUsageEvent()); } ParcelableUsageEventList slice; Parcel parcel = Parcel.obtain(); try { parcel.writeParcelable(new ParcelableUsageEventList(smallList), 0); parcel.setDataPosition(0); slice = parcel.readParcelable(getClass().getClassLoader(), ParcelableUsageEventList.class); } finally { parcel.recycle(); } assertNotNull(slice); assertNotNull(slice.getList()); assertEquals(eventCount, slice.getList().size()); for (int i = 0; i < eventCount; i++) { compareUsageEvent(smallList.get(i), slice.getList().get(i)); } } private Event generateUsageEvent() { final Event event = new Event(); event.mEventType = mRandom.nextInt(Event.MAX_EVENT_TYPE + 1); event.mPackage = anyString(); event.mClass = anyString(); event.mTimeStamp = anyLong(); event.mInstanceId = anyInt(); event.mTimeStamp = anyLong(); switch (event.mEventType) { case Event.CONFIGURATION_CHANGE: event.mConfiguration = new Configuration(); event.mConfiguration.seq = anyInt(); event.mConfiguration.screenLayout = Configuration.SCREENLAYOUT_ROUND_YES; event.mConfiguration.smallestScreenWidthDp = 100; event.mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE; event.mConfiguration.windowConfiguration.setRotation(ROTATION_90); break; case Event.SHORTCUT_INVOCATION: event.mShortcutId = anyString(); break; case Event.CHOOSER_ACTION: event.mAction = anyString(); event.mContentType = anyString(); event.mContentAnnotations = new String[mRandom.nextInt(10)]; for (int i = 0; i < event.mContentAnnotations.length; i++) { event.mContentAnnotations[i] = anyString(); } break; case Event.STANDBY_BUCKET_CHANGED: event.mBucketAndReason = anyInt(); break; case Event.NOTIFICATION_INTERRUPTION: event.mNotificationChannelId = anyString(); break; case Event.LOCUS_ID_SET: event.mLocusId = anyString(); break; } event.mFlags = anyInt(); return event; } private static void compareUsageEvent(Event ue1, Event ue2) { assertEquals(ue1.mPackage, ue2.mPackage); assertEquals(ue1.mClass, ue2.mClass); assertEquals(ue1.mTaskRootPackage, ue2.mTaskRootPackage); assertEquals(ue1.mTaskRootClass, ue2.mTaskRootClass); assertEquals(ue1.mInstanceId, ue2.mInstanceId); assertEquals(ue1.mEventType, ue2.mEventType); assertEquals(ue1.mTimeStamp, ue2.mTimeStamp); switch (ue1.mEventType) { case Event.CONFIGURATION_CHANGE: assertEquals(ue1.mConfiguration, ue2.mConfiguration); break; case Event.SHORTCUT_INVOCATION: assertEquals(ue1.mShortcutId, ue2.mShortcutId); break; case Event.CHOOSER_ACTION: assertEquals(ue1.mAction, ue2.mAction); assertEquals(ue1.mContentType, ue2.mContentType); assertTrue(Arrays.equals(ue1.mContentAnnotations, ue2.mContentAnnotations)); break; case Event.STANDBY_BUCKET_CHANGED: assertEquals(ue1.mBucketAndReason, ue2.mBucketAndReason); break; case Event.NOTIFICATION_INTERRUPTION: assertEquals(ue1.mNotificationChannelId, ue1.mNotificationChannelId); break; case Event.LOCUS_ID_SET: assertEquals(ue1.mLocusId, ue2.mLocusId); break; } assertEquals(ue1.mFlags, ue2.mFlags); } }