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