blob: 78c8570eccef0266963ef19bd613ac35481afc6f [file] [log] [blame]
/*
* 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 android.appenumeration.cts;
import static android.appenumeration.cts.Constants.ACTION_GET_INSTALLED_PACKAGES;
import static android.appenumeration.cts.Constants.ACTION_GET_PACKAGE_INFO;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_ACTIVITY;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_PROVIDER;
import static android.appenumeration.cts.Constants.ACTION_MANIFEST_SERVICE;
import static android.appenumeration.cts.Constants.ACTION_QUERY_ACTIVITIES;
import static android.appenumeration.cts.Constants.ACTION_QUERY_PROVIDERS;
import static android.appenumeration.cts.Constants.ACTION_QUERY_RESOLVER;
import static android.appenumeration.cts.Constants.ACTION_QUERY_SERVICES;
import static android.appenumeration.cts.Constants.ACTION_START_DIRECTLY;
import static android.appenumeration.cts.Constants.ACTION_START_FOR_RESULT;
import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_DUMMY_ACTIVITY;
import static android.appenumeration.cts.Constants.ACTIVITY_CLASS_TEST;
import static android.appenumeration.cts.Constants.EXTRA_DATA;
import static android.appenumeration.cts.Constants.EXTRA_ERROR;
import static android.appenumeration.cts.Constants.EXTRA_FLAGS;
import static android.appenumeration.cts.Constants.EXTRA_REMOTE_CALLBACK;
import static android.appenumeration.cts.Constants.QUERIES_ACTIVITY_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PERM;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_PROVIDER;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_Q;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SEES_INSTALLER_APK;
import static android.appenumeration.cts.Constants.QUERIES_NOTHING_SHARED_USER;
import static android.appenumeration.cts.Constants.QUERIES_PACKAGE;
import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_PROVIDER_AUTH;
import static android.appenumeration.cts.Constants.QUERIES_SERVICE_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_ACTIVITY_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_PROVIDER_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_PROVIDER_AUTH;
import static android.appenumeration.cts.Constants.QUERIES_UNEXPORTED_SERVICE_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_ACTION;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_BROWSABLE;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_BROWSER;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_CONTACTS;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_EDITOR;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_SHARE;
import static android.appenumeration.cts.Constants.QUERIES_WILDCARD_WEB;
import static android.appenumeration.cts.Constants.TARGET_BROWSER;
import static android.appenumeration.cts.Constants.TARGET_BROWSER_WILDCARD;
import static android.appenumeration.cts.Constants.TARGET_CONTACTS;
import static android.appenumeration.cts.Constants.TARGET_EDITOR;
import static android.appenumeration.cts.Constants.TARGET_FILTERS;
import static android.appenumeration.cts.Constants.TARGET_FILTERS_APK;
import static android.appenumeration.cts.Constants.TARGET_FORCEQUERYABLE;
import static android.appenumeration.cts.Constants.TARGET_NO_API;
import static android.appenumeration.cts.Constants.TARGET_SHARE;
import static android.appenumeration.cts.Constants.TARGET_SHARED_USER;
import static android.appenumeration.cts.Constants.TARGET_WEB;
import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Parcelable;
import android.os.RemoteCallback;
import androidx.annotation.Nullable;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.compatibility.common.util.SystemUtil;
import org.hamcrest.core.IsNull;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
@RunWith(AndroidJUnit4.class)
public class AppEnumerationTests {
private static Handler sResponseHandler;
private static HandlerThread sResponseThread;
private static boolean sGlobalFeatureEnabled;
@Rule
public TestName name = new TestName();
@BeforeClass
public static void setup() {
final String deviceConfigResponse =
SystemUtil.runShellCommand(
"device_config get package_manager_service "
+ "package_query_filtering_enabled")
.trim();
if ("null".equalsIgnoreCase(deviceConfigResponse) || deviceConfigResponse.isEmpty()) {
sGlobalFeatureEnabled = true;
} else {
sGlobalFeatureEnabled = Boolean.parseBoolean(deviceConfigResponse);
}
System.out.println("Feature enabled: " + sGlobalFeatureEnabled);
if (!sGlobalFeatureEnabled) return;
sResponseThread = new HandlerThread("response");
sResponseThread.start();
sResponseHandler = new Handler(sResponseThread.getLooper());
}
@AfterClass
public static void tearDown() {
if (!sGlobalFeatureEnabled) return;
sResponseThread.quit();
}
@Test
public void systemPackagesQueryable_notEnabled() throws Exception {
final Resources resources = Resources.getSystem();
assertFalse(
"config_forceSystemPackagesQueryable must not be true.",
resources.getBoolean(resources.getIdentifier(
"config_forceSystemPackagesQueryable", "bool", "android")));
// now let's assert that that the actual set of system apps is limited
assertThat("Not all system apps should be visible.",
getInstalledPackages(QUERIES_NOTHING_PERM, MATCH_SYSTEM_ONLY).length,
greaterThan(getInstalledPackages(QUERIES_NOTHING, MATCH_SYSTEM_ONLY).length));
}
@Test
public void all_canSeeForceQueryable() throws Exception {
assertVisible(QUERIES_NOTHING, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_SERVICE_ACTION, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_PACKAGE, TARGET_FORCEQUERYABLE);
}
@Test
public void startExplicitly_cannotStartNonVisible() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
try {
startExplicitIntentViaComponent(QUERIES_NOTHING, TARGET_FILTERS);
fail("Package cannot start a package it cannot see via component name");
} catch (ActivityNotFoundException e) {
// hooray!
}
try {
startExplicitIntentViaPackageName(QUERIES_NOTHING, TARGET_FILTERS);
fail("Package cannot start a package it cannot see via package name");
} catch (ActivityNotFoundException e) {
// hooray!
}
}
@Test
public void startExplicitly_canStartVisible() throws Exception {
assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
startExplicitIntentViaComponent(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
startExplicitIntentViaPackageName(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
}
@Test
public void startImplicitly_canStartNonVisible() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
startImplicitIntent(QUERIES_NOTHING);
}
@Test
public void queriesNothing_cannotSeeNonForceQueryable() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_NO_API);
assertNotVisible(QUERIES_NOTHING, TARGET_FILTERS);
}
@Test
public void queriesNothingTargetsQ_canSeeAll() throws Exception {
assertVisible(QUERIES_NOTHING_Q, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_NOTHING_Q, TARGET_NO_API);
assertVisible(QUERIES_NOTHING_Q, TARGET_FILTERS);
}
@Test
public void queriesNothingHasPermission_canSeeAll() throws Exception {
assertVisible(QUERIES_NOTHING_PERM, TARGET_FORCEQUERYABLE);
assertVisible(QUERIES_NOTHING_PERM, TARGET_NO_API);
assertVisible(QUERIES_NOTHING_PERM, TARGET_FILTERS);
}
@Test
public void queriesNothing_cannotSeeFilters() throws Exception {
assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
ACTION_MANIFEST_SERVICE, this::queryIntentServices);
assertNotQueryable(QUERIES_NOTHING, TARGET_FILTERS,
ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
}
@Test
public void queriesActivityAction_canSeeFilters() throws Exception {
assertQueryable(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS,
ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
assertQueryable(QUERIES_SERVICE_ACTION, TARGET_FILTERS,
ACTION_MANIFEST_SERVICE, this::queryIntentServices);
assertQueryable(QUERIES_PROVIDER_AUTH, TARGET_FILTERS,
ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
assertQueryable(QUERIES_PROVIDER_ACTION, TARGET_FILTERS,
ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
}
@Test
public void queriesNothingHasPermission_canSeeFilters() throws Exception {
assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
ACTION_MANIFEST_ACTIVITY, this::queryIntentActivities);
assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
ACTION_MANIFEST_SERVICE, this::queryIntentServices);
assertQueryable(QUERIES_NOTHING_PERM, TARGET_FILTERS,
ACTION_MANIFEST_PROVIDER, this::queryIntentProviders);
}
@Test
public void queriesSomething_cannotSeeNoApi() throws Exception {
assertNotVisible(QUERIES_ACTIVITY_ACTION, TARGET_NO_API);
assertNotVisible(QUERIES_SERVICE_ACTION, TARGET_NO_API);
assertNotVisible(QUERIES_PROVIDER_AUTH, TARGET_NO_API);
assertNotVisible(QUERIES_PROVIDER_ACTION, TARGET_NO_API);
}
@Test
public void queriesActivityAction_canSeeTarget() throws Exception {
assertVisible(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS);
}
@Test
public void queriesServiceAction_canSeeTarget() throws Exception {
assertVisible(QUERIES_SERVICE_ACTION, TARGET_FILTERS);
}
@Test
public void queriesWildcardAction_canSeeTargets() throws Exception {
assertVisible(QUERIES_WILDCARD_ACTION, TARGET_FILTERS);
}
@Test
public void queriesProviderAuthority_canSeeTarget() throws Exception {
assertVisible(QUERIES_PROVIDER_AUTH, TARGET_FILTERS);
}
@Test
public void queriesProviderAction_canSeeTarget() throws Exception {
assertVisible(QUERIES_PROVIDER_ACTION, TARGET_FILTERS);
}
@Test
public void queriesActivityAction_cannotSeeUnexportedTarget() throws Exception {
assertNotVisible(QUERIES_UNEXPORTED_ACTIVITY_ACTION, TARGET_FILTERS);
}
@Test
public void queriesServiceAction_cannotSeeUnexportedTarget() throws Exception {
assertNotVisible(QUERIES_UNEXPORTED_SERVICE_ACTION, TARGET_FILTERS);
}
@Test
public void queriesProviderAuthority_cannotSeeUnexportedTarget() throws Exception {
assertNotVisible(QUERIES_UNEXPORTED_PROVIDER_AUTH, TARGET_FILTERS);
}
@Test
public void queriesProviderAction_cannotSeeUnexportedTarget() throws Exception {
assertNotVisible(QUERIES_UNEXPORTED_PROVIDER_ACTION, TARGET_FILTERS);
}
@Test
public void queriesPackage_canSeeTarget() throws Exception {
assertVisible(QUERIES_PACKAGE, TARGET_NO_API);
}
@Test
public void queriesNothing_canSeeInstaller() throws Exception {
runShellCommand("pm uninstall " + QUERIES_NOTHING_SEES_INSTALLER);
runShellCommand("pm install"
+ " -i " + TARGET_NO_API
+ " --pkg " + QUERIES_NOTHING_SEES_INSTALLER
+ " " + QUERIES_NOTHING_SEES_INSTALLER_APK);
try {
assertVisible(QUERIES_NOTHING_SEES_INSTALLER, TARGET_NO_API);
} finally {
runShellCommand("pm uninstall " + QUERIES_NOTHING_SEES_INSTALLER);
}
}
@Test
public void whenStarted_canSeeCaller() throws Exception {
// let's first make sure that the target cannot see the caller.
assertNotVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
// now let's start the target and make sure that it can see the caller as part of that call
PackageInfo packageInfo = startForResult(QUERIES_NOTHING_PERM, QUERIES_NOTHING);
assertThat(packageInfo, IsNull.notNullValue());
assertThat(packageInfo.packageName, is(QUERIES_NOTHING_PERM));
// and finally let's re-run the last check to make sure that the target can still see the
// caller
assertVisible(QUERIES_NOTHING, QUERIES_NOTHING_PERM);
}
@Test
public void whenStartedViaIntentSender_canSeeCaller() throws Exception {
// let's first make sure that the target cannot see the caller.
assertNotVisible(QUERIES_NOTHING, QUERIES_NOTHING_Q);
// now let's start the target via pending intent and make sure that it can see the caller
// as part of that call
PackageInfo packageInfo = startSenderForResult(QUERIES_NOTHING_Q, QUERIES_NOTHING);
assertThat(packageInfo, IsNull.notNullValue());
assertThat(packageInfo.packageName, is(QUERIES_NOTHING_Q));
// and finally let's re-run the last check to make sure that the target can still see the
// caller
assertVisible(QUERIES_NOTHING, QUERIES_NOTHING_Q);
}
@Test
public void sharedUserMember_canSeeOtherMember() throws Exception {
assertVisible(QUERIES_NOTHING_SHARED_USER, TARGET_SHARED_USER);
}
@Test
public void queriesPackage_canSeeAllSharedUserMembers() throws Exception {
// explicitly queries target via manifest
assertVisible(QUERIES_PACKAGE, TARGET_SHARED_USER);
// implicitly granted visibility to other member of shared user
assertVisible(QUERIES_PACKAGE, QUERIES_NOTHING_SHARED_USER);
}
@Test
public void queriesWildcardContacts() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_CONTACTS);
assertVisible(QUERIES_WILDCARD_CONTACTS, TARGET_CONTACTS);
}
@Test
public void queriesWildcardWeb() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_WEB);
assertVisible(QUERIES_WILDCARD_BROWSABLE, TARGET_WEB);
assertVisible(QUERIES_WILDCARD_WEB, TARGET_WEB);
}
@Test
public void queriesWildcardBrowser() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_BROWSER);
assertNotVisible(QUERIES_WILDCARD_BROWSER, TARGET_WEB);
assertVisible(QUERIES_WILDCARD_BROWSER, TARGET_BROWSER);
assertVisible(QUERIES_WILDCARD_BROWSER, TARGET_BROWSER_WILDCARD);
}
@Test
public void queriesWildcardEditor() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_EDITOR);
assertVisible(QUERIES_WILDCARD_EDITOR, TARGET_EDITOR);
}
@Test
public void queriesWildcardShareSheet() throws Exception {
assertNotVisible(QUERIES_NOTHING, TARGET_SHARE);
assertVisible(QUERIES_WILDCARD_SHARE, TARGET_SHARE);
}
private void assertVisible(String sourcePackageName, String targetPackageName)
throws Exception {
if (!sGlobalFeatureEnabled) return;
Assert.assertNotNull(sourcePackageName + " should be able to see " + targetPackageName,
getPackageInfo(sourcePackageName, targetPackageName));
}
@Test
public void broadcastAdded_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS, null,
Constants.ACTION_AWAIT_PACKAGE_ADDED);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
fail();
} catch (MissingBroadcastException e) {
// hooray
}
}
@Test
public void broadcastAdded_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS, null,
Constants.ACTION_AWAIT_PACKAGE_ADDED);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
} catch (MissingBroadcastException e) {
fail();
}
}
@Test
public void broadcastRemoved_notVisibleDoesNotReceive() throws Exception {
final Result result = sendCommand(QUERIES_NOTHING, TARGET_FILTERS, null,
Constants.ACTION_AWAIT_PACKAGE_REMOVED);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
result.await();
fail();
} catch (MissingBroadcastException e) {
// hooray
}
}
@Test
public void broadcastRemoved_visibleReceives() throws Exception {
final Result result = sendCommand(QUERIES_ACTIVITY_ACTION, TARGET_FILTERS, null,
Constants.ACTION_AWAIT_PACKAGE_REMOVED);
runShellCommand("pm install " + TARGET_FILTERS_APK);
try {
Assert.assertEquals(TARGET_FILTERS,
Uri.parse(result.await().getString(EXTRA_DATA)).getSchemeSpecificPart());
} catch (MissingBroadcastException e) {
fail();
}
}
@Test
public void queriesResolver_grantsVisibilityToProvider() throws Exception {
assertNotVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
String[] result = sendCommandBlocking(
QUERIES_NOTHING_PERM, QUERIES_NOTHING_PROVIDER, null, ACTION_QUERY_RESOLVER)
.getStringArray(Intent.EXTRA_RETURN_RESULT);
Arrays.sort(result);
assertThat(QUERIES_NOTHING_PERM + " not visible to " + QUERIES_NOTHING_PROVIDER
+ " during resolver interaction",
Arrays.binarySearch(result, QUERIES_NOTHING_PERM),
greaterThanOrEqualTo(0));
assertVisible(QUERIES_NOTHING_PROVIDER, QUERIES_NOTHING_PERM);
}
private void assertNotVisible(String sourcePackageName, String targetPackageName)
throws Exception {
if (!sGlobalFeatureEnabled) return;
try {
getPackageInfo(sourcePackageName, targetPackageName);
fail(sourcePackageName + " should not be able to see " + targetPackageName);
} catch (PackageManager.NameNotFoundException ignored) {
}
}
interface ThrowingBiFunction<T, U, R> {
R apply(T arg1, U arg2) throws Exception;
}
private void assertNotQueryable(String sourcePackageName, String targetPackageName,
String intentAction, ThrowingBiFunction<String, Intent, String[]> commandMethod)
throws Exception {
if (!sGlobalFeatureEnabled) return;
Intent intent = new Intent(intentAction);
String[] queryablePackageNames = commandMethod.apply(sourcePackageName, intent);
for (String packageName : queryablePackageNames) {
if (packageName.contentEquals(targetPackageName)) {
fail(sourcePackageName + " should not be able to query " + targetPackageName +
" via " + intentAction);
}
}
}
private void assertQueryable(String sourcePackageName, String targetPackageName,
String intentAction, ThrowingBiFunction<String, Intent, String[]> commandMethod)
throws Exception {
if (!sGlobalFeatureEnabled) return;
Intent intent = new Intent(intentAction);
String[] queryablePackageNames = commandMethod.apply(sourcePackageName, intent);
for (String packageName : queryablePackageNames) {
if (packageName.contentEquals(targetPackageName)) {
return;
}
}
fail(sourcePackageName + " should be able to query " + targetPackageName + " via "
+ intentAction);
}
private PackageInfo getPackageInfo(String sourcePackageName, String targetPackageName)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
null /*queryIntent*/, ACTION_GET_PACKAGE_INFO);
return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
}
private PackageInfo startForResult(String sourcePackageName, String targetPackageName)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
null /*queryIntent*/, ACTION_START_FOR_RESULT);
return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
}
private PackageInfo startSenderForResult(String sourcePackageName, String targetPackageName)
throws Exception {
PendingIntent pendingIntent = PendingIntent.getActivity(
InstrumentationRegistry.getInstrumentation().getContext(), 100,
new Intent("android.appenumeration.cts.action.SEND_RESULT").setComponent(
new ComponentName(targetPackageName,
"android.appenumeration.cts.query.TestActivity")),
PendingIntent.FLAG_ONE_SHOT);
Bundle response = sendCommandBlocking(sourcePackageName, targetPackageName,
pendingIntent /*queryIntent*/, Constants.ACTION_START_SENDER_FOR_RESULT);
return response.getParcelable(Intent.EXTRA_RETURN_RESULT);
}
private String[] queryIntentActivities(String sourcePackageName, Intent queryIntent)
throws Exception {
Bundle response =
sendCommandBlocking(sourcePackageName, null, queryIntent, ACTION_QUERY_ACTIVITIES);
return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
}
private String[] queryIntentServices(String sourcePackageName, Intent queryIntent)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, null, queryIntent,
ACTION_QUERY_SERVICES);
return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
}
private String[] queryIntentProviders(String sourcePackageName, Intent queryIntent)
throws Exception {
Bundle response = sendCommandBlocking(sourcePackageName, null, queryIntent,
ACTION_QUERY_PROVIDERS);
return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
}
private String[] getInstalledPackages(String sourcePackageNames, int flags) throws Exception {
Bundle response = sendCommandBlocking(sourcePackageNames, null,
new Intent().putExtra(EXTRA_FLAGS, flags), ACTION_GET_INSTALLED_PACKAGES);
return response.getStringArray(Intent.EXTRA_RETURN_RESULT);
}
private void startExplicitIntentViaComponent(String sourcePackage, String targetPackage)
throws Exception {
sendCommandBlocking(sourcePackage, targetPackage,
new Intent().setComponent(new ComponentName(targetPackage,
ACTIVITY_CLASS_DUMMY_ACTIVITY)),
ACTION_START_DIRECTLY);
}
private void startExplicitIntentViaPackageName(String sourcePackage, String targetPackage)
throws Exception {
sendCommandBlocking(sourcePackage, targetPackage,
new Intent().setPackage(targetPackage),
ACTION_START_DIRECTLY);
}
private void startImplicitIntent(String sourcePackage) throws Exception {
sendCommandBlocking(sourcePackage, TARGET_FILTERS, new Intent(ACTION_MANIFEST_ACTIVITY),
ACTION_START_DIRECTLY);
}
interface Result {
Bundle await() throws Exception;
}
private Result sendCommand(String sourcePackageName,
@Nullable String targetPackageName,
@Nullable Parcelable intentExtra, String action) {
final Intent intent = new Intent(action)
.setComponent(new ComponentName(sourcePackageName, ACTIVITY_CLASS_TEST))
// data uri unique to each activity start to ensure actual launch and not just
// redisplay
.setData(Uri.parse("test://" + UUID.randomUUID().toString()))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
if (targetPackageName != null) {
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, targetPackageName);
}
if (intentExtra != null) {
if (intentExtra instanceof Intent) {
intent.putExtra(Intent.EXTRA_INTENT, intentExtra);
} else if (intentExtra instanceof PendingIntent) {
intent.putExtra("pendingIntent", intentExtra);
}
}
final ConditionVariable latch = new ConditionVariable();
final AtomicReference<Bundle> resultReference = new AtomicReference<>();
final RemoteCallback callback = new RemoteCallback(
bundle -> {
resultReference.set(bundle);
latch.open();
},
sResponseHandler);
intent.putExtra(EXTRA_REMOTE_CALLBACK, callback);
InstrumentationRegistry.getInstrumentation().getContext().startActivity(intent);
return () -> {
if (!latch.block(TimeUnit.SECONDS.toMillis(10))) {
throw new TimeoutException(
"Latch timed out while awiating a response from " + sourcePackageName);
}
final Bundle bundle = resultReference.get();
if (bundle != null && bundle.containsKey(EXTRA_ERROR)) {
throw (Exception) Objects.requireNonNull(bundle.getSerializable(EXTRA_ERROR));
}
return bundle;
};
}
private Bundle sendCommandBlocking(String sourcePackageName, @Nullable String targetPackageName,
@Nullable Parcelable intentExtra, String action)
throws Exception {
Result result = sendCommand(sourcePackageName, targetPackageName, intentExtra, action);
return result.await();
}
}