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.AllInOneActivity;
     20 import com.android.calendar.AsyncQueryService;
     21 import com.android.calendar.R;
     22 import com.android.calendar.Utils;
     23 
     24 import android.app.Activity;
     25 import android.app.AlarmManager;
     26 import android.app.NotificationManager;
     27 import android.app.PendingIntent;
     28 import android.content.ContentUris;
     29 import android.content.ContentValues;
     30 import android.content.Context;
     31 import android.content.Intent;
     32 import android.database.Cursor;
     33 import android.net.Uri;
     34 import android.net.Uri.Builder;
     35 import android.os.Bundle;
     36 import android.provider.CalendarContract;
     37 import android.provider.CalendarContract.CalendarAlerts;
     38 import android.util.Log;
     39 import android.view.View;
     40 import android.view.View.OnClickListener;
     41 import android.widget.AdapterView;
     42 import android.widget.AdapterView.OnItemClickListener;
     43 import android.widget.Button;
     44 import android.widget.ListView;
     45 
     46 /**
     47  * The alert panel that pops up when there is a calendar event alarm.
     48  * This activity is started by an intent that specifies an event id.
     49   */
     50 public class AlertActivity extends Activity implements OnClickListener {
     51     private static final String TAG = "AlertActivity";
     52 
     53     // The default snooze delay: 5 minutes
     54     public static final long SNOOZE_DELAY = 5 * 60 * 1000L;
     55 
     56     private static final String[] PROJECTION = new String[] {
     57         CalendarAlerts._ID,              // 0
     58         CalendarAlerts.TITLE,            // 1
     59         CalendarAlerts.EVENT_LOCATION,   // 2
     60         CalendarAlerts.ALL_DAY,          // 3
     61         CalendarAlerts.BEGIN,            // 4
     62         CalendarAlerts.END,              // 5
     63         CalendarAlerts.EVENT_ID,         // 6
     64         CalendarAlerts.CALENDAR_COLOR,            // 7
     65         CalendarAlerts.RRULE,            // 8
     66         CalendarAlerts.HAS_ALARM,        // 9
     67         CalendarAlerts.STATE,            // 10
     68         CalendarAlerts.ALARM_TIME,       // 11
     69     };
     70 
     71     public static final int INDEX_ROW_ID = 0;
     72     public static final int INDEX_TITLE = 1;
     73     public static final int INDEX_EVENT_LOCATION = 2;
     74     public static final int INDEX_ALL_DAY = 3;
     75     public static final int INDEX_BEGIN = 4;
     76     public static final int INDEX_END = 5;
     77     public static final int INDEX_EVENT_ID = 6;
     78     public static final int INDEX_COLOR = 7;
     79     public static final int INDEX_RRULE = 8;
     80     public static final int INDEX_HAS_ALARM = 9;
     81     public static final int INDEX_STATE = 10;
     82     public static final int INDEX_ALARM_TIME = 11;
     83 
     84     private static final String SELECTION = CalendarAlerts.STATE + "=?";
     85     private static final String[] SELECTIONARG = new String[] {
     86         Integer.toString(CalendarAlerts.STATE_FIRED)
     87     };
     88 
     89     // We use one notification id for all events so that we don't clutter
     90     // the notification screen.  It doesn't matter what the id is, as long
     91     // as it is used consistently everywhere.
     92     public static final int NOTIFICATION_ID = 0;
     93 
     94     private AlertAdapter mAdapter;
     95     private QueryHandler mQueryHandler;
     96     private Cursor mCursor;
     97     private ListView mListView;
     98     private Button mSnoozeAllButton;
     99     private Button mDismissAllButton;
    100 
    101 
    102     private void dismissFiredAlarms() {
    103         ContentValues values = new ContentValues(1 /* size */);
    104         values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
    105         String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED;
    106         mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
    107                 selection, null /* selectionArgs */, Utils.UNDO_DELAY);
    108     }
    109 
    110     private void dismissAlarm(long id) {
    111         ContentValues values = new ContentValues(1 /* size */);
    112         values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
    113         String selection = CalendarAlerts._ID + "=" + id;
    114         mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
    115                 selection, null /* selectionArgs */, Utils.UNDO_DELAY);
    116     }
    117 
    118     private class QueryHandler extends AsyncQueryService {
    119         public QueryHandler(Context context) {
    120             super(context);
    121         }
    122 
    123         @Override
    124         protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
    125             // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
    126             if (!isFinishing()) {
    127                 mCursor = cursor;
    128                 mAdapter.changeCursor(cursor);
    129                 mListView.setSelection(cursor.getCount() - 1);
    130 
    131                 // The results are in, enable the buttons
    132                 mSnoozeAllButton.setEnabled(true);
    133                 mDismissAllButton.setEnabled(true);
    134             } else {
    135                 cursor.close();
    136             }
    137         }
    138 
    139         @Override
    140         protected void onInsertComplete(int token, Object cookie, Uri uri) {
    141             if (uri != null) {
    142                 Long alarmTime = (Long) cookie;
    143 
    144                 if (alarmTime != 0) {
    145                     // Set a new alarm to go off after the snooze delay.
    146                     // TODO make provider schedule this automatically when
    147                     // inserting an alarm
    148                     AlarmManager alarmManager =
    149                             (AlarmManager) getSystemService(Context.ALARM_SERVICE);
    150                     scheduleAlarm(AlertActivity.this, alarmManager, alarmTime);
    151                 }
    152             }
    153         }
    154 
    155         @Override
    156         protected void onUpdateComplete(int token, Object cookie, int result) {
    157             // Ignore
    158         }
    159     }
    160 
    161     /**
    162      * Schedules an alarm intent with the system AlarmManager that will notify
    163      * listeners when a reminder should be fired. The provider will keep
    164      * scheduled reminders up to date but apps may use this to implement snooze
    165      * functionality without modifying the reminders table. Scheduled alarms
    166      * will generate an intent using {@link #ACTION_EVENT_REMINDER}.
    167      *
    168      * @param context A context for referencing system resources
    169      * @param manager The AlarmManager to use or null
    170      * @param alarmTime The time to fire the intent in UTC millis since epoch
    171      */
    172     public static void scheduleAlarm(Context context, AlarmManager manager, long alarmTime) {
    173 
    174         if (manager == null) {
    175             manager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
    176         }
    177 
    178         Intent intent = new Intent(CalendarContract.ACTION_EVENT_REMINDER);
    179         intent.setData(ContentUris.withAppendedId(CalendarContract.CONTENT_URI, alarmTime));
    180         intent.putExtra(CalendarContract.CalendarAlerts.ALARM_TIME, alarmTime);
    181         PendingIntent pi = PendingIntent.getBroadcast(context, 0, intent, 0);
    182         manager.set(AlarmManager.RTC_WAKEUP, alarmTime, pi);
    183     }
    184 
    185     private static ContentValues makeContentValues(long eventId, long begin, long end,
    186             long alarmTime, int minutes) {
    187         ContentValues values = new ContentValues();
    188         values.put(CalendarAlerts.EVENT_ID, eventId);
    189         values.put(CalendarAlerts.BEGIN, begin);
    190         values.put(CalendarAlerts.END, end);
    191         values.put(CalendarAlerts.ALARM_TIME, alarmTime);
    192         long currentTime = System.currentTimeMillis();
    193         values.put(CalendarAlerts.CREATION_TIME, currentTime);
    194         values.put(CalendarAlerts.RECEIVED_TIME, 0);
    195         values.put(CalendarAlerts.NOTIFY_TIME, 0);
    196         values.put(CalendarAlerts.STATE, CalendarAlerts.STATE_SCHEDULED);
    197         values.put(CalendarAlerts.MINUTES, minutes);
    198         return values;
    199     }
    200 
    201     private OnItemClickListener mViewListener = new OnItemClickListener() {
    202 
    203         public void onItemClick(AdapterView<?> parent, View view, int position,
    204                 long i) {
    205             AlertActivity alertActivity = AlertActivity.this;
    206             Cursor cursor = alertActivity.getItemForView(view);
    207 
    208             // Mark this alarm as DISMISSED
    209             dismissAlarm(cursor.getLong(INDEX_ROW_ID));
    210 
    211             long id = cursor.getInt(AlertActivity.INDEX_EVENT_ID);
    212             long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
    213             long endMillis = cursor.getLong(AlertActivity.INDEX_END);
    214             Intent eventIntent = new Intent(Intent.ACTION_VIEW);
    215             Builder builder = CalendarContract.CONTENT_URI.buildUpon();
    216             builder.appendEncodedPath("events/" + id);
    217             eventIntent.setData(builder.build());
    218             eventIntent.setClass(AlertActivity.this, AllInOneActivity.class);
    219             eventIntent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, startMillis);
    220             eventIntent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endMillis);
    221             alertActivity.startActivity(eventIntent);
    222 
    223             alertActivity.finish();
    224         }
    225     };
    226 
    227     @Override
    228     protected void onCreate(Bundle icicle) {
    229         super.onCreate(icicle);
    230 
    231         setContentView(R.layout.alert_activity);
    232         setTitle(R.string.alert_title);
    233 
    234         mQueryHandler = new QueryHandler(this);
    235         mAdapter = new AlertAdapter(this, R.layout.alert_item);
    236 
    237         mListView = (ListView) findViewById(R.id.alert_container);
    238         mListView.setItemsCanFocus(true);
    239         mListView.setAdapter(mAdapter);
    240         mListView.setOnItemClickListener(mViewListener);
    241 
    242         mSnoozeAllButton = (Button) findViewById(R.id.snooze_all);
    243         mSnoozeAllButton.setOnClickListener(this);
    244         mDismissAllButton = (Button) findViewById(R.id.dismiss_all);
    245         mDismissAllButton.setOnClickListener(this);
    246 
    247         // Disable the buttons, since they need mCursor, which is created asynchronously
    248         mSnoozeAllButton.setEnabled(false);
    249         mDismissAllButton.setEnabled(false);
    250     }
    251 
    252     @Override
    253     protected void onResume() {
    254         super.onResume();
    255 
    256         // If the cursor is null, start the async handler. If it is not null just requery.
    257         if (mCursor == null) {
    258             Uri uri = CalendarAlerts.CONTENT_URI_BY_INSTANCE;
    259             mQueryHandler.startQuery(0, null, uri, PROJECTION, SELECTION,
    260                     SELECTIONARG, CalendarAlerts.DEFAULT_SORT_ORDER);
    261         } else {
    262             if (!mCursor.requery()) {
    263                 Log.w(TAG, "Cursor#requery() failed.");
    264                 mCursor.close();
    265                 mCursor = null;
    266             }
    267         }
    268     }
    269 
    270     @Override
    271     protected void onStop() {
    272         super.onStop();
    273         AlertService.updateAlertNotification(this);
    274 
    275         if (mCursor != null) {
    276             mCursor.deactivate();
    277         }
    278     }
    279 
    280     @Override
    281     protected void onDestroy() {
    282         super.onDestroy();
    283         if (mCursor != null) {
    284             mCursor.close();
    285         }
    286     }
    287 
    288     @Override
    289     public void onClick(View v) {
    290         if (v == mSnoozeAllButton) {
    291             long alarmTime = System.currentTimeMillis() + SNOOZE_DELAY;
    292 
    293             NotificationManager nm =
    294                 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    295             nm.cancel(NOTIFICATION_ID);
    296 
    297             if (mCursor != null) {
    298                 long scheduleAlarmTime = 0;
    299                 mCursor.moveToPosition(-1);
    300                 while (mCursor.moveToNext()) {
    301                     long eventId = mCursor.getLong(INDEX_EVENT_ID);
    302                     long begin = mCursor.getLong(INDEX_BEGIN);
    303                     long end = mCursor.getLong(INDEX_END);
    304 
    305                     // Set the "minutes" to zero to indicate this is a snoozed
    306                     // alarm.  There is code in AlertService.java that checks
    307                     // this field.
    308                     ContentValues values =
    309                             makeContentValues(eventId, begin, end, alarmTime, 0 /* minutes */);
    310 
    311                     // Create a new alarm entry in the CalendarAlerts table
    312                     if (mCursor.isLast()) {
    313                         scheduleAlarmTime = alarmTime;
    314                     }
    315                     mQueryHandler.startInsert(0,
    316                             scheduleAlarmTime, CalendarAlerts.CONTENT_URI, values,
    317                             Utils.UNDO_DELAY);
    318                 }
    319             } else {
    320                 Log.d(TAG, "Cursor object is null. Ignore the Snooze request.");
    321             }
    322 
    323             dismissFiredAlarms();
    324             finish();
    325         } else if (v == mDismissAllButton) {
    326             NotificationManager nm =
    327                 (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
    328             nm.cancel(NOTIFICATION_ID);
    329 
    330             dismissFiredAlarms();
    331 
    332             finish();
    333         }
    334     }
    335 
    336     public boolean isEmpty() {
    337         return mCursor != null ? (mCursor.getCount() == 0) : true;
    338     }
    339 
    340     public Cursor getItemForView(View view) {
    341         final int index = mListView.getPositionForView(view);
    342         if (index < 0) {
    343             return null;
    344         }
    345         return (Cursor) mListView.getAdapter().getItem(index);
    346     }
    347 }
    348