blob: 4cc198cf80e414efefd551044f5bbfbefc746f55 [file] [log] [blame]
/*
* 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 android.app.role.cts;
import static com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity;
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObject;
import static com.android.compatibility.common.util.UiAutomatorUtils.waitFindObjectOrNull;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.Instrumentation;
import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.PermissionInfo;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Telephony;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.BySelector;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.UiObjectNotFoundException;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SdkSuppress;
import androidx.test.rule.ActivityTestRule;
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.DisableAnimationRule;
import com.android.compatibility.common.util.FreezeRotationRule;
import com.android.compatibility.common.util.TestUtils;
import com.android.compatibility.common.util.ThrowingRunnable;
import com.android.compatibility.common.util.UiAutomatorUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* Tests {@link RoleManager}.
*/
@RunWith(AndroidJUnit4.class)
public class RoleManagerTest {
private static final long TIMEOUT_MILLIS = 15 * 1000;
private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
private static final String ROLE_NAME = RoleManager.ROLE_BROWSER;
private static final String ROLE_SHORT_LABEL = "Browser app";
private static final String APP_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp.apk";
private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
private static final String APP_LABEL = "CtsRoleTestApp";
private static final String APP_IS_ROLE_HELD_ACTIVITY_NAME = APP_PACKAGE_NAME
+ ".IsRoleHeldActivity";
private static final String APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD = APP_PACKAGE_NAME
+ ".extra.IS_ROLE_HELD";
private static final String APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME
+ ".RequestRoleActivity";
private static final String APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_PACKAGE_NAME
+ ".ChangeDefaultDialerActivity";
private static final String APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_PACKAGE_NAME
+ ".ChangeDefaultSmsActivity";
private static final String APP_28_APK_PATH = "/data/local/tmp/cts/role/CtsRoleTestApp28.apk";
private static final String APP_28_PACKAGE_NAME = "android.app.role.cts.app28";
private static final String APP_28_LABEL = "CtsRoleTestApp28";
private static final String APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ ".ChangeDefaultDialerActivity";
private static final String APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME = APP_28_PACKAGE_NAME
+ ".ChangeDefaultSmsActivity";
private static final String APP_33_WITHOUT_INCALLSERVICE_APK_PATH =
"/data/local/tmp/cts/role/CtsRoleTestApp33WithoutInCallService.apk";
private static final String APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME =
"android.app.role.cts.app33WithoutInCallService";
private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
"com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
private static final Context sContext = InstrumentationRegistry.getTargetContext();
private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
@Rule
public DisableAnimationRule mDisableAnimationRule = new DisableAnimationRule();
@Rule
public FreezeRotationRule mFreezeRotationRule = new FreezeRotationRule();
@Rule
public ActivityTestRule<WaitForResultActivity> mActivityRule =
new ActivityTestRule<>(WaitForResultActivity.class);
private String mRoleHolder;
@Before
public void saveRoleHolder() throws Exception {
List<String> roleHolders = getRoleHolders(ROLE_NAME);
mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null;
if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) {
removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
mRoleHolder = null;
}
}
@After
public void restoreRoleHolder() throws Exception {
removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
if (mRoleHolder != null) {
addRoleHolder(ROLE_NAME, mRoleHolder);
}
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Before
public void installApp() throws Exception {
installPackage(APP_APK_PATH);
installPackage(APP_28_APK_PATH);
installPackage(APP_33_WITHOUT_INCALLSERVICE_APK_PATH);
}
@After
public void uninstallApp() throws Exception {
uninstallPackage(APP_PACKAGE_NAME);
uninstallPackage(APP_28_PACKAGE_NAME);
uninstallPackage(APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME);
}
@Before
public void wakeUpScreen() throws IOException {
runShellCommand(sInstrumentation, "input keyevent KEYCODE_WAKEUP");
}
@Before
public void closeNotificationShade() {
sContext.sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
}
@Test
public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
assertThat(intent.getPackage()).isEqualTo(
sPackageManager.getPermissionControllerPackageName());
}
@Test
public void requestRoleIntentHasExtraRoleName() throws Exception {
Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(ROLE_NAME);
}
@Test
public void requestRoleAndDenyThenIsNotRoleHolder() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Test
public void requestRoleAndAllowThenIsRoleHolder() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(true);
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
}
@Test
public void requestRoleFirstTimeNoDontAskAgain() throws Exception {
requestRole(ROLE_NAME);
UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
assertThat(dontAskAgainCheck).isNull();
respondToRoleRequest(false);
}
@Test
public void requestRoleAndDenyThenHasDontAskAgain() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
requestRole(ROLE_NAME);
UiObject2 dontAskAgainCheck = findDontAskAgainCheck();
assertThat(dontAskAgainCheck).isNotNull();
respondToRoleRequest(false);
}
@Test
public void requestRoleAndDenyWithDontAskAgainReturnsCanceled() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
requestRole(ROLE_NAME);
findDontAskAgainCheck().click();
Pair<Integer, Intent> result = clickButtonAndWaitForResult(true);
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void requestRoleAndDenyWithDontAskAgainThenDeniedAutomatically() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
requestRole(ROLE_NAME);
findDontAskAgainCheck().click();
clickButtonAndWaitForResult(true);
requestRole(ROLE_NAME);
Pair<Integer, Intent> result = waitForResult();
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void requestRoleAndDenyWithDontAskAgainAndClearDataThenShowsUiWithoutDontAskAgain()
throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
requestRole(ROLE_NAME);
findDontAskAgainCheck().click();
clickButtonAndWaitForResult(true);
// Wait for the RequestRoleActivity inside the test app to be removed from our task so that
// when the test app is force stopped, our task isn't force finished and our
// WaitForResultActivity can survive.
Thread.sleep(5000);
clearPackageData(APP_PACKAGE_NAME);
// Wait for the don't ask again to be forgotten.
Thread.sleep(5000);
TestUtils.waitUntil("Find and respond to request role UI", () -> {
requestRole(ROLE_NAME);
UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
if (cancelButton == null) {
// Dialog not found, try again later.
return false;
}
UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
assertThat(dontAskAgainCheck).isNull();
respondToRoleRequest(false);
return true;
});
}
@Test
public void requestRoleAndDenyWithDontAskAgainAndReinstallThenShowsUiWithoutDontAskAgain()
throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
requestRole(ROLE_NAME);
findDontAskAgainCheck().click();
clickButtonAndWaitForResult(true);
// Wait for the RequestRoleActivity inside the test app to be removed from our task so that
// when the test app is uninstalled, our task isn't force finished and our
// WaitForResultActivity can survive.
Thread.sleep(5000);
uninstallPackage(APP_PACKAGE_NAME);
// Wait for the don't ask again to be forgotten.
Thread.sleep(5000);
installPackage(APP_APK_PATH);
TestUtils.waitUntil("Find and respond to request role UI", () -> {
requestRole(ROLE_NAME);
UiObject2 cancelButton = waitFindObjectOrNull(By.res("android:id/button2"));
if (cancelButton == null) {
// Dialog not found, try again later.
return false;
}
UiObject2 dontAskAgainCheck = findDontAskAgainCheck(false);
assertThat(dontAskAgainCheck).isNull();
respondToRoleRequest(false);
return true;
});
}
@Test
public void requestInvalidRoleThenDeniedAutomatically() throws Exception {
requestRole("invalid");
Pair<Integer, Intent> result = waitForResult();
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void requestUnqualifiedRoleThenDeniedAutomatically() throws Exception {
requestRole(RoleManager.ROLE_HOME);
Pair<Integer, Intent> result = waitForResult();
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void requestAssistantRoleThenDeniedAutomatically() throws Exception {
requestRole(RoleManager.ROLE_ASSISTANT);
Pair<Integer, Intent> result = waitForResult();
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void requestHoldingRoleThenAllowedAutomatically() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(true);
requestRole(ROLE_NAME);
Pair<Integer, Intent> result = waitForResult();
assertThat(result.first).isEqualTo(Activity.RESULT_OK);
}
private void requestRole(@NonNull String roleName) {
Intent intent = new Intent()
.setComponent(new ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_ROLE_NAME, roleName);
mActivityRule.getActivity().startActivityToWaitForResult(intent);
}
private void respondToRoleRequest(boolean allow)
throws InterruptedException, UiObjectNotFoundException {
if (allow) {
waitFindObject(By.text(APP_LABEL)).click();
}
Pair<Integer, Intent> result = clickButtonAndWaitForResult(allow);
int expectedResult = allow ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
assertThat(result.first).isEqualTo(expectedResult);
}
@Nullable
private UiObject2 findDontAskAgainCheck(boolean expected) throws UiObjectNotFoundException {
BySelector selector = By.res("com.android.permissioncontroller:id/dont_ask_again");
return expected
? waitFindObject(selector)
: waitFindObjectOrNull(selector, UNEXPECTED_TIMEOUT_MILLIS);
}
@Nullable
private UiObject2 findDontAskAgainCheck() throws UiObjectNotFoundException {
return findDontAskAgainCheck(true);
}
@NonNull
private Pair<Integer, Intent> clickButtonAndWaitForResult(boolean positive)
throws InterruptedException, UiObjectNotFoundException {
waitFindObject(By.res(positive ? "android:id/button1" : "android:id/button2")).click();
return waitForResult();
}
@NonNull
private Pair<Integer, Intent> waitForResult() throws InterruptedException {
return mActivityRule.getActivity().waitForActivityResult(TIMEOUT_MILLIS);
}
private void clearPackageData(@NonNull String packageName) {
runShellCommand("pm clear --user " + Process.myUserHandle().getIdentifier() + " "
+ packageName);
}
private void installPackage(@NonNull String apkPath) {
runShellCommand("pm install -r --user " + Process.myUserHandle().getIdentifier() + " "
+ apkPath);
}
private void uninstallPackage(@NonNull String packageName) {
runShellCommand("pm uninstall --user " + Process.myUserHandle().getIdentifier() + " "
+ packageName);
}
@Test
public void targetCurrentSdkAndChangeDefaultDialerThenDeniedAutomatically() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
WaitForResultActivity activity = mActivityRule.getActivity();
activity.startActivityToWaitForResult(new Intent()
.setComponent(new ComponentName(APP_PACKAGE_NAME,
APP_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void targetCurrentSdkAndChangeDefaultSmsThenDeniedAutomatically() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
WaitForResultActivity activity = mActivityRule.getActivity();
activity.startActivityToWaitForResult(new Intent()
.setComponent(new ComponentName(APP_PACKAGE_NAME,
APP_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void targetSdk28AndChangeDefaultDialerAndAllowThenIsDefaultDialer() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
sContext.startActivity(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
waitFindObject(By.text(APP_28_LABEL)).click();
waitFindObject(By.res("android:id/button1")).click();
// TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
//TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
//TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
// telecomManager.getDefaultDialerPackage(), APP_28_PACKAGE_NAME));
TestUtils.waitUntil("App is not set as default dialer app", () ->
getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_28_PACKAGE_NAME));
}
@Test
public void targetSdk28AndChangeDefaultSmsAndAllowThenIsDefaultSms() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
sContext.startActivity(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_28_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
waitFindObject(By.text(APP_28_LABEL)).click();
waitFindObject(By.res("android:id/button1")).click();
TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
Telephony.Sms.getDefaultSmsPackage(sContext), APP_28_PACKAGE_NAME));
}
@Test
public void targetSdk28AndChangeDefaultDialerForAnotherAppThenDeniedAutomatically()
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
WaitForResultActivity activity = mActivityRule.getActivity();
activity.startActivityToWaitForResult(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void targetSdk28AndChangeDefaultSmsForAnotherAppThenDeniedAutomatically()
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
WaitForResultActivity activity = mActivityRule.getActivity();
activity.startActivityToWaitForResult(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME));
Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
}
@Test
public void
targetSdk28AndChangeDefaultDialerForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultDialer()
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
addRoleHolder(RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME);
sContext.startActivity(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_DIALER_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
waitFindObject(By.text(APP_LABEL)).click();
waitFindObject(By.res("android:id/button1")).click();
// TODO(b/149037075): Use TelecomManager.getDefaultDialerPackage() once the bug is fixed.
//TelecomManager telecomManager = sContext.getSystemService(TelecomManager.class);
//TestUtils.waitUntil("App is not set as default dialer app", () -> Objects.equals(
// telecomManager.getDefaultDialerPackage(), APP_PACKAGE_NAME));
TestUtils.waitUntil("App is not set as default dialer app", () ->
getRoleHolders(RoleManager.ROLE_DIALER).contains(APP_PACKAGE_NAME));
}
@Test
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU, codeName = "Tiramisu")
public void testHoldDialerRoleRequirementWithInCallServiceAndSdk()
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER));
// target below sdk 33 without InCallService component can hold dialer role
addRoleHolder(
RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
assertIsRoleHolder(
RoleManager.ROLE_DIALER, APP_28_PACKAGE_NAME, true);
// target sdk 33 without InCallService component cannot hold dialer role
addRoleHolder(
RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
assertIsRoleHolder(
RoleManager.ROLE_DIALER, APP_33_WITHOUT_INCALLSERVICE_PACKAGE_NAME, false);
// target sdk 33 with InCallService component can hold dialer role
addRoleHolder(
RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
assertIsRoleHolder(
RoleManager.ROLE_DIALER, APP_PACKAGE_NAME, true);
}
@Test
public void
targetSdk28AndChangeDefaultSmsForAnotherAppAsHolderAndAllowThenTheOtherAppIsDefaultSms()
throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
addRoleHolder(RoleManager.ROLE_SMS, APP_28_PACKAGE_NAME);
sContext.startActivity(new Intent()
.setComponent(new ComponentName(APP_28_PACKAGE_NAME,
APP_28_CHANGE_DEFAULT_SMS_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_PACKAGE_NAME, APP_PACKAGE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
waitFindObject(By.text(APP_LABEL)).click();
waitFindObject(By.res("android:id/button1")).click();
TestUtils.waitUntil("App is not set as default sms app", () -> Objects.equals(
Telephony.Sms.getDefaultSmsPackage(sContext), APP_PACKAGE_NAME));
}
@Test
public void openDefaultAppDetailsThenIsNotDefaultApp() throws Exception {
runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
Intent.ACTION_MANAGE_DEFAULT_APP)
.addCategory(Intent.CATEGORY_DEFAULT)
.putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK)));
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL)));
pressBack();
}
@Test
public void openDefaultAppDetailsAndSetDefaultAppThenIsDefaultApp() throws Exception {
runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
Intent.ACTION_MANAGE_DEFAULT_APP)
.addCategory(Intent.CATEGORY_DEFAULT)
.putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK)));
waitForIdle();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL))).click();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
.hasDescendant(By.text(APP_LABEL)));
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
pressBack();
}
@Test
public void openDefaultAppDetailsAndSetDefaultAppAndSetAnotherThenIsNotDefaultApp()
throws Exception {
runWithShellPermissionIdentity(() -> sContext.startActivity(new Intent(
Intent.ACTION_MANAGE_DEFAULT_APP)
.addCategory(Intent.CATEGORY_DEFAULT)
.putExtra(Intent.EXTRA_ROLE_NAME, ROLE_NAME)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK)));
waitForIdle();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL))).click();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
.hasDescendant(By.text(APP_LABEL)));
waitForIdle();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))).click();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL)));
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
pressBack();
}
@Test
public void openDefaultAppListThenHasDefaultApp() throws Exception {
sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
waitFindObject(By.text(ROLE_SHORT_LABEL));
pressBack();
}
@Test
public void openDefaultAppListThenIsNotDefaultAppInList() throws Exception {
sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
assertThat(waitFindObjectOrNull(By.text(APP_LABEL), UNEXPECTED_TIMEOUT_MILLIS))
.isNull();
pressBack();
}
@Test
public void openDefaultAppListAndSetDefaultAppThenIsDefaultApp() throws Exception {
sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
waitForIdle();
waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
waitForIdle();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL))).click();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
.hasDescendant(By.text(APP_LABEL)));
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
pressBack();
pressBack();
}
@Test
public void openDefaultAppListAndSetDefaultAppThenIsDefaultAppInList() throws Exception {
sContext.startActivity(new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
.addCategory(Intent.CATEGORY_DEFAULT)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK));
waitForIdle();
waitFindObject(By.text(ROLE_SHORT_LABEL)).click();
waitForIdle();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(false))
.hasDescendant(By.text(APP_LABEL))).click();
waitFindObject(By.clickable(true).hasDescendant(By.checkable(true).checked(true))
.hasDescendant(By.text(APP_LABEL)));
pressBack();
waitFindObject(By.text(APP_LABEL));
pressBack();
}
private static void waitForIdle() {
UiAutomatorUtils.getUiDevice().waitForIdle();
}
private static void pressBack() {
UiAutomatorUtils.getUiDevice().pressBack();
waitForIdle();
}
@Test
public void roleIsAvailable() {
assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
}
@Test
public void dontAddRoleHolderThenRoleIsNotHeld() throws Exception {
assertRoleIsHeld(ROLE_NAME, false);
}
@Test
public void addRoleHolderThenRoleIsHeld() throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertRoleIsHeld(ROLE_NAME, true);
}
@Test
public void addAndRemoveRoleHolderThenRoleIsNotHeld() throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertRoleIsHeld(ROLE_NAME, false);
}
private void assertRoleIsHeld(@NonNull String roleName, boolean isHeld)
throws InterruptedException {
Intent intent = new Intent()
.setComponent(new ComponentName(APP_PACKAGE_NAME, APP_IS_ROLE_HELD_ACTIVITY_NAME))
.putExtra(Intent.EXTRA_ROLE_NAME, roleName);
WaitForResultActivity activity = mActivityRule.getActivity();
activity.startActivityToWaitForResult(intent);
Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
assertThat(result.first).isEqualTo(Activity.RESULT_OK);
assertThat(result.second).isNotNull();
assertThat(result.second.hasExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD)).isTrue();
assertThat(result.second.getBooleanExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD, false))
.isEqualTo(isHeld);
}
@Test
public void dontAddRoleHolderThenIsNotRoleHolder() throws Exception {
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Test
public void addRoleHolderThenIsRoleHolder() throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
}
@Test
public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Test
public void addAndClearRoleHoldersThenIsNotRoleHolder() throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
clearRoleHolders(ROLE_NAME);
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Test
public void addInvalidRoleHolderThenFails() throws Exception {
addRoleHolder("invalid", APP_PACKAGE_NAME, false);
}
@Test
public void addUnqualifiedRoleHolderThenFails() throws Exception {
addRoleHolder(RoleManager.ROLE_HOME, APP_PACKAGE_NAME, false);
}
@Test
public void removeInvalidRoleHolderThenFails() throws Exception {
removeRoleHolder("invalid", APP_PACKAGE_NAME, false);
}
@Test
public void clearInvalidRoleHoldersThenFails() throws Exception {
clearRoleHolders("invalid", false);
}
@Test
public void addOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotified() throws Exception {
assertOnRoleHoldersChangedListenerIsNotified(() -> addRoleHolder(ROLE_NAME,
APP_PACKAGE_NAME));
}
@Test
public void addOnRoleHoldersChangedListenerAndRemoveRoleHolderThenIsNotified()
throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertOnRoleHoldersChangedListenerIsNotified(() -> removeRoleHolder(ROLE_NAME,
APP_PACKAGE_NAME));
}
@Test
public void addOnRoleHoldersChangedListenerAndClearRoleHoldersThenIsNotified()
throws Exception {
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
assertOnRoleHoldersChangedListenerIsNotified(() -> clearRoleHolders(ROLE_NAME));
}
private void assertOnRoleHoldersChangedListenerIsNotified(@NonNull ThrowingRunnable runnable)
throws Exception {
ListenerFuture future = new ListenerFuture();
UserHandle user = Process.myUserHandle();
runWithShellPermissionIdentity(() -> sRoleManager.addOnRoleHoldersChangedListenerAsUser(
sContext.getMainExecutor(), future, user));
Pair<String, UserHandle> result;
try {
runnable.run();
result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} finally {
runWithShellPermissionIdentity(() ->
sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user));
}
assertThat(result.first).isEqualTo(ROLE_NAME);
assertThat(result.second).isEqualTo(user);
}
@Test
public void addAndRemoveOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotNotified()
throws Exception {
ListenerFuture future = new ListenerFuture();
UserHandle user = Process.myUserHandle();
runWithShellPermissionIdentity(() -> {
sRoleManager.addOnRoleHoldersChangedListenerAsUser(sContext.getMainExecutor(), future,
user);
sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user);
});
addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
try {
future.get(UNEXPECTED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
} catch (TimeoutException e) {
// Expected
return;
}
throw new AssertionError("OnRoleHoldersChangedListener was notified after removal");
}
@Test
public void setRoleNamesFromControllerShouldRequireManageRolesFromControllerPermission() {
assertRequiresManageRolesFromControllerPermission(
() -> sRoleManager.setRoleNamesFromController(Collections.emptyList()),
"setRoleNamesFromController");
}
@Test
public void addRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
assertRequiresManageRolesFromControllerPermission(
() -> sRoleManager.addRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
"addRoleHolderFromController");
}
@Test
public void removeRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
assertRequiresManageRolesFromControllerPermission(
() -> sRoleManager.removeRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
"removeRoleHolderFromController");
}
@Test
public void getHeldRolesFromControllerShouldRequireManageRolesFromControllerPermission() {
assertRequiresManageRolesFromControllerPermission(
() -> sRoleManager.getHeldRolesFromController(APP_PACKAGE_NAME),
"getHeldRolesFromController");
}
private void assertRequiresManageRolesFromControllerPermission(@NonNull Runnable runnable,
@NonNull String methodName) {
try {
runnable.run();
} catch (SecurityException e) {
if (e.getMessage().contains(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)) {
// Expected
return;
}
throw e;
}
fail("RoleManager." + methodName + "() should require "
+ PERMISSION_MANAGE_ROLES_FROM_CONTROLLER);
}
@Test
public void manageRolesFromControllerPermissionShouldBeDeclaredByPermissionController()
throws PackageManager.NameNotFoundException {
PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
assertThat(permissionInfo.packageName).isEqualTo(
sPackageManager.getPermissionControllerPackageName());
assertThat(permissionInfo.getProtection()).isEqualTo(PermissionInfo.PROTECTION_SIGNATURE);
assertThat(permissionInfo.getProtectionFlags()).isEqualTo(0);
}
@Test
public void smsRoleHasHolder() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
}
@Test
public void addSmsRoleHolderThenPermissionIsGranted() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
}
@Test
public void removeSmsRoleHolderThenPermissionIsRevoked() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_DENIED);
}
@Test
public void removeSmsRoleHolderThenDialerRolePermissionIsRetained() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER)
&& sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
String smsRoleHolder = getRoleHolders(RoleManager.ROLE_SMS).get(0);
addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
addRoleHolder(RoleManager.ROLE_SMS, smsRoleHolder);
assertThat(sPackageManager.checkPermission(android.Manifest.permission.SEND_SMS,
APP_PACKAGE_NAME)).isEqualTo(PackageManager.PERMISSION_GRANTED);
}
@Test
public void packageManagerGetDefaultBrowserBackedByRole() throws Exception {
addRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME);
assertThat(sPackageManager.getDefaultBrowserPackageNameAsUser(UserHandle.myUserId()))
.isEqualTo(APP_PACKAGE_NAME);
}
@Test
public void packageManagerSetDefaultBrowserBackedByRole() throws Exception {
callWithShellPermissionIdentity(() -> sPackageManager.setDefaultBrowserPackageNameAsUser(
APP_PACKAGE_NAME, UserHandle.myUserId()));
assertIsRoleHolder(RoleManager.ROLE_BROWSER, APP_PACKAGE_NAME, true);
}
@Test
public void telephonySmsGetDefaultSmsPackageBackedByRole() throws Exception {
assumeTrue(sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS));
addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
assertThat(Telephony.Sms.getDefaultSmsPackage(sContext)).isEqualTo(APP_PACKAGE_NAME);
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
@Test
public void cannotBypassRoleQualificationWithoutPermission() throws Exception {
assertThrows(SecurityException.class, () ->
sRoleManager.setBypassingRoleQualification(true));
}
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S, codeName = "S")
@Test
public void bypassRoleQualificationThenCanAddUnqualifiedRoleHolder() throws Exception {
assertThat(sRoleManager.isRoleAvailable(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
.isTrue();
runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(true));
try {
assertThat(callWithShellPermissionIdentity(() ->
sRoleManager.isBypassingRoleQualification())).isTrue();
// The System Activity Recognizer role requires a system app, so this won't succeed
// without bypassing role qualification.
addRoleHolder(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER, APP_PACKAGE_NAME);
assertThat(getRoleHolders(RoleManager.ROLE_SYSTEM_ACTIVITY_RECOGNIZER))
.contains(APP_PACKAGE_NAME);
} finally {
runWithShellPermissionIdentity(() -> sRoleManager.setBypassingRoleQualification(false));
}
assertThat(callWithShellPermissionIdentity(() ->
sRoleManager.isBypassingRoleQualification())).isFalse();
}
@NonNull
private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
}
private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName,
boolean shouldBeRoleHolder) throws Exception {
List<String> packageNames = getRoleHolders(roleName);
if (shouldBeRoleHolder) {
assertThat(packageNames).contains(packageName);
} else {
assertThat(packageNames).doesNotContain(packageName);
}
}
private void addRoleHolder(@NonNull String roleName, @NonNull String packageName,
boolean expectSuccess) throws Exception {
CallbackFuture future = new CallbackFuture();
runWithShellPermissionIdentity(() -> sRoleManager.addRoleHolderAsUser(roleName,
packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
}
private void addRoleHolder(@NonNull String roleName, @NonNull String packageName)
throws Exception {
addRoleHolder(roleName, packageName, true);
}
private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName,
boolean expectSuccess) throws Exception {
CallbackFuture future = new CallbackFuture();
runWithShellPermissionIdentity(() -> sRoleManager.removeRoleHolderAsUser(roleName,
packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
}
private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName)
throws Exception {
removeRoleHolder(roleName, packageName, true);
}
private void clearRoleHolders(@NonNull String roleName, boolean expectSuccess)
throws Exception {
CallbackFuture future = new CallbackFuture();
runWithShellPermissionIdentity(() -> sRoleManager.clearRoleHoldersAsUser(roleName, 0,
Process.myUserHandle(), sContext.getMainExecutor(), future));
assertThat(future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)).isEqualTo(expectSuccess);
}
private void clearRoleHolders(@NonNull String roleName) throws Exception {
clearRoleHolders(roleName, true);
}
private static class ListenerFuture extends CompletableFuture<Pair<String, UserHandle>>
implements OnRoleHoldersChangedListener {
@Override
public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
complete(new Pair<>(roleName, user));
}
}
private static class CallbackFuture extends CompletableFuture<Boolean>
implements Consumer<Boolean> {
@Override
public void accept(Boolean successful) {
complete(successful);
}
}
}