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 static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.ArgumentMatchers.anyInt; 22 import static org.mockito.Mockito.doReturn; 23 import static org.mockito.Mockito.spy; 24 25 import android.app.ActivityManager; 26 import android.app.PendingIntent; 27 import android.app.RemoteInput; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.ShortcutManager; 32 import android.os.Handler; 33 import android.os.Process; 34 import android.os.UserHandle; 35 import android.testing.AndroidTestingRunner; 36 import android.testing.TestableLooper; 37 import android.view.View; 38 import android.view.inputmethod.EditorInfo; 39 import android.view.inputmethod.InputConnection; 40 import android.widget.EditText; 41 import android.widget.ImageButton; 42 43 import androidx.test.filters.SmallTest; 44 45 import com.android.internal.logging.UiEventLogger; 46 import com.android.internal.logging.testing.UiEventLoggerFake; 47 import com.android.systemui.Dependency; 48 import com.android.systemui.R; 49 import com.android.systemui.SysuiTestCase; 50 import com.android.systemui.statusbar.NotificationRemoteInputManager; 51 import com.android.systemui.statusbar.RemoteInputController; 52 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 53 import com.android.systemui.statusbar.notification.row.NotificationTestHelper; 54 import com.android.systemui.statusbar.phone.LightBarController; 55 56 import org.junit.After; 57 import org.junit.Before; 58 import org.junit.Test; 59 import org.junit.runner.RunWith; 60 import org.mockito.Mock; 61 import org.mockito.MockitoAnnotations; 62 63 @RunWith(AndroidTestingRunner.class) 64 @TestableLooper.RunWithLooper 65 @SmallTest 66 public class RemoteInputViewTest extends SysuiTestCase { 67 68 private static final String TEST_RESULT_KEY = "test_result_key"; 69 private static final String TEST_REPLY = "hello"; 70 private static final String TEST_ACTION = "com.android.REMOTE_INPUT_VIEW_ACTION"; 71 72 private static final String DUMMY_MESSAGE_APP_PKG = 73 "com.android.sysuitest.dummynotificationsender"; 74 private static final int DUMMY_MESSAGE_APP_ID = Process.LAST_APPLICATION_UID - 1; 75 76 @Mock private RemoteInputController mController; 77 @Mock private ShortcutManager mShortcutManager; 78 @Mock private RemoteInputQuickSettingsDisabler mRemoteInputQuickSettingsDisabler; 79 @Mock private LightBarController mLightBarController; 80 private BlockingQueueIntentReceiver mReceiver; 81 private final UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); 82 private RemoteInputView mView; 83 84 @Before setUp()85 public void setUp() throws Exception { 86 allowTestableLooperAsMainThread(); 87 MockitoAnnotations.initMocks(this); 88 89 mDependency.injectTestDependency(RemoteInputQuickSettingsDisabler.class, 90 mRemoteInputQuickSettingsDisabler); 91 mDependency.injectTestDependency(LightBarController.class, 92 mLightBarController); 93 mDependency.injectTestDependency(UiEventLogger.class, mUiEventLoggerFake); 94 mDependency.injectMockDependency(NotificationRemoteInputManager.class); 95 96 mReceiver = new BlockingQueueIntentReceiver(); 97 mContext.registerReceiver(mReceiver, new IntentFilter(TEST_ACTION), null, 98 Handler.createAsync(Dependency.get(Dependency.BG_LOOPER))); 99 100 // Avoid SecurityException RemoteInputView#sendRemoteInput(). 101 mContext.addMockSystemService(ShortcutManager.class, mShortcutManager); 102 } 103 104 @After tearDown()105 public void tearDown() { 106 mContext.unregisterReceiver(mReceiver); 107 } 108 setTestPendingIntent(RemoteInputView view)109 private void setTestPendingIntent(RemoteInputView view) { 110 PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, 111 new Intent(TEST_ACTION), PendingIntent.FLAG_MUTABLE); 112 RemoteInput input = new RemoteInput.Builder(TEST_RESULT_KEY).build(); 113 114 view.setPendingIntent(pendingIntent); 115 view.setRemoteInput(new RemoteInput[]{input}, input, null /* editedSuggestionInfo */); 116 } 117 118 @Test testSendRemoteInput_intentContainsResultsAndSource()119 public void testSendRemoteInput_intentContainsResultsAndSource() throws Exception { 120 NotificationTestHelper helper = new NotificationTestHelper( 121 mContext, 122 mDependency, 123 TestableLooper.get(this)); 124 ExpandableNotificationRow row = helper.createRow(); 125 RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); 126 127 setTestPendingIntent(view); 128 129 view.focus(); 130 131 EditText editText = view.findViewById(R.id.remote_input_text); 132 editText.setText(TEST_REPLY); 133 ImageButton sendButton = view.findViewById(R.id.remote_input_send); 134 sendButton.performClick(); 135 136 Intent resultIntent = mReceiver.waitForIntent(); 137 assertEquals(TEST_REPLY, 138 RemoteInput.getResultsFromIntent(resultIntent).get(TEST_RESULT_KEY)); 139 assertEquals(RemoteInput.SOURCE_FREE_FORM_INPUT, 140 RemoteInput.getResultsSource(resultIntent)); 141 } 142 getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser)143 private UserHandle getTargetInputMethodUser(UserHandle fromUser, UserHandle toUser) 144 throws Exception { 145 /** 146 * RemoteInputView, Icon, and Bubble have the situation need to handle the other user. 147 * SystemUI cross multiple user but this test(com.android.systemui.tests) doesn't cross 148 * multiple user. It needs some of mocking multiple user environment to ensure the 149 * createContextAsUser without throwing IllegalStateException. 150 */ 151 Context contextSpy = spy(mContext); 152 doReturn(contextSpy).when(contextSpy).createContextAsUser(any(), anyInt()); 153 doReturn(toUser.getIdentifier()).when(contextSpy).getUserId(); 154 155 NotificationTestHelper helper = new NotificationTestHelper( 156 contextSpy, 157 mDependency, 158 TestableLooper.get(this)); 159 ExpandableNotificationRow row = helper.createRow( 160 DUMMY_MESSAGE_APP_PKG, 161 UserHandle.getUid(fromUser.getIdentifier(), DUMMY_MESSAGE_APP_ID), 162 toUser); 163 RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); 164 165 setTestPendingIntent(view); 166 167 view.focus(); 168 169 EditText editText = view.findViewById(R.id.remote_input_text); 170 EditorInfo editorInfo = new EditorInfo(); 171 editorInfo.packageName = DUMMY_MESSAGE_APP_PKG; 172 editorInfo.fieldId = editText.getId(); 173 InputConnection ic = editText.onCreateInputConnection(editorInfo); 174 assertNotNull(ic); 175 return editorInfo.targetInputMethodUser; 176 } 177 178 @Test testEditorInfoTargetInputMethodUserForCallingUser()179 public void testEditorInfoTargetInputMethodUserForCallingUser() throws Exception { 180 UserHandle callingUser = Process.myUserHandle(); 181 assertEquals(callingUser, getTargetInputMethodUser(callingUser, callingUser)); 182 } 183 184 @Test testEditorInfoTargetInputMethodUserForDifferentUser()185 public void testEditorInfoTargetInputMethodUserForDifferentUser() throws Exception { 186 UserHandle differentUser = UserHandle.of(UserHandle.getCallingUserId() + 1); 187 assertEquals(differentUser, getTargetInputMethodUser(differentUser, differentUser)); 188 } 189 190 @Test testEditorInfoTargetInputMethodUserForAllUser()191 public void testEditorInfoTargetInputMethodUserForAllUser() throws Exception { 192 // For the special pseudo user UserHandle.ALL, EditorInfo#targetInputMethodUser must be 193 // resolved as the current user. 194 UserHandle callingUser = Process.myUserHandle(); 195 assertEquals(UserHandle.of(ActivityManager.getCurrentUser()), 196 getTargetInputMethodUser(callingUser, UserHandle.ALL)); 197 } 198 199 @Test testNoCrashWithoutVisibilityListener()200 public void testNoCrashWithoutVisibilityListener() throws Exception { 201 NotificationTestHelper helper = new NotificationTestHelper( 202 mContext, 203 mDependency, 204 TestableLooper.get(this)); 205 ExpandableNotificationRow row = helper.createRow(); 206 RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); 207 208 view.addOnVisibilityChangedListener(null); 209 view.setVisibility(View.INVISIBLE); 210 view.setVisibility(View.VISIBLE); 211 } 212 213 @Test testUiEventLogging_openAndSend()214 public void testUiEventLogging_openAndSend() throws Exception { 215 NotificationTestHelper helper = new NotificationTestHelper( 216 mContext, 217 mDependency, 218 TestableLooper.get(this)); 219 ExpandableNotificationRow row = helper.createRow(); 220 RemoteInputView view = RemoteInputView.inflate(mContext, null, row.getEntry(), mController); 221 222 setTestPendingIntent(view); 223 224 // Open view, send a reply 225 view.focus(); 226 EditText editText = view.findViewById(R.id.remote_input_text); 227 editText.setText(TEST_REPLY); 228 ImageButton sendButton = view.findViewById(R.id.remote_input_send); 229 sendButton.performClick(); 230 231 mReceiver.waitForIntent(); 232 233 assertEquals(2, mUiEventLoggerFake.numLogs()); 234 assertEquals( 235 RemoteInputView.NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_OPEN.getId(), 236 mUiEventLoggerFake.eventId(0)); 237 assertEquals( 238 RemoteInputView.NotificationRemoteInputEvent.NOTIFICATION_REMOTE_INPUT_SEND.getId(), 239 mUiEventLoggerFake.eventId(1)); 240 } 241 } 242