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 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