Add content capture performance test cases

Bug: 153770130
Test: atest ContentCapturePerfTests
Change-Id: I6b0226ece380733b364cf4ebd0ee80daf70c56f2
This commit is contained in:
TYM Tsai
2020-07-03 21:27:14 +08:00
parent f3fcee4f23
commit f2a8b43aa0
9 changed files with 868 additions and 0 deletions

View File

@@ -0,0 +1,28 @@
// 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.
android_test {
name: "ContentCapturePerfTests",
srcs: ["src/**/*.java"],
resource_dirs: ["res"],
static_libs: [
"androidx.test.rules",
"androidx.annotation_annotation",
"apct-perftests-utils",
"collector-device-lib-platform",
"compatibility-device-util-axt",
],
platform_apis: true,
test_suites: ["device-tests"],
}

View File

@@ -0,0 +1,44 @@
<?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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.perftests.contentcapture">
<uses-sdk android:targetSdkVersion="28" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.REAL_GET_TASKS" />
<application>
<uses-library android:name="android.test.runner" />
<activity android:name="android.view.contentcapture.CustomTestActivity"
android:exported="true">
</activity>
<service
android:name="android.view.contentcapture.MyContentCaptureService"
android:label="PERF ContentCaptureService"
android:permission="android.permission.BIND_CONTENT_CAPTURE_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.service.contentcapture.ContentCaptureService" />
</intent-filter>
</service>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="com.android.perftests.contentcapture" />
</manifest>

View File

@@ -0,0 +1,27 @@
<?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 ContentCapturePerfTests metric instrumentation.">
<option name="test-suite-tag" value="apct" />
<option name="test-suite-tag" value="apct-metric-instrumentation" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="ContentCapturePerfTests.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="com.android.perftests.contentcapture" />
</test>
</configuration>

View File

@@ -0,0 +1,26 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical" >
</LinearLayout>

View File

@@ -0,0 +1,50 @@
<?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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="true"
android:focusableInTouchMode="true"
android:orientation="vertical" >
<TextView
android:id="@+id/username_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Username" />
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/password_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Password" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword" />
</LinearLayout>

View File

@@ -0,0 +1,269 @@
/*
* 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 android.view.contentcapture;
import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_CUSTOM_VIEWS;
import static android.view.contentcapture.CustomTestActivity.INTENT_EXTRA_LAYOUT_ID;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import android.app.Application;
import android.content.ContentCaptureOptions;
import android.content.Context;
import android.content.Intent;
import android.os.BatteryManager;
import android.os.UserHandle;
import android.perftests.utils.PerfStatusReporter;
import android.provider.Settings;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.test.filters.LargeTest;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.ActivitiesWatcher;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.perftests.contentcapture.R;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runners.model.Statement;
/**
* Base class for all content capture tests.
*/
@LargeTest
public abstract class AbstractContentCapturePerfTestCase {
private static final String TAG = AbstractContentCapturePerfTestCase.class.getSimpleName();
private static final long GENERIC_TIMEOUT_MS = 10_000;
private static int sOriginalStayOnWhilePluggedIn;
private static Context sContext = getInstrumentation().getTargetContext();
protected ActivitiesWatcher mActivitiesWatcher;
private MyContentCaptureService.ServiceWatcher mServiceWatcher;
@Rule
public ActivityTestRule<CustomTestActivity> mActivityRule =
new ActivityTestRule<>(CustomTestActivity.class, false, false);
@Rule
public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@Rule
public TestRule mServiceDisablerRule = (base, description) -> {
return new Statement() {
@Override
public void evaluate() throws Throwable {
try {
base.evaluate();
} finally {
Log.v(TAG, "@mServiceDisablerRule: safelyDisableService()");
safelyDisableService();
}
}
};
};
private void safelyDisableService() {
try {
resetService();
MyContentCaptureService.resetStaticState();
if (mServiceWatcher != null) {
mServiceWatcher.waitOnDestroy();
}
} catch (Throwable t) {
Log.e(TAG, "error disabling service", t);
}
}
/**
* Sets the content capture service.
*/
private static void setService(@NonNull String service) {
final int userId = getCurrentUserId();
Log.d(TAG, "Setting service for user " + userId + " to " + service);
// TODO(b/123540602): use @TestingAPI to get max duration constant
runShellCommand("cmd content_capture set temporary-service %d %s 119000", userId, service);
}
/**
* Resets the content capture service.
*/
private static void resetService() {
final int userId = getCurrentUserId();
Log.d(TAG, "Resetting back user " + userId + " to default service");
runShellCommand("cmd content_capture set temporary-service %d", userId);
}
private static int getCurrentUserId() {
return UserHandle.myUserId();
}
@BeforeClass
public static void setStayAwake() {
Log.v(TAG, "@BeforeClass: setStayAwake()");
// Some test cases will restart the activity, and stay awake is necessary to ensure that
// the test will not screen off during the test.
// Keeping the activity screen on is not enough, screen off may occur between the activity
// finished and the next start
final int stayOnWhilePluggedIn = Settings.Global.getInt(sContext.getContentResolver(),
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
sOriginalStayOnWhilePluggedIn = -1;
if (stayOnWhilePluggedIn != BatteryManager.BATTERY_PLUGGED_ANY) {
sOriginalStayOnWhilePluggedIn = stayOnWhilePluggedIn;
// Keep the device awake during testing.
setStayOnWhilePluggedIn(BatteryManager.BATTERY_PLUGGED_ANY);
}
}
@AfterClass
public static void resetStayAwake() {
Log.v(TAG, "@AfterClass: resetStayAwake()");
if (sOriginalStayOnWhilePluggedIn != -1) {
setStayOnWhilePluggedIn(sOriginalStayOnWhilePluggedIn);
}
}
private static void setStayOnWhilePluggedIn(int value) {
runShellCommand(String.format("settings put global %s %d",
Settings.Global.STAY_ON_WHILE_PLUGGED_IN, value));
}
@BeforeClass
public static void setAllowSelf() {
final ContentCaptureOptions options = new ContentCaptureOptions(null);
Log.v(TAG, "@BeforeClass: setAllowSelf(): options=" + options);
sContext.getApplicationContext().setContentCaptureOptions(options);
}
@AfterClass
public static void unsetAllowSelf() {
Log.v(TAG, "@AfterClass: unsetAllowSelf()");
clearOptions();
}
protected static void clearOptions() {
sContext.getApplicationContext().setContentCaptureOptions(null);
}
@BeforeClass
public static void disableDefaultService() {
Log.v(TAG, "@BeforeClass: disableDefaultService()");
setDefaultServiceEnabled(false);
}
@AfterClass
public static void enableDefaultService() {
Log.v(TAG, "@AfterClass: enableDefaultService()");
setDefaultServiceEnabled(true);
}
/**
* Enables / disables the default service.
*/
private static void setDefaultServiceEnabled(boolean enabled) {
final int userId = getCurrentUserId();
Log.d(TAG, "setDefaultServiceEnabled(user=" + userId + ", enabled= " + enabled + ")");
runShellCommand("cmd content_capture set default-service-enabled %d %s", userId,
Boolean.toString(enabled));
}
@Before
public void prepareDevice() throws Exception {
Log.v(TAG, "@Before: prepareDevice()");
// Unlock screen.
runShellCommand("input keyevent KEYCODE_WAKEUP");
// Dismiss keyguard, in case it's set as "Swipe to unlock".
runShellCommand("wm dismiss-keyguard");
// Collapse notifications.
runShellCommand("cmd statusbar collapse");
}
@Before
public void registerLifecycleCallback() {
Log.v(TAG, "@Before: Registering lifecycle callback");
final Application app = (Application) sContext.getApplicationContext();
mActivitiesWatcher = new ActivitiesWatcher(GENERIC_TIMEOUT_MS);
app.registerActivityLifecycleCallbacks(mActivitiesWatcher);
}
@After
public void unregisterLifecycleCallback() {
Log.d(TAG, "@After: Unregistering lifecycle callback: " + mActivitiesWatcher);
if (mActivitiesWatcher != null) {
final Application app = (Application) sContext.getApplicationContext();
app.unregisterActivityLifecycleCallbacks(mActivitiesWatcher);
}
}
/**
* Sets {@link MyContentCaptureService} as the service for the current user and waits until
* its created, then add the perf test package into allow list.
*/
public MyContentCaptureService enableService() throws InterruptedException {
if (mServiceWatcher != null) {
throw new IllegalStateException("There Can Be Only One!");
}
mServiceWatcher = MyContentCaptureService.setServiceWatcher();
setService(MyContentCaptureService.SERVICE_NAME);
mServiceWatcher.setAllowSelf();
return mServiceWatcher.waitOnCreate();
}
@NonNull
protected ActivityWatcher startWatcher() {
return mActivitiesWatcher.watch(CustomTestActivity.class);
}
/**
* Launch test activity with default login layout
*/
protected CustomTestActivity launchActivity() {
return launchActivity(R.layout.test_login_activity, 0);
}
/**
* Launch test activity with give layout and parameter
*/
protected CustomTestActivity launchActivity(int layoutId, int numViews) {
final Intent intent = new Intent(sContext, CustomTestActivity.class);
intent.putExtra(INTENT_EXTRA_LAYOUT_ID, layoutId);
intent.putExtra(INTENT_EXTRA_CUSTOM_VIEWS, numViews);
return mActivityRule.launchActivity(intent);
}
protected void finishActivity() {
try {
mActivityRule.finishActivity();
} catch (IllegalStateException e) {
// no op
}
}
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2016 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 android.view.contentcapture;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.android.perftests.contentcapture.R;
/**
* A simple activity used for testing, e.g. performance of activity switching, or as a base
* container of testing view.
*/
public class CustomTestActivity extends Activity {
public static final String INTENT_EXTRA_LAYOUT_ID = "layout_id";
public static final String INTENT_EXTRA_CUSTOM_VIEWS = "custom_view_number";
public static final int MAX_VIEWS = 500;
private static final int CUSTOM_CONTAINER_LAYOUT_ID = R.layout.test_container_activity;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().hasExtra(INTENT_EXTRA_LAYOUT_ID)) {
final int layoutId = getIntent().getIntExtra(INTENT_EXTRA_LAYOUT_ID,
/* defaultValue= */0);
setContentView(layoutId);
if (layoutId == CUSTOM_CONTAINER_LAYOUT_ID) {
createCustomViews(findViewById(R.id.root_view),
getIntent().getIntExtra(INTENT_EXTRA_CUSTOM_VIEWS, MAX_VIEWS));
}
}
}
private void createCustomViews(LinearLayout root, int number) {
LinearLayout horizontalLayout = null;
for (int i = 0; i < number; i++) {
final int j = i % 8;
if (horizontalLayout != null && j == 0) {
root.addView(horizontalLayout);
horizontalLayout = null;
}
if (horizontalLayout == null) {
horizontalLayout = createHorizontalLayout();
}
horizontalLayout.addView(createItem(null, i));
}
if (horizontalLayout != null) {
root.addView(horizontalLayout);
}
}
private LinearLayout createHorizontalLayout() {
final LinearLayout layout = new LinearLayout(getApplicationContext());
layout.setOrientation(LinearLayout.HORIZONTAL);
return layout;
}
private LinearLayout createItem(Drawable drawable, int index) {
final LinearLayout group = new LinearLayout(getApplicationContext());
group.setOrientation(LinearLayout.VERTICAL);
group.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, /* weight= */ 1.0f));
final TextView text = new TextView(this);
text.setText("i = " + index);
group.addView(text);
return group;
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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 android.view.contentcapture;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.CREATED;
import static com.android.compatibility.common.util.ActivitiesWatcher.ActivityLifecycle.DESTROYED;
import android.perftests.utils.BenchmarkState;
import android.view.View;
import com.android.compatibility.common.util.ActivitiesWatcher.ActivityWatcher;
import com.android.perftests.contentcapture.R;
import org.junit.Test;
public class LoginTest extends AbstractContentCapturePerfTestCase {
@Test
public void testLaunchActivity() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_login_activity, 0);
}
@Test
public void testLaunchActivity_contain100Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 100);
}
@Test
public void testLaunchActivity_contain300Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 300);
}
@Test
public void testLaunchActivity_contain500Views() throws Throwable {
enableService();
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
@Test
public void testLaunchActivity_noService() throws Throwable {
testActivityLaunchTime(R.layout.test_login_activity, 0);
}
@Test
public void testLaunchActivity_noService_contain100Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 100);
}
@Test
public void testLaunchActivity_noService_contain300Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 300);
}
@Test
public void testLaunchActivity_noService_contain500Views() throws Throwable {
testActivityLaunchTime(R.layout.test_container_activity, 500);
}
private void testActivityLaunchTime(int layoutId, int numViews) throws Throwable {
final ActivityWatcher watcher = startWatcher();
final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
launchActivity(layoutId, numViews);
// Ignore the time to finish the activity
state.pauseTiming();
watcher.waitFor(CREATED);
finishActivity();
watcher.waitFor(DESTROYED);
state.resumeTiming();
}
}
@Test
public void testOnVisibilityAggregated_visibleChanged() throws Throwable {
enableService();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_noService() throws Throwable {
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_noOptions() throws Throwable {
enableService();
clearOptions();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
testOnVisibilityAggregated(username);
}
@Test
public void testOnVisibilityAggregated_visibleChanged_notImportant() throws Throwable {
enableService();
final CustomTestActivity activity = launchActivity();
final View root = activity.getWindow().getDecorView();
final View username = root.findViewById(R.id.username);
username.setImportantForContentCapture(View.IMPORTANT_FOR_CONTENT_CAPTURE_NO);
testOnVisibilityAggregated(username);
}
private void testOnVisibilityAggregated(View view) throws Throwable {
BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
while (state.keepRunning()) {
// Only count the time of onVisibilityAggregated()
state.pauseTiming();
mActivityRule.runOnUiThread(() -> {
state.resumeTiming();
view.onVisibilityAggregated(false);
state.pauseTiming();
});
mActivityRule.runOnUiThread(() -> {
state.resumeTiming();
view.onVisibilityAggregated(true);
state.pauseTiming();
});
state.resumeTiming();
}
}
}

View File

@@ -0,0 +1,181 @@
/*
* 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 android.view.contentcapture;
import android.content.ComponentName;
import android.service.contentcapture.ActivityEvent;
import android.service.contentcapture.ContentCaptureService;
import android.util.ArraySet;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
public class MyContentCaptureService extends ContentCaptureService {
private static final String TAG = MyContentCaptureService.class.getSimpleName();
private static final String MY_PACKAGE = "com.android.perftests.contentcapture";
public static final String SERVICE_NAME = MY_PACKAGE + "/"
+ MyContentCaptureService.class.getName();
private static ServiceWatcher sServiceWatcher;
@NonNull
public static ServiceWatcher setServiceWatcher() {
if (sServiceWatcher != null) {
throw new IllegalStateException("There Can Be Only One!");
}
sServiceWatcher = new ServiceWatcher();
return sServiceWatcher;
}
public static void resetStaticState() {
sServiceWatcher = null;
}
public static void clearServiceWatcher() {
if (sServiceWatcher != null) {
if (sServiceWatcher.mReadyToClear) {
sServiceWatcher.mService = null;
sServiceWatcher = null;
} else {
sServiceWatcher.mReadyToClear = true;
}
}
}
@Override
public void onConnected() {
Log.i(TAG, "onConnected: sServiceWatcher=" + sServiceWatcher);
if (sServiceWatcher == null) {
Log.e(TAG, "onConnected() without a watcher");
return;
}
if (!sServiceWatcher.mReadyToClear && sServiceWatcher.mService != null) {
Log.e(TAG, "onConnected(): already created: " + sServiceWatcher);
return;
}
sServiceWatcher.mService = this;
sServiceWatcher.mCreated.countDown();
sServiceWatcher.mReadyToClear = false;
}
@Override
public void onDisconnected() {
Log.i(TAG, "onDisconnected: sServiceWatcher=" + sServiceWatcher);
if (sServiceWatcher == null) {
Log.e(TAG, "onDisconnected() without a watcher");
return;
}
if (sServiceWatcher.mService == null) {
Log.e(TAG, "onDisconnected(): no service on " + sServiceWatcher);
return;
}
sServiceWatcher.mDestroyed.countDown();
clearServiceWatcher();
}
@Override
public void onCreateContentCaptureSession(ContentCaptureContext context,
ContentCaptureSessionId sessionId) {
Log.i(TAG, "onCreateContentCaptureSession(ctx=" + context + ", session=" + sessionId);
}
@Override
public void onDestroyContentCaptureSession(ContentCaptureSessionId sessionId) {
Log.i(TAG, "onDestroyContentCaptureSession(session=" + sessionId + ")");
}
@Override
public void onContentCaptureEvent(ContentCaptureSessionId sessionId,
ContentCaptureEvent event) {
Log.i(TAG, "onContentCaptureEventsRequest(session=" + sessionId + "): " + event);
}
@Override
public void onActivityEvent(ActivityEvent event) {
Log.i(TAG, "onActivityEvent(): " + event);
}
public static final class ServiceWatcher {
private static final long GENERIC_TIMEOUT_MS = 10_000;
private final CountDownLatch mCreated = new CountDownLatch(1);
private final CountDownLatch mDestroyed = new CountDownLatch(1);
private boolean mReadyToClear = true;
private Pair<Set<String>, Set<ComponentName>> mAllowList;
private MyContentCaptureService mService;
@NonNull
public MyContentCaptureService waitOnCreate() throws InterruptedException {
await(mCreated, "not created");
if (mService == null) {
throw new IllegalStateException("not created");
}
if (mAllowList != null) {
Log.d(TAG, "Allow after created: " + mAllowList);
mService.setContentCaptureWhitelist(mAllowList.first, mAllowList.second);
}
return mService;
}
public void waitOnDestroy() throws InterruptedException {
await(mDestroyed, "not destroyed");
}
/**
* Allow just this package.
*/
public void setAllowSelf() {
final ArraySet<String> pkgs = new ArraySet<>(1);
pkgs.add(MY_PACKAGE);
mAllowList = new Pair<>(pkgs, null);
}
@Override
public String toString() {
return "mService: " + mService + " created: " + (mCreated.getCount() == 0)
+ " destroyed: " + (mDestroyed.getCount() == 0);
}
/**
* Awaits for a latch to be counted down.
*/
private static void await(@NonNull CountDownLatch latch, @NonNull String fmt,
@Nullable Object... args)
throws InterruptedException {
final boolean called = latch.await(GENERIC_TIMEOUT_MS, TimeUnit.MILLISECONDS);
if (!called) {
throw new IllegalStateException(String.format(fmt, args)
+ " in " + GENERIC_TIMEOUT_MS + "ms");
}
}
}
}