[Re-land] Remove iorap framework codes
This reverts commit2f393cdbce. - It's decided to remove iorap from Android. - This CL removes iorap framework codes. - Removing iorap daemon and git repo will follow. Bug: 211461392 Test: build okay Change-Id: Ie1bebf1dfdce8b5db59c4e5a685c8f1afb46e266 Merged-In: Ie1bebf1dfdce8b5db59c4e5a685c8f1afb46e266 (cherry picked from commit7ee89676b9)
This commit is contained in:
@@ -6352,10 +6352,6 @@
|
|||||||
android:resource="@xml/autofill_compat_accessibility_service" />
|
android:resource="@xml/autofill_compat_accessibility_service" />
|
||||||
</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"
|
<service android:name="com.android.server.blob.BlobStoreIdleJobService"
|
||||||
android:permission="android.permission.BIND_JOB_SERVICE">
|
android:permission="android.permission.BIND_JOB_SERVICE">
|
||||||
</service>
|
</service>
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ filegroup {
|
|||||||
":services.searchui-sources",
|
":services.searchui-sources",
|
||||||
":services.smartspace-sources",
|
":services.smartspace-sources",
|
||||||
":services.speech-sources",
|
":services.speech-sources",
|
||||||
":services.startop.iorap-sources",
|
|
||||||
":services.systemcaptions-sources",
|
":services.systemcaptions-sources",
|
||||||
":services.translation-sources",
|
":services.translation-sources",
|
||||||
":services.texttospeech-sources",
|
":services.texttospeech-sources",
|
||||||
@@ -147,7 +146,6 @@ java_library {
|
|||||||
"services.searchui",
|
"services.searchui",
|
||||||
"services.smartspace",
|
"services.smartspace",
|
||||||
"services.speech",
|
"services.speech",
|
||||||
"services.startop",
|
|
||||||
"services.systemcaptions",
|
"services.systemcaptions",
|
||||||
"services.translation",
|
"services.translation",
|
||||||
"services.texttospeech",
|
"services.texttospeech",
|
||||||
@@ -200,7 +198,6 @@ stubs_defaults {
|
|||||||
" --hide-annotation android.annotation.Hide" +
|
" --hide-annotation android.annotation.Hide" +
|
||||||
" --hide InternalClasses" + // com.android.* classes are okay in this interface
|
" --hide InternalClasses" + // com.android.* classes are okay in this interface
|
||||||
// TODO: remove the --hide options below
|
// TODO: remove the --hide options below
|
||||||
" --hide-package com.google.android.startop.iorap" +
|
|
||||||
" --hide DeprecationMismatch" +
|
" --hide DeprecationMismatch" +
|
||||||
" --hide HiddenTypedefConstant",
|
" --hide HiddenTypedefConstant",
|
||||||
visibility: ["//frameworks/base:__subpackages__"],
|
visibility: ["//frameworks/base:__subpackages__"],
|
||||||
|
|||||||
@@ -206,8 +206,6 @@ import com.android.server.wm.WindowManagerService;
|
|||||||
|
|
||||||
import dalvik.system.VMRuntime;
|
import dalvik.system.VMRuntime;
|
||||||
|
|
||||||
import com.google.android.startop.iorap.IorapForwardingService;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -1554,10 +1552,6 @@ public final class SystemServer implements Dumpable {
|
|||||||
mSystemServiceManager.startService(PinnerService.class);
|
mSystemServiceManager.startService(PinnerService.class);
|
||||||
t.traceEnd();
|
t.traceEnd();
|
||||||
|
|
||||||
t.traceBegin("IorapForwardingService");
|
|
||||||
mSystemServiceManager.startService(IorapForwardingService.class);
|
|
||||||
t.traceEnd();
|
|
||||||
|
|
||||||
if (Build.IS_DEBUGGABLE && ProfcollectForwardingService.enabled()) {
|
if (Build.IS_DEBUGGABLE && ProfcollectForwardingService.enabled()) {
|
||||||
t.traceBegin("ProfcollectForwardingService");
|
t.traceBegin("ProfcollectForwardingService");
|
||||||
mSystemServiceManager.startService(ProfcollectForwardingService.class);
|
mSystemServiceManager.startService(ProfcollectForwardingService.class);
|
||||||
|
|||||||
@@ -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",
|
|
||||||
],
|
|
||||||
}
|
|
||||||
@@ -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"],
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
{
|
|
||||||
"presubmit": [
|
|
||||||
{
|
|
||||||
"name": "libiorap-java-tests"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"imports": [
|
|
||||||
{
|
|
||||||
"path": "system/iorap"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -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 + "'");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
}
|
|
||||||
@@ -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,
|
|
||||||
}
|
|
||||||
@@ -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>
|
|
||||||
@@ -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>
|
|
||||||
|
|
||||||
@@ -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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user