summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/usage/ParcelableUsageEventList.aidl19
-rw-r--r--core/java/android/app/usage/ParcelableUsageEventList.java266
-rw-r--r--core/java/android/app/usage/UsageEvents.java45
-rw-r--r--core/java/android/app/usage/flags.aconfig7
-rw-r--r--core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java164
5 files changed, 498 insertions, 3 deletions
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.aidl b/core/java/android/app/usage/ParcelableUsageEventList.aidl
new file mode 100644
index 000000000000..165299651cff
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
diff --git a/core/java/android/app/usage/ParcelableUsageEventList.java b/core/java/android/app/usage/ParcelableUsageEventList.java
new file mode 100644
index 000000000000..016d97f60e26
--- /dev/null
+++ b/core/java/android/app/usage/ParcelableUsageEventList.java
@@ -0,0 +1,266 @@
+/*
+ * 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);
+ }
+}
diff --git a/core/java/android/app/usage/UsageEvents.java b/core/java/android/app/usage/UsageEvents.java
index 3c256ad97f21..c188686d2d5b 100644
--- a/core/java/android/app/usage/UsageEvents.java
+++ b/core/java/android/app/usage/UsageEvents.java
@@ -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;
@@ -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);
@@ -752,7 +769,6 @@ public final class UsageEvents implements Parcelable {
mParcel.setDataSize(mParcel.dataPosition());
mParcel.setDataPosition(positionInParcel);
}
- mIncludeTaskRoots = true;
}
/**
@@ -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 {
@@ -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.
*
@@ -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();
@@ -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);
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index 4f1c65b1e395..0b8e29f954a5 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -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"
+}
diff --git a/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
new file mode 100644
index 000000000000..2ec58d43477d
--- /dev/null
+++ b/core/tests/coretests/src/android/app/usage/ParcelableUsageEventListTest.java
@@ -0,0 +1,164 @@
+/*
+ * 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);
+ }
+}