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
17package android.app;
18
19import android.content.ContentResolver;
20import android.content.Context;
21import android.content.Intent;
22import android.database.Cursor;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Typeface;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.Parcel;
32import android.os.SystemClock;
33import android.provider.ContactsContract;
34import android.test.AndroidTestCase;
35import android.test.suitebuilder.annotation.Suppress;
36import android.text.SpannableStringBuilder;
37import android.text.TextUtils;
38import android.text.style.StyleSpan;
39import android.util.Log;
40import android.view.View;
41import android.widget.Toast;
42
43import java.lang.reflect.Method;
44import java.lang.InterruptedException;
45import java.lang.NoSuchMethodError;
46import java.lang.NoSuchMethodException;
47import java.util.ArrayList;
48
49import com.android.frameworks.tests.notification.R;
50
51public class NotificationTests extends AndroidTestCase {
52    private static final String TAG = "NOTEST";
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
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
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;
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
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
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
191        public ToastService() {
192            super(TAG);
193        }
194        public ToastService(String name) {
195            super(name);
196        }
197
198        @Override
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
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
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
230    public static void sleepIfYouCan(int ms) {
231        try {
232            Thread.sleep(ms);
233        } catch (InterruptedException e) {}
234    }
235
236    @Override
237    public void setUp() throws Exception {
238        super.setUp();
239    }
240
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
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
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
479        public void dismiss(View v) {
480            finish();
481        }
482
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