1 /* 2 * Copyright (C) 2022 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.inputmethod.stresstest; 18 19 import static android.app.NotificationManager.IMPORTANCE_HIGH; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assume.assumeFalse; 24 25 import android.app.Notification; 26 import android.app.NotificationChannel; 27 import android.app.NotificationManager; 28 import android.app.PendingIntent; 29 import android.app.RemoteInput; 30 import android.content.ComponentName; 31 import android.content.Context; 32 import android.content.Intent; 33 import android.content.pm.PackageManager; 34 import android.graphics.drawable.Icon; 35 import android.platform.test.annotations.RootPermissionTest; 36 import android.platform.test.rule.UnlockScreenRule; 37 import android.provider.Settings; 38 import android.view.KeyEvent; 39 40 import androidx.test.ext.junit.runners.AndroidJUnit4; 41 import androidx.test.platform.app.InstrumentationRegistry; 42 import androidx.test.uiautomator.By; 43 import androidx.test.uiautomator.BySelector; 44 import androidx.test.uiautomator.UiDevice; 45 import androidx.test.uiautomator.UiObject2; 46 import androidx.test.uiautomator.Until; 47 48 import org.junit.After; 49 import org.junit.Before; 50 import org.junit.Rule; 51 import org.junit.Test; 52 import org.junit.runner.RunWith; 53 54 import java.util.concurrent.TimeUnit; 55 import java.util.regex.Pattern; 56 57 @RootPermissionTest 58 @RunWith(AndroidJUnit4.class) 59 public final class NotificationTest { 60 61 private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(10); 62 63 private static final String CHANNEL_ID = "TEST_CHANNEL"; 64 private static final String CHANNEL_NAME = "Test channel"; 65 66 private static final String REPLY_INPUT_KEY = "REPLY_KEY"; 67 private static final String REPLY_INPUT_LABEL = "Test reply label"; 68 private static final String ACTION_REPLY = "com.android.inputmethod.stresstest.ACTION_REPLY"; 69 private static final String REPLY_ACTION_LABEL = "Test reply"; 70 private static final int REPLY_REQUEST_CODE = 1; 71 72 private static final String NOTIFICATION_TITLE = "Test notification"; 73 private static final String NOTIFICATION_CONTENT = "Test notification content"; 74 private static final int NOTIFICATION_ID = 2000; 75 76 // This is for AOSP System UI for phones. When testing customized System UI, please modify here. 77 private static final BySelector REPLY_SEND_BUTTON_SELECTOR = 78 By.res("com.android.systemui", "remote_input_send").enabled(true); 79 80 @Rule(order = 0) public UnlockScreenRule mUnlockScreenRule = new UnlockScreenRule(); 81 @Rule(order = 1) public ImeStressTestRule mImeStressTestRule = 82 new ImeStressTestRule(true /* useSimpleTestIme */); 83 @Rule(order = 2) public ScreenCaptureRule mScreenCaptureRule = 84 new ScreenCaptureRule("/sdcard/InputMethodStressTest"); 85 86 private Context mContext; 87 private NotificationManager mNotificationManager; 88 private UiDevice mUiDevice; 89 90 @Before setUp()91 public void setUp() { 92 mContext = InstrumentationRegistry.getInstrumentation().getContext(); 93 mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()); 94 mNotificationManager = mContext.getSystemService(NotificationManager.class); 95 PackageManager pm = mContext.getPackageManager(); 96 // Do not run on Automotive. 97 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)); 98 // Do not run on TV. Direct Reply isn't supported on TV. 99 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)); 100 // Do not run on Wear. Direct Reply isn't supported on Wear. 101 assumeFalse(pm.hasSystemFeature(PackageManager.FEATURE_WATCH)); 102 } 103 104 @After tearDown()105 public void tearDown() { 106 mNotificationManager.cancelAll(); 107 mUiDevice.pressHome(); 108 } 109 110 @Test testDirectReply()111 public void testDirectReply() { 112 postMessagingNotification(); 113 mUiDevice.openNotification(); 114 // The text can be shown as-is, or all-caps, depending on the system. 115 Pattern actionLabelPattern = Pattern.compile(REPLY_ACTION_LABEL, Pattern.CASE_INSENSITIVE); 116 mUiDevice.wait(Until.findObject(By.text(actionLabelPattern)), TIMEOUT).click(); 117 // Verify that IME is visible. 118 assertThat(mUiDevice.wait(Until.findObject(By.pkg(getImePackage(mContext))), TIMEOUT)) 119 .isNotNull(); 120 // Type something, which enables the Send button, then click the Send button. 121 mUiDevice.pressKeyCode(KeyEvent.KEYCODE_A); 122 mUiDevice.pressKeyCode(KeyEvent.KEYCODE_B); 123 mUiDevice.pressKeyCode(KeyEvent.KEYCODE_C); 124 UiObject2 sendButton = mUiDevice.wait( 125 Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT); 126 if (sendButton == null) { 127 // If the screen is too small, sendButton may be hidden by IME. 128 // Dismiss IME and try again. 129 mUiDevice.pressBack(); 130 sendButton = mUiDevice.wait(Until.findObject(REPLY_SEND_BUTTON_SELECTOR), TIMEOUT); 131 } 132 sendButton.click(); 133 // Verify that IME is gone. 134 assertThat(mUiDevice.wait(Until.gone(By.pkg(getImePackage(mContext))), TIMEOUT)).isTrue(); 135 } 136 postMessagingNotification()137 private void postMessagingNotification() { 138 // Register the channel. It's safe to register the same channel again and again. 139 NotificationChannel channel = 140 new NotificationChannel(CHANNEL_ID, CHANNEL_NAME, IMPORTANCE_HIGH); 141 mNotificationManager.createNotificationChannel(channel); 142 143 // Post inline reply notification. 144 PendingIntent pendingIntent = PendingIntent.getBroadcast( 145 mContext, REPLY_REQUEST_CODE, 146 new Intent().setAction(ACTION_REPLY).setClass(mContext, NotificationTest.class), 147 PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE); 148 RemoteInput remoteInput = new RemoteInput.Builder(REPLY_INPUT_KEY) 149 .setLabel(REPLY_INPUT_LABEL) 150 .build(); 151 Icon icon = Icon.createWithResource(mContext, android.R.drawable.ic_menu_edit); 152 Notification.Action action = 153 new Notification.Action.Builder(icon, REPLY_ACTION_LABEL, pendingIntent) 154 .addRemoteInput(remoteInput) 155 .build(); 156 Notification notification = new Notification.Builder(mContext, CHANNEL_ID) 157 .setSmallIcon(android.R.drawable.ic_menu_edit) 158 .setContentTitle(NOTIFICATION_TITLE) 159 .setContentText(NOTIFICATION_CONTENT) 160 .addAction(action) 161 .build(); 162 mNotificationManager.notify(NOTIFICATION_ID, notification); 163 } 164 getImePackage(Context context)165 private static String getImePackage(Context context) { 166 String imeId = Settings.Secure.getString( 167 context.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 168 ComponentName cn = ComponentName.unflattenFromString(imeId); 169 assertThat(cn).isNotNull(); 170 return cn.getPackageName(); 171 } 172 } 173