HandleApiCalls.java revision 07424a1e5b07d2f1e00c302c2c7510d0df380a90
1/* 2 * Copyright (C) 2010 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.deskclock; 18 19import android.app.Activity; 20import android.content.ContentResolver; 21import android.content.Context; 22import android.content.Intent; 23import android.content.SharedPreferences; 24import android.media.RingtoneManager; 25import android.net.Uri; 26import android.os.AsyncTask; 27import android.os.Bundle; 28import android.os.Looper; 29import android.os.Parcelable; 30import android.preference.PreferenceManager; 31import android.provider.AlarmClock; 32import android.text.TextUtils; 33 34import com.android.deskclock.alarms.AlarmStateManager; 35import com.android.deskclock.events.Events; 36import com.android.deskclock.provider.Alarm; 37import com.android.deskclock.provider.AlarmInstance; 38import com.android.deskclock.provider.DaysOfWeek; 39import com.android.deskclock.timer.TimerFullScreenFragment; 40import com.android.deskclock.timer.TimerObj; 41import com.android.deskclock.timer.Timers; 42 43import java.util.ArrayList; 44import java.util.Calendar; 45import java.util.Iterator; 46import java.util.List; 47 48public class HandleApiCalls extends Activity { 49 50 public static final long TIMER_MIN_LENGTH = 1000; 51 public static final long TIMER_MAX_LENGTH = 24 * 60 * 60 * 1000; 52 53 private Context mAppContext; 54 55 @Override 56 protected void onCreate(Bundle icicle) { 57 try { 58 super.onCreate(icicle); 59 mAppContext = getApplicationContext(); 60 final Intent intent = getIntent(); 61 final String action = intent == null ? null : intent.getAction(); 62 if (action == null) { 63 return; 64 } 65 switch (action) { 66 case AlarmClock.ACTION_SET_ALARM: 67 handleSetAlarm(intent); 68 break; 69 case AlarmClock.ACTION_SHOW_ALARMS: 70 handleShowAlarms(); 71 break; 72 case AlarmClock.ACTION_SET_TIMER: 73 handleSetTimer(intent); 74 break; 75 case AlarmClock.ACTION_DISMISS_ALARM: 76 handleDismissAlarm(intent.getAction()); 77 break; 78 case AlarmClock.ACTION_SNOOZE_ALARM: 79 handleSnoozeAlarm(); 80 } 81 } finally { 82 finish(); 83 } 84 } 85 86 private void handleDismissAlarm(final String action) { 87 // Opens the UI for Alarms 88 final Intent alarmIntent = 89 Alarm.createIntent(mAppContext, DeskClock.class, Alarm.INVALID_ID) 90 .setAction(action) 91 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX); 92 startActivity(alarmIntent); 93 94 final Intent intent = getIntent(); 95 96 new DismissAlarmAsync(mAppContext, intent, this).execute(); 97 } 98 99 public static void dismissAlarm(Alarm alarm, Context context, Activity activity) { 100 // only allow on background thread 101 if (Looper.myLooper() == Looper.getMainLooper()) { 102 throw new IllegalStateException("dismissAlarm must be called on a " + 103 "background thread"); 104 } 105 106 final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId( 107 context.getContentResolver(), alarm.id); 108 if (alarmInstance == null) { 109 final String reason = context.getString(R.string.no_alarm_scheduled_for_this_time); 110 Voice.notifyFailure(activity, reason); 111 LogUtils.i(reason); 112 return; 113 } 114 115 if (Utils.isAlarmWithin24Hours(alarmInstance)) { 116 AlarmStateManager.setPreDismissState(context, alarmInstance); 117 final String reason = context.getString(R.string.alarm_is_dismissed, alarm); 118 LogUtils.i(reason); 119 Voice.notifySuccess(activity, reason); 120 Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent); 121 } else { 122 final String reason = context.getString( 123 R.string.alarm_cant_be_dismissed_still_more_than_24_hours_away, alarm); 124 Voice.notifyFailure(activity, reason); 125 LogUtils.i(reason); 126 } 127 } 128 129 private static class DismissAlarmAsync extends AsyncTask<Void, Void, Void> { 130 131 private final Context mContext; 132 private final Intent mIntent; 133 private final Activity mActivity; 134 135 public DismissAlarmAsync(Context context, Intent intent, Activity activity) { 136 mContext = context; 137 mIntent = intent; 138 mActivity = activity; 139 } 140 141 @Override 142 protected Void doInBackground(Void... parameters) { 143 final List<Alarm> alarms = getEnabledAlarms(mContext); 144 if (alarms.isEmpty()) { 145 final String reason = mContext.getString(R.string.no_scheduled_alarms); 146 LogUtils.i(reason); 147 Voice.notifyFailure(mActivity, reason); 148 return null; 149 } 150 151 // remove Alarms in MISSED, DISMISSED, and PREDISMISSED states 152 for (Iterator<Alarm> i = alarms.iterator(); i.hasNext();) { 153 final AlarmInstance alarmInstance = AlarmInstance.getNextUpcomingInstanceByAlarmId( 154 mContext.getContentResolver(), i.next().id); 155 if (alarmInstance == null || 156 alarmInstance.mAlarmState > AlarmInstance.FIRED_STATE) { 157 i.remove(); 158 } 159 } 160 161 final String searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE); 162 if (searchMode == null && alarms.size() > 1) { 163 // shows the UI where user picks which alarm they want to DISMISS 164 final Intent pickSelectionIntent = new Intent(mContext, 165 AlarmSelectionActivity.class) 166 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 167 .putExtra(AlarmSelectionActivity.EXTRA_ALARMS, 168 alarms.toArray(new Parcelable[alarms.size()])); 169 mContext.startActivity(pickSelectionIntent); 170 return null; 171 } 172 173 // fetch the alarms that are specified by the intent 174 final FetchMatchingAlarmsAction fmaa = 175 new FetchMatchingAlarmsAction(mContext, alarms, mIntent, mActivity); 176 fmaa.run(); 177 final List<Alarm> matchingAlarms = fmaa.getMatchingAlarms(); 178 179 // If there are multiple matching alarms and it wasn't expected 180 // disambiguate what the user meant 181 if (!AlarmClock.ALARM_SEARCH_MODE_ALL.equals(searchMode) && matchingAlarms.size() > 1) { 182 final Intent pickSelectionIntent = new Intent(mContext, AlarmSelectionActivity.class) 183 .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 184 .putExtra(AlarmSelectionActivity.EXTRA_ALARMS, 185 matchingAlarms.toArray(new Parcelable[matchingAlarms.size()])); 186 mContext.startActivity(pickSelectionIntent); 187 final String reason = mContext.getString(R.string.pick_alarm_to_dismiss); 188 Voice.notifySuccess(mActivity, reason); 189 return null; 190 } 191 192 // Apply the action to the matching alarms 193 for (Alarm alarm : matchingAlarms) { 194 dismissAlarm(alarm, mContext, mActivity); 195 LogUtils.i("Alarm %s is dismissed", alarm); 196 } 197 return null; 198 } 199 200 private static List<Alarm> getEnabledAlarms(Context context) { 201 final String selection = String.format("%s=?", Alarm.ENABLED); 202 final String[] args = { "1" }; 203 return Alarm.getAlarms(context.getContentResolver(), selection, args); 204 } 205 } 206 207 private void handleSnoozeAlarm() { 208 new SnoozeAlarmAsync(mAppContext, this).execute(); 209 } 210 211 private static class SnoozeAlarmAsync extends AsyncTask<Void, Void, Void> { 212 213 private final Context mContext; 214 private final Activity mActivity; 215 216 public SnoozeAlarmAsync(Context context, Activity activity) { 217 mContext = context; 218 mActivity = activity; 219 } 220 221 @Override 222 protected Void doInBackground(Void... parameters) { 223 final List<AlarmInstance> alarmInstances = AlarmInstance.getInstancesByState( 224 mContext.getContentResolver(), AlarmInstance.FIRED_STATE); 225 if (alarmInstances.isEmpty()) { 226 final String reason = mContext.getString(R.string.no_firing_alarms); 227 LogUtils.i(reason); 228 Voice.notifyFailure(mActivity, reason); 229 return null; 230 } 231 232 for (AlarmInstance firingAlarmInstance : alarmInstances) { 233 snoozeAlarm(firingAlarmInstance, mContext, mActivity); 234 } 235 return null; 236 } 237 } 238 239 static void snoozeAlarm(AlarmInstance alarmInstance, Context context, Activity activity) { 240 // only allow on background thread 241 if (Looper.myLooper() == Looper.getMainLooper()) { 242 throw new IllegalStateException("snoozeAlarm must be called on a " + 243 "background thread"); 244 } 245 final String reason = context.getString(R.string.alarm_is_snoozed, alarmInstance); 246 LogUtils.i(reason); 247 Voice.notifySuccess(activity, reason); 248 AlarmStateManager.setSnoozeState(context, alarmInstance, true); 249 LogUtils.i("Snooze %d:%d", alarmInstance.mHour, alarmInstance.mMinute); 250 Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent); 251 } 252 253 /*** 254 * Processes the SET_ALARM intent 255 * @param intent Intent passed to the app 256 */ 257 private void handleSetAlarm(Intent intent) { 258 // If not provided or invalid, show UI 259 final int hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, -1); 260 261 // If not provided, use zero. If it is provided, make sure it's valid, otherwise, show UI 262 final int minutes; 263 if (intent.hasExtra(AlarmClock.EXTRA_MINUTES)) { 264 minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, -1); 265 } else { 266 minutes = 0; 267 } 268 if (hour < 0 || hour > 23 || minutes < 0 || minutes > 59) { 269 // Intent has no time or an invalid time, open the alarm creation UI 270 Intent createAlarm = Alarm.createIntent(this, DeskClock.class, Alarm.INVALID_ID); 271 createAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 272 createAlarm.putExtra(AlarmClockFragment.ALARM_CREATE_NEW_INTENT_EXTRA, true); 273 createAlarm.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX); 274 startActivity(createAlarm); 275 Voice.notifyFailure(this, getString(R.string.invalid_time, hour, minutes)); 276 LogUtils.i("HandleApiCalls no/invalid time; opening UI"); 277 return; 278 } 279 280 Events.sendAlarmEvent(R.string.action_create, R.string.label_intent); 281 final boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false); 282 283 final StringBuilder selection = new StringBuilder(); 284 final List<String> args = new ArrayList<>(); 285 setSelectionFromIntent(intent, hour, minutes, selection, args); 286 287 final String message = getMessageFromIntent(intent); 288 final DaysOfWeek daysOfWeek = getDaysFromIntent(intent); 289 final boolean vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false); 290 final String alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE); 291 292 Alarm alarm = new Alarm(hour, minutes); 293 alarm.enabled = true; 294 alarm.label = message; 295 alarm.daysOfWeek = daysOfWeek; 296 alarm.vibrate = vibrate; 297 298 if (alert == null) { 299 alarm.alert = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM); 300 } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(alert) || alert.isEmpty()) { 301 alarm.alert = Alarm.NO_RINGTONE_URI; 302 } else { 303 alarm.alert = Uri.parse(alert); 304 } 305 alarm.deleteAfterUse = !daysOfWeek.isRepeating() && skipUi; 306 307 final ContentResolver cr = getContentResolver(); 308 alarm = Alarm.addAlarm(cr, alarm); 309 setupInstance(alarm.createInstanceAfter(Calendar.getInstance()), skipUi); 310 Voice.notifySuccess(this, getString(R.string.alarm_is_set, alarm)); 311 LogUtils.i("HandleApiCalls set up alarm: %s", alarm); 312 } 313 314 private void handleShowAlarms() { 315 startActivity(new Intent(this, DeskClock.class) 316 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX)); 317 Events.sendAlarmEvent(R.string.action_show, R.string.label_intent); 318 LogUtils.i("HandleApiCalls show alarms"); 319 } 320 321 private void handleSetTimer(Intent intent) { 322 final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 323 // If no length is supplied, show the timer setup view 324 if (!intent.hasExtra(AlarmClock.EXTRA_LENGTH)) { 325 startActivity(new Intent(this, DeskClock.class) 326 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX) 327 .putExtra(TimerFullScreenFragment.GOTO_SETUP_VIEW, true)); 328 LogUtils.i("HandleApiCalls showing timer setup"); 329 return; 330 } 331 332 final long length = 1000l * intent.getIntExtra(AlarmClock.EXTRA_LENGTH, 0); 333 if (length < TIMER_MIN_LENGTH || length > TIMER_MAX_LENGTH) { 334 Voice.notifyFailure(this, getString(R.string.invalid_timer_length)); 335 LogUtils.i("Invalid timer length requested: " + length); 336 return; 337 } 338 String label = getMessageFromIntent(intent); 339 340 TimerObj timer = null; 341 // Find an existing matching time 342 final List<TimerObj> timers = new ArrayList<>(); 343 TimerObj.getTimersFromSharedPrefs(prefs, timers); 344 for (TimerObj t : timers) { 345 if (t.mSetupLength == length && (TextUtils.equals(label, t.mLabel)) 346 && t.mState == TimerObj.STATE_RESTART) { 347 timer = t; 348 break; 349 } 350 } 351 352 boolean skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false); 353 if (timer == null) { 354 // Use a new timer 355 timer = new TimerObj(length, label, this /* context */); 356 // Timers set without presenting UI to the user will be deleted after use 357 timer.mDeleteAfterUse = skipUi; 358 359 Events.sendTimerEvent(R.string.action_create, R.string.label_intent); 360 } 361 362 timer.setState(TimerObj.STATE_RUNNING); 363 timer.mStartTime = Utils.getTimeNow(); 364 timer.writeToSharedPref(prefs); 365 366 Events.sendTimerEvent(R.string.action_start, R.string.label_intent); 367 368 // Tell TimerReceiver that the timer was started 369 sendBroadcast(new Intent().setAction(Timers.START_TIMER) 370 .putExtra(Timers.TIMER_INTENT_EXTRA, timer.mTimerId)); 371 372 if (skipUi) { 373 Utils.showInUseNotifications(this); 374 } else { 375 startActivity(new Intent(this, DeskClock.class) 376 .putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.TIMER_TAB_INDEX) 377 .putExtra(Timers.FIRST_LAUNCH_FROM_API_CALL, true)); 378 } 379 Voice.notifySuccess(this, getString(R.string.timer_created)); 380 LogUtils.i("HandleApiCalls timer created: %s", timer); 381 } 382 383 private void setupInstance(AlarmInstance instance, boolean skipUi) { 384 instance = AlarmInstance.addInstance(this.getContentResolver(), instance); 385 AlarmStateManager.registerInstance(this, instance, true); 386 AlarmUtils.popAlarmSetToast(this, instance.getAlarmTime().getTimeInMillis()); 387 if (!skipUi) { 388 Intent showAlarm = Alarm.createIntent(this, DeskClock.class, instance.mAlarmId); 389 showAlarm.putExtra(DeskClock.SELECT_TAB_INTENT_EXTRA, DeskClock.ALARM_TAB_INDEX); 390 showAlarm.putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA, instance.mAlarmId); 391 showAlarm.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 392 startActivity(showAlarm); 393 } 394 } 395 396 private String getMessageFromIntent(Intent intent) { 397 final String message = intent.getStringExtra(AlarmClock.EXTRA_MESSAGE); 398 return message == null ? "" : message; 399 } 400 401 private DaysOfWeek getDaysFromIntent(Intent intent) { 402 final DaysOfWeek daysOfWeek = new DaysOfWeek(0); 403 final ArrayList<Integer> days = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS); 404 if (days != null) { 405 final int[] daysArray = new int[days.size()]; 406 for (int i = 0; i < days.size(); i++) { 407 daysArray[i] = days.get(i); 408 } 409 daysOfWeek.setDaysOfWeek(true, daysArray); 410 } else { 411 // API says to use an ArrayList<Integer> but we allow the user to use a int[] too. 412 final int[] daysArray = intent.getIntArrayExtra(AlarmClock.EXTRA_DAYS); 413 if (daysArray != null) { 414 daysOfWeek.setDaysOfWeek(true, daysArray); 415 } 416 } 417 return daysOfWeek; 418 } 419 420 private void setSelectionFromIntent( 421 Intent intent, 422 int hour, 423 int minutes, 424 StringBuilder selection, 425 List<String> args) { 426 selection.append(Alarm.HOUR).append("=?"); 427 args.add(String.valueOf(hour)); 428 selection.append(" AND ").append(Alarm.MINUTES).append("=?"); 429 args.add(String.valueOf(minutes)); 430 431 if (intent.hasExtra(AlarmClock.EXTRA_MESSAGE)) { 432 selection.append(" AND ").append(Alarm.LABEL).append("=?"); 433 args.add(getMessageFromIntent(intent)); 434 } 435 436 // Days is treated differently that other fields because if days is not specified, it 437 // explicitly means "not recurring". 438 selection.append(" AND ").append(Alarm.DAYS_OF_WEEK).append("=?"); 439 args.add(String.valueOf(intent.hasExtra(AlarmClock.EXTRA_DAYS) 440 ? getDaysFromIntent(intent).getBitSet() : DaysOfWeek.NO_DAYS_SET)); 441 442 if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) { 443 selection.append(" AND ").append(Alarm.VIBRATE).append("=?"); 444 args.add(intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false) ? "1" : "0"); 445 } 446 447 if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) { 448 selection.append(" AND ").append(Alarm.RINGTONE).append("=?"); 449 450 String ringTone = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE); 451 if (ringTone == null) { 452 // If the intent explicitly specified a NULL ringtone, treat it as the default 453 // ringtone. 454 ringTone = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM).toString(); 455 } else if (AlarmClock.VALUE_RINGTONE_SILENT.equals(ringTone) || ringTone.isEmpty()) { 456 ringTone = Alarm.NO_RINGTONE; 457 } 458 args.add(ringTone); 459 } 460 } 461} 462