1 /* 2 * Copyright (C) 2016 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.notification.functional; 18 19 import android.app.Instrumentation; 20 import android.app.IntentService; 21 import android.app.KeyguardManager; 22 import android.app.Notification; 23 import android.app.NotificationChannel; 24 import android.app.NotificationManager; 25 import android.app.PendingIntent; 26 import android.app.RemoteInput; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.graphics.Typeface; 30 import android.net.Uri; 31 import android.os.Handler; 32 import android.os.RemoteException; 33 import android.provider.Settings; 34 import android.service.notification.StatusBarNotification; 35 import android.support.test.uiautomator.By; 36 import android.support.test.uiautomator.UiDevice; 37 import android.support.test.uiautomator.UiObject; 38 import android.support.test.uiautomator.UiObjectNotFoundException; 39 import android.support.test.uiautomator.UiScrollable; 40 import android.support.test.uiautomator.UiSelector; 41 import android.support.test.uiautomator.Until; 42 import android.text.SpannableStringBuilder; 43 import android.text.style.StyleSpan; 44 import android.util.Log; 45 import android.widget.EditText; 46 import android.widget.ListView; 47 import android.widget.TextView; 48 import android.widget.Toast; 49 50 import com.android.notification.functional.R; 51 52 import java.lang.InterruptedException; 53 import java.util.List; 54 import java.util.Map; 55 56 57 public class NotificationHelper { 58 59 private static final String LOG_TAG = NotificationHelper.class.getSimpleName(); 60 private static final int LONG_TIMEOUT = 2500; 61 private static final int SHORT_TIMEOUT = 200; 62 private static final String KEY_QUICK_REPLY_TEXT = "quick_reply"; 63 private static final UiSelector LIST_VIEW = new UiSelector().className(ListView.class); 64 private static final UiSelector LIST_ITEM_VALUE = new UiSelector().className(TextView.class); 65 public static final String FIRST_ACTION = "FIRST ACTION"; 66 public static final String SECOND_ACTION = "SECOND ACTION"; 67 public static final String CONTENT_TITLE = "THIS IS A NOTIFICATION"; 68 69 private UiDevice mDevice; 70 private Instrumentation mInst; 71 private NotificationManager mNotificationManager = null; 72 private Context mContext = null; 73 NotificationHelper(UiDevice device, Instrumentation inst, NotificationManager nm)74 public NotificationHelper(UiDevice device, Instrumentation inst, NotificationManager nm) { 75 this.mDevice = device; 76 mInst = inst; 77 mNotificationManager = nm; 78 mContext = inst.getContext(); 79 } 80 sleepAndWakeUpDevice()81 public void sleepAndWakeUpDevice() throws RemoteException, InterruptedException { 82 mDevice.sleep(); 83 Thread.sleep(LONG_TIMEOUT); 84 mDevice.wakeUp(); 85 } 86 launchSettingsPage(Context ctx, String pageName)87 public static void launchSettingsPage(Context ctx, String pageName) throws Exception { 88 Intent intent = new Intent(pageName); 89 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 90 ctx.startActivity(intent); 91 Thread.sleep(LONG_TIMEOUT * 2); 92 } 93 94 /** 95 * Sets the screen lock pin 96 * @param pin 4 digits 97 * @return false if a pin is already set or pin value is not 4 digits 98 * @throws UiObjectNotFoundException 99 */ setScreenLockPin(int pin)100 public boolean setScreenLockPin(int pin) throws Exception { 101 if (pin >= 0 && pin <= 9999) { 102 navigateToScreenLock(); 103 if (new UiObject(new UiSelector().text("Confirm your PIN")).exists()) { 104 UiObject pinField = new UiObject( 105 new UiSelector().className(EditText.class.getName())); 106 pinField.setText(String.format("%04d", pin)); 107 mDevice.pressEnter(); 108 } 109 new UiObject(new UiSelector().text("PIN")).click(); 110 // If there's an option to set 'require PIN to start device' 111 // choose 'No thanks', otherwise just skip ahead. 112 if (new UiObject(new UiSelector().text("No thanks")).exists()) { 113 clickText("No thanks"); 114 } 115 UiObject pinField = new UiObject(new UiSelector().className(EditText.class.getName())); 116 pinField.setText(String.format("%04d", pin)); 117 mDevice.pressEnter(); 118 pinField.setText(String.format("%04d", pin)); 119 mDevice.pressEnter(); 120 clickText("Hide sensitive notification content"); 121 clickText("DONE"); 122 return true; 123 } 124 return false; 125 } 126 removeScreenLock(int pin, String mode)127 public boolean removeScreenLock(int pin, String mode) throws Exception { 128 navigateToScreenLock(); 129 if (new UiObject(new UiSelector().text("Confirm your PIN")).exists()) { 130 UiObject pinField = new UiObject(new UiSelector().className(EditText.class.getName())); 131 pinField.setText(String.format("%04d", pin)); 132 mDevice.pressEnter(); 133 clickText(mode); 134 clickText("YES, REMOVE"); 135 } else { 136 clickText(mode); 137 } 138 return true; 139 } 140 unlockScreenByPin(int pin)141 public void unlockScreenByPin(int pin) throws Exception { 142 String command = String.format(" %s %s %s", "input", "text", Integer.toString(pin)); 143 executeAdbCommand(command); 144 Thread.sleep(SHORT_TIMEOUT); 145 mDevice.pressEnter(); 146 } 147 enableNotificationViaAdb(boolean isShow)148 public void enableNotificationViaAdb(boolean isShow) { 149 String command = String.format(" %s %s %s %s %s", "settings", "put", "secure", 150 "lock_screen_show_notifications", 151 isShow ? "1" : "0"); 152 executeAdbCommand(command); 153 } 154 executeAdbCommand(String command)155 public void executeAdbCommand(String command) { 156 Log.i(LOG_TAG, String.format("executing - %s", command)); 157 mInst.getUiAutomation().executeShellCommand(command); 158 mDevice.waitForIdle(); 159 } 160 navigateToScreenLock()161 private void navigateToScreenLock() throws Exception { 162 launchSettingsPage(mInst.getContext(), Settings.ACTION_SECURITY_SETTINGS); 163 new UiObject(new UiSelector().text("Screen lock")).click(); 164 } 165 clickText(String text)166 private void clickText(String text) throws UiObjectNotFoundException { 167 mDevice.wait(Until.findObject(By.text(text)), LONG_TIMEOUT).click(); 168 } 169 sendNotification(int id, int visibility, String title)170 public void sendNotification(int id, int visibility, String title) throws Exception { 171 sendNotification(id, visibility, title, false); 172 } 173 sendNotification(int id, int visibility, String title, boolean buzz)174 public void sendNotification(int id, int visibility, String title, boolean buzz) 175 throws Exception { 176 Log.v(LOG_TAG, "Sending out notification..."); 177 PendingIntent emptyIntent = PendingIntent.getBroadcast(mContext, 0, 178 new Intent("an.action.that.nobody.will.be.listening.for"), 0); 179 Intent intent = new Intent(Intent.ACTION_VIEW); 180 PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); 181 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 182 Notification.Builder notification = new Notification.Builder(mContext) 183 .setSmallIcon(R.drawable.stat_notify_email) 184 .setWhen(System.currentTimeMillis()) 185 .setContentTitle(title) 186 .setContentText(subtitle) 187 .setContentIntent(pendingIntent) 188 .setVisibility(visibility) 189 .setPriority(Notification.PRIORITY_HIGH) 190 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 191 FIRST_ACTION, emptyIntent) 192 .build()) 193 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 194 SECOND_ACTION, emptyIntent) 195 .build()) 196 .setAutoCancel(false); 197 if (buzz) { 198 notification.setDefaults(Notification.DEFAULT_VIBRATE); 199 } 200 mNotificationManager.notify(id, notification.build()); 201 Thread.sleep(LONG_TIMEOUT); 202 } 203 sendNotifications(Map<Integer, String> lists, boolean withDelay)204 public void sendNotifications(Map<Integer, String> lists, boolean withDelay) throws Exception { 205 Log.v(LOG_TAG, "Sending out notification..."); 206 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 207 for (Map.Entry<Integer, String> l : lists.entrySet()) { 208 Notification.Builder notification = new Notification.Builder(mContext) 209 .setSmallIcon(R.drawable.stat_notify_email) 210 .setWhen(System.currentTimeMillis()).setContentTitle(l.getValue()) 211 .setContentTitle(CONTENT_TITLE) 212 .setContentText(subtitle); 213 mNotificationManager.notify(l.getKey(), notification.build()); 214 if (withDelay) { 215 Thread.sleep(SHORT_TIMEOUT); 216 } 217 } 218 Thread.sleep(LONG_TIMEOUT); 219 } 220 sendBundlingNotifications(List<Integer> lists, String groupKey)221 public void sendBundlingNotifications(List<Integer> lists, String groupKey) throws Exception { 222 Notification childNotification = new Notification.Builder(mContext) 223 .setContentTitle(lists.get(1).toString()) 224 .setSmallIcon(R.drawable.stat_notify_email) 225 .setGroup(groupKey) 226 .build(); 227 mNotificationManager.notify(lists.get(1), 228 childNotification); 229 childNotification = new Notification.Builder(mContext) 230 .setContentText(lists.get(2).toString()) 231 .setSmallIcon(R.drawable.stat_notify_email) 232 .setGroup(groupKey) 233 .build(); 234 mNotificationManager.notify(lists.get(2), 235 childNotification); 236 Notification notification = new Notification.Builder(mContext) 237 .setContentTitle(lists.get(0).toString()) 238 .setSubText(groupKey) 239 .setSmallIcon(R.drawable.stat_notify_email) 240 .setGroup(groupKey) 241 .setGroupSummary(true) 242 .build(); 243 mNotificationManager.notify(lists.get(0), 244 notification); 245 } 246 BOLD(CharSequence str)247 static SpannableStringBuilder BOLD(CharSequence str) { 248 final SpannableStringBuilder ssb = new SpannableStringBuilder(str); 249 ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); 250 return ssb; 251 } 252 checkNotificationExistence(int id, boolean exists)253 public boolean checkNotificationExistence(int id, boolean exists) throws Exception { 254 boolean isFound = false; 255 for (int tries = 3; tries-- > 0;) { 256 isFound = false; 257 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 258 for (StatusBarNotification sbn : sbns) { 259 if (sbn.getId() == id) { 260 isFound = true; 261 break; 262 } 263 } 264 if (isFound == exists) { 265 break; 266 } 267 Thread.sleep(SHORT_TIMEOUT); 268 } 269 Log.i(LOG_TAG, "checkNotificationExistence..." + isFound); 270 return isFound == exists; 271 } 272 getStatusBarNotification(int id)273 public StatusBarNotification getStatusBarNotification(int id) { 274 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 275 StatusBarNotification n = null; 276 for (StatusBarNotification sbn : sbns) { 277 if (sbn.getId() == id) { 278 n = sbn; 279 break; 280 } 281 } 282 return n; 283 } 284 swipeUp()285 public void swipeUp() throws Exception { 286 mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight()*3/4, 287 mDevice.getDisplayWidth() / 2, 0, 30); 288 Thread.sleep(SHORT_TIMEOUT); 289 } 290 swipeDown()291 public void swipeDown() throws Exception { 292 mDevice.swipe(mDevice.getDisplayWidth() / 2, 0, mDevice.getDisplayWidth() / 2, 293 mDevice.getDisplayHeight() / 2 + 50, 20); 294 Thread.sleep(SHORT_TIMEOUT); 295 } 296 unlockScreen()297 public void unlockScreen() throws Exception { 298 KeyguardManager myKM = (KeyguardManager) mContext 299 .getSystemService(Context.KEYGUARD_SERVICE); 300 if (myKM.inKeyguardRestrictedInputMode()) { 301 // it is locked 302 swipeUp(); 303 } 304 } 305 showAppNotificationSettings(Context context)306 public void showAppNotificationSettings(Context context) throws Exception { 307 Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 308 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 309 intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName()); 310 intent.putExtra(Settings.EXTRA_APP_UID, mContext.getApplicationInfo().uid); 311 context.startActivity(intent); 312 Thread.sleep(LONG_TIMEOUT * 2); 313 } 314 315 /** 316 * This is the main list view containing the items that settings are possible for 317 */ 318 public static class SettingsListView { selectSettingsFor(String name)319 public static boolean selectSettingsFor(String name) throws UiObjectNotFoundException { 320 UiScrollable settingsList = new UiScrollable( 321 new UiSelector().resourceId("android:id/content")); 322 UiObject appSettings = settingsList.getChildByText(LIST_ITEM_VALUE, name); 323 if (appSettings != null) { 324 return appSettings.click(); 325 } 326 return false; 327 } 328 checkSettingsExists(String name)329 public boolean checkSettingsExists(String name) { 330 try { 331 UiScrollable settingsList = new UiScrollable(LIST_VIEW); 332 UiObject appSettings = settingsList.getChildByText(LIST_ITEM_VALUE, name); 333 return appSettings.exists(); 334 } catch (UiObjectNotFoundException e) { 335 return false; 336 } 337 } 338 } 339 sendNotificationsWithInLineReply(int notificationId, boolean isHeadsUp)340 public void sendNotificationsWithInLineReply(int notificationId, boolean isHeadsUp) { 341 Notification.Action action = new Notification.Action.Builder( 342 R.drawable.stat_notify_email, "Reply", ToastService.getPendingIntent(mContext, 343 "inline reply test")) 344 .addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT) 345 .setLabel("Quick reply").build()) 346 .build(); 347 Notification.Builder n = new Notification.Builder(mContext) 348 .setContentTitle(Integer.toString(notificationId)) 349 .setContentText("INLINE REPLY TEST") 350 .setWhen(System.currentTimeMillis()) 351 .setSmallIcon(R.drawable.stat_notify_email) 352 .addAction(action); 353 if (isHeadsUp) { 354 n.setPriority(Notification.PRIORITY_HIGH) 355 .setDefaults(Notification.DEFAULT_VIBRATE); 356 } 357 mNotificationManager.notify(notificationId, n.build()); 358 } 359 getDefaultChannel()360 public NotificationChannel getDefaultChannel() { 361 return mNotificationManager.getNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID); 362 } 363 364 public static class ToastService extends IntentService { 365 private static final String TAG = "ToastService"; 366 private static final String ACTION_TOAST = "toast"; 367 private Handler handler; 368 ToastService()369 public ToastService() { 370 super(TAG); 371 } 372 ToastService(String name)373 public ToastService(String name) { 374 super(name); 375 } 376 377 @Override onStartCommand(Intent intent, int flags, int startId)378 public int onStartCommand(Intent intent, int flags, int startId) { 379 handler = new Handler(); 380 return super.onStartCommand(intent, flags, startId); 381 } 382 383 @Override onHandleIntent(Intent intent)384 protected void onHandleIntent(Intent intent) { 385 if (intent.hasExtra("text")) { 386 final String text = intent.getStringExtra("text"); 387 handler.post(new Runnable() { 388 @Override 389 public void run() { 390 Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show(); 391 Log.v(TAG, "toast " + text); 392 } 393 }); 394 } 395 } 396 getPendingIntent(Context context, String text)397 public static PendingIntent getPendingIntent(Context context, String text) { 398 Intent toastIntent = new Intent(context, ToastService.class); 399 toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 400 toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message 401 toastIntent.putExtra("text", text); 402 PendingIntent pi = PendingIntent.getService( 403 context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); 404 return pi; 405 } 406 } 407 } 408