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;
18
19import static android.provider.Calendar.EVENT_BEGIN_TIME;
20import static android.provider.Calendar.EVENT_END_TIME;
21
22import android.app.Activity;
23import android.app.AlarmManager;
24import android.app.NotificationManager;
25import android.content.AsyncQueryHandler;
26import android.content.ContentResolver;
27import android.content.ContentUris;
28import android.content.ContentValues;
29import android.content.Context;
30import android.content.Intent;
31import android.content.res.TypedArray;
32import android.database.Cursor;
33import android.net.Uri;
34import android.os.Bundle;
35import android.provider.Calendar.CalendarAlerts;
36import android.provider.Calendar.CalendarAlertsColumns;
37import android.provider.Calendar.Events;
38import android.view.View;
39import android.view.ViewGroup;
40import android.view.WindowManager;
41import android.view.View.OnClickListener;
42import android.widget.AdapterView;
43import android.widget.Button;
44import android.widget.ListView;
45import android.widget.AdapterView.OnItemClickListener;
46
47/**
48 * The alert panel that pops up when there is a calendar event alarm.
49 * This activity is started by an intent that specifies an event id.
50  */
51public class AlertActivity extends Activity {
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.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.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 ContentResolver mResolver;
95    private AlertAdapter mAdapter;
96    private QueryHandler mQueryHandler;
97    private Cursor mCursor;
98    private ListView mListView;
99    private Button mSnoozeAllButton;
100    private Button mDismissAllButton;
101
102
103    private void dismissFiredAlarms() {
104        ContentValues values = new ContentValues(1 /* size */);
105        values.put(PROJECTION[INDEX_STATE], CalendarAlerts.DISMISSED);
106        String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.FIRED;
107        mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
108                selection, null /* selectionArgs */);
109    }
110
111    private void dismissAlarm(long id) {
112        ContentValues values = new ContentValues(1 /* size */);
113        values.put(PROJECTION[INDEX_STATE], CalendarAlerts.DISMISSED);
114        String selection = CalendarAlerts._ID + "=" + id;
115        mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
116                selection, null /* selectionArgs */);
117    }
118
119    private class QueryHandler extends AsyncQueryHandler {
120        public QueryHandler(ContentResolver cr) {
121            super(cr);
122        }
123
124        @Override
125        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
126            // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
127            if (!isFinishing()) {
128                mCursor = cursor;
129                mAdapter.changeCursor(cursor);
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                    AlarmManager alarmManager =
147                            (AlarmManager) getSystemService(Context.ALARM_SERVICE);
148                    CalendarAlerts.scheduleAlarm(AlertActivity.this, alarmManager, alarmTime);
149                }
150            }
151        }
152
153        @Override
154        protected void onUpdateComplete(int token, Object cookie, int result) {
155            // Ignore
156        }
157    }
158
159    private static ContentValues makeContentValues(long eventId, long begin, long end,
160            long alarmTime, int minutes) {
161        ContentValues values = new ContentValues();
162        values.put(CalendarAlerts.EVENT_ID, eventId);
163        values.put(CalendarAlerts.BEGIN, begin);
164        values.put(CalendarAlerts.END, end);
165        values.put(CalendarAlerts.ALARM_TIME, alarmTime);
166        long currentTime = System.currentTimeMillis();
167        values.put(CalendarAlerts.CREATION_TIME, currentTime);
168        values.put(CalendarAlerts.RECEIVED_TIME, 0);
169        values.put(CalendarAlerts.NOTIFY_TIME, 0);
170        values.put(CalendarAlerts.STATE, CalendarAlertsColumns.SCHEDULED);
171        values.put(CalendarAlerts.MINUTES, minutes);
172        return values;
173    }
174
175    private OnItemClickListener mViewListener = new OnItemClickListener() {
176
177        public void onItemClick(AdapterView parent, View view, int position,
178                long i) {
179            AlertActivity alertActivity = AlertActivity.this;
180            Cursor cursor = alertActivity.getItemForView(view);
181
182            long id = cursor.getInt(AlertActivity.INDEX_EVENT_ID);
183            long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
184            long endMillis = cursor.getLong(AlertActivity.INDEX_END);
185
186            Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, id);
187            Intent intent = new Intent(Intent.ACTION_VIEW, uri);
188            intent.setClass(alertActivity, EventInfoActivity.class);
189            intent.putExtra(EVENT_BEGIN_TIME, startMillis);
190            intent.putExtra(EVENT_END_TIME, endMillis);
191
192            // Mark this alarm as DISMISSED
193            dismissAlarm(cursor.getLong(INDEX_ROW_ID));
194
195            startActivity(intent);
196            alertActivity.finish();
197        }
198    };
199
200    @Override
201    protected void onCreate(Bundle icicle) {
202        super.onCreate(icicle);
203
204        setContentView(R.layout.alert_activity);
205        setTitle(R.string.alert_title);
206
207        WindowManager.LayoutParams lp = getWindow().getAttributes();
208        lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
209        lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
210
211        getWindow().setAttributes(lp);
212
213        mResolver = getContentResolver();
214        mQueryHandler = new QueryHandler(mResolver);
215        mAdapter = new AlertAdapter(this, R.layout.alert_item);
216
217        mListView = (ListView) findViewById(R.id.alert_container);
218        mListView.setItemsCanFocus(true);
219        mListView.setAdapter(mAdapter);
220        mListView.setOnItemClickListener(mViewListener);
221
222        mSnoozeAllButton = (Button) findViewById(R.id.snooze_all);
223        mSnoozeAllButton.setOnClickListener(mSnoozeAllListener);
224        mDismissAllButton = (Button) findViewById(R.id.dismiss_all);
225        mDismissAllButton.setOnClickListener(mDismissAllListener);
226
227        // Disable the buttons, since they need mCursor, which is created asynchronously
228        mSnoozeAllButton.setEnabled(false);
229        mDismissAllButton.setEnabled(false);
230    }
231
232    @Override
233    protected void onResume() {
234        super.onResume();
235
236        // If the cursor is null, start the async handler. If it is not null just requery.
237        if (mCursor == null) {
238            Uri uri = CalendarAlerts.CONTENT_URI_BY_INSTANCE;
239            mQueryHandler.startQuery(0, null, uri, PROJECTION, SELECTION,
240                    SELECTIONARG, CalendarAlerts.DEFAULT_SORT_ORDER);
241        } else {
242            mCursor.requery();
243        }
244    }
245
246    @Override
247    protected void onStop() {
248        super.onStop();
249        AlertService.updateAlertNotification(this);
250
251        if (mCursor != null) {
252            mCursor.deactivate();
253        }
254    }
255
256    @Override
257    protected void onDestroy() {
258        super.onDestroy();
259        if (mCursor != null) {
260            mCursor.close();
261        }
262    }
263
264    private OnClickListener mSnoozeAllListener = new OnClickListener() {
265        public void onClick(View v) {
266            long alarmTime = System.currentTimeMillis() + SNOOZE_DELAY;
267
268            NotificationManager nm =
269                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
270            nm.cancel(NOTIFICATION_ID);
271
272            long scheduleAlarmTime = 0;
273            mCursor.moveToPosition(-1);
274            while (mCursor.moveToNext()) {
275                long eventId = mCursor.getLong(INDEX_EVENT_ID);
276                long begin = mCursor.getLong(INDEX_BEGIN);
277                long end = mCursor.getLong(INDEX_END);
278
279                // Set the "minutes" to zero to indicate this is a snoozed
280                // alarm.  There is code in AlertService.java that checks
281                // this field.
282                ContentValues values =
283                        makeContentValues(eventId, begin, end, alarmTime, 0 /* minutes */);
284
285                // Create a new alarm entry in the CalendarAlerts table
286                if (mCursor.isLast()) {
287                    scheduleAlarmTime = alarmTime;
288                }
289                mQueryHandler.startInsert(0, scheduleAlarmTime, CalendarAlerts.CONTENT_URI, values);
290            }
291
292            dismissFiredAlarms();
293
294            finish();
295        }
296    };
297
298    private OnClickListener mDismissAllListener = new OnClickListener() {
299        public void onClick(View v) {
300            NotificationManager nm =
301                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
302            nm.cancel(NOTIFICATION_ID);
303
304            dismissFiredAlarms();
305
306            finish();
307        }
308    };
309
310    public boolean isEmpty() {
311        return (mCursor.getCount() == 0);
312    }
313
314    public Cursor getItemForView(View view) {
315        int index = mListView.getPositionForView(view);
316        if (index < 0) {
317            return null;
318        }
319        return (Cursor) mListView.getAdapter().getItem(index);
320    }
321}
322