[Re-land] Remove iorap framework codes am: a46adf380d

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2025085

Change-Id: I16eec5c304c1bc0fb99fbbeace672e4fbff179da
This commit is contained in:
Eric Jeong 2022-03-22 04:28:05 +00:00 committed by Automerger Merge Worker
commit de56f63a65
32 changed files with 0 additions and 4355 deletions

View File

@ -6352,10 +6352,6 @@
android:resource="@xml/autofill_compat_accessibility_service" />
</service>
<service android:name="com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy"
android:permission="android.permission.BIND_JOB_SERVICE" >
</service>
<service android:name="com.android.server.blob.BlobStoreIdleJobService"
android:permission="android.permission.BIND_JOB_SERVICE">
</service>

View File

@ -92,7 +92,6 @@ filegroup {
":services.searchui-sources",
":services.smartspace-sources",
":services.speech-sources",
":services.startop.iorap-sources",
":services.systemcaptions-sources",
":services.translation-sources",
":services.texttospeech-sources",
@ -147,7 +146,6 @@ java_library {
"services.searchui",
"services.smartspace",
"services.speech",
"services.startop",
"services.systemcaptions",
"services.translation",
"services.texttospeech",
@ -200,7 +198,6 @@ stubs_defaults {
" --hide-annotation android.annotation.Hide" +
" --hide InternalClasses" + // com.android.* classes are okay in this interface
// TODO: remove the --hide options below
" --hide-package com.google.android.startop.iorap" +
" --hide DeprecationMismatch" +
" --hide HiddenTypedefConstant",
visibility: ["//frameworks/base:__subpackages__"],

View File

@ -206,8 +206,6 @@ import com.android.server.wm.WindowManagerService;
import dalvik.system.VMRuntime;
import com.google.android.startop.iorap.IorapForwardingService;
import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
@ -1552,10 +1550,6 @@ public final class SystemServer implements Dumpable {
mSystemServiceManager.startService(PinnerService.class);
t.traceEnd();
t.traceBegin("IorapForwardingService");
mSystemServiceManager.startService(IorapForwardingService.class);
t.traceEnd();
if (Build.IS_DEBUGGABLE && ProfcollectForwardingService.enabled()) {
t.traceBegin("ProfcollectForwardingService");
mSystemServiceManager.startService(ProfcollectForwardingService.class);

View File

@ -1,36 +0,0 @@
/*
* 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
// SPDX-license-identifier-MIT
// SPDX-license-identifier-Unicode-DFS
default_applicable_licenses: ["frameworks_base_license"],
}
java_library_static {
name: "services.startop",
defaults: ["platform_service_defaults"],
static_libs: [
// frameworks/base/startop/iorap
"services.startop.iorap",
],
}

View File

@ -1,44 +0,0 @@
// 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
filegroup {
name: "services.startop.iorap-javasources",
srcs: ["src/**/*.java"],
path: "src",
visibility: ["//visibility:private"],
}
filegroup {
name: "services.startop.iorap-sources",
srcs: [
":services.startop.iorap-javasources",
":iorap-aidl",
],
visibility: ["//frameworks/base/services:__subpackages__"],
}
java_library_static {
name: "services.startop.iorap",
srcs: [":services.startop.iorap-sources"],
libs: ["services.core"],
}

View File

@ -1,12 +0,0 @@
{
"presubmit": [
{
"name": "libiorap-java-tests"
}
],
"imports": [
{
"path": "system/iorap"
}
]
}

View File

@ -1,50 +0,0 @@
// Copyright (C) 2020 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
android_test {
name: "iorap-functional-tests",
srcs: ["src/**/*.java"],
data: [":iorap-functional-test-apps"],
static_libs: [
// Non-test dependencies
// library under test
"services.startop.iorap",
// Test Dependencies
// test android dependencies
"platform-test-annotations",
"androidx.test.rules",
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
// test framework dependencies
"truth-prebuilt",
],
dxflags: ["--multi-dex"],
test_suites: ["device-tests"],
compile_multilib: "both",
libs: [
"android.test.base",
"android.test.runner",
],
certificate: "platform",
platform_apis: true,
}

View File

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.functional"
android:versionCode="1"
android:versionName="1.0" >
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!--suppress AndroidDomInspection -->
<instrumentation
android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.google.android.startop.iorap.tests" />
<!--
'debuggable=true' is required to properly load mockito jvmti dependencies,
otherwise it gives the following error at runtime:
Openjdkjvmti plugin was loaded on a non-debuggable Runtime.
Plugin was loaded too late to change runtime state to DEBUGGABLE. -->
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@ -1,70 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2020 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.
-->
<configuration description="Runs iorap-functional-tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="iorap-functional-tests.apk" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
<target_preparer
class="com.android.tradefed.targetprep.DeviceSetup">
<!-- iorapd does not pick up the above changes until we restart it -->
<option name="run-command" value="stop iorapd" />
<!-- Clean up the existing iorap database. -->
<option name="run-command" value="rm -r /data/misc/iorapd/*" />
<option name="run-command" value="sleep 1" />
<!-- Set system properties to enable perfetto tracing, readahead and detailed logging. -->
<option name="run-command" value="setprop iorapd.perfetto.enable true" />
<option name="run-command" value="setprop iorapd.readahead.enable true" />
<option name="run-command" value="setprop iorapd.log.verbose true" />
<option name="run-command" value="start iorapd" />
<!-- give it some time to restart the service; otherwise the first unit test might fail -->
<option name="run-command" value="sleep 1" />
</target_preparer>
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="abort-on-push-failure" value="true" />
<option name="push-file"
key="iorap_test_app_v1.apk"
value="/data/misc/iorapd/iorap_test_app_v1.apk" />
<option name="push-file"
key="iorap_test_app_v2.apk"
value="/data/misc/iorapd/iorap_test_app_v2.apk" />
<option name="push-file"
key="iorap_test_app_v3.apk"
value="/data/misc/iorapd/iorap_test_app_v3.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.google.android.startop.iorap.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
<!-- test-timeout unit is ms, value = 30 min -->
<option name="test-timeout" value="1800000" />
</test>
</configuration>

View File

@ -1,416 +0,0 @@
/*
* Copyright (C) 2020 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.iorapd;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
import androidx.test.uiautomator.Until;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.Date;
import java.util.function.BooleanSupplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.List;
import java.text.SimpleDateFormat;
/**
* Test for the work flow of iorap.
*
* <p> This test tests the function of iorap from:
* perfetto collection -> compilation -> prefetching -> version update -> perfetto collection.
*/
@RunWith(AndroidJUnit4.class)
public class IorapWorkFlowTest {
private static final String TAG = "IorapWorkFlowTest";
private static final String TEST_APP_VERSION_ONE_PATH = "/data/misc/iorapd/iorap_test_app_v1.apk";
private static final String TEST_APP_VERSION_TWO_PATH = "/data/misc/iorapd/iorap_test_app_v2.apk";
private static final String TEST_APP_VERSION_THREE_PATH = "/data/misc/iorapd/iorap_test_app_v3.apk";
private static final String DB_PATH = "/data/misc/iorapd/sqlite.db";
private static final Duration TIMEOUT = Duration.ofSeconds(300L);
private UiDevice mDevice;
@Before
public void setUp() throws Exception {
// Initialize UiDevice instance
mDevice = UiDevice.getInstance(getInstrumentation());
// Start from the home screen
mDevice.pressHome();
// Wait for launcher
final String launcherPackage = mDevice.getLauncherPackageName();
assertThat(launcherPackage, notNullValue());
mDevice.wait(Until.hasObject(By.pkg(launcherPackage).depth(0)), TIMEOUT.getSeconds());
}
@After
public void tearDown() throws Exception {
String packageName = "com.example.ioraptestapp";
uninstallApk(packageName);
}
@Test (timeout = 300000)
public void testNormalWorkFlow() throws Exception {
assertThat(mDevice, notNullValue());
// Install test app version one
installApk(TEST_APP_VERSION_ONE_PATH);
String packageName = "com.example.ioraptestapp";
String activityName = "com.example.ioraptestapp.MainActivity";
// Perfetto trace collection phase.
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/1L));
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/1L));
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/1L));
// Trigger maintenance service for compilation.
TimeUnit.SECONDS.sleep(5L);
assertTrue(compile(packageName, activityName, /*version=*/1L));
// Run app with prefetching
assertTrue(startAppWithCompiledTrace(
packageName, activityName, /*version=*/1L));
}
@Test (timeout = 300000)
public void testUpdateApp() throws Exception {
assertThat(mDevice, notNullValue());
// Install test app version two,
String packageName = "com.example.ioraptestapp";
String activityName = "com.example.ioraptestapp.MainActivity";
installApk(TEST_APP_VERSION_TWO_PATH);
// Perfetto trace collection phase.
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/2L));
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/2L));
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/2L));
// Trigger maintenance service for compilation.
TimeUnit.SECONDS.sleep(5L);
assertTrue(compile(packageName, activityName, /*version=*/2L));
// Run app with prefetching
assertTrue(startAppWithCompiledTrace(
packageName, activityName, /*version=*/2L));
// Update test app to version 3
installApk(TEST_APP_VERSION_THREE_PATH);
// Rerun app, should do pefetto tracing.
assertTrue(startAppForPerfettoTrace(
packageName, activityName, /*version=*/3L));
}
private static void installApk(String apkPath) throws Exception {
// Disable the selinux to allow pm install apk in the dir.
executeShellCommand("setenforce 0");
executeShellCommand("pm install -r -d " + apkPath);
executeShellCommand("setenforce 1");
}
private static void uninstallApk(String apkPath) throws Exception {
executeShellCommand("pm uninstall " + apkPath);
}
/**
* Starts the testing app to collect the perfetto trace.
*
* @param expectPerfettoTraceCount is the expected count of perfetto traces.
*/
private boolean startAppForPerfettoTrace(
String packageName, String activityName, long version)
throws Exception {
LogcatTimestamp timestamp = runAppOnce(packageName, activityName);
return waitForPerfettoTraceSavedFromLogcat(
packageName, activityName, version, timestamp);
}
private boolean startAppWithCompiledTrace(
String packageName, String activityName, long version)
throws Exception {
LogcatTimestamp timestamp = runAppOnce(packageName, activityName);
return waitForPrefetchingFromLogcat(
packageName, activityName, version, timestamp);
}
private LogcatTimestamp runAppOnce(String packageName, String activityName) throws Exception {
// Close the specified app if it's open
closeApp(packageName);
LogcatTimestamp timestamp = new LogcatTimestamp();
// Launch the specified app
startApp(packageName, activityName);
// Wait for the app to appear
mDevice.wait(Until.hasObject(By.pkg(packageName).depth(0)), TIMEOUT.getSeconds());
return timestamp;
}
// Invokes the maintenance to compile the perfetto traces to compiled trace.
private boolean compile(
String packageName, String activityName, long version) throws Exception {
// The job id (283673059) is defined in class IorapForwardingService.
executeShellCommandViaTmpFile("cmd jobscheduler run -f android 283673059");
return waitForFileExistence(getCompiledTracePath(packageName, activityName, version));
}
private String getCompiledTracePath(
String packageName, String activityName, long version) {
return String.format(
"/data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb",
packageName, version, activityName);
}
/**
* Starts the testing app.
*/
private void startApp(String packageName, String activityName) throws Exception {
executeShellCommandViaTmpFile(
String.format("am start %s/%s", packageName, activityName));
}
/**
* Closes the testing app.
* <p> Keep trying to kill the process of the app until no process of the app package
* appears.</p>
*/
private void closeApp(String packageName) throws Exception {
while (true) {
String pid = executeShellCommand("pidof " + packageName);
if (pid.isEmpty()) {
Log.i(TAG, "Closed app " + packageName);
return;
}
executeShellCommand("kill -9 " + pid);
TimeUnit.SECONDS.sleep(1L);
}
}
/** Waits for a file to appear. */
private boolean waitForFileExistence(String fileName) throws Exception {
return retryWithTimeout(TIMEOUT, () -> {
try {
String fileExists = executeShellCommandViaTmpFile(
String.format("test -f %s; echo $?", fileName));
Log.i(TAG, fileName + " existence is " + fileExists);
return fileExists.trim().equals("0");
} catch (Exception e) {
Log.i(TAG, e.getMessage());
return false;
}
});
}
/** Waits for the perfetto trace saved message from logcat. */
private boolean waitForPerfettoTraceSavedFromLogcat(
String packageName, String activityName, long version, LogcatTimestamp timestamp)
throws Exception {
Pattern p = Pattern.compile(".*"
+ getPerfettoTraceSavedIndicator(packageName, activityName, version)
+ "(.*[.]perfetto_trace[.]pb)\n.*", Pattern.DOTALL);
return retryWithTimeout(TIMEOUT, () -> {
try {
String log = timestamp.getLogcatAfter();
Matcher m = p.matcher(log);
Log.d(TAG, "Tries to find perfetto trace...");
if (!m.matches()) {
Log.i(TAG, "Cannot find perfetto trace saved in log.");
return false;
}
String filePath = m.group(1);
Log.i(TAG, "Perfetto trace is saved to " + filePath);
return true;
} catch(Exception e) {
Log.e(TAG, e.getMessage());
return false;
}
});
}
private String getPerfettoTraceSavedIndicator(
String packageName, String activityName, long version) {
return String.format(
"Perfetto TraceBuffer saved to file: /data/misc/iorapd/%s/%d/%s/raw_traces/",
packageName, version, activityName);
}
/**
* Waits for the prefetching log in the logcat.
*
* <p> When prefetching works, the perfetto traces should not be collected. </p>
*/
private boolean waitForPrefetchingFromLogcat(
String packageName, String activityName, long version, LogcatTimestamp timestamp)
throws Exception {
Pattern p = Pattern.compile(
".*" + getReadaheadIndicator(packageName, activityName, version) +
".*Total File Paths=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
+ ".*Total Entries=(\\d+) \\(good: (\\d+[.]?\\d*)%\\)\n"
+ ".*Total Bytes=(\\d+) \\(good: (\\d+[.]?\\d*)%\\).*",
Pattern.DOTALL);
return retryWithTimeout(TIMEOUT, () -> {
try {
String log = timestamp.getLogcatAfter();
Matcher m = p.matcher(log);
if (!m.matches()) {
Log.i(TAG, "Cannot find readahead log.");
return false;
}
int totalFilePath = Integer.parseInt(m.group(1));
float totalFilePathGoodRate = Float.parseFloat(m.group(2)) / 100;
int totalEntries = Integer.parseInt(m.group(3));
float totalEntriesGoodRate = Float.parseFloat(m.group(4)) / 100;
int totalBytes = Integer.parseInt(m.group(5));
float totalBytesGoodRate = Float.parseFloat(m.group(6)) / 100;
Log.i(TAG, String.format(
"totalFilePath: %d (good %.2f) totalEntries: %d (good %.2f) totalBytes: %d (good %.2f)",
totalFilePath, totalFilePathGoodRate, totalEntries, totalEntriesGoodRate, totalBytes,
totalBytesGoodRate));
return totalFilePath > 0 &&
totalEntries > 0 &&
totalBytes > 0 &&
totalFilePathGoodRate > 0.5 &&
totalEntriesGoodRate > 0.5 &&
totalBytesGoodRate > 0.5;
} catch(Exception e) {
return false;
}
});
}
private static String getReadaheadIndicator(
String packageName, String activityName, long version) {
return String.format(
"Description = /data/misc/iorapd/%s/%d/%s/compiled_traces/compiled_trace.pb",
packageName, version, activityName);
}
/** Retry until timeout. */
private boolean retryWithTimeout(Duration timeout, BooleanSupplier supplier) throws Exception {
long totalSleepTimeSeconds = 0L;
long sleepIntervalSeconds = 2L;
while (true) {
if (supplier.getAsBoolean()) {
return true;
}
TimeUnit.SECONDS.sleep(sleepIntervalSeconds);
totalSleepTimeSeconds += sleepIntervalSeconds;
if (totalSleepTimeSeconds > timeout.getSeconds()) {
return false;
}
}
}
/**
* Executes command in adb shell via a tmp file.
*
* <p> This should be run as root.</p>
*/
private static String executeShellCommandViaTmpFile(String cmd) throws Exception {
Log.i(TAG, "Execute via tmp file: " + cmd);
Path tmp = null;
try {
tmp = Files.createTempFile(/*prefix=*/null, /*suffix=*/".sh");
Files.write(tmp, cmd.getBytes(StandardCharsets.UTF_8));
tmp.toFile().setExecutable(true);
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).
executeShellCommand(tmp.toString());
} finally {
if (tmp != null) {
Files.delete(tmp);
}
}
}
/**
* Executes command in adb shell.
*
* <p> This should be run as root.</p>
*/
private static String executeShellCommand(String cmd) throws Exception {
Log.i(TAG, "Execute: " + cmd);
return UiDevice.getInstance(
InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
}
static class LogcatTimestamp {
private String epochTime;
public LogcatTimestamp() throws Exception{
long currentTimeMillis = System.currentTimeMillis();
epochTime = String.format(
"%d.%03d", currentTimeMillis/1000, currentTimeMillis%1000);
Log.i(TAG, "Current logcat timestamp is " + epochTime);
}
// For example, 1585264100.000
public String getEpochTime() {
return epochTime;
}
// Gets the logcat after this epoch time.
public String getLogcatAfter() throws Exception {
return executeShellCommandViaTmpFile(
"logcat -v epoch -t '" + epochTime + "'");
}
}
}

View File

@ -1,139 +0,0 @@
/*
* 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>
}

View File

@ -1,104 +0,0 @@
/*
* 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>
}

View File

@ -1,138 +0,0 @@
/*
* 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>
}

View File

@ -1,459 +0,0 @@
/*
* 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;
import android.util.proto.ProtoOutputStream;
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.Arrays;
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 final long timestampNs;
public IntentStarted(@SequenceId long sequenceId,
Intent intent,
long timestampNs) {
super(sequenceId);
this.intent = intent;
this.timestampNs = timestampNs;
Objects.requireNonNull(intent, "intent");
}
@Override
public boolean equals(Object other) {
if (other instanceof IntentStarted) {
return intent.equals(((IntentStarted)other).intent) &&
timestampNs == ((IntentStarted)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return ", intent=" + intent.toString() +
" , timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
IntentProtoParcelable.write(p, intent, flags);
p.writeLong(timestampNs);
}
IntentStarted(Parcel p) {
super(p);
intent = IntentProtoParcelable.create(p);
timestampNs = p.readLong();
}
}
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 (Arrays.equals(activityRecordSnapshot,
((BaseWithActivityRecordData)other).activityRecordSnapshot)) &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return ", " + new String(activityRecordSnapshot);
}
@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 super.toStringBody() + ", 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 final long timestampNs;
public ActivityLaunchFinished(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot,
long timestampNs) {
super(sequenceId, snapshot);
this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
if (other instanceof ActivityLaunchFinished) {
return timestampNs == ((ActivityLaunchFinished)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
p.writeLong(timestampNs);
}
ActivityLaunchFinished(Parcel p) {
super(p);
timestampNs = p.readLong();
}
}
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 Arrays.equals(activityRecordSnapshot,
((ActivityLaunchCancelled)other).activityRecordSnapshot) &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", " + new String(activityRecordSnapshot);
}
@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;
}
}
}
public static final class ReportFullyDrawn extends BaseWithActivityRecordData {
public final long timestampNs;
public ReportFullyDrawn(@SequenceId long sequenceId,
@NonNull @ActivityRecordProto byte[] snapshot,
long timestampNs) {
super(sequenceId, snapshot);
this.timestampNs = timestampNs;
}
@Override
public boolean equals(Object other) {
if (other instanceof ReportFullyDrawn) {
return timestampNs == ((ReportFullyDrawn)other).timestampNs &&
super.equals(other);
}
return false;
}
@Override
protected String toStringBody() {
return super.toStringBody() + ", timestampNs=" + Long.toString(timestampNs);
}
@Override
protected void writeToParcelImpl(Parcel p, int flags) {
super.writeToParcelImpl(p, flags);
p.writeLong(timestampNs);
}
ReportFullyDrawn(Parcel p) {
super(p);
timestampNs = p.readLong();
}
}
@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,
ReportFullyDrawn.class,
};
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;
}
}
public static class IntentProtoParcelable {
private static final int INTENT_PROTO_CHUNK_SIZE = 1024;
public static void write(Parcel p, @NonNull Intent intent, int flags) {
// There does not appear to be a way to 'reset' a ProtoOutputBuffer stream,
// so create a new one every time.
final ProtoOutputStream protoOutputStream =
new ProtoOutputStream(INTENT_PROTO_CHUNK_SIZE);
// Write this data out as the top-most IntentProto (i.e. it is not a sub-object).
intent.dumpDebug(protoOutputStream);
final byte[] bytes = protoOutputStream.getBytes();
p.writeByteArray(bytes);
}
// TODO: Should be mockable for testing?
// We cannot deserialize in the platform because we don't have a 'readFromProto'
// code.
public static @NonNull Intent create(Parcel p) {
// This will "read" the correct amount of data, but then we discard it.
byte[] data = p.createByteArray();
// Never called by real code in a platform, this binder API is implemented only in C++.
return new Intent("<cannot deserialize IntentProto>");
}
}
}

View File

@ -1,46 +0,0 @@
/*
* 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));
}
}
}

View File

@ -1,114 +0,0 @@
/*
* Copyright (C) 2020 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.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
* Notifications for iorapd specifying when a package is updated by dexopt service.<br /><br />
*
* @hide
*/
public class DexOptEvent implements Parcelable {
public static final int TYPE_PACKAGE_UPDATE = 0;
private static final int TYPE_MAX = 0;
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
TYPE_PACKAGE_UPDATE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@Type public final int type;
public final String packageName;
@NonNull
public static DexOptEvent createPackageUpdate(String packageName) {
return new DexOptEvent(TYPE_PACKAGE_UPDATE, packageName);
}
private DexOptEvent(@Type int type, String packageName) {
this.type = type;
this.packageName = packageName;
checkConstructorArguments();
}
private void checkConstructorArguments() {
CheckHelpers.checkTypeInRange(type, TYPE_MAX);
Objects.requireNonNull(packageName, "packageName");
}
@Override
public String toString() {
return String.format("{DexOptEvent: packageName: %s}", packageName);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (other instanceof DexOptEvent) {
return equals((DexOptEvent) other);
}
return false;
}
private boolean equals(DexOptEvent other) {
return packageName.equals(other.packageName);
}
//<editor-fold desc="Binder boilerplate">
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(type);
out.writeString(packageName);
}
private DexOptEvent(Parcel in) {
this.type = in.readInt();
this.packageName = in.readString();
checkConstructorArguments();
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<DexOptEvent> CREATOR
= new Parcelable.Creator<DexOptEvent>() {
public DexOptEvent createFromParcel(Parcel in) {
return new DexOptEvent(in);
}
public DexOptEvent[] newArray(int size) {
return new DexOptEvent[size];
}
};
//</editor-fold>
}

View File

@ -1,267 +0,0 @@
/*
* Copyright (C) 2019 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.annotation.Nullable;
import android.content.Intent;
import android.util.Log;
import com.android.server.wm.ActivityMetricsLaunchObserver;
import java.io.StringWriter;
import java.io.PrintWriter;
/**
* A validator to check the correctness of event sequence during app startup.
*
* <p> A valid state transition of event sequence is shown as the following:
*
* <pre>
*
* +--------------------+
* | |
* | INIT |
* | |
* +--------------------+
* |
* |
*
* +--------------------+
* | |
* +-------------------| INTENT_STARTED | --------------------------------+
* | | | |
* | +--------------------+ |
* | | |
* | | |
* |
* +--------------------+ +--------------------+ |
* | | | | |
* | INTENT_FAILED | | ACTIVITY_LAUNCHED |------------------+ |
* | | | | | |
* +--------------------+ +--------------------+ | |
* | | | |
* | |
* | +--------------------+ +--------------------+ |
* | | | | | |
* +------------------ | ACTIVITY_FINISHED | | ACTIVITY_CANCELLED | |
* | | | | | |
* | +--------------------+ +--------------------+ |
* | | | |
* | | | |
* | | |
* | +--------------------+ | |
* | | | | |
* | | REPORT_FULLY_DRAWN | | |
* | | | | |
* | +--------------------+ | |
* | | | |
* | | | |
* | | |
* | +--------------------+ | |
* | | | | |
* +----------------- | END |-----------------+ |
* | | |
* +--------------------+ |
* | |
* | |
* | |
* +---------------------------------------------
*
* <p> END is not a real state in implementation. All states that points to END directly
* could transition to INTENT_STARTED.
*
* <p> If any bad transition happened, the state becomse UNKNOWN. The UNKNOWN state
* could be accumulated, because during the UNKNOWN state more IntentStarted may
* be triggered. To recover from UNKNOWN to INIT, all the accumualted IntentStarted
* should termniate.
*
* <p> During UNKNOWN state, each IntentStarted increases the accumulation, and any of
* IntentFailed, ActivityLaunchCancelled and ActivityFinished decreases the accumulation.
* ReportFullyDrawn doesn't impact the accumulation.
*/
public class EventSequenceValidator implements ActivityMetricsLaunchObserver {
static final String TAG = "EventSequenceValidator";
/** $> adb shell 'setprop log.tag.EventSequenceValidator VERBOSE' */
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private State state = State.INIT;
private long accIntentStartedEvents = 0;
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("IntentStarted during UNKNOWN. " + intent);
incAccIntentStartedEvents();
return;
}
if (state != State.INIT &&
state != State.INTENT_FAILED &&
state != State.ACTIVITY_CANCELLED &&
state != State.ACTIVITY_FINISHED &&
state != State.REPORT_FULLY_DRAWN) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.INTENT_STARTED));
incAccIntentStartedEvents();
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_STARTED));
state = State.INTENT_STARTED;
}
@Override
public void onIntentFailed() {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onIntentFailed during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.INTENT_STARTED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.INTENT_FAILED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.INTENT_FAILED));
state = State.INTENT_FAILED;
}
@Override
public void onActivityLaunched(@NonNull @ActivityRecordProto byte[] activity,
@Temperature int temperature) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunched during UNKNOWN.");
return;
}
if (state != State.INTENT_STARTED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_LAUNCHED));
state = State.ACTIVITY_LAUNCHED;
}
@Override
public void onActivityLaunchCancelled(@Nullable @ActivityRecordProto byte[] activity) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunchCancelled during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_CANCELLED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_CANCELLED));
state = State.ACTIVITY_CANCELLED;
}
@Override
public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onActivityLaunchFinished during UNKNOWN.");
decAccIntentStartedEvents();
return;
}
if (state != State.ACTIVITY_LAUNCHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.ACTIVITY_FINISHED));
incAccIntentStartedEvents();
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.ACTIVITY_FINISHED));
state = State.ACTIVITY_FINISHED;
}
@Override
public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (state == State.UNKNOWN) {
logWarningWithStackTrace("onReportFullyDrawn during UNKNOWN.");
return;
}
if (state == State.INIT) {
return;
}
if (state != State.ACTIVITY_FINISHED) {
logWarningWithStackTrace(
String.format("Cannot transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
return;
}
Log.i(TAG, String.format("Transition from %s to %s", state, State.REPORT_FULLY_DRAWN));
state = State.REPORT_FULLY_DRAWN;
}
enum State {
INIT,
INTENT_STARTED,
INTENT_FAILED,
ACTIVITY_LAUNCHED,
ACTIVITY_CANCELLED,
ACTIVITY_FINISHED,
REPORT_FULLY_DRAWN,
UNKNOWN,
}
private void incAccIntentStartedEvents() {
if (accIntentStartedEvents < 0) {
throw new AssertionError("The number of unknowns cannot be negative");
}
if (accIntentStartedEvents == 0) {
state = State.UNKNOWN;
}
++accIntentStartedEvents;
Log.i(TAG,
String.format("inc AccIntentStartedEvents to %d", accIntentStartedEvents));
}
private void decAccIntentStartedEvents() {
if (accIntentStartedEvents <= 0) {
throw new AssertionError("The number of unknowns cannot be negative");
}
if(accIntentStartedEvents == 1) {
state = State.INIT;
}
--accIntentStartedEvents;
Log.i(TAG,
String.format("dec AccIntentStartedEvents to %d", accIntentStartedEvents));
}
private void logWarningWithStackTrace(String log) {
if (DEBUG) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
new Throwable("EventSequenceValidator#getStackTrace").printStackTrace(pw);
Log.wtf(TAG, String.format("%s\n%s", log, sw));
}
}
}

View File

@ -1,812 +0,0 @@
/*
* 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.app.job.JobInfo;
import android.app.job.JobParameters;
import android.app.job.JobService;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.Handler;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemProperties;
import android.provider.DeviceConfig;
import android.util.ArraySet;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.IoThread;
import com.android.server.LocalServices;
import com.android.server.SystemService;
import com.android.server.pm.BackgroundDexOptService;
import com.android.server.pm.PackageManagerService;
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;
import java.time.Duration;
import java.util.ArrayList;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.HashMap;
import java.util.List;
/**
* System-server-local proxy into the {@code IIorap} native service.
*/
public class IorapForwardingService extends SystemService {
public static final String TAG = "IorapForwardingService";
/** $> adb shell 'setprop log.tag.IorapForwardingService VERBOSE' */
public static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
/** $> adb shell 'setprop ro.iorapd.enable true' */
private static boolean IS_ENABLED = SystemProperties.getBoolean("ro.iorapd.enable", true);
/** $> adb shell 'setprop iorapd.forwarding_service.wtf_crash true' */
private static boolean WTF_CRASH = SystemProperties.getBoolean(
"iorapd.forwarding_service.wtf_crash", false);
private static final Duration TIMEOUT = Duration.ofSeconds(600L);
// "Unique" job ID from the service name. Also equal to 283673059.
public static final int JOB_ID_IORAPD = encodeEnglishAlphabetStringIntoInt("iorapd");
// Run every 24 hours.
public static final long JOB_INTERVAL_MS = TimeUnit.HOURS.toMillis(24);
private IIorap mIorapRemote;
private final Object mLock = new Object();
/** Handle onBinderDeath by periodically trying to reconnect. */
private final Handler mHandler =
new BinderConnectionHandler(IoThread.getHandler().getLooper());
private volatile IorapdJobService mJobService; // Write-once (null -> non-null forever).
private volatile static IorapForwardingService sSelfService; // Write once (null -> non-null).
/**
* Atomics set to true if the JobScheduler requests an abort.
*/
private final AtomicBoolean mAbortIdleCompilation = new AtomicBoolean(false);
/**
* 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);
if (DEBUG) {
Log.v(TAG, "IorapForwardingService (Context=" + context.toString() + ")");
}
if (sSelfService != null) {
throw new AssertionError("only one service instance allowed");
}
sSelfService = this;
}
//<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() {
IIorap iorap;
try {
iorap = IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"));
} catch (ServiceManager.ServiceNotFoundException e) {
Log.w(TAG, e.getMessage());
return null;
}
try {
iorap.asBinder().linkToDeath(provideDeathRecipient(), /*flags*/0);
} catch (RemoteException e) {
handleRemoteError(e);
return null;
}
return iorap;
}
@VisibleForTesting
protected DeathRecipient provideDeathRecipient() {
return new DeathRecipient() {
@Override
public void binderDied() {
Log.w(TAG, "iorapd has died");
retryConnectToRemoteAndConfigure(/*attempts*/0);
if (mJobService != null) {
mJobService.onIorapdDisconnected();
}
}
};
}
@VisibleForTesting
protected boolean isIorapEnabled() {
// These two mendel flags should match those in iorapd native process
// system/iorapd/src/common/property.h
boolean isTracingEnabled =
getMendelFlag("iorap_perfetto_enable", "iorapd.perfetto.enable", false);
boolean isReadAheadEnabled =
getMendelFlag("iorap_readahead_enable", "iorapd.readahead.enable", false);
// Same as the property in iorapd.rc -- disabling this will mean the 'iorapd' binder process
// never comes up, so all binder connections will fail indefinitely.
return IS_ENABLED && (isTracingEnabled || isReadAheadEnabled);
}
private boolean getMendelFlag(String mendelFlag, String sysProperty, boolean defaultValue) {
// TODO(yawanng) use DeviceConfig to get mendel property.
// DeviceConfig doesn't work and the reason is not clear.
// Provider service is already up before IORapForwardService.
String mendelProperty = "persist.device_config."
+ DeviceConfig.NAMESPACE_RUNTIME_NATIVE_BOOT
+ "."
+ mendelFlag;
return SystemProperties.getBoolean(mendelProperty,
SystemProperties.getBoolean(sysProperty, defaultValue));
}
//</editor-fold>
@Override
public void onStart() {
if (DEBUG) {
Log.v(TAG, "onStart");
}
retryConnectToRemoteAndConfigure(/*attempts*/0);
}
@Override
public void onBootPhase(int phase) {
if (phase == PHASE_BOOT_COMPLETED) {
if (DEBUG) {
Log.v(TAG, "onBootPhase(PHASE_BOOT_COMPLETED)");
}
if (isIorapEnabled()) {
// Set up a recurring background job. This has to be done in a later phase since it
// has a dependency the job scheduler.
//
// Doing this too early can result in a ServiceNotFoundException for 'jobservice'
// or a null reference for #getSystemService(JobScheduler.class)
mJobService = new IorapdJobService(getContext());
}
}
}
private class BinderConnectionHandler extends Handler {
public BinderConnectionHandler(android.os.Looper looper) {
super(looper);
}
public static final int MESSAGE_BINDER_CONNECT = 0;
private int mAttempts = 0;
@Override
public void handleMessage(android.os.Message message) {
switch (message.what) {
case MESSAGE_BINDER_CONNECT:
if (!retryConnectToRemoteAndConfigure(mAttempts)) {
mAttempts++;
} else {
mAttempts = 0;
}
break;
default:
throw new AssertionError("Unknown message: " + message.toString());
}
}
}
/**
* Handle iorapd shutdowns and crashes, by attempting to reconnect
* until the service is reached again.
*
* <p>The first connection attempt is synchronous,
* subsequent attempts are done by posting delayed tasks to the IoThread.</p>
*
* @return true if connection succeeded now, or false if it failed now [and needs to requeue].
*/
private boolean retryConnectToRemoteAndConfigure(int attempts) {
final int sleepTime = 1000; // ms
if (DEBUG) {
Log.v(TAG, "retryConnectToRemoteAndConfigure - attempt #" + attempts);
}
if (connectToRemoteAndConfigure()) {
return true;
}
// Either 'iorapd' is stuck in a crash loop (ouch!!) or we manually
// called 'adb shell stop iorapd' , which means this would loop until it comes back
// up.
//
// TODO: it would be good to get nodified of 'adb shell stop iorapd' to avoid
// printing this warning.
if (DEBUG) {
Log.v(TAG, "Failed to connect to iorapd, is it down? Delay for " + sleepTime);
}
// Use a handler instead of Thread#sleep to avoid backing up the binder thread
// when this is called from the death recipient callback.
mHandler.sendMessageDelayed(
mHandler.obtainMessage(BinderConnectionHandler.MESSAGE_BINDER_CONNECT),
sleepTime);
return false;
// Log.e(TAG, "Can't connect to iorapd - giving up after " + attempts + " attempts");
}
private boolean connectToRemoteAndConfigure() {
synchronized (mLock) {
// Synchronize against any concurrent calls to this via the DeathRecipient.
return connectToRemoteAndConfigureLocked();
}
}
private boolean connectToRemoteAndConfigureLocked() {
if (!isIorapEnabled()) {
if (DEBUG) {
Log.v(TAG, "connectToRemoteAndConfigure - iorapd is disabled, skip rest of work");
}
// When we see that iorapd is disabled (when system server comes up),
// it stays disabled permanently until the next system server reset.
// TODO: consider listening to property changes as a callback, then we can
// be more dynamic about handling enable/disable.
return true;
}
// Connect to the native binder service.
mIorapRemote = provideIorapRemote();
if (mIorapRemote == null) {
if (DEBUG) {
Log.e(TAG, "connectToRemoteAndConfigure - null iorap remote. check for Log.wtf?");
}
return false;
}
invokeRemote(mIorapRemote,
(IIorap remote) -> remote.setTaskListener(new RemoteTaskListener()) );
registerInProcessListenersLocked();
Log.i(TAG, "Connected to iorapd native service.");
return true;
}
private final AppLaunchObserver mAppLaunchObserver = new AppLaunchObserver();
private final EventSequenceValidator mEventSequenceValidator = new EventSequenceValidator();
private final DexOptPackagesUpdated mDexOptPackagesUpdated = new DexOptPackagesUpdated();
private boolean mRegisteredListeners = false;
private void registerInProcessListenersLocked() {
if (mRegisteredListeners) {
// Listeners are registered only once (idempotent operation).
//
// Today listeners are tolerant of the remote side going away
// by handling remote errors.
//
// We could try to 'unregister' the listener when we get a binder disconnect,
// but we'd still have to handle the case of encountering synchronous errors so
// it really wouldn't be a win (other than having less log spew).
return;
}
// Listen to App Launch Sequence events from ActivityTaskManager,
// and forward them to the native binder service.
ActivityMetricsLaunchObserverRegistry launchObserverRegistry =
provideLaunchObserverRegistry();
launchObserverRegistry.registerLaunchObserver(mAppLaunchObserver);
launchObserverRegistry.registerLaunchObserver(mEventSequenceValidator);
BackgroundDexOptService.addPackagesUpdatedListener(mDexOptPackagesUpdated);
mRegisteredListeners = true;
}
private class DexOptPackagesUpdated implements BackgroundDexOptService.PackagesUpdatedListener {
@Override
public void onPackagesUpdated(ArraySet<String> updatedPackages) {
String[] updated = updatedPackages.toArray(new String[0]);
for (String packageName : updated) {
Log.d(TAG, "onPackagesUpdated: " + packageName);
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onDexOptEvent(RequestId.nextValueForSequence(),
DexOptEvent.createPackageUpdate(packageName))
);
}
}
}
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;
// All callbacks occur on the same background thread. Don't synchronize explicitly.
@Override
public void onIntentStarted(@NonNull Intent intent, long timestampNs) {
// #onIntentStarted [is the only transition that] initiates a new launch sequence.
++mSequenceId;
if (DEBUG) {
Log.v(TAG, String.format("AppLaunchObserver#onIntentStarted(%d, %s, %d)",
mSequenceId, intent, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
new AppLaunchEvent.IntentStarted(mSequenceId, intent, timestampNs))
);
}
@Override
public void onIntentFailed() {
if (DEBUG) {
Log.v(TAG, String.format("AppLaunchObserver#onIntentFailed(%d)", mSequenceId));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.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,
(IIorap remote) ->
remote.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,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
new AppLaunchEvent.ActivityLaunchCancelled(mSequenceId,
activity)));
}
@Override
public void onActivityLaunchFinished(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (DEBUG) {
Log.v(TAG, String.format("AppLaunchObserver#onActivityLaunchFinished(%d, %s, %d)",
mSequenceId, activity, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
new AppLaunchEvent.ActivityLaunchFinished(mSequenceId,
activity,
timestampNs))
);
}
@Override
public void onReportFullyDrawn(@NonNull @ActivityRecordProto byte[] activity,
long timestampNs) {
if (DEBUG) {
Log.v(TAG, String.format("AppLaunchObserver#onReportFullyDrawn(%d, %s, %d)",
mSequenceId, activity, timestampNs));
}
invokeRemote(mIorapRemote,
(IIorap remote) ->
remote.onAppLaunchEvent(RequestId.nextValueForSequence(),
new AppLaunchEvent.ReportFullyDrawn(mSequenceId, activity, timestampNs))
);
}
}
/**
* Debugging:
*
* $> adb shell dumpsys jobscheduler
*
* Search for 'IorapdJobServiceProxy'.
*
* JOB #1000/283673059: 6e54ed android/com.google.android.startop.iorap.IorapForwardingService$IorapdJobServiceProxy
* ^ ^ ^
* (uid, job id) ComponentName(package/class)
*
* Forcing the job to be run, ignoring constraints:
*
* $> adb shell cmd jobscheduler run -f android 283673059
* ^ ^
* package job_id
*
* ------------------------------------------------------------
*
* This class is instantiated newly by the JobService every time
* it wants to run a new job.
*
* We need to forward invocations to the current running instance of
* IorapForwardingService#IorapdJobService.
*
* Visibility: Must be accessible from android.app.AppComponentFactory
*/
public static class IorapdJobServiceProxy extends JobService {
public IorapdJobServiceProxy() {
getActualIorapdJobService().bindProxy(this);
}
@NonNull
private IorapdJobService getActualIorapdJobService() {
// Can't ever be null, because the guarantee is that the
// IorapForwardingService is always running.
// We are in the same process as Job Service.
return sSelfService.mJobService;
}
// Called by system to start the job.
@Override
public boolean onStartJob(JobParameters params) {
return getActualIorapdJobService().onStartJob(params);
}
// Called by system to prematurely stop the job.
@Override
public boolean onStopJob(JobParameters params) {
return getActualIorapdJobService().onStopJob(params);
}
}
private class IorapdJobService extends JobService {
private final ComponentName IORAPD_COMPONENT_NAME;
private final Object mLock = new Object();
// Jobs currently running remotely on iorapd.
// They were started by the JobScheduler and need to be finished.
private final HashMap<RequestId, JobParameters> mRunningJobs = new HashMap<>();
private final JobInfo IORAPD_JOB_INFO;
private volatile IorapdJobServiceProxy mProxy;
public void bindProxy(IorapdJobServiceProxy proxy) {
mProxy = proxy;
}
// Create a new job service which immediately schedules a 24-hour idle maintenance mode
// background job to execute.
public IorapdJobService(Context context) {
if (DEBUG) {
Log.v(TAG, "IorapdJobService (Context=" + context.toString() + ")");
}
// Schedule the proxy class to be instantiated by the JobScheduler
// when it is time to invoke background jobs for IorapForwardingService.
// This also needs a BIND_JOB_SERVICE permission in
// frameworks/base/core/res/AndroidManifest.xml
IORAPD_COMPONENT_NAME = new ComponentName(context, IorapdJobServiceProxy.class);
JobInfo.Builder builder = new JobInfo.Builder(JOB_ID_IORAPD, IORAPD_COMPONENT_NAME);
builder.setPeriodic(JOB_INTERVAL_MS);
builder.setPrefetch(true);
builder.setRequiresCharging(true);
builder.setRequiresDeviceIdle(true);
builder.setRequiresStorageNotLow(true);
IORAPD_JOB_INFO = builder.build();
JobScheduler js = context.getSystemService(JobScheduler.class);
js.schedule(IORAPD_JOB_INFO);
Log.d(TAG,
"BgJob Scheduled (jobId=" + JOB_ID_IORAPD
+ ", interval: " + JOB_INTERVAL_MS + "ms)");
}
// Called by system to start the job.
@Override
public boolean onStartJob(JobParameters params) {
// Tell iorapd to start a background job.
Log.d(TAG, "Starting background job: " + params.toString());
mAbortIdleCompilation.set(false);
// PackageManagerService starts before IORap service.
PackageManagerService pm = (PackageManagerService)ServiceManager.getService("package");
List<String> pkgs = pm.getAllPackages();
runIdleCompilationAsync(params, pkgs);
return true;
}
private void runIdleCompilationAsync(final JobParameters params, final List<String> pkgs) {
new Thread("IORap_IdleCompilation") {
@Override
public void run() {
for (int i = 0; i < pkgs.size(); i++) {
String pkg = pkgs.get(i);
if (mAbortIdleCompilation.get()) {
Log.i(TAG, "The idle compilation is aborted");
return;
}
// We wait until that job's sequence ID returns to us with 'Completed',
RequestId request;
synchronized (mLock) {
// TODO: would be cleaner if we got the request from the
// 'invokeRemote' function. Better yet, consider
// a Pair<RequestId, Future<TaskResult>> or similar.
request = RequestId.nextValueForSequence();
mRunningJobs.put(request, params);
}
Log.i(TAG, String.format("IORap compile package: %s, [%d/%d]",
pkg, i + 1, pkgs.size()));
boolean shouldUpdateVersions = (i == 0);
if (!invokeRemote(mIorapRemote, (IIorap remote) ->
remote.onJobScheduledEvent(request,
JobScheduledEvent.createIdleMaintenance(
JobScheduledEvent.TYPE_START_JOB,
params,
pkg,
shouldUpdateVersions)))) {
synchronized (mLock) {
mRunningJobs.remove(request); // Avoid memory leaks.
}
}
// Wait until the job is complete and removed from the running jobs.
retryWithTimeout(TIMEOUT, () -> {
synchronized (mLock) {
return !mRunningJobs.containsKey(request);
}
});
}
// Finish the job after all packages are compiled.
if (mProxy != null) {
mProxy.jobFinished(params, /*reschedule*/false);
}
}
}.start();
}
/** Retry until timeout. */
private boolean retryWithTimeout(final Duration timeout, BooleanSupplier supplier) {
long totalSleepTimeMs = 0L;
long sleepIntervalMs = 10L;
while (true) {
if (supplier.getAsBoolean()) {
return true;
}
try {
TimeUnit.MILLISECONDS.sleep(sleepIntervalMs);
} catch (InterruptedException e) {
Log.e(TAG, e.getMessage());
return false;
}
totalSleepTimeMs += sleepIntervalMs;
if (totalSleepTimeMs > timeout.toMillis()) {
return false;
}
}
}
// Called by system to prematurely stop the job.
@Override
public boolean onStopJob(JobParameters params) {
// As this is unexpected behavior, print a warning.
Log.w(TAG, "onStopJob(params=" + params.toString() + ")");
mAbortIdleCompilation.set(true);
// Yes, retry the job at a later time no matter what.
return true;
}
// Listen to *all* task completes for all requests.
// The majority of these might be unrelated to background jobs.
public void onIorapdTaskCompleted(RequestId requestId) {
JobParameters jobParameters;
synchronized (mLock) {
jobParameters = mRunningJobs.remove(requestId);
}
// Typical case: This was a task callback unrelated to our jobs.
if (jobParameters == null) {
return;
}
if (DEBUG) {
Log.v(TAG,
String.format("IorapdJobService#onIorapdTaskCompleted(%s), found params=%s",
requestId, jobParameters));
}
Log.d(TAG, "Finished background job: " + jobParameters.toString());
}
public void onIorapdDisconnected() {
synchronized (mLock) {
mRunningJobs.clear();
}
if (DEBUG) {
Log.v(TAG, String.format("IorapdJobService#onIorapdDisconnected"));
}
// TODO: should we try to resubmit all incomplete jobs after it's reconnected?
}
}
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));
}
if (mJobService != null) {
mJobService.onIorapdTaskCompleted(requestId);
}
// TODO: implement rest.
}
}
/** Allow passing lambdas to #invokeRemote */
private interface RemoteRunnable {
// TODO: run(RequestId) ?
void run(IIorap iorap) throws RemoteException;
}
// Always pass in the iorap directly here to avoid data race.
private static boolean invokeRemote(IIorap iorap, RemoteRunnable r) {
if (iorap == null) {
Log.w(TAG, "IIorap went to null in this thread, drop invokeRemote.");
return false;
}
try {
r.run(iorap);
return true;
} catch (RemoteException e) {
// This could be a logic error (remote side returning error), which we need to fix.
//
// This could also be a DeadObjectException in which case its probably just iorapd
// being manually restarted.
//
// Don't make any assumption, since DeadObjectException could also mean iorapd crashed
// unexpectedly.
//
// DeadObjectExceptions are recovered from using DeathRecipient and #linkToDeath.
handleRemoteError(e);
return false;
}
}
private static void handleRemoteError(Throwable t) {
if (WTF_CRASH) {
// In development modes, we just want to crash.
throw new AssertionError("unexpected remote error", t);
} else {
// Log to wtf which gets sent to dropbox, and in system_server this does not crash.
Log.wtf(TAG, t);
}
}
// Encode A-Z bitstring into bits. Every character is bits.
// Characters outside of the range [a,z] are considered out of range.
//
// The least significant bits hold the last character.
// First 2 bits are left as 0.
private static int encodeEnglishAlphabetStringIntoInt(String name) {
int value = 0;
final int CHARS_PER_INT = 6;
final int BITS_PER_CHAR = 5;
// Note: 2 top bits are unused, this also means our values are non-negative.
final char CHAR_LOWER = 'a';
final char CHAR_UPPER = 'z';
if (name.length() > CHARS_PER_INT) {
throw new IllegalArgumentException(
"String too long. Cannot encode more than 6 chars: " + name);
}
for (int i = 0; i < name.length(); ++i) {
char c = name.charAt(i);
if (c < CHAR_LOWER || c > CHAR_UPPER) {
throw new IllegalArgumentException("String has out-of-range [a-z] chars: " + name);
}
// Avoid sign extension during promotion.
int cur_value = (c & 0xFFFF) - (CHAR_LOWER & 0xFFFF);
if (cur_value >= (1 << BITS_PER_CHAR)) {
throw new AssertionError("wtf? i=" + i + ", name=" + name);
}
value = value << BITS_PER_CHAR;
value = value | cur_value;
}
return value;
}
}

View File

@ -1,174 +0,0 @@
/*
* 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.app.job.JobParameters;
import android.annotation.NonNull;
import android.os.Parcelable;
import android.os.Parcel;
import android.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* Forward JobService events to iorapd. <br /><br />
*
* iorapd sometimes need to use background jobs. Forwarding these events to iorapd
* notifies iorapd when it is an opportune time to execute these background jobs.
*
* @hide
*/
public class JobScheduledEvent implements Parcelable {
/** JobService#onJobStarted */
public static final int TYPE_START_JOB = 0;
/** JobService#onJobStopped */
public static final int TYPE_STOP_JOB = 1;
private static final int TYPE_MAX = 1;
/** @hide */
@IntDef(flag = true, prefix = { "TYPE_" }, value = {
TYPE_START_JOB,
TYPE_STOP_JOB,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Type {}
@Type public final int type;
/** @see JobParameters#getJobId() */
public final int jobId;
public final String packageName;
public final boolean shouldUpdateVersions;
/** Device is 'idle' and it's charging (plugged in). */
public static final int SORT_IDLE_MAINTENANCE = 0;
private static final int SORT_MAX = 0;
/** @hide */
@IntDef(flag = true, prefix = { "SORT_" }, value = {
SORT_IDLE_MAINTENANCE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Sort {}
/**
* Roughly corresponds to the {@code extras} fields in a JobParameters.
*/
@Sort public final int sort;
/**
* Creates a {@link #SORT_IDLE_MAINTENANCE} event from the type and job parameters.
*
* Only the job ID is retained from {@code jobParams}, all other param info is dropped.
*/
@NonNull
public static JobScheduledEvent createIdleMaintenance(
@Type int type, JobParameters jobParams, String packageName, boolean shouldUpdateVersions) {
return new JobScheduledEvent(
type, jobParams.getJobId(), SORT_IDLE_MAINTENANCE, packageName, shouldUpdateVersions);
}
private JobScheduledEvent(@Type int type,
int jobId,
@Sort int sort,
String packageName,
boolean shouldUpdateVersions) {
this.type = type;
this.jobId = jobId;
this.sort = sort;
this.packageName = packageName;
this.shouldUpdateVersions = shouldUpdateVersions;
checkConstructorArguments();
}
private void checkConstructorArguments() {
CheckHelpers.checkTypeInRange(type, TYPE_MAX);
// No check for 'jobId': any int is valid.
CheckHelpers.checkTypeInRange(sort, SORT_MAX);
}
@Override
public boolean equals(Object other) {
if (this == other) {
return true;
} else if (other instanceof JobScheduledEvent) {
return equals((JobScheduledEvent) other);
}
return false;
}
private boolean equals(JobScheduledEvent other) {
return type == other.type &&
jobId == other.jobId &&
sort == other.sort &&
packageName.equals(other.packageName) &&
shouldUpdateVersions == other.shouldUpdateVersions;
}
@Override
public String toString() {
return String.format(
"{type: %d, jobId: %d, sort: %d, packageName: %s, shouldUpdateVersions %b}",
type, jobId, sort, packageName, shouldUpdateVersions);
}
//<editor-fold desc="Binder boilerplate">
@Override
public void writeToParcel(Parcel out, int flags) {
out.writeInt(type);
out.writeInt(jobId);
out.writeInt(sort);
out.writeString(packageName);
out.writeBoolean(shouldUpdateVersions);
// We do not parcel the entire JobParameters here because there is no C++ equivalent
// of that class [which the iorapd side of the binder interface requires].
}
private JobScheduledEvent(Parcel in) {
this.type = in.readInt();
this.jobId = in.readInt();
this.sort = in.readInt();
this.packageName = in.readString();
this.shouldUpdateVersions = in.readBoolean();
checkConstructorArguments();
}
@Override
public int describeContents() {
return 0;
}
public static final Parcelable.Creator<JobScheduledEvent> CREATOR
= new Parcelable.Creator<JobScheduledEvent>() {
public JobScheduledEvent createFromParcel(Parcel in) {
return new JobScheduledEvent(in);
}
public JobScheduledEvent[] newArray(int size) {
return new JobScheduledEvent[size];
}
};
//</editor-fold>
}

View File

@ -1,131 +0,0 @@
/*
* 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>
}

View File

@ -1,125 +0,0 @@
/*
* 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: %d}", requestId);
}
@Override
public int hashCode() {
return Long.hashCode(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>
}

View File

@ -1,109 +0,0 @@
/*
* 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>
}

View File

@ -1,127 +0,0 @@
/*
* 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#onUserStarting */
public static final int TYPE_START_USER = 0;
/** @see com.android.server.SystemService#onUserUnlocking */
public static final int TYPE_UNLOCK_USER = 1;
/** @see com.android.server.SystemService#onUserSwitching*/
public static final int TYPE_SWITCH_USER = 2;
/** @see com.android.server.SystemService#onUserStopping */
public static final int TYPE_STOP_USER = 3;
/** @see com.android.server.SystemService#onUserStopped */
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>
}

View File

@ -1,130 +0,0 @@
/*
* 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>
}

View File

@ -1,42 +0,0 @@
//
// Copyright (C) 2020 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 {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
cc_binary {
name: "iorap.stress.memory",
srcs: ["main_memory.cc"],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-unused-parameter"
],
shared_libs: [
"libbase"
],
host_supported: true,
}

View File

@ -1,126 +0,0 @@
//
// Copyright (C) 2020 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.
//
#include <chrono>
#include <fstream>
#include <iostream>
#include <random>
#include <string>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <android-base/parseint.h>
static constexpr size_t kBytesPerMb = 1048576;
const size_t kMemoryAllocationSize = 2 * 1024 * kBytesPerMb;
#define USE_MLOCKALL 0
std::string GetProcessStatus(const char* key) {
// Build search pattern of key and separator.
std::string pattern(key);
pattern.push_back(':');
// Search for status lines starting with pattern.
std::ifstream fs("/proc/self/status");
std::string line;
while (std::getline(fs, line)) {
if (strncmp(pattern.c_str(), line.c_str(), pattern.size()) == 0) {
// Skip whitespace in matching line (if any).
size_t pos = line.find_first_not_of(" \t", pattern.size());
if (pos == std::string::npos) {
break;
}
return std::string(line, pos);
}
}
return "<unknown>";
}
int main(int argc, char** argv) {
size_t allocationSize = 0;
if (argc >= 2) {
if (!android::base::ParseUint(argv[1], /*out*/&allocationSize)) {
std::cerr << "Failed to parse the allocation size (must be 0,MAX_SIZE_T)" << std::endl;
return 1;
}
} else {
allocationSize = kMemoryAllocationSize;
}
void* mem = malloc(allocationSize);
if (mem == nullptr) {
std::cerr << "Malloc failed" << std::endl;
return 1;
}
volatile int* imem = static_cast<int *>(mem); // don't optimize out memory usage
size_t imemCount = allocationSize / sizeof(int);
std::cout << "Allocated " << allocationSize << " bytes" << std::endl;
auto seed = std::chrono::high_resolution_clock::now().time_since_epoch().count();
std::mt19937 mt_rand(seed);
size_t randPrintCount = 10;
// Write random numbers:
// * Ensures each page is resident
// * Avoids zeroed out pages (zRAM)
// * Avoids same-page merging
for (size_t i = 0; i < imemCount; ++i) {
imem[i] = mt_rand();
if (i < randPrintCount) {
std::cout << "Generated random value: " << imem[i] << std::endl;
}
}
#if USE_MLOCKALL
/*
* Lock all pages from the address space of this process.
*/
if (mlockall(MCL_CURRENT | MCL_FUTURE) != 0) {
std::cerr << "Mlockall failed" << std::endl;
return 1;
}
#else
// Use mlock because of the predictable VmLck size.
// Using mlockall tends to bring in anywhere from 2-2.5GB depending on the device.
if (mlock(mem, allocationSize) != 0) {
std::cerr << "Mlock failed" << std::endl;
return 1;
}
#endif
// Validate memory is actually resident and locked with:
// $> cat /proc/$(pidof iorap.stress.memory)/status | grep VmLck
std::cout << "Locked memory (VmLck) = " << GetProcessStatus("VmLck") << std::endl;
std::cout << "Press any key to terminate" << std::endl;
int any_input;
std::cin >> any_input;
std::cout << "Terminating..." << std::endl;
munlockall();
free(mem);
return 0;
}

View File

@ -1,72 +0,0 @@
// 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.
// TODO: once b/80095087 is fixed, rewrite this back to android_test
package {
// See: http://go/android-license-faq
// A large-scale-change added 'default_applicable_licenses' to import
// all of the 'license_kinds' from "frameworks_base_license"
// to get the below license kinds:
// SPDX-license-identifier-Apache-2.0
default_applicable_licenses: ["frameworks_base_license"],
}
java_library {
name: "libiorap-java-test-lib",
srcs: ["src/**/*.kt"],
static_libs: [
// 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",
"androidx.test.rules",
// test framework dependencies
"mockito-target-inline-minus-junit4",
// "mockito-target-minus-junit4",
// Mockito also requires JNI (see Android.mk)
// and android:debuggable=true (see AndroidManifest.xml)
"truth-prebuilt",
],
// sdk_version: "current",
// certificate: "platform",
libs: [
"android.test.base",
"android.test.runner",
],
// test_suites: ["device-tests"],
}
android_test {
name: "libiorap-java-tests",
dxflags: ["--multi-dex"],
test_suites: ["device-tests"],
static_libs: ["libiorap-java-test-lib"],
compile_multilib: "both",
jni_libs: [
"libdexmakerjvmtiagent",
"libstaticjvmtiagent",
"libmultiplejvmtiagentsinterferenceagent",
],
libs: [
"android.test.base",
"android.test.runner",
],
// Use private APIs
certificate: "platform",
platform_apis: true,
}

View File

@ -1,37 +0,0 @@
<?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="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.google.android.startop.iorap.tests" />
<!--
'debuggable=true' is required to properly load mockito jvmti dependencies,
otherwise it gives the following error at runtime:
Openjdkjvmti plugin was loaded on a non-debuggable Runtime.
Plugin was loaded too late to change runtime state to DEBUGGABLE. -->
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
</application>
</manifest>

View File

@ -1,66 +0,0 @@
<?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.
-->
<configuration description="Runs libiorap-java-tests.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="libiorap-java-tests.apk" />
</target_preparer>
<!--
Our IIorapIntegrationTest.kt requires setlinux to be disabled:
it connects to the iorapd binder service but this requires selinux permissions:
avc: denied { find } for service=iorapd pid=2738 uid=10050
scontext=u:r:platform_app:s0:c512,c768 tcontext=u:object_r:iorapd_service:s0
tclass=service_manager permissive=0
-->
<target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer">
</target_preparer>
<!-- do not use DeviceSetup#set-property because it reboots the device b/136200738.
furthermore the changes in /data/local.prop don't actually seem to get picked up.
-->
<target_preparer
class="com.android.tradefed.targetprep.DeviceSetup">
<!-- we need this magic flag, otherwise it always reboots and breaks the selinux -->
<option name="force-skip-system-props" value="true" />
<!-- Crash instead of using Log.wtf within the system_server iorap code. -->
<option name="run-command" value="setprop iorapd.forwarding_service.wtf_crash true" />
<!-- IIorapd has fake behavior: it doesn't do anything but reply with 'DONE' status -->
<option name="run-command" value="setprop iorapd.binder.fake true" />
<!-- iorapd does not pick up the above changes until we restart it -->
<option name="run-command" value="stop iorapd" />
<option name="run-command" value="start iorapd" />
<!-- give it some time to restart the service; otherwise the first unit test might fail -->
<option name="run-command" value="sleep 1" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.google.android.startop.iorap.tests" />
<option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
</test>
<!-- using DeviceSetup again does not work. we simply leave the device in a semi-bad
state. there is no way to clean this up as far as I know.
-->
</configuration>

View File

@ -1,181 +0,0 @@
/*
* Copyright (C) 2019 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.content.Intent;
import android.net.Uri
import android.os.Parcel
import android.os.Parcelable
import androidx.test.filters.SmallTest
import com.google.android.startop.iorap.AppLaunchEvent;
import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunched
import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchCancelled
import com.google.android.startop.iorap.AppLaunchEvent.ActivityLaunchFinished
import com.google.android.startop.iorap.AppLaunchEvent.IntentStarted;
import com.google.android.startop.iorap.AppLaunchEvent.IntentFailed;
import com.google.android.startop.iorap.AppLaunchEvent.ReportFullyDrawn
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
/**
* Basic unit tests to test all of the [AppLaunchEvent]s in [com.google.android.startop.iorap].
*/
@SmallTest
class AppLaunchEventTest {
/**
* Test for IntentStarted.
*/
@Test
fun testIntentStarted() {
var intent = Intent()
val valid = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
val copy = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 1L)
val noneCopy1 = IntentStarted(/* sequenceId= */1L, intent, /* timestampNs= */ 1L)
val noneCopy2 = IntentStarted(/* sequenceId= */2L, intent, /* timestampNs= */ 2L)
val noneCopy3 = IntentStarted(/* sequenceId= */2L, Intent(), /* timestampNs= */ 1L)
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy1)
assertThat(valid).isNotEqualTo(noneCopy2)
assertThat(valid).isNotEqualTo(noneCopy3)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("IntentStarted{sequenceId=2, intent=Intent { } , timestampNs=1}")
}
/**
* Test for IntentFailed.
*/
@Test
fun testIntentFailed() {
val valid = IntentFailed(/* sequenceId= */2L)
val copy = IntentFailed(/* sequenceId= */2L)
val noneCopy = IntentFailed(/* sequenceId= */1L)
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("IntentFailed{sequenceId=2}")
}
/**
* Test for ActivityLaunched.
*/
@Test
fun testActivityLaunched() {
//var activityRecord =
val valid = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
/* temperature= */ 0)
val copy = ActivityLaunched(/* sequenceId= */2L, "test".toByteArray(),
/* temperature= */ 0)
val noneCopy1 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
/* temperature= */ 0)
val noneCopy2 = ActivityLaunched(/* sequenceId= */1L, "test".toByteArray(),
/* temperature= */ 1)
val noneCopy3 = ActivityLaunched(/* sequenceId= */1L, "test1".toByteArray(),
/* temperature= */ 0)
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy1)
assertThat(valid).isNotEqualTo(noneCopy2)
assertThat(valid).isNotEqualTo(noneCopy3)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("ActivityLaunched{sequenceId=2, test, temperature=0}")
}
/**
* Test for ActivityLaunchFinished.
*/
@Test
fun testActivityLaunchFinished() {
val valid = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
/* timestampNs= */ 1L)
val copy = ActivityLaunchFinished(/* sequenceId= */2L, "test".toByteArray(),
/* timestampNs= */ 1L)
val noneCopy1 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
/* timestampNs= */ 1L)
val noneCopy2 = ActivityLaunchFinished(/* sequenceId= */1L, "test".toByteArray(),
/* timestampNs= */ 2L)
val noneCopy3 = ActivityLaunchFinished(/* sequenceId= */2L, "test1".toByteArray(),
/* timestampNs= */ 1L)
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy1)
assertThat(valid).isNotEqualTo(noneCopy2)
assertThat(valid).isNotEqualTo(noneCopy3)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("ActivityLaunchFinished{sequenceId=2, test, timestampNs=1}")
}
/**
* Test for ActivityLaunchCancelled.
*/
@Test
fun testActivityLaunchCancelled() {
val valid = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
val copy = ActivityLaunchCancelled(/* sequenceId= */2L, "test".toByteArray())
val noneCopy1 = ActivityLaunchCancelled(/* sequenceId= */1L, "test".toByteArray())
val noneCopy2 = ActivityLaunchCancelled(/* sequenceId= */2L, "test1".toByteArray())
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy1)
assertThat(valid).isNotEqualTo(noneCopy2)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("ActivityLaunchCancelled{sequenceId=2, test}")
}
/**
* Test for ReportFullyDrawn.
*/
@Test
fun testReportFullyDrawn() {
val valid = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
val copy = ReportFullyDrawn(/* sequenceId= */2L, "test".toByteArray(), /* timestampNs= */ 1L)
val noneCopy1 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
/* timestampNs= */ 1L)
val noneCopy2 = ReportFullyDrawn(/* sequenceId= */1L, "test".toByteArray(),
/* timestampNs= */ 1L)
val noneCopy3 = ReportFullyDrawn(/* sequenceId= */2L, "test1".toByteArray(),
/* timestampNs= */ 1L)
// equals(Object other)
assertThat(valid).isEqualTo(copy)
assertThat(valid).isNotEqualTo(noneCopy1)
assertThat(valid).isNotEqualTo(noneCopy2)
assertThat(valid).isNotEqualTo(noneCopy3)
// test toString()
val result = valid.toString()
assertThat(result).isEqualTo("ReportFullyDrawn{sequenceId=2, test, timestampNs=1}")
}
}

View File

@ -1,137 +0,0 @@
/*
* 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.ServiceManager
import androidx.test.filters.FlakyTest
import androidx.test.filters.MediumTest
import org.junit.Test
import org.mockito.Mockito.argThat
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.spy
import org.mockito.Mockito.timeout
// @Ignore("Test is disabled until iorapd is added to init and there's selinux policies for it")
@MediumTest
@FlakyTest(bugId = 149098310) // Failing on cuttlefish with SecurityException.
class IIorapIntegrationTest {
/**
* @throws ServiceManager.ServiceNotFoundException if iorapd service could not be found
*/
private val iorapService: IIorap by lazy {
// TODO: connect to 'iorapd.stub' which doesn't actually do any work other than reply.
IIorap.Stub.asInterface(ServiceManager.getServiceOrThrow("iorapd"))
// Use 'adb shell setenforce 0' otherwise this whole test fails,
// because the servicemanager is not allowed to hand out the binder token for iorapd.
// TODO: implement the selinux policies for iorapd.
}
// A dummy binder stub implementation is required to use with mockito#spy.
// Mockito overrides the methods at runtime and tracks how methods were invoked.
open class DummyTaskListener : ITaskListener.Stub() {
// Note: make parameters nullable to avoid the kotlin IllegalStateExceptions
// from using the mockito matchers (eq, argThat, etc).
override fun onProgress(requestId: RequestId?, result: TaskResult?) {
}
override fun onComplete(requestId: RequestId?, result: TaskResult?) {
}
}
private fun testAnyMethod(func: (RequestId) -> Unit) {
val taskListener = spy(DummyTaskListener())!!
// FIXME: b/149098310
return
try {
iorapService.setTaskListener(taskListener)
// Note: Binder guarantees total order for oneway messages sent to the same binder
// interface, so we don't need any additional blocking here before sending later calls.
// Every new method call should have a unique request id.
val requestId = RequestId.nextValueForSequence()!!
// Apply the specific function under test.
func(requestId)
// Typical mockito behavior is to allow any-order callbacks, but we want to test order.
val inOrder = inOrder(taskListener)
// The "stub" behavior of iorapd is that every request immediately gets a response of
// BEGAN,ONGOING,COMPLETED
inOrder.verify(taskListener, timeout(100))
.onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_BEGAN })
inOrder.verify(taskListener, timeout(100))
.onProgress(eq(requestId), argThat { it!!.state == TaskResult.STATE_ONGOING })
inOrder.verify(taskListener, timeout(100))
.onComplete(eq(requestId), argThat { it!!.state == TaskResult.STATE_COMPLETED })
inOrder.verifyNoMoreInteractions()
} finally {
// iorapService.setTaskListener(null)
// FIXME: null is broken, C++ side sees a non-null object.
}
}
@Test
fun testOnPackageEvent() {
// FIXME (b/137134253): implement PackageEvent parsing on the C++ side.
// This is currently (silently: b/137135024) failing because IIorap is 'oneway' and the
// C++ PackageEvent un-parceling fails since its not implemented fully.
/*
testAnyMethod { requestId : RequestId ->
iorapService.onPackageEvent(requestId,
PackageEvent.createReplaced(
Uri.parse("https://www.google.com"), "com.fake.package"))
}
*/
}
@Test
fun testOnAppIntentEvent() {
testAnyMethod { requestId: RequestId ->
iorapService.onAppIntentEvent(requestId, AppIntentEvent.createDefaultIntentChanged(
ActivityInfo("dont care", "dont care"),
ActivityInfo("dont care 2", "dont care 2")))
}
}
@Test
fun testOnAppLaunchEvent() {
testAnyMethod { requestId : RequestId ->
iorapService.onAppLaunchEvent(requestId, AppLaunchEvent.IntentFailed(/*sequenceId*/123))
}
}
@Test
fun testOnSystemServiceEvent() {
testAnyMethod { requestId: RequestId ->
iorapService.onSystemServiceEvent(requestId,
SystemServiceEvent(SystemServiceEvent.TYPE_START))
}
}
@Test
fun testOnSystemServiceUserEvent() {
testAnyMethod { requestId: RequestId ->
iorapService.onSystemServiceUserEvent(requestId,
SystemServiceUserEvent(SystemServiceUserEvent.TYPE_START_USER, 0))
}
}
}

View File

@ -1,140 +0,0 @@
/*
* 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 androidx.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).isNotSameInstanceAs(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)
}
}
}