diff options
12 files changed, 1266 insertions, 0 deletions
diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp new file mode 100644 index 000000000000..91f6aace2192 --- /dev/null +++ b/startop/iorap/Android.bp @@ -0,0 +1,51 @@ +// Copyright (C) 2018 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. + +java_library_static { + name: "libiorap-java", + + aidl: { + include_dirs: [ + "system/iorap/binder", + ], + }, + + srcs: [ + ":iorap-aidl", + "**/*.java", + ], +} + +android_test { + name: "libiorap-java-tests", + srcs: ["tests/src/**/*.kt"], + + static_libs: [ + // non-test dependencies + "libiorap-java", + // test android dependencies + "platform-test-annotations", + "android-support-test", + // test framework dependencies + "mockito-target-inline-minus-junit4", + "truth-prebuilt", + ], + + //sdk_version: "current", + //certificate: "platform", + + libs: ["android.test.base"], + + test_suites: ["device-tests"], +} diff --git a/startop/iorap/AndroidManifest.xml b/startop/iorap/AndroidManifest.xml new file mode 100644 index 000000000000..8e5fe975b522 --- /dev/null +++ b/startop/iorap/AndroidManifest.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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. +--> +<!--suppress AndroidUnknownAttribute --> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.google.android.startop.iorap.tests" + android:sharedUserId="com.google.android.startop.iorap.tests" + android:versionCode="1" + android:versionName="1.0" > + + <!--suppress AndroidDomInspection --> + <instrumentation + android:name="android.support.test.runner.AndroidJUnitRunner" + android:targetPackage="com.google.android.startop.iorap.tests" /> + + <application> + <uses-library android:name="android.test.runner" /> + </application> +</manifest> diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java new file mode 100644 index 000000000000..1d38f4c1e23d --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityHintEvent.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Provide a hint to iorapd that an activity has transitioned state.<br /><br /> + * + * Knowledge of when an activity starts/stops can be used by iorapd to increase system + * performance (e.g. by launching perfetto tracing to record an io profile, or by + * playing back an ioprofile via readahead) over the long run.<br /><br /> + * + * /@see com.google.android.startop.iorap.IIorap#onActivityHintEvent<br /><br /> + * + * Once an activity hint is in {@link #TYPE_STARTED} it must transition to another type. + * All other states could be terminal, see below: <br /><br /> + * + * <pre> + * + * ┌──────────────────────────────────────┐ + * │ ▼ + * ┌─────────┐ ╔════════════════╗ ╔═══════════╗ + * ──▶ │ STARTED │ ──▶ ║ COMPLETED ║ ──▶ ║ CANCELLED ║ + * └─────────┘ ╚════════════════╝ ╚═══════════╝ + * │ + * │ + * ▼ + * ╔════════════════╗ + * ║ POST_COMPLETED ║ + * ╚════════════════╝ + * + * </pre> <!-- system/iorap/docs/binder/ActivityHint.dot --> + * + * @hide + */ +public class ActivityHintEvent implements Parcelable { + + public static final int TYPE_STARTED = 0; + public static final int TYPE_CANCELLED = 1; + public static final int TYPE_COMPLETED = 2; + public static final int TYPE_POST_COMPLETED = 3; + private static final int TYPE_MAX = TYPE_POST_COMPLETED; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_STARTED, + TYPE_CANCELLED, + TYPE_COMPLETED, + TYPE_POST_COMPLETED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + public final ActivityInfo activityInfo; + + public ActivityHintEvent(@Type int type, ActivityInfo activityInfo) { + this.type = type; + this.activityInfo = activityInfo; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(activityInfo, "activityInfo"); + } + + @Override + public String toString() { + return String.format("{type: %d, activityInfo: %s}", type, activityInfo); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof ActivityHintEvent) { + return equals((ActivityHintEvent) other); + } + return false; + } + + private boolean equals(ActivityHintEvent other) { + return type == other.type && + Objects.equals(activityInfo, other.activityInfo); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + activityInfo.writeToParcel(out, flags); + } + + private ActivityHintEvent(Parcel in) { + this.type = in.readInt(); + this.activityInfo = ActivityInfo.CREATOR.createFromParcel(in); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ActivityHintEvent> CREATOR + = new Parcelable.Creator<ActivityHintEvent>() { + public ActivityHintEvent createFromParcel(Parcel in) { + return new ActivityHintEvent(in); + } + + public ActivityHintEvent[] newArray(int size) { + return new ActivityHintEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java new file mode 100644 index 000000000000..f47a42cffdd8 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/ActivityInfo.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import java.util.Objects; + +import android.os.Parcelable; +import android.os.Parcel; + +/** + * Provide minimal information for launched activities to iorap.<br /><br /> + * + * This uniquely identifies a system-wide activity by providing the {@link #packageName} and + * {@link #activityName}. + * + * @see ActivityHintEvent + * @see AppIntentEvent + * + * @hide + */ +public class ActivityInfo implements Parcelable { + + /** The name of the package, for example {@code com.android.calculator}. */ + public final String packageName; + /** The name of the activity, for example {@code .activities.activity.MainActivity} */ + public final String activityName; + + public ActivityInfo(String packageName, String activityName) { + this.packageName = packageName; + this.activityName = activityName; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + Objects.requireNonNull(packageName, "packageName"); + Objects.requireNonNull(activityName, "activityName"); + } + + @Override + public String toString() { + return String.format("{packageName: %s, activityName: %s}", packageName, activityName); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof ActivityInfo) { + return equals((ActivityInfo) other); + } + return false; + } + + private boolean equals(ActivityInfo other) { + return Objects.equals(packageName, other.packageName) && + Objects.equals(activityName, other.activityName); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeString(packageName); + out.writeString(activityName); + } + + private ActivityInfo(Parcel in) { + packageName = in.readString(); + activityName = in.readString(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<ActivityInfo> CREATOR + = new Parcelable.Creator<ActivityInfo>() { + public ActivityInfo createFromParcel(Parcel in) { + return new ActivityInfo(in); + } + + public ActivityInfo[] newArray(int size) { + return new ActivityInfo[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java new file mode 100644 index 000000000000..1cd37b5546b9 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/AppIntentEvent.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Notifications for iorapd specifying when a system-wide intent defaults change.<br /><br /> + * + * Intent defaults provide a mechanism for an app to register itself as an automatic handler. + * For example the camera app might be registered as the default handler for + * {@link android.provider.MediaStore#INTENT_ACTION_STILL_IMAGE_CAMERA} intent. Subsequently, + * if an arbitrary other app requests for a still image camera photo to be taken, the system + * will launch the respective default camera app to be launched to handle that request.<br /><br /> + * + * In some cases iorapd might need to know default intents, e.g. for boot-time pinning of + * applications that resolve from the default intent. If the application would now be resolved + * differently, iorapd would unpin the old application and pin the new application.<br /><br /> + * + * @hide + */ +public class AppIntentEvent implements Parcelable { + + /** @see android.content.Intent#CATEGORY_DEFAULT */ + public static final int TYPE_DEFAULT_INTENT_CHANGED = 0; + private static final int TYPE_MAX = 0; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_DEFAULT_INTENT_CHANGED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + public final ActivityInfo oldActivityInfo; + public final ActivityInfo newActivityInfo; + + // TODO: Probably need the corresponding action here as well. + + public static AppIntentEvent createDefaultIntentChanged(ActivityInfo oldActivityInfo, + ActivityInfo newActivityInfo) { + return new AppIntentEvent(TYPE_DEFAULT_INTENT_CHANGED, oldActivityInfo, + newActivityInfo); + } + + private AppIntentEvent(@Type int type, ActivityInfo oldActivityInfo, + ActivityInfo newActivityInfo) { + this.type = type; + this.oldActivityInfo = oldActivityInfo; + this.newActivityInfo = newActivityInfo; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(oldActivityInfo, "oldActivityInfo"); + Objects.requireNonNull(oldActivityInfo, "newActivityInfo"); + } + + @Override + public String toString() { + return String.format("{oldActivityInfo: %s, newActivityInfo: %s}", oldActivityInfo, + newActivityInfo); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof AppIntentEvent) { + return equals((AppIntentEvent) other); + } + return false; + } + + private boolean equals(AppIntentEvent other) { + return type == other.type && + Objects.equals(oldActivityInfo, other.oldActivityInfo) && + Objects.equals(newActivityInfo, other.newActivityInfo); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + oldActivityInfo.writeToParcel(out, flags); + newActivityInfo.writeToParcel(out, flags); + } + + private AppIntentEvent(Parcel in) { + this.type = in.readInt(); + this.oldActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); + this.newActivityInfo = ActivityInfo.CREATOR.createFromParcel(in); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<AppIntentEvent> CREATOR + = new Parcelable.Creator<AppIntentEvent>() { + public AppIntentEvent createFromParcel(Parcel in) { + return new AppIntentEvent(in); + } + + public AppIntentEvent[] newArray(int size) { + return new AppIntentEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java new file mode 100644 index 000000000000..34aedd7685d8 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/CheckHelpers.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +/** + * Convenience short-hand to throw {@link IllegalAccessException} when the arguments + * are out-of-range. + */ +public class CheckHelpers { + /** @throws IllegalAccessException if {@param type} is not in {@code [0..maxValue]} */ + public static void checkTypeInRange(int type, int maxValue) { + if (type < 0) { + throw new IllegalArgumentException( + String.format("type must be non-negative (value=%d)", type)); + } + if (type > maxValue) { + throw new IllegalArgumentException( + String.format("type out of range (value=%d, max=%d)", type, maxValue)); + } + } + + /** @throws IllegalAccessException if {@param state} is not in {@code [0..maxValue]} */ + public static void checkStateInRange(int state, int maxValue) { + if (state < 0) { + throw new IllegalArgumentException( + String.format("state must be non-negative (value=%d)", state)); + } + if (state > maxValue) { + throw new IllegalArgumentException( + String.format("state out of range (value=%d, max=%d)", state, maxValue)); + } + } +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java new file mode 100644 index 000000000000..aa4eea716363 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/PackageEvent.java @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.annotation.NonNull; +import android.os.Parcelable; +import android.os.Parcel; +import android.net.Uri; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Objects; + +/** + * Forward package manager events to iorapd. <br /><br /> + * + * Knowing when packages are modified by the system are a useful tidbit to help with performance: + * for example when a package is replaced, it could be a hint used to invalidate any collected + * io profiles used for prefetching or pinning. + * + * @hide + */ +public class PackageEvent implements Parcelable { + + /** @see android.content.Intent#ACTION_PACKAGE_REPLACED */ + public static final int TYPE_REPLACED = 0; + private static final int TYPE_MAX = 0; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_REPLACED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + /** The path that a package is installed in, for example {@code /data/app/.../base.apk}. */ + public final Uri packageUri; + /** The name of the package, for example {@code com.android.calculator}. */ + public final String packageName; + + @NonNull + public static PackageEvent createReplaced(Uri packageUri, String packageName) { + return new PackageEvent(TYPE_REPLACED, packageUri, packageName); + } + + private PackageEvent(@Type int type, Uri packageUri, String packageName) { + this.type = type; + this.packageUri = packageUri; + this.packageName = packageName; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + Objects.requireNonNull(packageUri, "packageUri"); + Objects.requireNonNull(packageName, "packageName"); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof PackageEvent) { + return equals((PackageEvent) other); + } + return false; + } + + private boolean equals(PackageEvent other) { + return type == other.type && + Objects.equals(packageUri, other.packageUri) && + Objects.equals(packageName, other.packageName); + } + + @Override + public String toString() { + return String.format("{packageUri: %s, packageName: %s}", packageUri, packageName); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + packageUri.writeToParcel(out, flags); + out.writeString(packageName); + } + + private PackageEvent(Parcel in) { + this.type = in.readInt(); + this.packageUri = Uri.CREATOR.createFromParcel(in); + this.packageName = in.readString(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<PackageEvent> CREATOR + = new Parcelable.Creator<PackageEvent>() { + public PackageEvent createFromParcel(Parcel in) { + return new PackageEvent(in); + } + + public PackageEvent[] newArray(int size) { + return new PackageEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java new file mode 100644 index 000000000000..2c79319a1459 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.NonNull; + +/** + * Uniquely identify an {@link com.google.android.startop.iorap.IIorap} method invocation, + * used for asynchronous callbacks by the server. <br /><br /> + * + * As all system server binder calls must be {@code oneway}, this means all invocations + * into {@link com.google.android.startop.iorap.IIorap} are non-blocking. The request ID + * exists to associate all calls with their respective callbacks in + * {@link com.google.android.startop.iorap.ITaskListener}. + * + * @see com.google.android.startop.iorap.IIorap + * + * @hide + */ +public class RequestId implements Parcelable { + + public final long requestId; + + private static Object mLock = new Object(); + private static long mNextRequestId = 0; + + /** + * Create a monotonically increasing request ID.<br /><br /> + * + * It is invalid to re-use the same request ID for multiple method calls on + * {@link com.google.android.startop.iorap.IIorap}; a new request ID must be created + * each time. + */ + @NonNull public static RequestId nextValueForSequence() { + long currentRequestId; + synchronized (mLock) { + currentRequestId = mNextRequestId; + ++mNextRequestId; + } + return new RequestId(currentRequestId); + } + + private RequestId(long requestId) { + this.requestId = requestId; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + if (requestId < 0) { + throw new IllegalArgumentException("request id must be non-negative"); + } + } + + @Override + public String toString() { + return String.format("{requestId: %ld}", requestId); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof RequestId) { + return equals((RequestId) other); + } + return false; + } + + private boolean equals(RequestId other) { + return requestId == other.requestId; + } + + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeLong(requestId); + } + + private RequestId(Parcel in) { + requestId = in.readLong(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<RequestId> CREATOR + = new Parcelable.Creator<RequestId>() { + public RequestId createFromParcel(Parcel in) { + return new RequestId(in); + } + + public RequestId[] newArray(int size) { + return new RequestId[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java new file mode 100644 index 000000000000..75d47f9e3d17 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceEvent.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Forward system service events to iorapd. + * + * @see com.android.server.SystemService + * + * @hide + */ +public class SystemServiceEvent implements Parcelable { + + /** @see com.android.server.SystemService#onBootPhase */ + public static final int TYPE_BOOT_PHASE = 0; + /** @see com.android.server.SystemService#onStart */ + public static final int TYPE_START = 1; + private static final int TYPE_MAX = TYPE_START; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_BOOT_PHASE, + TYPE_START, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + + // TODO: do we want to pass the exact build phase enum? + + public SystemServiceEvent(@Type int type) { + this.type = type; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + } + + @Override + public String toString() { + return String.format("{type: %d}", type); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof SystemServiceEvent) { + return equals((SystemServiceEvent) other); + } + return false; + } + + private boolean equals(SystemServiceEvent other) { + return type == other.type; + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + } + + private SystemServiceEvent(Parcel in) { + this.type = in.readInt(); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<SystemServiceEvent> CREATOR + = new Parcelable.Creator<SystemServiceEvent>() { + public SystemServiceEvent createFromParcel(Parcel in) { + return new SystemServiceEvent(in); + } + + public SystemServiceEvent[] newArray(int size) { + return new SystemServiceEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java new file mode 100644 index 000000000000..b77c03c1584a --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/SystemServiceUserEvent.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Forward user events to iorapd.<br /><br /> + * + * Knowledge of the logged-in user is reserved to be used to set-up appropriate policies + * by iorapd (e.g. to handle user default pinned applications changing). + * + * @see com.android.server.SystemService + * + * @hide + */ +public class SystemServiceUserEvent implements Parcelable { + + /** @see com.android.server.SystemService#onStartUser */ + public static final int TYPE_START_USER = 0; + /** @see com.android.server.SystemService#onUnlockUser */ + public static final int TYPE_UNLOCK_USER = 1; + /** @see com.android.server.SystemService#onSwitchUser*/ + public static final int TYPE_SWITCH_USER = 2; + /** @see com.android.server.SystemService#onStopUser */ + public static final int TYPE_STOP_USER = 3; + /** @see com.android.server.SystemService#onCleanupUser */ + public static final int TYPE_CLEANUP_USER = 4; + private static final int TYPE_MAX = TYPE_CLEANUP_USER; + + /** @hide */ + @IntDef(flag = true, prefix = { "TYPE_" }, value = { + TYPE_START_USER, + TYPE_UNLOCK_USER, + TYPE_SWITCH_USER, + TYPE_STOP_USER, + TYPE_CLEANUP_USER, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface Type {} + + @Type public final int type; + public final int userHandle; + + public SystemServiceUserEvent(@Type int type, int userHandle) { + this.type = type; + this.userHandle = userHandle; + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkTypeInRange(type, TYPE_MAX); + if (userHandle < 0) { + throw new IllegalArgumentException("userHandle must be non-negative"); + } + } + + @Override + public String toString() { + return String.format("{type: %d, userHandle: %d}", type, userHandle); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof SystemServiceUserEvent) { + return equals((SystemServiceUserEvent) other); + } + return false; + } + + private boolean equals(SystemServiceUserEvent other) { + return type == other.type && + userHandle == other.userHandle; + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(type); + out.writeInt(userHandle); + } + + private SystemServiceUserEvent(Parcel in) { + this.type = in.readInt(); + this.userHandle = in.readInt(); + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<SystemServiceUserEvent> CREATOR + = new Parcelable.Creator<SystemServiceUserEvent>() { + public SystemServiceUserEvent createFromParcel(Parcel in) { + return new SystemServiceUserEvent(in); + } + + public SystemServiceUserEvent[] newArray(int size) { + return new SystemServiceUserEvent[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java new file mode 100644 index 000000000000..b5fd6d8d1c45 --- /dev/null +++ b/startop/iorap/src/com/google/android/startop/iorap/TaskResult.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap; + +import android.os.Parcelable; +import android.os.Parcel; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Result data accompanying a request for {@link com.google.android.startop.iorap.ITaskListener} + * callbacks.<br /><br /> + * + * Following {@link com.google.android.startop.iorap.IIorap} method invocation, + * iorapd will issue in-order callbacks for that corresponding {@link RequestId}.<br /><br /> + * + * State transitions are as follows: <br /><br /> + * + * <pre> + * ┌─────────────────────────────┐ + * │ ▼ + * ┌───────┐ ┌─────────┐ ╔═══════════╗ + * ──▶ │ BEGAN │ ──▶ │ ONGOING │ ──▶ ║ COMPLETED ║ + * └───────┘ └─────────┘ ╚═══════════╝ + * │ │ + * │ │ + * ▼ │ + * ╔═══════╗ │ + * ──▶ ║ ERROR ║ ◀─────┘ + * ╚═══════╝ + * + * </pre> <!-- system/iorap/docs/binder/TaskResult.dot --> + * + * @hide + */ +public class TaskResult implements Parcelable { + + public static final int STATE_BEGAN = 0; + public static final int STATE_ONGOING = 1; + public static final int STATE_COMPLETED = 2; + public static final int STATE_ERROR = 3; + private static final int STATE_MAX = STATE_ERROR; + + /** @hide */ + @IntDef(flag = true, prefix = { "STATE_" }, value = { + STATE_BEGAN, + STATE_ONGOING, + STATE_COMPLETED, + STATE_ERROR, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface State {} + + @State public final int state; + + @Override + public String toString() { + return String.format("{state: %d}", state); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } else if (other instanceof TaskResult) { + return equals((TaskResult) other); + } + return false; + } + + private boolean equals(TaskResult other) { + return state == other.state; + } + + public TaskResult(@State int state) { + this.state = state; + + checkConstructorArguments(); + } + + private void checkConstructorArguments() { + CheckHelpers.checkStateInRange(state, STATE_MAX); + } + + //<editor-fold desc="Binder boilerplate"> + @Override + public void writeToParcel(Parcel out, int flags) { + out.writeInt(state); + } + + private TaskResult(Parcel in) { + state = in.readInt(); + + checkConstructorArguments(); + } + + @Override + public int describeContents() { + return 0; + } + + public static final Parcelable.Creator<TaskResult> CREATOR + = new Parcelable.Creator<TaskResult>() { + public TaskResult createFromParcel(Parcel in) { + return new TaskResult(in); + } + + public TaskResult[] newArray(int size) { + return new TaskResult[size]; + } + }; + //</editor-fold> +} diff --git a/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt new file mode 100644 index 000000000000..4abbb3e9f162 --- /dev/null +++ b/startop/iorap/tests/src/com/google/android/startop/iorap/ParcelablesTest.kt @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2018 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.google.android.startop.iorap + +import android.net.Uri +import android.os.Parcel +import android.os.Parcelable +import android.support.test.filters.SmallTest +import org.junit.Test +import org.junit.runner.RunWith +import com.google.common.truth.Truth.assertThat +import org.junit.runners.Parameterized + +/** + * Basic unit tests to ensure that all of the [Parcelable]s in [com.google.android.startop.iorap] + * have a valid-conforming interface implementation. + */ +@SmallTest +@RunWith(Parameterized::class) +class ParcelablesTest<T : Parcelable>(private val inputData : InputData<T>) { + companion object { + private val initialRequestId = RequestId.nextValueForSequence()!! + + @JvmStatic + @Parameterized.Parameters + fun data() = listOf( + InputData( + newActivityInfo(), + newActivityInfo(), + ActivityInfo("some package", "some other activity")), + InputData( + ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), + ActivityHintEvent(ActivityHintEvent.TYPE_COMPLETED, newActivityInfo()), + ActivityHintEvent(ActivityHintEvent.TYPE_POST_COMPLETED, + newActivityInfo())), + InputData( + AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), + newActivityInfoOther()), + AppIntentEvent.createDefaultIntentChanged(newActivityInfo(), + newActivityInfoOther()), + AppIntentEvent.createDefaultIntentChanged(newActivityInfoOther(), + newActivityInfo())), + InputData( + PackageEvent.createReplaced(newUri(), "some package"), + PackageEvent.createReplaced(newUri(), "some package"), + PackageEvent.createReplaced(newUri(), "some other package") + ), + InputData(initialRequestId, cloneRequestId(initialRequestId), + RequestId.nextValueForSequence()), + InputData( + SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), + SystemServiceEvent(SystemServiceEvent.TYPE_BOOT_PHASE), + SystemServiceEvent(SystemServiceEvent.TYPE_START)), + InputData( + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 12345), + SystemServiceUserEvent(SystemServiceUserEvent.TYPE_CLEANUP_USER, 12345)), + InputData( + TaskResult(TaskResult.STATE_COMPLETED), + TaskResult(TaskResult.STATE_COMPLETED), + TaskResult(TaskResult.STATE_ONGOING)) + ) + + private fun newActivityInfo() : ActivityInfo { + return ActivityInfo("some package", "some activity") + } + + private fun newActivityInfoOther() : ActivityInfo { + return ActivityInfo("some package 2", "some activity 2") + } + + private fun newUri() : Uri { + return Uri.parse("https://www.google.com") + } + + private fun cloneRequestId(requestId: RequestId) : RequestId { + val constructor = requestId::class.java.declaredConstructors[0] + constructor.isAccessible = true + return constructor.newInstance(requestId.requestId) as RequestId + } + } + + /** + * Test for [Object.equals] implementation. + */ + @Test + fun testEquality() { + assertThat(inputData.valid).isEqualTo(inputData.valid) + assertThat(inputData.valid).isEqualTo(inputData.validCopy) + assertThat(inputData.valid).isNotEqualTo(inputData.validOther) + } + + /** + * Test for [Parcelable] implementation. + */ + @Test + fun testParcelRoundTrip() { + // calling writeToParcel and then T::CREATOR.createFromParcel would return the same data. + val assertParcels = { it : T, data : InputData<T> -> + val parcel = Parcel.obtain() + it.writeToParcel(parcel, 0) + parcel.setDataPosition(0) // future reads will see all previous writes. + assertThat(it).isEqualTo(data.createFromParcel(parcel)) + parcel.recycle() + } + + assertParcels(inputData.valid, inputData) + assertParcels(inputData.validCopy, inputData) + assertParcels(inputData.validOther, inputData) + } + + data class InputData<T : Parcelable>(val valid : T, val validCopy : T, val validOther : T) { + val kls = valid.javaClass + init { + assertThat(valid).isNotSameAs(validCopy) + // Don't use isInstanceOf because of phantom warnings in intellij about Class! + assertThat(validCopy.javaClass).isEqualTo(valid.javaClass) + assertThat(validOther.javaClass).isEqualTo(valid.javaClass) + } + + fun createFromParcel(parcel : Parcel) : T { + val field = kls.getDeclaredField("CREATOR") + val creator = field.get(null) as Parcelable.Creator<T> + + return creator.createFromParcel(parcel) + } + } +} |