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 android.annotation.SuppressLint;
20import android.app.Activity;
21import android.app.NotificationManager;
22import android.app.TaskStackBuilder;
23import android.content.ContentValues;
24import android.content.Context;
25import android.content.Intent;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.AsyncTask;
29import android.os.Bundle;
30import android.provider.CalendarContract;
31import android.provider.CalendarContract.CalendarAlerts;
32import android.util.Log;
33import android.view.View;
34import android.view.View.OnClickListener;
35import android.widget.AdapterView;
36import android.widget.AdapterView.OnItemClickListener;
37import android.widget.Button;
38import android.widget.ListView;
39
40import com.android.calendar.AsyncQueryService;
41import com.android.calendar.EventInfoActivity;
42import com.android.calendar.R;
43import com.android.calendar.Utils;
44import com.android.calendar.alerts.GlobalDismissManager.AlarmId;
45
46import java.util.LinkedList;
47import java.util.List;
48
49/**
50 * The alert panel that pops up when there is a calendar event alarm.
51 * This activity is started by an intent that specifies an event id.
52  */
53public class AlertActivity extends Activity implements OnClickListener {
54    private static final String TAG = "AlertActivity";
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    private AlertAdapter mAdapter;
90    private QueryHandler mQueryHandler;
91    private Cursor mCursor;
92    private ListView mListView;
93    private Button mDismissAllButton;
94
95
96    private void dismissFiredAlarms() {
97        ContentValues values = new ContentValues(1 /* size */);
98        values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
99        String selection = CalendarAlerts.STATE + "=" + CalendarAlerts.STATE_FIRED;
100        mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
101                selection, null /* selectionArgs */, Utils.UNDO_DELAY);
102
103        if (mCursor == null) {
104            Log.e(TAG, "Unable to globally dismiss all notifications because cursor was null.");
105            return;
106        }
107        if (mCursor.isClosed()) {
108            Log.e(TAG, "Unable to globally dismiss all notifications because cursor was closed.");
109            return;
110        }
111        if (!mCursor.moveToFirst()) {
112            Log.e(TAG, "Unable to globally dismiss all notifications because cursor was empty.");
113            return;
114        }
115
116        List<AlarmId> alarmIds = new LinkedList<AlarmId>();
117        do {
118            long eventId = mCursor.getLong(INDEX_EVENT_ID);
119            long eventStart = mCursor.getLong(INDEX_BEGIN);
120            alarmIds.add(new AlarmId(eventId, eventStart));
121        } while (mCursor.moveToNext());
122        initiateGlobalDismiss(alarmIds);
123    }
124
125    private void dismissAlarm(long id, long eventId, long startTime) {
126        ContentValues values = new ContentValues(1 /* size */);
127        values.put(PROJECTION[INDEX_STATE], CalendarAlerts.STATE_DISMISSED);
128        String selection = CalendarAlerts._ID + "=" + id;
129        mQueryHandler.startUpdate(0, null, CalendarAlerts.CONTENT_URI, values,
130                selection, null /* selectionArgs */, Utils.UNDO_DELAY);
131
132        List<AlarmId> alarmIds = new LinkedList<AlarmId>();
133        alarmIds.add(new AlarmId(eventId, startTime));
134        initiateGlobalDismiss(alarmIds);
135    }
136
137    @SuppressWarnings("unchecked")
138    private void initiateGlobalDismiss(List<AlarmId> alarmIds) {
139        new AsyncTask<List<AlarmId>, Void, Void>() {
140            @Override
141            protected Void doInBackground(List<AlarmId>... params) {
142                GlobalDismissManager.dismissGlobally(getApplicationContext(), params[0]);
143                return null;
144            }
145        }.execute(alarmIds);
146    }
147
148    private class QueryHandler extends AsyncQueryService {
149        public QueryHandler(Context context) {
150            super(context);
151        }
152
153        @Override
154        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
155            // Only set mCursor if the Activity is not finishing. Otherwise close the cursor.
156            if (!isFinishing()) {
157                mCursor = cursor;
158                mAdapter.changeCursor(cursor);
159                mListView.setSelection(cursor.getCount() - 1);
160
161                // The results are in, enable the buttons
162                mDismissAllButton.setEnabled(true);
163            } else {
164                cursor.close();
165            }
166        }
167
168        @Override
169        protected void onUpdateComplete(int token, Object cookie, int result) {
170            // Ignore
171        }
172    }
173
174    private final OnItemClickListener mViewListener = new OnItemClickListener() {
175
176        @SuppressLint("NewApi")
177        @Override
178        public void onItemClick(AdapterView<?> parent, View view, int position,
179                long i) {
180            AlertActivity alertActivity = AlertActivity.this;
181            Cursor cursor = alertActivity.getItemForView(view);
182
183            long alarmId = cursor.getLong(INDEX_ROW_ID);
184            long eventId = cursor.getLong(AlertActivity.INDEX_EVENT_ID);
185            long startMillis = cursor.getLong(AlertActivity.INDEX_BEGIN);
186
187            // Mark this alarm as DISMISSED
188            dismissAlarm(alarmId, eventId, startMillis);
189
190            // build an intent and task stack to start EventInfoActivity with AllInOneActivity
191            // as the parent activity rooted to home.
192            long endMillis = cursor.getLong(AlertActivity.INDEX_END);
193            Intent eventIntent = AlertUtils.buildEventViewIntent(AlertActivity.this, eventId,
194                    startMillis, endMillis);
195
196            if (Utils.isJellybeanOrLater()) {
197                TaskStackBuilder.create(AlertActivity.this).addParentStack(EventInfoActivity.class)
198                        .addNextIntent(eventIntent).startActivities();
199            } else {
200                alertActivity.startActivity(eventIntent);
201            }
202
203            alertActivity.finish();
204        }
205    };
206
207    @Override
208    protected void onCreate(Bundle icicle) {
209        super.onCreate(icicle);
210
211        setContentView(R.layout.alert_activity);
212        setTitle(R.string.alert_title);
213
214        mQueryHandler = new QueryHandler(this);
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        mDismissAllButton = (Button) findViewById(R.id.dismiss_all);
223        mDismissAllButton.setOnClickListener(this);
224
225        // Disable the buttons, since they need mCursor, which is created asynchronously
226        mDismissAllButton.setEnabled(false);
227    }
228
229    @Override
230    protected void onResume() {
231        super.onResume();
232
233        // If the cursor is null, start the async handler. If it is not null just requery.
234        if (mCursor == null) {
235            Uri uri = CalendarAlerts.CONTENT_URI_BY_INSTANCE;
236            mQueryHandler.startQuery(0, null, uri, PROJECTION, SELECTION, SELECTIONARG,
237                    CalendarContract.CalendarAlerts.DEFAULT_SORT_ORDER);
238        } else {
239            if (!mCursor.requery()) {
240                Log.w(TAG, "Cursor#requery() failed.");
241                mCursor.close();
242                mCursor = null;
243            }
244        }
245    }
246
247    void closeActivityIfEmpty() {
248        if (mCursor != null && !mCursor.isClosed() && mCursor.getCount() == 0) {
249            AlertActivity.this.finish();
250        }
251    }
252
253    @Override
254    protected void onStop() {
255        super.onStop();
256        // Can't run updateAlertNotification in main thread
257        AsyncTask task = new AsyncTask<Context, Void, Void>() {
258            @Override
259            protected Void doInBackground(Context ... params) {
260                AlertService.updateAlertNotification(params[0]);
261                return null;
262            }
263        }.execute(this);
264
265
266        if (mCursor != null) {
267            mCursor.deactivate();
268        }
269    }
270
271    @Override
272    protected void onDestroy() {
273        super.onDestroy();
274        if (mCursor != null) {
275            mCursor.close();
276        }
277    }
278
279    @Override
280    public void onClick(View v) {
281        if (v == mDismissAllButton) {
282            NotificationManager nm =
283                (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
284            nm.cancelAll();
285
286            dismissFiredAlarms();
287
288            finish();
289        }
290    }
291
292    public boolean isEmpty() {
293        return mCursor != null ? (mCursor.getCount() == 0) : true;
294    }
295
296    public Cursor getItemForView(View view) {
297        final int index = mListView.getPositionForView(view);
298        if (index < 0) {
299            return null;
300        }
301        return (Cursor) mListView.getAdapter().getItem(index);
302    }
303}
304