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