• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.compatibility.common.util.concurrentuser;
18 
19 import static android.Manifest.permission.CREATE_USERS;
20 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
21 import static android.Manifest.permission.INTERNAL_SYSTEM_WINDOW;
22 
23 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
24 
25 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
26 
27 import static com.google.common.truth.Truth.assertThat;
28 
29 import static org.junit.Assert.assertNotEquals;
30 import static org.junit.Assert.fail;
31 
32 import static java.util.concurrent.TimeUnit.MILLISECONDS;
33 
34 import android.content.ComponentName;
35 import android.content.Context;
36 import android.content.Intent;
37 import android.content.pm.UserInfo;
38 import android.os.Bundle;
39 import android.os.RemoteCallback;
40 import android.os.UserHandle;
41 import android.os.UserManager;
42 
43 import androidx.annotation.Nullable;
44 
45 import java.util.List;
46 import java.util.concurrent.CountDownLatch;
47 
48 /** Provides utility methods to interact with {@link ConcurrentUserActivityBase}. */
49 public final class ConcurrentUserActivityUtils {
ConcurrentUserActivityUtils()50     private ConcurrentUserActivityUtils() {
51     }
52 
53     static final String BROADCAST_ACTION_TRIGGER = "broadcast_action_trigger";
54     static final String KEY_USER_ID = "key_user_id";
55     static final String KEY_CALLBACK = "key_callback";
56     static final String KEY_BUNDLE = "key_bundle";
57 
58     private static final long LAUNCH_ACTIVITY_TIMEOUT_MS = 3000;
59     private static final long SEND_BROADCAST_TIMEOUT_MS = 3000;
60 
61     /**
62      * Gets the user that responds to the test. See {@link ConcurrentUserActivityBase}.
63      *
64      * @throws AssertionError if such user doesn't exist
65      */
getResponderUserId()66     public static int getResponderUserId() {
67         UserManager userManager =
68                 getInstrumentation().getTargetContext().getSystemService(UserManager.class);
69         int initiatorUserId = getInstrumentation().getTargetContext().getUserId();
70         int[] responderUserId = new int[]{UserHandle.USER_NULL};
71         runWithShellPermissionIdentity(
72                 () -> {
73                     List<UserInfo> users = userManager.getAliveUsers();
74                     for (UserInfo info : users) {
75                         if (info.id != initiatorUserId && info.isFull()) {
76                             responderUserId[0] = info.id;
77                             break;
78                         }
79                     }
80                 }, CREATE_USERS);
81 
82         assertNotEquals("Failed to find the responder user on the device",
83                 UserHandle.USER_NULL, responderUserId[0]);
84         return responderUserId[0];
85     }
86 
87     /**
88      * Launches the given activity as the given {@code userId} and waits for it to be launched.
89      *
90      * @param activityName the ComponentName of the Activity, which must be a subclass of
91      *                     {@link ConcurrentUserActivityBase}
92      * @param userId       the user ID
93      */
launchActivityAsUserSync(ComponentName activityName, int userId)94     public static void launchActivityAsUserSync(ComponentName activityName, int userId) {
95         CountDownLatch latch = new CountDownLatch(1);
96         RemoteCallback callback = new RemoteCallback(bundle -> {
97             int actualUserId = bundle.getInt(KEY_USER_ID);
98             assertThat(actualUserId).isEqualTo(userId);
99             latch.countDown();
100         });
101 
102         Context context = getInstrumentation().getContext();
103         UserHandle userHandle = UserHandle.of(userId);
104         Intent intent = new Intent(Intent.ACTION_MAIN)
105                 .setClassName(activityName.getPackageName(), activityName.getClassName())
106                 .putExtra(KEY_CALLBACK, callback)
107                 .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK
108                         // Remove activity animation to avoid flakiness.
109                         | Intent.FLAG_ACTIVITY_NO_ANIMATION);
110         runWithShellPermissionIdentity(
111                 () -> context.startActivityAsUser(intent, userHandle),
112                 // INTERNAL_SYSTEM_WINDOW is needed to launch the activity on secondary display.
113                 INTERACT_ACROSS_USERS_FULL, INTERNAL_SYSTEM_WINDOW);
114         try {
115             if (!latch.await(LAUNCH_ACTIVITY_TIMEOUT_MS, MILLISECONDS)) {
116                 fail(String.format("Failed to launch activity %s as user %d", activityName,
117                         userId));
118             }
119         } catch (InterruptedException e) {
120             Thread.currentThread().interrupt();
121             throw new RuntimeException(e);
122         }
123     }
124 
125     /**
126      * Sends a message to the testing app running as the given user, and waits for its reply.
127      *
128      * @param packageName    the package name of the receiver app, which has a
129      *                       {@link ConcurrentUserActivityBase}
130      * @param receiverUserId the user ID of the receiver app
131      * @param bundleToSend   the Bundle to be sent to the receiver app
132      * @return the Bundle sent from the receiver app
133      */
134     @Nullable
sendBundleAndWaitForReply(String packageName, int receiverUserId, Bundle bundleToSend)135     public static Bundle sendBundleAndWaitForReply(String packageName, int receiverUserId,
136             Bundle bundleToSend) {
137         Bundle[] receivedBundle = new Bundle[1];
138         CountDownLatch latch = new CountDownLatch(1);
139         RemoteCallback callback = new RemoteCallback(bundle -> {
140             receivedBundle[0] = bundle;
141             latch.countDown();
142         });
143 
144         Intent intent = new Intent(BROADCAST_ACTION_TRIGGER)
145                 .setPackage(packageName)
146                 .putExtra(KEY_BUNDLE, bundleToSend)
147                 .putExtra(KEY_CALLBACK, callback);
148         runWithShellPermissionIdentity(
149                 () -> getInstrumentation().getContext().sendBroadcastAsUser(
150                         intent, UserHandle.of(receiverUserId)),
151                 INTERACT_ACROSS_USERS_FULL);
152         try {
153             if (!latch.await(SEND_BROADCAST_TIMEOUT_MS, MILLISECONDS)) {
154                 fail(String.format("Failed to send %s to %s (user %d)", bundleToSend,
155                         packageName, receiverUserId));
156             }
157         } catch (InterruptedException e) {
158             Thread.currentThread().interrupt();
159             throw new RuntimeException(e);
160         }
161         return receivedBundle[0];
162     }
163 }
164