1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.view.inputmethod.cts.installtests; 18 19 import static com.android.bedstead.multiuser.MultiUserDeviceStateExtensionsKt.additionalUser; 20 21 import static org.junit.Assert.assertTrue; 22 23 import android.Manifest; 24 import android.content.ComponentName; 25 import android.content.Context; 26 import android.text.TextUtils; 27 import android.view.inputmethod.InputMethodInfo; 28 import android.view.inputmethod.InputMethodManager; 29 30 import androidx.annotation.NonNull; 31 import androidx.test.filters.LargeTest; 32 import androidx.test.platform.app.InstrumentationRegistry; 33 34 import com.android.bedstead.harrier.BedsteadJUnit4; 35 import com.android.bedstead.harrier.DeviceState; 36 import com.android.bedstead.multiuser.annotations.EnsureHasAdditionalUser; 37 import com.android.bedstead.multiuser.annotations.RequireMultiUserSupport; 38 import com.android.bedstead.nene.TestApis; 39 import com.android.bedstead.nene.users.UserReference; 40 import com.android.compatibility.common.util.PollingCheck; 41 import com.android.compatibility.common.util.SystemUtil; 42 43 import org.junit.After; 44 import org.junit.ClassRule; 45 import org.junit.Rule; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.io.File; 50 import java.util.List; 51 import java.util.Objects; 52 import java.util.concurrent.Callable; 53 import java.util.concurrent.TimeUnit; 54 import java.util.function.Predicate; 55 56 @LargeTest 57 @RequireMultiUserSupport 58 @RunWith(BedsteadJUnit4.class) 59 public final class UserUnlockTest { 60 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(15); 61 62 private static final ComponentName DIRECT_BOOT_AWARE_IME = ComponentName.createRelative( 63 "com.android.cts.directbootawareime", ".DirectBootAwareIme"); 64 private static final String DIRECT_BOOT_AWARE_IME_ID = 65 DIRECT_BOOT_AWARE_IME.flattenToShortString(); 66 67 private static final ComponentName DIRECT_BOOT_UNAWARE_IME = ComponentName.createRelative( 68 "com.android.cts.directbootawareime", ".DirectBootUnawareIme"); 69 private static final String DIRECT_BOOT_UNAWARE_IME_ID = 70 DIRECT_BOOT_UNAWARE_IME.flattenToShortString(); 71 72 private static final String TEST_APK_PACKAGE_NAME = DIRECT_BOOT_AWARE_IME.getPackageName(); 73 private static final File TEST_APK_PACKAGE_FILE = 74 new File("/data/local/tmp/cts/inputmethod/CtsDirectBootAwareIme.apk"); 75 76 @ClassRule 77 @Rule 78 public static final DeviceState sDeviceState = new DeviceState(); // Required by Bedstead. 79 80 @After tearDown()81 public void tearDown() { 82 TestApis.packages().find(TEST_APK_PACKAGE_NAME).uninstallFromAllUsers(); 83 } 84 getInputMethodListAsUser( @onNull InputMethodManager imm, int userId)85 private static List<InputMethodInfo> getInputMethodListAsUser( 86 @NonNull InputMethodManager imm, int userId) { 87 return SystemUtil.runWithShellPermissionIdentity( 88 () -> imm.getInputMethodListAsUser(userId), 89 Manifest.permission.INTERACT_ACROSS_USERS_FULL); 90 } 91 imeIdMatcher(@onNull String imeId)92 private static Predicate<InputMethodInfo> imeIdMatcher(@NonNull String imeId) { 93 return imi -> TextUtils.equals(imi.getId(), imeId); 94 } 95 assertImeInApiResult(@onNull InputMethodManager imm, @NonNull String imeId, int userId, long timeout)96 private static void assertImeInApiResult(@NonNull InputMethodManager imm, 97 @NonNull String imeId, int userId, long timeout) throws Exception { 98 pollingCheck("Ime " + imeId + " must exist.", timeout, () -> 99 getInputMethodListAsUser(imm, userId).stream().anyMatch(imeIdMatcher(imeId))); 100 } 101 assertImeNotInApiResult(@onNull InputMethodManager imm, @NonNull String imeId, int userId, long timeout)102 private static void assertImeNotInApiResult(@NonNull InputMethodManager imm, 103 @NonNull String imeId, int userId, long timeout) throws Exception { 104 pollingCheck("Ime " + imeId + " must not exist.", timeout, () -> 105 getInputMethodListAsUser(imm, userId).stream().noneMatch(imeIdMatcher(imeId))); 106 } 107 pollingCheck(@onNull String message, long timeout, @NonNull Callable<Boolean> condition)108 private static void pollingCheck(@NonNull String message, long timeout, 109 @NonNull Callable<Boolean> condition) throws Exception { 110 if (timeout <= 0) { 111 assertTrue(message, condition.call()); 112 } else { 113 PollingCheck.check(message, timeout, condition); 114 } 115 } 116 117 /** 118 * A regression test for b/356037588. 119 */ 120 @Test 121 @EnsureHasAdditionalUser testDirectBootUnawareImesInvisibleAfterStoppingUser()122 public void testDirectBootUnawareImesInvisibleAfterStoppingUser() throws Exception { 123 final Context context = InstrumentationRegistry.getInstrumentation().getContext(); 124 final InputMethodManager imm = Objects.requireNonNull( 125 context.getSystemService(InputMethodManager.class)); 126 127 final UserReference additionalUser = additionalUser(sDeviceState); 128 final int additionalUserId = additionalUser.id(); 129 assertTrue(additionalUser.isUnlocked()); 130 131 // Install the test IME 132 TestApis.packages().install(additionalUser, TEST_APK_PACKAGE_FILE); 133 134 // When the user is unlocked, both direct-boot aware/unaware IMEs should be visible. 135 assertImeInApiResult(imm, DIRECT_BOOT_AWARE_IME_ID, additionalUserId, TIMEOUT); 136 assertImeInApiResult(imm, DIRECT_BOOT_UNAWARE_IME_ID, additionalUserId, 0 /* timeout */); 137 138 try { 139 // Stopping the user makes the user storage be locked again. 140 additionalUser.stop(); 141 142 PollingCheck.check("Waiting for the user=" + additionalUserId + " to be locked", 143 TIMEOUT, () -> !additionalUser.isUnlocked()); 144 145 // When the user is unlocked, only direct-boot aware IME should be visible. 146 assertImeNotInApiResult(imm, DIRECT_BOOT_UNAWARE_IME_ID, additionalUserId, TIMEOUT); 147 assertImeInApiResult(imm, DIRECT_BOOT_AWARE_IME_ID, additionalUserId, 0 /* timeout */); 148 } finally { 149 // The test would get stuck if the additional user is not running upon existing. 150 if (!additionalUser.isRunning()) { 151 additionalUser.start(); 152 } 153 } 154 } 155 } 156