xref: /aosp_15_r20/cts/tests/inputmethod/mockime/src/com/android/cts/mockime/MultiUserUtils.java (revision b7c941bb3fa97aba169d73cee0bed2de8ac964bf)
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 com.android.cts.mockime;
18 
19 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
20 
21 import android.Manifest;
22 import android.app.ActivityManager;
23 import android.app.ApplicationExitInfo;
24 import android.app.UiAutomation;
25 import android.content.ContentProviderClient;
26 import android.content.Context;
27 import android.content.pm.PackageManager;
28 import android.os.Bundle;
29 import android.os.UserHandle;
30 import android.util.ArraySet;
31 import android.view.inputmethod.InputMethodInfo;
32 import android.view.inputmethod.InputMethodManager;
33 
34 import androidx.annotation.IntRange;
35 import androidx.annotation.NonNull;
36 import androidx.annotation.Nullable;
37 
38 import com.android.compatibility.common.util.SystemUtil;
39 import com.android.compatibility.common.util.ThrowingSupplier;
40 
41 import java.io.IOException;
42 import java.util.List;
43 import java.util.Objects;
44 
45 /**
46  * Utility methods for testing multi-user scenarios.
47  *
48  * <p>TODO(b/323251870): Consider creating a new utility class to host logic like this.</p>
49  */
50 final class MultiUserUtils {
51     /**
52      * Not intended to be instantiated.
53      */
MultiUserUtils()54     private MultiUserUtils() {
55     }
56 
57     @Nullable
runWithShellPermissionIdentity(@onNull UiAutomation uiAutomation, @NonNull ThrowingSupplier<T> supplier, String... permissions)58     private static <T> T runWithShellPermissionIdentity(@NonNull UiAutomation uiAutomation,
59             @NonNull ThrowingSupplier<T> supplier, String... permissions) {
60         Object[] placeholder = new Object[1];
61         SystemUtil.runWithShellPermissionIdentity(uiAutomation, () -> {
62             placeholder[0] = supplier.get();
63         }, permissions);
64         return (T) placeholder[0];
65     }
66 
67     @NonNull
runShellCommandOrThrow(@onNull UiAutomation uiAutomation, @NonNull String cmd)68     private static String runShellCommandOrThrow(@NonNull UiAutomation uiAutomation,
69             @NonNull String cmd) {
70         try {
71             return runShellCommand(uiAutomation, cmd);
72         } catch (IOException e) {
73             throw new RuntimeException(e);
74         }
75     }
76 
77     @Nullable
runWithShellPermissionIdentity(@onNull UiAutomation uiAutomation, @NonNull ThrowingSupplier<T> supplier)78     private static <T> T runWithShellPermissionIdentity(@NonNull UiAutomation uiAutomation,
79             @NonNull ThrowingSupplier<T> supplier) {
80         return runWithShellPermissionIdentity(uiAutomation, supplier,
81                 (String[]) null /* permissions */);
82     }
83 
84     @NonNull
callContentProvider(@onNull Context context, @NonNull UiAutomation uiAutomation, @NonNull String authority, @NonNull String method, @Nullable String arg, @Nullable Bundle extras, @NonNull UserHandle user)85     static Bundle callContentProvider(@NonNull Context context, @NonNull UiAutomation uiAutomation,
86             @NonNull String authority, @NonNull String method, @Nullable String arg,
87             @Nullable Bundle extras, @NonNull UserHandle user) {
88         return Objects.requireNonNull(runWithShellPermissionIdentity(uiAutomation, () -> {
89             final Context userAwareContext;
90             try {
91                 userAwareContext = context.createPackageContextAsUser("android", 0, user);
92             } catch (PackageManager.NameNotFoundException e) {
93                 throw new RuntimeException(e);
94             }
95             return userAwareContext.getContentResolver().call(authority, method, arg, extras);
96         }));
97     }
98 
99     @Nullable
getCurrentInputMethodInfoAsUser(@onNull Context context, @NonNull UiAutomation uiAutomation, @NonNull UserHandle user)100     static InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull Context context,
101             @NonNull UiAutomation uiAutomation, @NonNull UserHandle user) {
102         Objects.requireNonNull(user);
103         final InputMethodManager imm = Objects.requireNonNull(
104                 context.getSystemService(InputMethodManager.class));
105         return runWithShellPermissionIdentity(uiAutomation, () ->
106                 imm.getCurrentInputMethodInfoAsUser(user),
107                 Manifest.permission.INTERACT_ACROSS_USERS_FULL);
108     }
109 
110     @Nullable
getSecureSettings(@onNull UiAutomation uiAutomation, @NonNull String setting, @NonNull UserHandle user)111     static String getSecureSettings(@NonNull UiAutomation uiAutomation, @NonNull String setting,
112             @NonNull UserHandle user) {
113         Objects.requireNonNull(user);
114         final var command = "settings get --user " + user.getIdentifier() + " secure " + setting;
115         return runShellCommandOrThrow(uiAutomation, command).stripTrailing();
116     }
117 
118     @NonNull
getHistoricalProcessExitReasons(@onNull Context context, @NonNull UiAutomation uiAutomation, @Nullable String packageName, @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum, @NonNull UserHandle user)119     static List<ApplicationExitInfo> getHistoricalProcessExitReasons(@NonNull Context context,
120             @NonNull UiAutomation uiAutomation, @Nullable String packageName,
121             @IntRange(from = 0) int pid, @IntRange(from = 0) int maxNum, @NonNull UserHandle user) {
122         return Objects.requireNonNull(runWithShellPermissionIdentity(uiAutomation, () -> {
123             final Context userAwareContext;
124             try {
125                 userAwareContext = context.createPackageContextAsUser("android", 0, user);
126             } catch (PackageManager.NameNotFoundException e) {
127                 throw new RuntimeException(e);
128             }
129             return Objects.requireNonNull(userAwareContext.getSystemService(ActivityManager.class))
130                     .getHistoricalProcessExitReasons(packageName, pid, maxNum);
131         }, Manifest.permission.INTERACT_ACROSS_USERS_FULL, Manifest.permission.DUMP));
132     }
133 
134     @NonNull
135     static List<InputMethodInfo> getInputMethodListAsUser(@NonNull Context context,
136             @NonNull UiAutomation uiAutomation, @NonNull UserHandle user) {
137         final InputMethodManager imm = Objects.requireNonNull(
138                 context.getSystemService(InputMethodManager.class));
139         return Objects.requireNonNull(runWithShellPermissionIdentity(uiAutomation,
140                 () -> imm.getInputMethodListAsUser(user.getIdentifier()),
141                 Manifest.permission.INTERACT_ACROSS_USERS_FULL,
142                 Manifest.permission.QUERY_ALL_PACKAGES));
143     }
144 
145     @NonNull
146     static List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull Context context,
147             @NonNull UiAutomation uiAutomation, @NonNull UserHandle user) {
148         final InputMethodManager imm = Objects.requireNonNull(
149                 context.getSystemService(InputMethodManager.class));
150         final List<InputMethodInfo> result = runWithShellPermissionIdentity(uiAutomation, () -> {
151             try {
152                 return imm.getEnabledInputMethodListAsUser(user);
153             } catch (NoSuchMethodError unused) {
154                 return null;
155             }
156         }, Manifest.permission.INTERACT_ACROSS_USERS_FULL, Manifest.permission.QUERY_ALL_PACKAGES);
157         if (result != null) {
158             return result;
159         }
160 
161         // Use the shell command as a fallback.
162         final String command = "ime list -s --user " + user.getIdentifier();
163         final var enabledImes = new ArraySet<>(
164                 runShellCommandOrThrow(uiAutomation, command).split("\n"));
165         final List<InputMethodInfo> imes = getInputMethodListAsUser(context, uiAutomation, user);
166         imes.removeIf(imi -> !enabledImes.contains(imi.getId()));
167         return imes;
168     }
169 
170     @NonNull
171     static AutoCloseable acquireUnstableContentProviderClientSession(@NonNull Context context,
172             @NonNull UiAutomation uiAutomation, @NonNull String name, @NonNull UserHandle user) {
173         return Objects.requireNonNull(runWithShellPermissionIdentity(uiAutomation, () -> {
174             final Context userAwareContext;
175             try {
176                 userAwareContext = context.createPackageContextAsUser("android", 0, user);
177             } catch (PackageManager.NameNotFoundException e) {
178                 throw new RuntimeException(e);
179             }
180             final ContentProviderClient client = Objects.requireNonNull(
181                     userAwareContext.getContentResolver()
182                             .acquireUnstableContentProviderClient(name));
183             return () -> SystemUtil.runWithShellPermissionIdentity(uiAutomation, client::close);
184         }));
185     }
186 }
187