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