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 android.annotation.SuppressLint;
20 import android.app.Activity;
21 import android.app.NotificationManager;
22 import android.app.TaskStackBuilder;
23 import android.content.ContentValues;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.database.Cursor;
27 import android.net.Uri;
28 import android.os.AsyncTask;
29 import android.os.Bundle;
30 import android.provider.CalendarContract;
31 import android.provider.CalendarContract.CalendarAlerts;
32 import android.util.Log;
33 import android.view.View;
34 import android.view.View.OnClickListener;
35 import android.widget.AdapterView;
36 import android.widget.AdapterView.OnItemClickListener;
37 import android.widget.Button;
38 import android.widget.ListView;
39 
40 import com.android.calendar.AsyncQueryService;
41 import com.android.calendar.EventInfoActivity;
42 import com.android.calendar.R;
43 import com.android.calendar.Utils;
44 import com.android.calendar.alerts.GlobalDismissManager.AlarmId;
45 
46 import java.util.LinkedList;
47 import 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   */
53 public 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 
dismissFiredAlarms()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 
dismissAlarm(long id, long eventId, long startTime)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")
initiateGlobalDismiss(List<AlarmId> alarmIds)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 {
QueryHandler(Context context)149         public QueryHandler(Context context) {
150             super(context);
151         }
152 
153         @Override
onQueryComplete(int token, Object cookie, Cursor cursor)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
onUpdateComplete(int token, Object cookie, int result)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
onCreate(Bundle icicle)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
onResume()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 
closeActivityIfEmpty()247     void closeActivityIfEmpty() {
248         if (mCursor != null && !mCursor.isClosed() && mCursor.getCount() == 0) {
249             AlertActivity.this.finish();
250         }
251     }
252 
253     @Override
onStop()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
onDestroy()272     protected void onDestroy() {
273         super.onDestroy();
274         if (mCursor != null) {
275             mCursor.close();
276         }
277     }
278 
279     @Override
onClick(View v)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 
isEmpty()292     public boolean isEmpty() {
293         return mCursor != null ? (mCursor.getCount() == 0) : true;
294     }
295 
getItemForView(View view)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