1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.statusbar.policy;
16 
17 import static junit.framework.Assert.assertEquals;
18 import static junit.framework.Assert.assertNotNull;
19 
20 import android.app.ActivityManager;
21 import android.app.PendingIntent;
22 import android.app.RemoteInput;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ShortcutManager;
26 import android.os.Handler;
27 import android.os.Process;
28 import android.os.UserHandle;
29 import android.testing.AndroidTestingRunner;
30 import android.testing.TestableLooper;
31 import android.view.View;
32 import android.view.inputmethod.EditorInfo;
33 import android.view.inputmethod.InputConnection;
34 import android.widget.EditText;
35 import android.widget.ImageButton;
36 
37 import androidx.test.filters.SmallTest;
38 
39 import com.android.systemui.Dependency;
40 import com.android.systemui.R;
41 import com.android.systemui.SysuiTestCase;
42 import com.android.systemui.statusbar.NotificationTestHelper;
43 import com.android.systemui.statusbar.RemoteInputController;
44 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
45 import com.android.systemui.statusbar.phone.LightBarController;
46 import com.android.systemui.util.Assert;
47 
48 import org.junit.After;
49 import org.junit.Before;
50 import org.junit.Test;
51 import org.junit.runner.RunWith;
52 import org.mockito.Mock;
53 import org.mockito.MockitoAnnotations;
54 
55 @RunWith(AndroidTestingRunner.class)
56 @TestableLooper.RunWithLooper
57 @SmallTest
58 public class RemoteInputViewTest extends SysuiTestCase {
59 
60     private static final String TEST_RESULT_KEY = "test_result_key";
61     private static final String TEST_REPLY = "hello";
62     private static final String TEST_ACTION = "com.android.REMOTE_INPUT_VIEW_ACTION";
63 
64     private static final String DUMMY_MESSAGE_APP_PKG =
65             "com.android.sysuitest.dummynotificationsender";
66     private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1;
67 
68     @Mock private RemoteInputController mController;
69     @Mock private ShortcutManager mShortcutManager;
70     @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler;
71     @Mock private LightBarController mLightBarController;
72     private BlockingQueueIntentReceiver mReceiver;
73     private RemoteInputView mView;
74 
75     @Before
setUp()76     public void setUp() throws Exception {
77         Assert.sMainLooper = TestableLooper.get(this).getLooper();
78         MockitoAnnotations.initMocks(this);
79 
80         mDependency.injectTestDependency(RemoteInputQuickSettingsDisabler.class,
81                 mRemoteInputQuickSettingsDisabler);
82         mDependency.injectTestDependency(LightBarController.class,
83                 mLightBarController);
84 
85         mReceiver = new BlockingQueueIntentReceiver();
86         mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null,
87                 Handler.createAsync(Dependency.get(Dependency.BG_LOOPER)));
88 
89         // Avoid SecurityException RemoteInputView#sendRemoteInput().
90         mContext.addMockSystemService(ShortcutManager.class, mShortcutManager);
91     }
92 
93     @After
tearDown()94     public void tearDown() {
95         mContext.unregisterReceiver(mReceiver);
96     }
97 
setTestPendingIntent(RemoteInputView view)98     private void setTestPendingIntent(RemoteInputView view) {
99         PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0,
100                 new Intent(TEST_ACTION), 0);
101         RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build();
102 
103         view.setPendingIntent(pendingIntent);
104         view.setRemoteInput(new RemoteInput[]{input}, input, null /* editedSuggestionInfo */);
105     }
106 
107     @Test
testSendRemoteInput_intentContainsResultsAndSource()108     public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception {
109         ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
110         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
111 
112         setTestPendingIntent(view);
113 
114         view.focus();
115 
116         EditText editText = view.findViewById(R.id.remote_input_text);
117         editText.setText(TEST_REPLY);
118         ImageButton sendButton = view.findViewById(R.id.remote_input_send);
119         sendButton.performClick();
120 
121         Intent resultIntent = mReceiver.waitForIntent();
122         assertEquals(TEST_REPLY,
123                 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY));
124         assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT,
125                 RemoteInput.getResultsSource(resultIntent));
126     }
127 
getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)128     private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)
129             throws Exception {
130         ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow(
131                 DUMMY_MESSAGE_APP_PKG,
132                 UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID),
133                 toUser);
134         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
135 
136         setTestPendingIntent(view);
137 
138         view.focus();
139 
140         EditText editText = view.findViewById(R.id.remote_input_text);
141         EditorInfo editorInfo = new EditorInfo();
142         editorInfo.packageName = DUMMY_MESSAGE_APP_PKG;
143         editorInfo.fieldId = editText.getId();
144         InputConnection ic = editText.onCreateInputConnection(editorInfo);
145         assertNotNull(ic);
146         return editorInfo.targetInputMethodUser;
147     }
148 
149     @Test
testEditorInfoTargetInputMethodUserForCallingUser()150     public void testEditorInfoTargetInputMethodUserForCallingUser() throws Exception {
151         UserHandle callingUser = Process.myUserHandle();
152         assertEquals(callingUser, getTargetInputMethodUser(callingUser, callingUser));
153     }
154 
155     @Test
testEditorInfoTargetInputMethodUserForDifferentUser()156     public void testEditorInfoTargetInputMethodUserForDifferentUser() throws Exception {
157         UserHandle differentUser = UserHandle.of(UserHandle.getCallingUserId() + 1);
158         assertEquals(differentUser, getTargetInputMethodUser(differentUser, differentUser));
159     }
160 
161     @Test
testEditorInfoTargetInputMethodUserForAllUser()162     public void testEditorInfoTargetInputMethodUserForAllUser() throws Exception {
163         // For the special pseudo user UserHandle.ALL, EditorInfo#targetInputMethodUser must be
164         // resolved as the current user.
165         UserHandle callingUser = Process.myUserHandle();
166         assertEquals(UserHandle.of(ActivityManager.getCurrentUser()),
167                 getTargetInputMethodUser(callingUser, UserHandle.ALL));
168     }
169 
170     @Test
testNoCrashWithoutVisibilityListener()171     public void testNoCrashWithoutVisibilityListener() throws Exception {
172         ExpandableNotificationRow row = new NotificationTestHelper(mContext).createRow();
173         RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController);
174 
175         view.setOnVisibilityChangedListener(null);
176         view.setVisibility(View.INVISIBLE);
177         view.setVisibility(View.VISIBLE);
178     }
179 }
180