Home | History | Annotate | Download | only in alerts
      1 /*
      2  * Copyright (C) 2007 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.calendar.alerts;
     18 
     19 import com.android.calendar.R;
     20 import com.android.calendar.Utils;
     21 
     22 import android.app.Notification;
     23 import android.app.Notification.Builder;
     24 import android.app.PendingIntent;
     25 import android.app.Service;
     26 import android.content.BroadcastReceiver;
     27 import android.content.Context;
     28 import android.content.Intent;
     29 import android.content.res.Resources;
     30 import android.net.Uri;
     31 import android.os.PowerManager;
     32 import android.text.TextUtils;
     33 import android.text.format.DateFormat;
     34 import android.text.format.DateUtils;
     35 import android.text.format.Time;
     36 import android.util.Log;
     37 
     38 import java.util.Locale;
     39 import java.util.TimeZone;
     40 
     41 /**
     42  * Receives android.intent.action.EVENT_REMINDER intents and handles
     43  * event reminders.  The intent URI specifies an alert id in the
     44  * CalendarAlerts database table.  This class also receives the
     45  * BOOT_COMPLETED intent so that it can add a status bar notification
     46  * if there are Calendar event alarms that have not been dismissed.
     47  * It also receives the TIME_CHANGED action so that it can fire off
     48  * snoozed alarms that have become ready.  The real work is done in
     49  * the AlertService class.
     50  *
     51  * To trigger this code after pushing the apk to device:
     52  * adb shell am broadcast -a "android.intent.action.EVENT_REMINDER"
     53  *    -n "com.android.calendar/.alerts.AlertReceiver"
     54  */
     55 public class AlertReceiver extends BroadcastReceiver {
     56     private static final String TAG = "AlertReceiver";
     57 
     58     private static final String DELETE_ACTION = "delete";
     59 
     60     static final Object mStartingServiceSync = new Object();
     61     static PowerManager.WakeLock mStartingService;
     62 
     63     public static final String ACTION_DISMISS_OLD_REMINDERS = "removeOldReminders";
     64 
     65     @Override
     66     public void onReceive(Context context, Intent intent) {
     67         if (AlertService.DEBUG) {
     68             Log.d(TAG, "onReceive: a=" + intent.getAction() + " " + intent.toString());
     69         }
     70 
     71         if (DELETE_ACTION.equals(intent.getAction())) {
     72 
     73             /* The user has clicked the "Clear All Notifications"
     74              * buttons so dismiss all Calendar alerts.
     75              */
     76             // TODO Grab a wake lock here?
     77             Intent serviceIntent = new Intent(context, DismissAllAlarmsService.class);
     78             context.startService(serviceIntent);
     79         } else {
     80             Intent i = new Intent();
     81             i.setClass(context, AlertService.class);
     82             i.putExtras(intent);
     83             i.putExtra("action", intent.getAction());
     84             Uri uri = intent.getData();
     85 
     86             // This intent might be a BOOT_COMPLETED so it might not have a Uri.
     87             if (uri != null) {
     88                 i.putExtra("uri", uri.toString());
     89             }
     90             beginStartingService(context, i);
     91         }
     92     }
     93 
     94     /**
     95      * Start the service to process the current event notifications, acquiring
     96      * the wake lock before returning to ensure that the service will run.
     97      */
     98     public static void beginStartingService(Context context, Intent intent) {
     99         synchronized (mStartingServiceSync) {
    100             if (mStartingService == null) {
    101                 PowerManager pm =
    102                     (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    103                 mStartingService = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
    104                         "StartingAlertService");
    105                 mStartingService.setReferenceCounted(false);
    106             }
    107             mStartingService.acquire();
    108             context.startService(intent);
    109         }
    110     }
    111 
    112     /**
    113      * Called back by the service when it has finished processing notifications,
    114      * releasing the wake lock if the service is now stopping.
    115      */
    116     public static void finishStartingService(Service service, int startId) {
    117         synchronized (mStartingServiceSync) {
    118             if (mStartingService != null) {
    119                 if (service.stopSelfResult(startId)) {
    120                     mStartingService.release();
    121                 }
    122             }
    123         }
    124     }
    125 
    126     /**
    127      * Creates an alert notification. If high priority, this will set
    128      * FLAG_HIGH_PRIORITY on the resulting notification and attach the a pending
    129      * intent. Otherwise, it creates a standard notification.
    130      */
    131     public static Notification makeNewAlertNotification(Context context,
    132             String title, String location, int numReminders,
    133             boolean highPriority, long startMillis, boolean allDay) {
    134         Resources res = context.getResources();
    135 
    136         // Create an intent triggered by clicking on the status icon.
    137         Intent clickIntent = new Intent();
    138         clickIntent.setClass(context, AlertActivity.class);
    139         clickIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    140 
    141         // Create an intent triggered by clicking on the "Clear All Notifications" button
    142         Intent deleteIntent = new Intent();
    143         deleteIntent.setClass(context, AlertReceiver.class);
    144         deleteIntent.setAction(DELETE_ACTION);
    145 
    146         if (title == null || title.length() == 0) {
    147             title = res.getString(R.string.no_title_label);
    148         }
    149 
    150         Builder bob = new Notification.Builder(context);
    151         bob.setContentTitle(title);
    152         bob.setSmallIcon(R.drawable.stat_notify_calendar);
    153 
    154         PendingIntent pendingClickIntent = PendingIntent.getActivity(
    155                 context, 0, clickIntent, 0);
    156         bob.setContentIntent(pendingClickIntent);
    157         bob.setDeleteIntent(PendingIntent.getBroadcast(context, 0, deleteIntent, 0));
    158         if (highPriority) {
    159             bob.setFullScreenIntent(pendingClickIntent, true);
    160         }
    161 
    162         if (numReminders > 1) {
    163             bob.setNumber(numReminders);
    164         }
    165 
    166         // Format the second line which shows time and location.
    167         //
    168         // 1) Show time only for non-all day events
    169         // 2) No date for today
    170         // 3) Show "tomorrow" for tomorrow
    171         // 4) Show date for days beyond that
    172 
    173         String tz = Utils.getTimeZone(context, null);
    174         Time time = new Time(tz);
    175         time.setToNow();
    176         int today = Time.getJulianDay(time.toMillis(false), time.gmtoff);
    177         time.set(startMillis);
    178         int eventDay = Time.getJulianDay(time.toMillis(false), time.gmtoff);
    179 
    180         int flags = DateUtils.FORMAT_ABBREV_ALL;
    181         if (!allDay) {
    182             flags |= DateUtils.FORMAT_SHOW_TIME;
    183             if (DateFormat.is24HourFormat(context)) {
    184                 flags |= DateUtils.FORMAT_24HOUR;
    185             }
    186         } else {
    187             flags |= DateUtils.FORMAT_UTC;
    188         }
    189 
    190         if (eventDay > today + 1) {
    191             flags |= DateUtils.FORMAT_SHOW_DATE;
    192         }
    193 
    194         StringBuilder sb = new StringBuilder(Utils.formatDateRange(context, startMillis,
    195                 startMillis, flags));
    196 
    197         if (!allDay && tz != Time.getCurrentTimezone()) {
    198             // Assumes time was set to the current tz
    199             time.set(startMillis);
    200             boolean isDST = time.isDst != 0;
    201             sb.append(" ").append(TimeZone.getTimeZone(tz).getDisplayName(
    202                     isDST, TimeZone.SHORT, Locale.getDefault()));
    203         }
    204 
    205         if (eventDay == today + 1) {
    206             // Tomorrow
    207             sb.append(", ");
    208             sb.append(context.getString(R.string.tomorrow));
    209         }
    210 
    211         String loc;
    212         if (location != null && !TextUtils.isEmpty(loc = location.trim())) {
    213             sb.append(", ");
    214             sb.append(loc);
    215         }
    216         bob.setContentText(sb.toString());
    217 
    218         return bob.getNotification();
    219     }
    220 }
    221 
    222