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