() {
+ @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 0000000000000..7fcad360b8fe1
--- /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.
+ *
+ * Subclasses must define a single argument constructor that accepts the context
+ * and passes it to super.
+ *
+ *
+ * @param context The system server context.
+ */
+ public IorapForwardingService(Context context) {
+ super(context);
+ }
+
+ //
+ /*
+ * 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);
+ }
+ }
+
+ //
+
+ @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 2c79319a1459a..adb3a910f7fe1 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 @@ private void checkConstructorArguments() {
@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 76057846e8962..5ac4a46b81f15 100644
--- a/startop/iorap/tests/Android.bp
+++ b/startop/iorap/tests/Android.bp
@@ -18,8 +18,15 @@ java_library {
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",