1 /*
2  * Copyright (C) 2019 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.inputmethodservice.cts.devicetest;
18 
19 import static org.junit.Assert.assertEquals;
20 
21 import android.inputmethodservice.cts.ime.CtsBaseInputMethod;
22 import android.os.Bundle;
23 import android.os.Process;
24 import android.os.SystemClock;
25 import android.os.UserHandle;
26 import android.text.TextUtils;
27 import android.view.inputmethod.EditorInfo;
28 import android.view.inputmethod.InputConnection;
29 import android.view.inputmethod.InputConnectionWrapper;
30 import android.widget.EditText;
31 import android.widget.LinearLayout;
32 
33 import androidx.test.runner.AndroidJUnit4;
34 
35 import org.junit.Test;
36 import org.junit.runner.RunWith;
37 
38 import java.util.concurrent.BlockingDeque;
39 import java.util.concurrent.LinkedBlockingDeque;
40 import java.util.concurrent.TimeUnit;
41 
42 /**
43  * Provides test scenarios for multi-user scenario.
44  */
45 @RunWith(AndroidJUnit4.class)
46 public class MultiUserDeviceTest {
47 
48     private static final long TIMEOUT_MILLISEC = TimeUnit.SECONDS.toMillis(10);
49 
50     /**
51      * A special {@link InputConnection} to receive {@link UserHandle} from
52      * {@link CtsBaseInputMethod} via {@link InputConnection#performPrivateCommand(String, Bundle)}.
53      */
54     private static final class ReplyReceivingInputConnection extends InputConnectionWrapper {
55         private final String mSessionKey;
56         private final BlockingDeque<UserHandle> mResultQueue;
57 
ReplyReceivingInputConnection(InputConnection target, String sessionKey, BlockingDeque<UserHandle> resultQueue)58         ReplyReceivingInputConnection(InputConnection target, String sessionKey,
59                 BlockingDeque<UserHandle> resultQueue) {
60             super(target, false);
61             mSessionKey = sessionKey;
62             mResultQueue = resultQueue;
63         }
64 
65         /**
66          * {@inheritDoc}
67          */
68         @Override
performPrivateCommand(String action, Bundle data)69         public boolean performPrivateCommand(String action, Bundle data) {
70             if (TextUtils.equals(CtsBaseInputMethod.ACTION_KEY_REPLY_USER_HANDLE, action)
71                     && TextUtils.equals(mSessionKey, data.getString(
72                             CtsBaseInputMethod.BUNDLE_KEY_REPLY_USER_HANDLE_SESSION_ID))) {
73                 final UserHandle userHandle =
74                         data.getParcelable(CtsBaseInputMethod.BUNDLE_KEY_REPLY_USER_HANDLE);
75                 if (userHandle != null) {
76                     mResultQueue.add(userHandle);
77                 }
78                 return true;
79             }
80             return super.performPrivateCommand(action, data);
81         }
82     }
83 
84     /** Test to check if the app is connecting to the IME that runs under the same user ID. */
85     @Test
testConnectingToTheSameUserIme()86     public void testConnectingToTheSameUserIme() throws Exception {
87         final String sessionId = Long.toString(SystemClock.elapsedRealtimeNanos());
88         final BlockingDeque<UserHandle> resultQueue = new LinkedBlockingDeque<>();
89 
90         TestActivity.startSync(activity -> {
91             final LinearLayout layout = new LinearLayout(activity);
92             layout.setOrientation(LinearLayout.VERTICAL);
93             final EditText editText = new EditText(activity) {
94                 @Override
95                 public InputConnection onCreateInputConnection(EditorInfo editorInfo) {
96                     final InputConnection original = super.onCreateInputConnection(editorInfo);
97                     if (editorInfo.extras == null) {
98                         editorInfo.extras = new Bundle();
99                     }
100                     editorInfo.extras.putString(
101                             CtsBaseInputMethod.EDITOR_INFO_KEY_REPLY_USER_HANDLE_SESSION_ID,
102                             sessionId);
103                     return new ReplyReceivingInputConnection(original, sessionId, resultQueue);
104                 }
105             };
106             editText.requestFocus();
107             layout.addView(editText);
108             return layout;
109         });
110 
111         final UserHandle result = resultQueue.poll(TIMEOUT_MILLISEC, TimeUnit.MILLISECONDS);
112         assertEquals(Process.myUserHandle(), result);
113     }
114 }
115