1 /*
2  * Copyright (C) 2015 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 android.app;
18 
19 import android.content.ContentResolver;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.database.Cursor;
23 import android.graphics.Bitmap;
24 import android.graphics.Canvas;
25 import android.graphics.Typeface;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.net.Uri;
29 import android.os.Bundle;
30 import android.os.Handler;
31 import android.os.Parcel;
32 import android.os.SystemClock;
33 import android.provider.ContactsContract;
34 import android.test.AndroidTestCase;
35 import android.test.suitebuilder.annotation.Suppress;
36 import android.text.SpannableStringBuilder;
37 import android.text.TextUtils;
38 import android.text.style.StyleSpan;
39 import android.util.Log;
40 import android.view.View;
41 import android.widget.Toast;
42 
43 import java.lang.reflect.Method;
44 import java.lang.InterruptedException;
45 import java.lang.NoSuchMethodError;
46 import java.lang.NoSuchMethodException;
47 import java.util.ArrayList;
48 
49 import com.android.frameworks.tests.notification.R;
50 
51 public class NotificationTests extends AndroidTestCase {
52     private static final String TAG = "NOTEST";
L(String msg, Object... args)53     public static void L(String msg, Object... args) {
54         Log.v(TAG, (args == null || args.length == 0) ? msg : String.format(msg, args));
55     }
56 
57     public static final String ACTION_CREATE = "create";
58     public static final int NOTIFICATION_ID = 31338;
59 
60     public static final boolean SHOW_PHONE_CALL = false;
61     public static final boolean SHOW_INBOX = true;
62     public static final boolean SHOW_BIG_TEXT = true;
63     public static final boolean SHOW_BIG_PICTURE = true;
64     public static final boolean SHOW_MEDIA = true;
65     public static final boolean SHOW_STOPWATCH = false;
66     public static final boolean SHOW_SOCIAL = false;
67     public static final boolean SHOW_CALENDAR = false;
68     public static final boolean SHOW_PROGRESS = false;
69 
getBitmap(Context context, int resId)70     private static Bitmap getBitmap(Context context, int resId) {
71         int largeIconWidth = (int) context.getResources()
72                 .getDimension(R.dimen.notification_large_icon_width);
73         int largeIconHeight = (int) context.getResources()
74                 .getDimension(R.dimen.notification_large_icon_height);
75         Drawable d = context.getResources().getDrawable(resId);
76         Bitmap b = Bitmap.createBitmap(largeIconWidth, largeIconHeight, Bitmap.Config.ARGB_8888);
77         Canvas c = new Canvas(b);
78         d.setBounds(0, 0, largeIconWidth, largeIconHeight);
79         d.draw(c);
80         return b;
81     }
82 
makeEmailIntent(Context context, String who)83     private static PendingIntent makeEmailIntent(Context context, String who) {
84         final Intent intent = new Intent(android.content.Intent.ACTION_SENDTO,
85                 Uri.parse("mailto:" + who));
86         return PendingIntent.getActivity(
87                 context, 0, intent,
88                 PendingIntent.FLAG_CANCEL_CURRENT);
89     }
90 
91     static final String[] LINES = new String[] {
92             "Uh oh",
93             "Getting kicked out of this room",
94             "I'll be back in 5-10 minutes.",
95             "And now \u2026 I have to find my shoes. \uD83D\uDC63",
96             "\uD83D\uDC5F \uD83D\uDC5F",
97             "\uD83D\uDC60 \uD83D\uDC60",
98     };
99     static final int MAX_LINES = 5;
makeBigTextNotification(Context context, int update, int id, long when)100     public static Notification makeBigTextNotification(Context context, int update, int id,
101             long when) {
102         String personUri = null;
103         /*
104         Cursor c = null;
105         try {
106             String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.LOOKUP_KEY };
107             String selections = ContactsContract.Contacts.DISPLAY_NAME + " = 'Mike Cleron'";
108             final ContentResolver contentResolver = context.getContentResolver();
109             c = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
110                     projection, selections, null, null);
111             if (c != null && c.getCount() > 0) {
112                 c.moveToFirst();
113                 int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
114                 int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
115                 String lookupKey = c.getString(lookupIdx);
116                 long contactId = c.getLong(idIdx);
117                 Uri lookupUri = ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
118                 personUri = lookupUri.toString();
119             }
120         } finally {
121             if (c != null) {
122                 c.close();
123             }
124         }
125         if (TextUtils.isEmpty(personUri)) {
126             Log.w(TAG, "failed to find contact for Mike Cleron");
127         } else {
128             Log.w(TAG, "Mike Cleron is " + personUri);
129         }
130         */
131 
132         StringBuilder longSmsText = new StringBuilder();
133         int end = 2 + update;
134         if (end > LINES.length) {
135             end = LINES.length;
136         }
137         final int start = Math.max(0, end - MAX_LINES);
138         for (int i=start; i<end; i++) {
139             if (i >= LINES.length) break;
140             if (i > start) longSmsText.append("\n");
141             longSmsText.append(LINES[i]);
142         }
143         if (update > 2) {
144             when = System.currentTimeMillis();
145         }
146         Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle()
147                 .bigText(longSmsText);
148         Notification bigText = new Notification.Builder(context)
149                 .setContentTitle("Mike Cleron")
150                 .setContentIntent(ToastService.getPendingIntent(context, "Clicked on bigText"))
151                 .setContentText(longSmsText)
152                         //.setTicker("Mike Cleron: " + longSmsText)
153                 .setWhen(when)
154                 .setLargeIcon(getBitmap(context, R.drawable.bucket))
155                 .setPriority(Notification.PRIORITY_HIGH)
156                 .setNumber(update)
157                 .setSmallIcon(R.drawable.stat_notify_talk_text)
158                 .setStyle(bigTextStyle)
159                 .setDefaults(Notification.DEFAULT_SOUND)
160                 .addPerson(personUri)
161                 .build();
162         return bigText;
163     }
164 
makeUploadNotification(Context context, int progress, long when)165     public static Notification makeUploadNotification(Context context, int progress, long when) {
166         Notification.Builder uploadNotification = new Notification.Builder(context)
167                 .setContentTitle("File Upload")
168                 .setContentText("foo.txt")
169                 .setPriority(Notification.PRIORITY_MIN)
170                 .setContentIntent(ToastService.getPendingIntent(context, "Clicked on Upload"))
171                 .setWhen(when)
172                 .setSmallIcon(R.drawable.ic_menu_upload)
173                 .setProgress(100, Math.min(progress, 100), false);
174         return uploadNotification.build();
175     }
176 
BOLD(CharSequence str)177     static SpannableStringBuilder BOLD(CharSequence str) {
178         final SpannableStringBuilder ssb = new SpannableStringBuilder(str);
179         ssb.setSpan(new StyleSpan(Typeface.BOLD), 0, ssb.length(), 0);
180         return ssb;
181     }
182 
183     public static class ToastService extends IntentService {
184 
185         private static final String TAG = "ToastService";
186 
187         private static final String ACTION_TOAST = "toast";
188 
189         private Handler handler;
190 
ToastService()191         public ToastService() {
192             super(TAG);
193         }
ToastService(String name)194         public ToastService(String name) {
195             super(name);
196         }
197 
198         @Override
onStartCommand(Intent intent, int flags, int startId)199         public int onStartCommand(Intent intent, int flags, int startId) {
200             handler = new Handler();
201             return super.onStartCommand(intent, flags, startId);
202         }
203 
204         @Override
onHandleIntent(Intent intent)205         protected void onHandleIntent(Intent intent) {
206             Log.v(TAG, "clicked a thing! intent=" + intent.toString());
207             if (intent.hasExtra("text")) {
208                 final String text = intent.getStringExtra("text");
209                 handler.post(new Runnable() {
210                     @Override
211                     public void run() {
212                         Toast.makeText(ToastService.this, text, Toast.LENGTH_LONG).show();
213                         Log.v(TAG, "toast " + text);
214                     }
215                 });
216             }
217         }
218 
getPendingIntent(Context context, String text)219         public static PendingIntent getPendingIntent(Context context, String text) {
220             Intent toastIntent = new Intent(context, ToastService.class);
221             toastIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
222             toastIntent.setAction(ACTION_TOAST + ":" + text); // one per toast message
223             toastIntent.putExtra("text", text);
224             PendingIntent pi = PendingIntent.getService(
225                     context, 58, toastIntent, PendingIntent.FLAG_UPDATE_CURRENT);
226             return pi;
227         }
228     }
229 
sleepIfYouCan(int ms)230     public static void sleepIfYouCan(int ms) {
231         try {
232             Thread.sleep(ms);
233         } catch (InterruptedException e) {}
234     }
235 
236     @Override
setUp()237     public void setUp() throws Exception {
238         super.setUp();
239     }
240 
summarize(Notification n)241     public static String summarize(Notification n) {
242         return String.format("<notif title=\"%s\" icon=0x%08x view=%s>",
243                 n.extras.get(Notification.EXTRA_TITLE),
244                 n.icon,
245                 String.valueOf(n.contentView));
246     }
247 
testCreate()248     public void testCreate() throws Exception {
249         ArrayList<Notification> mNotifications = new ArrayList<Notification>();
250         NotificationManager noMa =
251                 (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
252 
253         L("Constructing notifications...");
254         if (SHOW_BIG_TEXT) {
255             int bigtextId = mNotifications.size();
256             final long time = SystemClock.currentThreadTimeMillis();
257             final Notification n = makeBigTextNotification(mContext, 0, bigtextId, System.currentTimeMillis());
258             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
259             mNotifications.add(n);
260         }
261 
262         int uploadId = mNotifications.size();
263         long uploadWhen = System.currentTimeMillis();
264 
265         if (SHOW_PROGRESS) {
266             mNotifications.add(makeUploadNotification(mContext, 0, uploadWhen));
267         }
268 
269         if (SHOW_PHONE_CALL) {
270             int phoneId = mNotifications.size();
271             final PendingIntent fullscreenIntent
272                     = FullScreenActivity.getPendingIntent(mContext, phoneId);
273             final long time = SystemClock.currentThreadTimeMillis();
274             Notification phoneCall = new Notification.Builder(mContext)
275                     .setContentTitle("Incoming call")
276                     .setContentText("Matias Duarte")
277                     .setLargeIcon(getBitmap(mContext, R.drawable.matias_hed))
278                     .setSmallIcon(R.drawable.stat_sys_phone_call)
279                     .setDefaults(Notification.DEFAULT_SOUND)
280                     .setPriority(Notification.PRIORITY_MAX)
281                     .setContentIntent(fullscreenIntent)
282                     .setFullScreenIntent(fullscreenIntent, true)
283                     .addAction(R.drawable.ic_dial_action_call, "Answer",
284                             ToastService.getPendingIntent(mContext, "Clicked on Answer"))
285                     .addAction(R.drawable.ic_end_call, "Ignore",
286                             ToastService.getPendingIntent(mContext, "Clicked on Ignore"))
287                     .setOngoing(true)
288                     .addPerson(Uri.fromParts("tel", "1 (617) 555-1212", null).toString())
289                     .build();
290             L("  %s: create=%dms", phoneCall.toString(), SystemClock.currentThreadTimeMillis() - time);
291             mNotifications.add(phoneCall);
292         }
293 
294         if (SHOW_STOPWATCH) {
295             final long time = SystemClock.currentThreadTimeMillis();
296             final Notification n = new Notification.Builder(mContext)
297                     .setContentTitle("Stopwatch PRO")
298                     .setContentText("Counting up")
299                     .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Stopwatch"))
300                     .setSmallIcon(R.drawable.stat_notify_alarm)
301                     .setUsesChronometer(true)
302                     .build();
303             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
304             mNotifications.add(n);
305         }
306 
307         if (SHOW_CALENDAR) {
308             final long time = SystemClock.currentThreadTimeMillis();
309             final Notification n = new Notification.Builder(mContext)
310                     .setContentTitle("J Planning")
311                     .setContentText("The Botcave")
312                     .setWhen(System.currentTimeMillis())
313                     .setSmallIcon(R.drawable.stat_notify_calendar)
314                     .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on calendar event"))
315                     .setContentInfo("7PM")
316                     .addAction(R.drawable.stat_notify_snooze, "+10 min",
317                             ToastService.getPendingIntent(mContext, "snoozed 10 min"))
318                     .addAction(R.drawable.stat_notify_snooze_longer, "+1 hour",
319                             ToastService.getPendingIntent(mContext, "snoozed 1 hr"))
320                     .addAction(R.drawable.stat_notify_email, "Email",
321                             ToastService.getPendingIntent(mContext,
322                                     "Congratulations, you just destroyed someone's inbox zero"))
323                     .build();
324             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
325             mNotifications.add(n);
326         }
327 
328         if (SHOW_BIG_PICTURE) {
329             BitmapDrawable d =
330                     (BitmapDrawable) mContext.getResources().getDrawable(R.drawable.romainguy_rockaway);
331             final long time = SystemClock.currentThreadTimeMillis();
332             final Notification n = new Notification.Builder(mContext)
333                         .setContentTitle("Romain Guy")
334                         .setContentText("I was lucky to find a Canon 5D Mk III at a local Bay Area "
335                                 + "store last week but I had not been able to try it in the field "
336                                 + "until tonight. After a few days of rain the sky finally cleared "
337                                 + "up. Rockaway Beach did not disappoint and I was finally able to "
338                                 + "see what my new camera feels like when shooting landscapes.")
339                         .setSmallIcon(android.R.drawable.stat_notify_chat)
340                         .setContentIntent(
341                                 ToastService.getPendingIntent(mContext, "Clicked picture"))
342                         .setLargeIcon(getBitmap(mContext, R.drawable.romainguy_hed))
343                         .addAction(R.drawable.add, "Add to Gallery",
344                                 ToastService.getPendingIntent(mContext, "Added"))
345                         .setStyle(new Notification.BigPictureStyle()
346                                 .bigPicture(d.getBitmap()))
347                         .build();
348             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
349             mNotifications.add(n);
350         }
351 
352         if (SHOW_INBOX) {
353             final long time = SystemClock.currentThreadTimeMillis();
354             final Notification n = new Notification.Builder(mContext)
355                     .setContentTitle("New mail")
356                     .setContentText("3 new messages")
357                     .setSubText("example@gmail.com")
358                     .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Mail"))
359                     .setSmallIcon(R.drawable.stat_notify_email)
360                     .setStyle(new Notification.InboxStyle()
361                                     .setSummaryText("example@gmail.com")
362                                     .addLine(BOLD("Alice:").append(" hey there!"))
363                                     .addLine(BOLD("Bob:").append(" hi there!"))
364                                     .addLine(BOLD("Charlie:").append(" Iz IN UR EMAILZ!!"))
365                     ).build();
366             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
367             mNotifications.add(n);
368         }
369 
370         if (SHOW_SOCIAL) {
371             final long time = SystemClock.currentThreadTimeMillis();
372             final Notification n = new Notification.Builder(mContext)
373                     .setContentTitle("Social Network")
374                     .setContentText("You were mentioned in a post")
375                     .setContentInfo("example@gmail.com")
376                     .setContentIntent(ToastService.getPendingIntent(mContext, "Clicked on Social"))
377                     .setSmallIcon(android.R.drawable.stat_notify_chat)
378                     .setPriority(Notification.PRIORITY_LOW)
379                     .build();
380             L("  %s: create=%dms", summarize(n), SystemClock.currentThreadTimeMillis() - time);
381             mNotifications.add(n);
382         }
383 
384         L("Posting notifications...");
385         for (int i=0; i<mNotifications.size(); i++) {
386             final int count = 4;
387             for (int j=0; j<count; j++) {
388                 long time = SystemClock.currentThreadTimeMillis();
389                 final Notification n = mNotifications.get(i);
390                 noMa.notify(NOTIFICATION_ID + i, n);
391                 time = SystemClock.currentThreadTimeMillis() - time;
392                 L("  %s: notify=%dms (%d/%d)", summarize(n), time,
393                         j + 1, count);
394                 sleepIfYouCan(150);
395             }
396         }
397 
398         sleepIfYouCan(1000);
399 
400         L("Canceling notifications...");
401         for (int i=0; i<mNotifications.size(); i++) {
402             final Notification n = mNotifications.get(i);
403             long time = SystemClock.currentThreadTimeMillis();
404             noMa.cancel(NOTIFICATION_ID + i);
405             time = SystemClock.currentThreadTimeMillis() - time;
406             L("  %s: cancel=%dms", summarize(n), time);
407         }
408 
409         sleepIfYouCan(500);
410 
411         L("Parceling notifications...");
412         // we want to be able to use this test on older OSes that do not have getBlobAshmemSize
413         Method getBlobAshmemSize = null;
414         try {
415             getBlobAshmemSize = Parcel.class.getMethod("getBlobAshmemSize");
416         } catch (NoSuchMethodException ex) {
417         }
418         for (int i=0; i<mNotifications.size(); i++) {
419             Parcel p = Parcel.obtain();
420             {
421                 final Notification n = mNotifications.get(i);
422                 long time = SystemClock.currentThreadTimeMillis();
423                 n.writeToParcel(p, 0);
424                 time = SystemClock.currentThreadTimeMillis() - time;
425                 L("  %s: write parcel=%dms size=%d ashmem=%s",
426                         summarize(n), time, p.dataPosition(),
427                         (getBlobAshmemSize != null)
428                             ? getBlobAshmemSize.invoke(p)
429                             : "???");
430                 p.setDataPosition(0);
431             }
432 
433             long time = SystemClock.currentThreadTimeMillis();
434             final Notification n2 = Notification.CREATOR.createFromParcel(p);
435             time = SystemClock.currentThreadTimeMillis() - time;
436             L("  %s: parcel read=%dms", summarize(n2), time);
437 
438             time = SystemClock.currentThreadTimeMillis();
439             noMa.notify(NOTIFICATION_ID + i, n2);
440             time = SystemClock.currentThreadTimeMillis() - time;
441             L("  %s: notify=%dms", summarize(n2), time);
442         }
443 
444         sleepIfYouCan(500);
445 
446         L("Canceling notifications...");
447         for (int i=0; i<mNotifications.size(); i++) {
448             long time = SystemClock.currentThreadTimeMillis();
449             final Notification n = mNotifications.get(i);
450             noMa.cancel(NOTIFICATION_ID + i);
451             time = SystemClock.currentThreadTimeMillis() - time;
452             L("  %s: cancel=%dms", summarize(n), time);
453         }
454 
455 
456 //            if (SHOW_PROGRESS) {
457 //                ProgressService.startProgressUpdater(this, uploadId, uploadWhen, 0);
458 //            }
459     }
460 
461     public static class FullScreenActivity extends Activity {
462         public static final String EXTRA_ID = "id";
463 
464         @Override
onCreate(Bundle savedInstanceState)465         public void onCreate(Bundle savedInstanceState) {
466             super.onCreate(savedInstanceState);
467             setContentView(R.layout.full_screen);
468             final Intent intent = getIntent();
469             if (intent != null && intent.hasExtra(EXTRA_ID)) {
470                 final int id = intent.getIntExtra(EXTRA_ID, -1);
471                 if (id >= 0) {
472                     NotificationManager noMa =
473                             (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
474                     noMa.cancel(NOTIFICATION_ID + id);
475                 }
476             }
477         }
478 
dismiss(View v)479         public void dismiss(View v) {
480             finish();
481         }
482 
getPendingIntent(Context context, int id)483         public static PendingIntent getPendingIntent(Context context, int id) {
484             Intent fullScreenIntent = new Intent(context, FullScreenActivity.class);
485             fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
486 
487             fullScreenIntent.putExtra(EXTRA_ID, id);
488             PendingIntent pi = PendingIntent.getActivity(
489                     context, 22, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT);
490             return pi;
491         }
492     }
493 }
494 
495