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 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 90 public void sleepAndWakeUpDevice() throws RemoteException, InterruptedException { 91 mDevice.sleep(); 92 Thread.sleep(LONG_TIMEOUT); 93 mDevice.wakeUp(); 94 } 95 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 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 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 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 121 public void sendNotification(int id, int visibility, String title) throws Exception { 122 sendNotification(id, visibility, title, false); 123 } 124 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"), 0); 130 Intent intent = new Intent(Intent.ACTION_VIEW); 131 PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0); 132 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 133 Notification.Builder notification = new Notification.Builder(mContext) 134 .setSmallIcon(R.drawable.stat_notify_email) 135 .setWhen(System.currentTimeMillis()) 136 .setContentTitle(title) 137 .setContentText(subtitle) 138 .setContentIntent(pendingIntent) 139 .setVisibility(visibility) 140 .setChannelId(buzz ? BUZZY_CHANNEL_ID : QUIET_CHANNEL_ID) 141 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 142 FIRST_ACTION, emptyIntent) 143 .build()) 144 .addAction(new Notification.Action.Builder(R.drawable.stat_notify_email, 145 SECOND_ACTION, emptyIntent) 146 .build()) 147 .setAutoCancel(false); 148 mNotificationManager.notify(id, notification.build()); 149 Thread.sleep(LONG_TIMEOUT); 150 } 151 152 public void sendNotifications(Map<Integer, String> lists, boolean withDelay) throws Exception { 153 Log.v(LOG_TAG, "Sending out notification..."); 154 CharSequence subtitle = String.valueOf(System.currentTimeMillis()); 155 for (Map.Entry<Integer, String> l : lists.entrySet()) { 156 Notification.Builder notification = new Notification.Builder(mContext) 157 .setSmallIcon(R.drawable.stat_notify_email) 158 .setWhen(System.currentTimeMillis()).setContentTitle(l.getValue()) 159 .setContentTitle(CONTENT_TITLE) 160 .setContentText(subtitle); 161 mNotificationManager.notify(l.getKey(), notification.build()); 162 if (withDelay) { 163 Thread.sleep(SHORT_TIMEOUT); 164 } 165 } 166 Thread.sleep(LONG_TIMEOUT); 167 } 168 169 public void sendBundlingNotifications(List<Integer> lists, String groupKey) throws Exception { 170 Notification childNotification = new Notification.Builder(mContext) 171 .setContentTitle(lists.get(1).toString()) 172 .setSmallIcon(R.drawable.stat_notify_email) 173 .setGroup(groupKey) 174 .build(); 175 mNotificationManager.notify(lists.get(1), 176 childNotification); 177 childNotification = new Notification.Builder(mContext) 178 .setContentText(lists.get(2).toString()) 179 .setSmallIcon(R.drawable.stat_notify_email) 180 .setGroup(groupKey) 181 .build(); 182 mNotificationManager.notify(lists.get(2), 183 childNotification); 184 Notification notification = new Notification.Builder(mContext) 185 .setContentTitle(lists.get(0).toString()) 186 .setSubText(groupKey) 187 .setSmallIcon(R.drawable.stat_notify_email) 188 .setGroup(groupKey) 189 .setGroupSummary(true) 190 .build(); 191 mNotificationManager.notify(lists.get(0), 192 notification); 193 } 194 195 static SpannableStringBuilder BOLD(CharSequence str) { 196 final SpannableStringBuilder ssb = new SpannableStringBuilder(str); 197 ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0); 198 return ssb; 199 } 200 201 public boolean checkNotificationExistence(int id, boolean exists) throws Exception { 202 boolean isFound = false; 203 for (int tries = 3; tries-- > 0;) { 204 isFound = false; 205 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 206 for (StatusBarNotification sbn : sbns) { 207 if (sbn.getId() == id) { 208 isFound = true; 209 break; 210 } 211 } 212 if (isFound == exists) { 213 break; 214 } 215 Thread.sleep(SHORT_TIMEOUT); 216 } 217 Log.i(LOG_TAG, "checkNotificationExistence..." + isFound); 218 return isFound == exists; 219 } 220 221 public StatusBarNotification getStatusBarNotification(int id) { 222 StatusBarNotification[] sbns = mNotificationManager.getActiveNotifications(); 223 StatusBarNotification n = null; 224 for (StatusBarNotification sbn : sbns) { 225 if (sbn.getId() == id) { 226 n = sbn; 227 break; 228 } 229 } 230 return n; 231 } 232 233 public void swipeUp() throws Exception { 234 mDevice.swipe(mDevice.getDisplayWidth() / 2, mDevice.getDisplayHeight()*3/4, 235 mDevice.getDisplayWidth() / 2, 0, 30); 236 Thread.sleep(SHORT_TIMEOUT); 237 } 238 239 public void swipeDown() throws Exception { 240 mDevice.swipe(mDevice.getDisplayWidth() / 2, 0, mDevice.getDisplayWidth() / 2, 241 mDevice.getDisplayHeight() / 2 + 50, 20); 242 Thread.sleep(SHORT_TIMEOUT); 243 } 244 245 public void unlockScreen() throws Exception { 246 KeyguardManager myKM = (KeyguardManager) mContext 247 .getSystemService(Context.KEYGUARD_SERVICE); 248 if (myKM.inKeyguardRestrictedInputMode()) { 249 // it is locked 250 swipeUp(); 251 } 252 } 253 254 public void showAppNotificationSettings(Context context) throws Exception { 255 Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); 256 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 257 intent.putExtra(Settings.EXTRA_APP_PACKAGE, mContext.getPackageName()); 258 intent.putExtra(Settings.EXTRA_APP_UID, mContext.getApplicationInfo().uid); 259 context.startActivity(intent); 260 Thread.sleep(LONG_TIMEOUT * 2); 261 } 262 263 public void sendNotificationsWithInlineReply(int notificationId, boolean isHeadsUp) { 264 Notification.Action action = new Notification.Action.Builder( 265 R.drawable.stat_notify_email, "Reply", ToastService.getPendingIntent(mContext, 266 "inline reply test")) 267 .addRemoteInput(new RemoteInput.Builder(KEY_QUICK_REPLY_TEXT) 268 .setLabel("Quick reply").build()) 269 .build(); 270 Notification.Builder n = new Notification.Builder(mContext) 271 .setContentTitle(Integer.toString(notificationId)) 272 .setContentText("INLINE REPLY TEST") 273 .setWhen(System.currentTimeMillis()) 274 .setSmallIcon(R.drawable.stat_notify_email) 275 .addAction(action); 276 if (isHeadsUp) { 277 n.setPriority(Notification.PRIORITY_HIGH) 278 .setDefaults(Notification.DEFAULT_VIBRATE); 279 } 280 mNotificationManager.notify(notificationId, n.build()); 281 } 282 283 public NotificationChannel getDefaultChannel() { 284 return mNotificationManager.getNotificationChannel(NotificationChannel.DEFAULT_CHANNEL_ID); 285 } 286 287 public NotificationChannel getChannel(boolean buzz) { 288 String id = (buzz ? BUZZY_CHANNEL_ID : QUIET_CHANNEL_ID); 289 String name = (buzz ? "This channel is buzzy" : "This channel is quiet"); 290 int importance = (buzz ? IMPORTANCE_DEFAULT : IMPORTANCE_LOW); 291 292 NotificationChannel channel = (buzz ? mBuzzyChannel : mQuietChannel); 293 if (channel == null) { 294 channel = mNotificationManager.getNotificationChannel(id); 295 } 296 if (channel == null){ 297 channel = new NotificationChannel(id, name, importance); 298 if (buzz) { 299 channel.enableVibration(true); 300 channel.setSound(null, null); 301 } 302 mNotificationManager.createNotificationChannel(channel); 303 } 304 return channel; 305 } 306 307 public static class ToastService extends IntentService { 308 private static final String TAG = "ToastService"; 309 private static final String ACTION_TOAST = "toast"; 310 private Handler handler; 311 312 public ToastService() { 313 super(TAG); 314 } 315 316 public ToastService(String name) { 317 super(name); 318 } 319 320 @Override 321 public int onStartCommand(Intent intent, int flags, int startId) { 322 handler = new Handler(); 323 return super.onStartCommand(intent, flags, startId); 324 } 325 326 @Override 327 protected void onHandleIntent(Intent intent) { 328 if (intent.hasExtra("text")) { 329 final String text = intent.getStringExtra("text"); 330 handler.post(new Runnable() { 331 @Override 332 public void run() { 333 Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show(); 334 Log.v(TAG, "toast " + text); 335 } 336 }); 337 } 338 } 339 340 public static PendingIntent getPendingIntent(Context context, String text) { 341 Intent toastIntent = new Intent(context, ToastService.class); 342 toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 343 toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message 344 toastIntent.putExtra("text", text); 345 PendingIntent pi = PendingIntent.getService( 346 context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT); 347 return pi; 348 } 349 } 350 } 351