Merge "services: Add iorap forwarding service"
diff --git a/services/Android.bp b/services/Android.bp
index 58a0997..01734f4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -28,6 +28,7 @@
"services.net",
"services.print",
"services.restrictions",
+ "services.startop",
"services.usage",
"services.usb",
"services.voiceinteraction",
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index be09aea..4326c39 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -145,6 +145,7 @@
import com.android.server.wm.ActivityTaskManagerService;
import com.android.server.wm.WindowManagerGlobalLock;
import com.android.server.wm.WindowManagerService;
+import com.google.android.startop.iorap.IorapForwardingService;
import dalvik.system.VMRuntime;
@@ -1007,10 +1008,13 @@
mSystemServiceManager.startService(PinnerService.class);
traceEnd();
+ traceBeginAndSlog("IorapForwardingService");
+ mSystemServiceManager.startService(IorapForwardingService.class);
+ traceEnd();
+
traceBeginAndSlog("SignedConfigService");
SignedConfigService.registerUpdateReceiver(mSystemContext);
traceEnd();
-
} catch (RuntimeException e) {
Slog.e("System", "******************************************");
Slog.e("System", "************ Failure starting core service", e);
diff --git a/services/startop/Android.bp b/services/startop/Android.bp
new file mode 100644
index 0000000..093b4ec
--- /dev/null
+++ b/services/startop/Android.bp
@@ -0,0 +1,24 @@
+/*
+ * 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: "services.startop",
+
+ static_libs: [
+ // frameworks/base/startop/iorap
+ "services.startop.iorap",
+ ],
+}
diff --git a/startop/iorap/Android.bp b/startop/iorap/Android.bp
index b3b0900..59a80fb 100644
--- a/startop/iorap/Android.bp
+++ b/startop/iorap/Android.bp
@@ -13,7 +13,7 @@
// limitations under the License.
java_library_static {
- name: "libiorap-java",
+ name: "services.startop.iorap",
aidl: {
include_dirs: [
@@ -21,6 +21,8 @@
],
},
+ libs: ["services.core"],
+
srcs: [
":iorap-aidl",
"**/*.java",
diff --git a/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
new file mode 100644
index 0000000..c2e4581
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/AppLaunchEvent.java
@@ -0,0 +1,368 @@
+/*
+ * 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.LongDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+// TODO: fix this. either move this class into system server or add a dependency on
+// these wm classes to libiorap-java and libiorap-java-tests (somehow).
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
+import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Objects;
+
+/**
+ * Provide a hint to iorapd that an app launch sequence 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#onAppLaunchEvent <br /><br />
+ * @see com.android.server.wm.ActivityMetricsLaunchObserver
+ * ActivityMetricsLaunchObserver for the possible event states.
+ * @hide
+ */
+public abstract class AppLaunchEvent implements Parcelable {
+ @LongDef
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface SequenceId {}
+
+ public final @SequenceId
+ long sequenceId;
+
+ protected AppLaunchEvent(@SequenceId long sequenceId) {
+ this.sequenceId = sequenceId;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof AppLaunchEvent) {
+ return equals((AppLaunchEvent) other);
+ }
+ return false;
+ }
+
+ protected boolean equals(AppLaunchEvent other) {
+ return sequenceId == other.sequenceId;
+ }
+
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() +
+ "{" + "sequenceId=" + Long.toString(sequenceId) +
+ toStringBody() + "}";
+ }
+
+ protected String toStringBody() { return ""; };
+
+ // List of possible variants:
+
+ public static final class IntentStarted extends AppLaunchEvent {
+ @NonNull
+ public final Intent intent;
+
+ public IntentStarted(@SequenceId long sequenceId, Intent intent) {
+ super(sequenceId);
+ this.intent = intent;
+
+ Objects.requireNonNull(intent, "intent");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof IntentStarted) {
+ return intent.equals(((IntentStarted)other).intent) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", intent=" + intent.toString();
+ }
+
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ intent.writeToParcel(p, flags);
+ }
+
+ IntentStarted(Parcel p) {
+ super(p);
+ intent = Intent.CREATOR.createFromParcel(p);
+ }
+ }
+
+ public static final class IntentFailed extends AppLaunchEvent {
+ public IntentFailed(@SequenceId long sequenceId) {
+ super(sequenceId);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof IntentFailed) {
+ return super.equals(other);
+ }
+ return false;
+ }
+
+ IntentFailed(Parcel p) {
+ super(p);
+ }
+ }
+
+ public static abstract class BaseWithActivityRecordData extends AppLaunchEvent {
+ public final @NonNull
+ @ActivityRecordProto byte[] activityRecordSnapshot;
+
+ protected BaseWithActivityRecordData(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId);
+ activityRecordSnapshot = snapshot;
+
+ Objects.requireNonNull(snapshot, "snapshot");
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof BaseWithActivityRecordData) {
+ return activityRecordSnapshot.equals(
+ ((BaseWithActivityRecordData)other).activityRecordSnapshot) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", " + activityRecordSnapshot.toString();
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
+ }
+
+ BaseWithActivityRecordData(Parcel p) {
+ super(p);
+ activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
+ }
+ }
+
+ public static final class ActivityLaunched extends BaseWithActivityRecordData {
+ public final @ActivityMetricsLaunchObserver.Temperature
+ int temperature;
+
+ public ActivityLaunched(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot,
+ @ActivityMetricsLaunchObserver.Temperature int temperature) {
+ super(sequenceId, snapshot);
+ this.temperature = temperature;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunched) {
+ return temperature == ((ActivityLaunched)other).temperature &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", temperature=" + Integer.toString(temperature);
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ p.writeInt(temperature);
+ }
+
+ ActivityLaunched(Parcel p) {
+ super(p);
+ temperature = p.readInt();
+ }
+ }
+
+ public static final class ActivityLaunchFinished extends BaseWithActivityRecordData {
+ public ActivityLaunchFinished(@SequenceId long sequenceId,
+ @NonNull @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId, snapshot);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunched) {
+ return super.equals(other);
+ }
+ return false;
+ }
+ }
+
+ public static class ActivityLaunchCancelled extends AppLaunchEvent {
+ public final @Nullable
+ @ActivityRecordProto byte[] activityRecordSnapshot;
+
+ public ActivityLaunchCancelled(@SequenceId long sequenceId,
+ @Nullable @ActivityRecordProto byte[] snapshot) {
+ super(sequenceId);
+ activityRecordSnapshot = snapshot;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other instanceof ActivityLaunchCancelled) {
+ return Objects.equals(activityRecordSnapshot,
+ ((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
+ super.equals(other);
+ }
+ return false;
+ }
+
+ @Override
+ protected String toStringBody() {
+ return ", " + activityRecordSnapshot.toString();
+ }
+
+ @Override
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ super.writeToParcelImpl(p, flags);
+ if (activityRecordSnapshot != null) {
+ p.writeBoolean(true);
+ ActivityRecordProtoParcelable.write(p, activityRecordSnapshot, flags);
+ } else {
+ p.writeBoolean(false);
+ }
+ }
+
+ ActivityLaunchCancelled(Parcel p) {
+ super(p);
+ if (p.readBoolean()) {
+ activityRecordSnapshot = ActivityRecordProtoParcelable.create(p);
+ } else {
+ activityRecordSnapshot = null;
+ }
+ }
+ }
+
+ @Override
+ public @ContentsFlags int describeContents() { return 0; }
+
+ @Override
+ public void writeToParcel(Parcel p, @WriteFlags int flags) {
+ p.writeInt(getTypeIndex());
+
+ writeToParcelImpl(p, flags);
+ }
+
+
+ public static Creator<AppLaunchEvent> CREATOR =
+ new Creator<AppLaunchEvent>() {
+ @Override
+ public AppLaunchEvent createFromParcel(Parcel source) {
+ int typeIndex = source.readInt();
+
+ Class<?> kls = getClassFromTypeIndex(typeIndex);
+ if (kls == null) {
+ throw new IllegalArgumentException("Invalid type index: " + typeIndex);
+ }
+
+ try {
+ return (AppLaunchEvent) kls.getConstructor(Parcel.class).newInstance(source);
+ } catch (InstantiationException e) {
+ throw new AssertionError(e);
+ } catch (IllegalAccessException e) {
+ throw new AssertionError(e);
+ } catch (InvocationTargetException e) {
+ throw new AssertionError(e);
+ } catch (NoSuchMethodException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ @Override
+ public AppLaunchEvent[] newArray(int size) {
+ return new AppLaunchEvent[0];
+ }
+ };
+
+ protected void writeToParcelImpl(Parcel p, int flags) {
+ p.writeLong(sequenceId);
+ }
+
+ protected AppLaunchEvent(Parcel p) {
+ sequenceId = p.readLong();
+ }
+
+ private int getTypeIndex() {
+ for (int i = 0; i < sTypes.length; ++i) {
+ if (sTypes[i].equals(this.getClass())) {
+ return i;
+ }
+ }
+ throw new AssertionError("sTypes did not include this type: " + this.getClass());
+ }
+
+ private static @Nullable Class<?> getClassFromTypeIndex(int typeIndex) {
+ if (typeIndex >= 0 && typeIndex < sTypes.length) {
+ return sTypes[typeIndex];
+ }
+ return null;
+ }
+
+ // Index position matters: It is used to encode the specific type in parceling.
+ // Keep up-to-date with C++ side.
+ private static Class<?>[] sTypes = new Class[] {
+ IntentStarted.class,
+ IntentFailed.class,
+ ActivityLaunched.class,
+ ActivityLaunchFinished.class,
+ ActivityLaunchCancelled.class,
+ };
+
+ // TODO: move to @ActivityRecordProto byte[] once we have unit tests.
+ public static class ActivityRecordProtoParcelable {
+ public static void write(Parcel p, @ActivityRecordProto byte[] activityRecordSnapshot,
+ int flags) {
+ p.writeByteArray(activityRecordSnapshot);
+ }
+
+ public static @ActivityRecordProto byte[] create(Parcel p) {
+ byte[] data = p.createByteArray();
+
+ return data;
+ }
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
new file mode 100644
index 0000000..7fcad36
--- /dev/null
+++ b/startop/iorap/src/com/google/android/startop/iorap/IorapForwardingService.java
@@ -0,0 +1,216 @@
+/*
+ * 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;
+// TODO: rename to com.android.server.startop.iorap
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.LocalServices;
+import com.android.server.SystemService;
+import com.android.server.wm.ActivityMetricsLaunchObserver;
+import com.android.server.wm.ActivityMetricsLaunchObserver.ActivityRecordProto;
+import com.android.server.wm.ActivityMetricsLaunchObserver.Temperature;
+import com.android.server.wm.ActivityMetricsLaunchObserverRegistry;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+/**
+ * System-server-local proxy into the {@code IIorap} native service.
+ */
+public class IorapForwardingService extends SystemService {
+
+ public static final boolean DEBUG = true; // TODO: read from a getprop?
+ public static final String TAG = "IorapForwardingService";
+
+ private IIorap mIorapRemote;
+
+ /**
+ * Initializes the system service.
+ * <p>
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ * </p>
+ *
+ * @param context The system server context.
+ */
+ public IorapForwardingService(Context context) {
+ super(context);
+ }
+
+ //<editor-fold desc="Providers">
+ /*
+ * Providers for external dependencies:
+ * - These are marked as protected to allow tests to inject different values via mocks.
+ */
+
+ @VisibleForTesting
+ protected ActivityMetricsLaunchObserverRegistry provideLaunchObserverRegistry() {
+ ActivityTaskManagerInternal amtInternal =
+ LocalServices.getService(ActivityTaskManagerInternal.class);
+ ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+ amtInternal.getLaunchObserverRegistry();
+ return launchObserverRegistry;
+ }
+
+ @VisibleForTesting
+ protected IIorap provideIorapRemote() {
+ try {
+ return IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
+ } catch (ServiceManager.ServiceNotFoundException e) {
+ // TODO: how do we handle service being missing?
+ throw new AssertionError(e);
+ }
+ }
+
+ //</editor-fold>
+
+ @Override
+ public void onStart() {
+ if (DEBUG) {
+ Log.v(TAG, "onStart");
+ }
+
+ // Connect to the native binder service.
+ mIorapRemote = provideIorapRemote();
+ invokeRemote( () -> mIorapRemote.setTaskListener(new RemoteTaskListener()) );
+
+ // Listen to App Launch Sequence events from ActivityTaskManager,
+ // and forward them to the native binder service.
+ ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
+ provideLaunchObserverRegistry();
+ launchObserverRegistry.registerLaunchObserver(new AppLaunchObserver());
+ }
+
+ private class AppLaunchObserver implements ActivityMetricsLaunchObserver {
+ // We add a synthetic sequence ID here to make it easier to differentiate new
+ // launch sequences on the native side.
+ private @AppLaunchEvent.SequenceId long mSequenceId = -1;
+
+ @Override
+ public void onIntentStarted(@NonNull Intent intent) {
+ // #onIntentStarted [is the only transition that] initiates a new launch sequence.
+ ++mSequenceId;
+
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s)",
+ mSequenceId, intent));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.IntentStarted(mSequenceId, intent))
+ );
+ }
+
+ @Override
+ public void onIntentFailed() {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.IntentFailed(mSequenceId))
+ );
+ }
+
+ @Override
+ public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
+ @Temperature int temperature) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunched(%d, %s, %d)",
+ mSequenceId, activity, temperature));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunched(mSequenceId, activity, temperature))
+ );
+ }
+
+ @Override
+ public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchCancelled(%d, %s)",
+ mSequenceId, activity));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId,
+ activity)));
+ }
+
+ @Override
+ public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity) {
+ if (DEBUG) {
+ Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s)",
+ mSequenceId, activity));
+ }
+
+ invokeRemote(() ->
+ mIorapRemote.onAppLaunchEvent(RequestId.nextValueForSequence(),
+ new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId, activity))
+ );
+ }
+ }
+
+ private class RemoteTaskListener extends ITaskListener.Stub {
+ @Override
+ public void onProgress(RequestId requestId, TaskResult result) throws RemoteException {
+ if (DEBUG) {
+ Log.v(TAG,
+ String.format("RemoteTaskListener#onProgress(%s, %s)", requestId, result));
+ }
+
+ // TODO: implement rest.
+ }
+
+ @Override
+ public void onComplete(RequestId requestId, TaskResult result) throws RemoteException {
+ if (DEBUG) {
+ Log.v(TAG,
+ String.format("RemoteTaskListener#onComplete(%s, %s)", requestId, result));
+ }
+
+ // TODO: implement rest.
+ }
+ }
+
+ private interface RemoteRunnable {
+ void run() throws RemoteException;
+ }
+
+ private static void invokeRemote(RemoteRunnable r) {
+ try {
+ r.run();
+ } catch (RemoteException e) {
+ // TODO: what do we do with exceptions?
+ throw new AssertionError("not implemented", e);
+ }
+ }
+}
diff --git a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
index 2c79319..adb3a91 100644
--- a/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
+++ b/startop/iorap/src/com/google/android/startop/iorap/RequestId.java
@@ -71,7 +71,7 @@
@Override
public String toString() {
- return String.format("{requestId: %ld}", requestId);
+ return String.format("{requestId: %d}", requestId);
}
@Override
diff --git a/startop/iorap/tests/Android.bp b/startop/iorap/tests/Android.bp
index 7605784..5ac4a46 100644
--- a/startop/iorap/tests/Android.bp
+++ b/startop/iorap/tests/Android.bp
@@ -18,8 +18,15 @@
srcs: ["src/**/*.kt"],
static_libs: [
- // non-test dependencies
- "libiorap-java",
+ // Non-test dependencies
+
+ // library under test
+ "services.startop.iorap",
+ // need the system_server code to be on the classpath,
+ "services.core",
+
+ // Test Dependencies
+
// test android dependencies
"platform-test-annotations",
"android-support-test",