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.UiObject2; 40 import android.support.test.uiautomator.UiObjectNotFoundException; 41 import android.support.test.uiautomator.UiSelector; 42 import android.support.test.uiautomator.Until; 43 import android.text.SpannableStringBuilder; 44 import android.text.style.StyleSpan; 45 import android.util.Log; 46 import android.widget.EditText; 47 import android.widget.ListView; 48 import android.widget.TextView; 49 import android.widget.Toast; 50 51 import com.android.notification.functional.R; 52 53 import java.lang.InterruptedException; 54 import java.util.List; 55 import java.util.Map; 56 57 58 public class NotificationHelper { 59 60 private static final String LOG_TAG = NotificationHelper.class.getSimpleName(); 61 private static final int LONG_TIMEOUT = 2500; 62 private static final int SHORT_TIMEOUT = 200; 63 private static final String KEY_QUICK_REPLY_TEXT = "quick_reply"; 64 private static final UiSelector LIST_VIEW = new UiSelector().className(ListView.class); 65 private static final UiSelector LIST_ITEM_VALUE = new UiSelector().className(TextView.class); 66 public static final String FIRST_ACTION = "FIRST ACTION"; 67 public static final String SECOND_ACTION = "SECOND ACTION"; 68 public static final String CONTENT_TITLE = "THIS IS A NOTIFICATION"; 69 private static final String BUZZY_CHANNEL_ID = "com.android.notification.functional.buzzy"; 70 private static final String QUIET_CHANNEL_ID = "com.android.notification.functional.quiet"; 71 private NotificationChannel mBuzzyChannel; 72 private NotificationChannel mQuietChannel; 73 74 private UiDevice mDevice; 75 private Instrumentation mInst; 76 private NotificationManager mNotificationManager = null; 77 private Context mContext = null; 78 NotificationHelper(UiDevice device, Instrumentation inst, NotificationManager nm)79 public NotificationHelper(UiDevice device, Instrumentation inst, NotificationManager nm) { 80 this.mDevice = device; 81 mInst = inst; 82 mNotificationManager = nm; 83 mContext = inst.getContext(); 84 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 enableNotificationViaAdb(boolean isShow)103 public void enableNotificationViaAdb(boolean isShow) { 104 String command = String.format(" %s %s %s %s %s", "settings", "put", "secure", 105 "lock_screen_show_notifications", 106 isShow ? "1" : "0"); 107 executeAdbCommand(command); 108 } 109 executeAdbCommand(String command)110 public void executeAdbCommand(String command) { 111 Log.i(LOG_TAG, String.format("executing - %s", command)); 112 mInst.getUiAutomation().executeShellCommand(command); 113 mDevice.waitForIdle(); 114 } 115 navigateToScreenLock()116 private void navigateToScreenLock() throws Exception { 117 launchSettingsPage(mInst.getContext(), Settings.ACTION_SECURITY_SETTINGS); 118 mDevice.wait(Until.findObject(By.text("Screen lock")), LONG_TIMEOUT).click(); 119 } 120 sendNotification(int id, int visibility, String title)121 public void sendNotification(int id, int visibility, String title) throws Exception { 122 sendNotification(id, visibility, title, false); 123 } 124 sendNotification(int id, int visibility, String title, boolean buzz)125 public void sendNotification(int id, int visibility, String title, boolean buzz) 126 throws Exception { 127 Log.v(LOG_TAG, "Sending out notification..."); 128 PendingIntent emptyIntent = PendingIntent.getBroadcast(mContext, 0, 129 new Intent("an.action.that.nobody.will.be.listening.for"), 130 PendingIntent.FLAG_IMMUTABLE); 131 Intent intent = new Intent(Intent.ACTION_VIEW); 132 PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 133 PendingIntent.FLAG_IMMUTABLE); 134 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 135 Notification.Builder notification = new Notification.Builder(mContext) 136 .setSmallIcon(R.drawable.stat_notify_email) 137 .setWhen(System.currentTimeMillis()) 138 .setContentTitle(title) 139 .setContentText(subtitle) 140 .setContentIntent(pendingIntent) 141 .setVisibility(visibility) 142 .setChannelId(buzz ? BUZZY_CHANNEL_ID : QUIET_CHANNEL_ID) 143 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 144 FIRST_ACTION, emptyIntent) 145 .build()) 146 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 147 SECOND_ACTION, emptyIntent) 148 .build()) 149 .setAutoCancel(false); 150 mNotificationManager.notify(id, notification.build()); 151 Thread.sleep(LONG_TIMEOUT); 152 } 153 sendNotifications(Map<Integer, String> lists, boolean withDelay)154 public void sendNotifications(Map<Integer, String> lists, boolean withDelay) throws Exception { 155 Log.v(LOG_TAG, "Sending out notification..."); 156 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 157 for (Map.Entry<Integer, String> l : lists.entrySet()) { 158 Notification.Builder notification = new Notification.Builder(mContext) 159 .setSmallIcon(R.drawable.stat_notify_email) 160 .setWhen(System.currentTimeMillis()).setContentTitle(l.getValue()) 161 .setContentTitle(CONTENT_TITLE) 162 .setContentText(subtitle); 163 mNotificationManager.notify(l.getKey(), notification.build()); 164 if (withDelay) { 165 Thread.sleep(SHORT_TIMEOUT); 166 } 167 } 168 Thread.sleep(LONG_TIMEOUT); 169 } 170 sendBundlingNotifications(List<Integer> lists, String groupKey)171 public void sendBundlingNotifications(List<Integer> lists, String groupKey) throws Exception { 172 Notification childNotification = new Notification.Builder(mContext) 173 .setContentTitle(lists.get(1).toString()) 174 .setSmallIcon(R.drawable.stat_notify_email) 175 .setGroup(groupKey) 176 .build(); 177 mNotificationManager.notify(lists.get(1), 178 childNotification); 179 childNotification = new Notification.Builder(mContext) 180 .setContentText(lists.get(2).toString()) 181 .setSmallIcon(R.drawable.stat_notify_email) 182 .setGroup(groupKey) 183 .build(); 184 mNotificationManager.notify(lists.get(2), 185 childNotification); 186 Notification notification = new Notification.Builder(mContext) 187 .setContentTitle(lists.get(0).toString()) 188 .setSubText(groupKey) 189 .setSmallIcon(R.drawable.stat_notify_email) 190 .setGroup(groupKey) 191 .setGroupSummary(true) 192 .build(); 193 mNotificationManager.notify(lists.get(0), 194 notification); 195 } 196 BOLD(CharSequence str)197 static SpannableStringBuilder BOLD(CharSequence str) { 198 final SpannableStringBuilder ssb = new SpannableStringBuilder(str); 199 ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); 200 return ssb; 201 } 202 checkNotificationExistence(int id, boolean exists)203 public boolean checkNotificationExistence(int id, boolean exists) throws Exception { 204 boolean isFound = false; 205 for (int tries = 3; tries-- > 0;) { 206 isFound = false; 207 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 208 for (StatusBarNotification sbn : sbns) { 209 if (sbn.getId() == id) { 210 isFound = true; 211 break; 212 } 213 } 214 if (isFound == exists) { 215 break; 216 } 217 Thread.sleep(SHORT_TIMEOUT); 218 } 219 Log.i(LOG_TAG, "checkNotificationExistence..." + isFound); 220 return isFound == exists; 221 } 222 getStatusBarNotification(int id)223 public StatusBarNotification getStatusBarNotification(int id) { 224 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 225 StatusBarNotification n = null; 226 for (StatusBarNotification sbn : sbns) { 227 if (sbn.getId() == id) { 228 n = sbn; 229 break; 230 } 231 } 232 return n; 233 } 234 swipeUp()235 public void swipeUp() throws Exception { 236 mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight()*3/4, 237 mDevice.getDisplayWidth() / 2, 0, 30); 238 Thread.sleep(SHORT_TIMEOUT); 239 } 240 swipeDown()241 public void swipeDown() throws Exception { 242 mDevice.swipe(mDevice.getDisplayWidth() / 2, 0, mDevice.getDisplayWidth() / 2, 243 mDevice.getDisplayHeight() / 2 + 50, 20); 244 Thread.sleep(SHORT_TIMEOUT); 245 } 246 unlockScreen()247 public void unlockScreen() throws Exception { 248 KeyguardManager myKM = (KeyguardManager) mContext 249 .getSystemService(Context.KEYGUARD_SERVICE); 250 if (myKM.inKeyguardRestrictedInputMode()) { 251 // it is locked 252 swipeUp(); 253 } 254 } 255 showAppNotificationSettings(Context context)256 public void showAppNotificationSettings(Context context) throws Exception { 257 Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 258 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 259 intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName()); 260 intent.putExtra(Settings.EXTRA_APP_UID, mContext.getApplicationInfo().uid); 261 context.startActivity(intent); 262 Thread.sleep(LONG_TIMEOUT * 2); 263 } 264 sendNotificationsWithInlineReply(int notificationId, boolean isHeadsUp)265 public void sendNotificationsWithInlineReply(int notificationId, boolean isHeadsUp) { 266 Notification.Action action = new Notification.Action.Builder( 267 R.drawable.stat_notify_email, "Reply", ToastService.getPendingIntent(mContext, 268 "inline reply test")) 269 .addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT) 270 .setLabel("Quick reply").build()) 271 .build(); 272 Notification.Builder n = new Notification.Builder(mContext) 273 .setContentTitle(Integer.toString(notificationId)) 274 .setContentText("INLINE REPLY TEST") 275 .setWhen(System.currentTimeMillis()) 276 .setSmallIcon(R.drawable.stat_notify_email) 277 .addAction(action); 278 if (isHeadsUp) { 279 n.setPriority(Notification.PRIORITY_HIGH) 280 .setDefaults(Notification.DEFAULT_VIBRATE); 281 } 282 mNotificationManager.notify(notificationId, n.build()); 283 } 284 getDefaultChannel()285 public NotificationChannel getDefaultChannel() { 286 return mNotificationManager.getNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID); 287 } 288 getChannel(boolean buzz)289 public NotificationChannel getChannel(boolean buzz) { 290 String id = (buzz ? BUZZY_CHANNEL_ID : QUIET_CHANNEL_ID); 291 String name = (buzz ? "This channel is buzzy" : "This channel is quiet"); 292 int importance = (buzz ? IMPORTANCE_DEFAULT : IMPORTANCE_LOW); 293 294 NotificationChannel channel = (buzz ? mBuzzyChannel : mQuietChannel); 295 if (channel == null) { 296 channel = mNotificationManager.getNotificationChannel(id); 297 } 298 if (channel == null){ 299 channel = new NotificationChannel(id, name, importance); 300 if (buzz) { 301 channel.enableVibration(true); 302 channel.setSound(null, null); 303 } 304 mNotificationManager.createNotificationChannel(channel); 305 } 306 return channel; 307 } 308 309 public static class ToastService extends IntentService { 310 private static final String TAG = "ToastService"; 311 private static final String ACTION_TOAST = "toast"; 312 private Handler handler; 313 ToastService()314 public ToastService() { 315 super(TAG); 316 } 317 ToastService(String name)318 public ToastService(String name) { 319 super(name); 320 } 321 322 @Override onStartCommand(Intent intent, int flags, int startId)323 public int onStartCommand(Intent intent, int flags, int startId) { 324 handler = new Handler(); 325 return super.onStartCommand(intent, flags, startId); 326 } 327 328 @Override onHandleIntent(Intent intent)329 protected void onHandleIntent(Intent intent) { 330 if (intent.hasExtra("text")) { 331 final String text = intent.getStringExtra("text"); 332 handler.post(new Runnable() { 333 @Override 334 public void run() { 335 Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show(); 336 Log.v(TAG, "toast " + text); 337 } 338 }); 339 } 340 } 341 getPendingIntent(Context context, String text)342 public static PendingIntent getPendingIntent(Context context, String text) { 343 Intent toastIntent = new Intent(context, ToastService.class); 344 toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 345 toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message 346 toastIntent.putExtra("text", text); 347 PendingIntent pi = PendingIntent.getService( 348 context, 58, toastIntent, 349 PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); 350 return pi; 351 } 352 } 353 } 354