1 /*
2  * Copyright (C) 2020 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.deskclock
18 
19 import android.app.Activity
20 import android.content.ContentUris
21 import android.content.Context
22 import android.content.Intent
23 import android.net.Uri
24 import android.os.AsyncTask
25 import android.os.Bundle
26 import android.os.Parcelable
27 import android.provider.AlarmClock
28 import android.text.TextUtils
29 import android.text.format.DateFormat
30 import android.text.format.DateUtils
31 
32 import com.android.deskclock.AlarmUtils.popAlarmSetToast
33 import com.android.deskclock.alarms.AlarmStateManager
34 import com.android.deskclock.controller.Controller
35 import com.android.deskclock.data.DataModel
36 import com.android.deskclock.data.Timer
37 import com.android.deskclock.data.Weekdays
38 import com.android.deskclock.events.Events
39 import com.android.deskclock.provider.Alarm
40 import com.android.deskclock.provider.AlarmInstance
41 import com.android.deskclock.provider.ClockContract
42 import com.android.deskclock.provider.ClockContract.AlarmSettingColumns
43 import com.android.deskclock.provider.ClockContract.AlarmsColumns
44 import com.android.deskclock.timer.TimerFragment
45 import com.android.deskclock.timer.TimerService
46 import com.android.deskclock.uidata.UiDataModel
47 
48 import java.util.Calendar
49 import java.util.Date
50 
51 /**
52  * This activity is never visible. It processes all public intents defined by [AlarmClock]
53  * that apply to alarms and timers. Its definition in AndroidManifest.xml requires callers to hold
54  * the com.android.alarm.permission.SET_ALARM permission to complete the requested action.
55  */
56 // TODO(b/165664115) Replace deprecated AsyncTask calls
57 class HandleApiCalls : Activity() {
58     private lateinit var mAppContext: Context
59 
onCreatenull60     override fun onCreate(icicle: Bundle?) {
61         super.onCreate(icicle)
62 
63         mAppContext = applicationContext
64 
65         try {
66             val intent = intent
67             val action = intent?.action ?: return
68             LOGGER.i("onCreate: $intent")
69 
70             when (action) {
71                 AlarmClock.ACTION_SET_ALARM -> handleSetAlarm(intent)
72                 AlarmClock.ACTION_SHOW_ALARMS -> handleShowAlarms()
73                 AlarmClock.ACTION_SET_TIMER -> handleSetTimer(intent)
74                 AlarmClock.ACTION_SHOW_TIMERS -> handleShowTimers(intent)
75                 AlarmClock.ACTION_DISMISS_ALARM -> handleDismissAlarm(intent)
76                 AlarmClock.ACTION_SNOOZE_ALARM -> handleSnoozeAlarm(intent)
77                 AlarmClock.ACTION_DISMISS_TIMER -> handleDismissTimer(intent)
78             }
79         } catch (e: Exception) {
80             LOGGER.wtf(e)
81         } finally {
82             finish()
83         }
84     }
85 
handleDismissAlarmnull86     private fun handleDismissAlarm(intent: Intent) {
87         // Change to the alarms tab.
88         UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.ALARMS
89 
90         // Open DeskClock which is now positioned on the alarms tab.
91         startActivity(Intent(mAppContext, DeskClock::class.java))
92 
93         DismissAlarmAsync(mAppContext, intent, this).execute()
94     }
95 
96     private class DismissAlarmAsync(
97         private val mContext: Context,
98         private val mIntent: Intent,
99         private val mActivity: Activity
100     ) : AsyncTask<Void?, Void?, Void?>() {
doInBackgroundnull101         override fun doInBackground(vararg parameters: Void?): Void? {
102             val cr = mContext.contentResolver
103             val alarms = getEnabledAlarms(mContext)
104             if (alarms.isEmpty()) {
105                 val reason = mContext.getString(R.string.no_scheduled_alarms)
106                 Controller.getController().notifyVoiceFailure(mActivity, reason)
107                 LOGGER.i("No scheduled alarms")
108                 return null
109             }
110 
111             // remove Alarms in MISSED, DISMISSED, and PREDISMISSED states
112             val i: MutableIterator<Alarm> = alarms.toMutableList().listIterator()
113             while (i.hasNext()) {
114                 val instance = AlarmInstance.getNextUpcomingInstanceByAlarmId(cr, i.next().id)
115                 if (instance == null ||
116                         instance.mAlarmState > ClockContract.InstancesColumns.FIRED_STATE) {
117                     i.remove()
118                 }
119             }
120 
121             val searchMode = mIntent.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE)
122             if (searchMode == null && alarms.size > 1) {
123                 // shows the UI where user picks which alarm they want to DISMISS
124                 val pickSelectionIntent = Intent(mContext,
125                         AlarmSelectionActivity::class.java)
126                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
127                         .putExtra(AlarmSelectionActivity.EXTRA_ACTION,
128                                 AlarmSelectionActivity.ACTION_DISMISS)
129                         .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
130                                 alarms.toTypedArray<Parcelable>())
131                 mContext.startActivity(pickSelectionIntent)
132                 val voiceMessage = mContext.getString(R.string.pick_alarm_to_dismiss)
133                 Controller.getController().notifyVoiceSuccess(mActivity, voiceMessage)
134                 return null
135             }
136 
137             // fetch the alarms that are specified by the intent
138             val fmaa = FetchMatchingAlarmsAction(mContext, alarms, mIntent, mActivity)
139             fmaa.run()
140             val matchingAlarms: List<Alarm> = fmaa.matchingAlarms
141 
142             // If there are multiple matching alarms and it wasn't expected
143             // disambiguate what the user meant
144             if (AlarmClock.ALARM_SEARCH_MODE_ALL != searchMode && matchingAlarms.size > 1) {
145                 val pickSelectionIntent = Intent(mContext, AlarmSelectionActivity::class.java)
146                         .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
147                         .putExtra(AlarmSelectionActivity.EXTRA_ACTION,
148                                 AlarmSelectionActivity.ACTION_DISMISS)
149                         .putExtra(AlarmSelectionActivity.EXTRA_ALARMS,
150                                 matchingAlarms.toTypedArray<Parcelable>())
151                 mContext.startActivity(pickSelectionIntent)
152                 val voiceMessage = mContext.getString(R.string.pick_alarm_to_dismiss)
153                 Controller.getController().notifyVoiceSuccess(mActivity, voiceMessage)
154                 return null
155             }
156 
157             // Apply the action to the matching alarms
158             for (alarm in matchingAlarms) {
159                 dismissAlarm(alarm, mActivity)
160                 LOGGER.i("Alarm dismissed: $alarm")
161             }
162             return null
163         }
164 
165         companion object {
getEnabledAlarmsnull166             private fun getEnabledAlarms(context: Context): List<Alarm> {
167                 val selection = String.format("%s=?", AlarmsColumns.ENABLED)
168                 val args = arrayOf("1")
169                 return Alarm.getAlarms(context.contentResolver, selection, *args)
170             }
171         }
172     }
173 
handleSnoozeAlarmnull174     private fun handleSnoozeAlarm(intent: Intent) {
175         SnoozeAlarmAsync(intent, this).execute()
176     }
177 
178     private class SnoozeAlarmAsync(
179         private val mIntent: Intent,
180         private val mActivity: Activity
181     ) : AsyncTask<Void?, Void?, Void?>() {
182         private val mContext: Context = mActivity.applicationContext
183 
doInBackgroundnull184         override fun doInBackground(vararg parameters: Void?): Void? {
185             val cr = mContext.contentResolver
186             val alarmInstances = AlarmInstance.getInstancesByState(
187                     cr, ClockContract.InstancesColumns.FIRED_STATE)
188             if (alarmInstances.isEmpty()) {
189                 val reason = mContext.getString(R.string.no_firing_alarms)
190                 Controller.getController().notifyVoiceFailure(mActivity, reason)
191                 LOGGER.i("No firing alarms")
192                 return null
193             }
194 
195             for (firingAlarmInstance in alarmInstances) {
196                 snoozeAlarm(firingAlarmInstance, mContext, mActivity)
197             }
198             return null
199         }
200     }
201 
202     /**
203      * Processes the SET_ALARM intent
204      * @param intent Intent passed to the app
205      */
handleSetAlarmnull206     private fun handleSetAlarm(intent: Intent) {
207         // Validate the hour, if one was given.
208         var hour = -1
209         if (intent.hasExtra(AlarmClock.EXTRA_HOUR)) {
210             hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, hour)
211             if (hour < 0 || hour > 23) {
212                 val mins = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0)
213                 val voiceMessage = getString(R.string.invalid_time, hour, mins, " ")
214                 Controller.getController().notifyVoiceFailure(this, voiceMessage)
215                 LOGGER.i("Illegal hour: $hour")
216                 return
217             }
218         }
219 
220         // Validate the minute, if one was given.
221         val minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, 0)
222         if (minutes < 0 || minutes > 59) {
223             val voiceMessage = getString(R.string.invalid_time, hour, minutes, " ")
224             Controller.getController().notifyVoiceFailure(this, voiceMessage)
225             LOGGER.i("Illegal minute: $minutes")
226             return
227         }
228 
229         val skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false)
230         val cr = contentResolver
231 
232         // If time information was not provided an existing alarm cannot be located and a new one
233         // cannot be created so show the UI for creating the alarm from scratch per spec.
234         if (hour == -1) {
235             // Change to the alarms tab.
236             UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.ALARMS
237 
238             // Intent has no time or an invalid time, open the alarm creation UI.
239             val createAlarm = Alarm.createIntent(this, DeskClock::class.java, Alarm.INVALID_ID)
240                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
241                     .putExtra(AlarmClockFragment.ALARM_CREATE_NEW_INTENT_EXTRA, true)
242 
243             // Open DeskClock which is now positioned on the alarms tab.
244             startActivity(createAlarm)
245             val voiceMessage = getString(R.string.invalid_time, hour, minutes, " ")
246             Controller.getController().notifyVoiceFailure(this, voiceMessage)
247             LOGGER.i("Missing alarm time; opening UI")
248             return
249         }
250 
251         val selection = StringBuilder()
252         val argsList: MutableList<String> = ArrayList()
253         setSelectionFromIntent(intent, hour, minutes, selection, argsList)
254 
255         // Try to locate an existing alarm using the intent data.
256         val args = argsList.toTypedArray()
257         val alarms = Alarm.getAlarms(cr, selection.toString(), *args)
258 
259         val alarm: Alarm
260         if (alarms.isNotEmpty()) {
261             // Enable the first matching alarm.
262             alarm = alarms[0]
263             alarm.enabled = true
264             Alarm.updateAlarm(cr, alarm)
265 
266             // Delete all old instances.
267             AlarmStateManager.deleteAllInstances(this, alarm.id)
268 
269             Events.sendAlarmEvent(R.string.action_update, R.string.label_intent)
270             LOGGER.i("Updated alarm: $alarm")
271         } else {
272             // No existing alarm could be located; create one using the intent data.
273             alarm = Alarm()
274             updateAlarmFromIntent(alarm, intent)
275             alarm.deleteAfterUse = !alarm.daysOfWeek.isRepeating && skipUi
276 
277             // Save the new alarm.
278             Alarm.addAlarm(cr, alarm)
279 
280             Events.sendAlarmEvent(R.string.action_create, R.string.label_intent)
281             LOGGER.i("Created new alarm: $alarm")
282         }
283 
284         // Schedule the next instance.
285         val now: Calendar = DataModel.dataModel.calendar
286         val alarmInstance = alarm.createInstanceAfter(now)
287         setupInstance(alarmInstance, skipUi)
288 
289         val time = DateFormat.getTimeFormat(this).format(alarmInstance.alarmTime.time)
290         Controller.getController().notifyVoiceSuccess(this, getString(R.string.alarm_is_set, time))
291     }
292 
handleDismissTimernull293     private fun handleDismissTimer(intent: Intent) {
294         val dataUri = intent.data
295         if (dataUri != null) {
296             val selectedTimer = getSelectedTimer(dataUri)
297             if (selectedTimer != null) {
298                 DataModel.dataModel.resetOrDeleteTimer(selectedTimer, R.string.label_intent)
299                 Controller.getController().notifyVoiceSuccess(this,
300                         resources.getQuantityString(R.plurals.expired_timers_dismissed, 1))
301                 LOGGER.i("Timer dismissed: $selectedTimer")
302             } else {
303                 Controller.getController().notifyVoiceFailure(this,
304                         getString(R.string.invalid_timer))
305                 LOGGER.e("Could not dismiss timer: invalid URI")
306             }
307         } else {
308             val expiredTimers: List<Timer> = DataModel.dataModel.expiredTimers
309             if (expiredTimers.isNotEmpty()) {
310                 for (timer in expiredTimers) {
311                     DataModel.dataModel.resetOrDeleteTimer(timer, R.string.label_intent)
312                 }
313                 val numberOfTimers = expiredTimers.size
314                 val timersDismissedMessage = resources.getQuantityString(
315                         R.plurals.expired_timers_dismissed, numberOfTimers, numberOfTimers)
316                 Controller.getController().notifyVoiceSuccess(this, timersDismissedMessage)
317                 LOGGER.i(timersDismissedMessage)
318             } else {
319                 Controller.getController().notifyVoiceFailure(this,
320                         getString(R.string.no_expired_timers))
321                 LOGGER.e("Could not dismiss timer: no expired timers")
322             }
323         }
324     }
325 
getSelectedTimernull326     private fun getSelectedTimer(dataUri: Uri): Timer? {
327         return try {
328             val timerId = ContentUris.parseId(dataUri).toInt()
329             DataModel.dataModel.getTimer(timerId)
330         } catch (e: NumberFormatException) {
331             null
332         }
333     }
334 
handleShowAlarmsnull335     private fun handleShowAlarms() {
336         Events.sendAlarmEvent(R.string.action_show, R.string.label_intent)
337 
338         // Open DeskClock positioned on the alarms tab.
339         UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.ALARMS
340         startActivity(Intent(this, DeskClock::class.java))
341     }
342 
handleShowTimersnull343     private fun handleShowTimers(intent: Intent) {
344         Events.sendTimerEvent(R.string.action_show, R.string.label_intent)
345 
346         val showTimersIntent = Intent(this, DeskClock::class.java)
347 
348         val timers: List<Timer> = DataModel.dataModel.timers
349         if (timers.isNotEmpty()) {
350             val newestTimer = timers[timers.size - 1]
351             showTimersIntent.putExtra(TimerService.EXTRA_TIMER_ID, newestTimer.id)
352         }
353 
354         // Open DeskClock positioned on the timers tab.
355         UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.TIMERS
356         startActivity(showTimersIntent)
357     }
358 
handleSetTimernull359     private fun handleSetTimer(intent: Intent) {
360         // If no length is supplied, show the timer setup view.
361         if (!intent.hasExtra(AlarmClock.EXTRA_LENGTH)) {
362             // Change to the timers tab.
363             UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.TIMERS
364 
365             // Open DeskClock which is now positioned on the timers tab and show the timer setup.
366             startActivity(TimerFragment.createTimerSetupIntent(this))
367             LOGGER.i("Showing timer setup")
368             return
369         }
370 
371         // Verify that the timer length is between one second and one day.
372         val lengthMillis =
373                 DateUtils.SECOND_IN_MILLIS * intent.getIntExtra(AlarmClock.EXTRA_LENGTH, 0)
374         if (lengthMillis < Timer.MIN_LENGTH) {
375             val voiceMessage = getString(R.string.invalid_timer_length)
376             Controller.getController().notifyVoiceFailure(this, voiceMessage)
377             LOGGER.i("Invalid timer length requested: $lengthMillis")
378             return
379         }
380 
381         val label = getLabelFromIntent(intent, "")
382         val skipUi = intent.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI, false)
383 
384         // Attempt to reuse an existing timer that is Reset with the same length and label.
385         var timer: Timer? = null
386         for (t in DataModel.dataModel.timers) {
387             if (!t.isReset) {
388                 continue
389             }
390             if (t.length != lengthMillis) {
391                 continue
392             }
393             if (!TextUtils.equals(label, t.label)) {
394                 continue
395             }
396 
397             timer = t
398             break
399         }
400 
401         // Create a new timer if one could not be reused.
402         if (timer == null) {
403             timer = DataModel.dataModel.addTimer(lengthMillis, label, skipUi)
404             Events.sendTimerEvent(R.string.action_create, R.string.label_intent)
405         }
406 
407         // Start the selected timer.
408         DataModel.dataModel.startTimer(timer)
409         Events.sendTimerEvent(R.string.action_start, R.string.label_intent)
410         Controller.getController().notifyVoiceSuccess(this, getString(R.string.timer_created))
411 
412         // If not instructed to skip the UI, display the running timer.
413         if (!skipUi) {
414             // Change to the timers tab.
415             UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.TIMERS
416 
417             // Open DeskClock which is now positioned on the timers tab.
418             startActivity(Intent(this, DeskClock::class.java)
419                     .putExtra(TimerService.EXTRA_TIMER_ID, timer.id))
420         }
421     }
422 
setupInstancenull423     private fun setupInstance(instance: AlarmInstance, skipUi: Boolean) {
424         var variableInstance = instance
425         variableInstance = AlarmInstance.addInstance(this.contentResolver, variableInstance)
426         AlarmStateManager.registerInstance(this, variableInstance, true)
427         popAlarmSetToast(this, variableInstance.alarmTime.timeInMillis)
428         if (!skipUi) {
429             // Change to the alarms tab.
430             UiDataModel.uiDataModel.selectedTab = UiDataModel.Tab.ALARMS
431 
432             // Open DeskClock which is now positioned on the alarms tab.
433             val showAlarm =
434                     Alarm.createIntent(this, DeskClock::class.java, variableInstance.mAlarmId!!)
435                     .putExtra(AlarmClockFragment.SCROLL_TO_ALARM_INTENT_EXTRA,
436                             variableInstance.mAlarmId!!)
437                     .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
438             startActivity(showAlarm)
439         }
440     }
441 
442     /**
443      * Assemble a database where clause to search for an alarm matching the given `hour` and
444      * `minutes` as well as all of the optional information within the `intent`
445      * including:
446      * <ul>
447      *     <li>alarm message</li>
448      *     <li>repeat days</li>
449      *     <li>vibration setting</li>
450      *     <li>ringtone uri</li>
451      * </ul>
452      *
453      * @param intent contains details of the alarm to be located
454      * @param hour the hour of the day of the alarm
455      * @param minutes the minute of the hour of the alarm
456      * @param selection an out parameter containing a SQL where clause
457      * @param args an out parameter containing the values to substitute into the `selection`
458      */
setSelectionFromIntentnull459     private fun setSelectionFromIntent(
460         intent: Intent,
461         hour: Int,
462         minutes: Int,
463         selection: StringBuilder,
464         args: MutableList<String>
465     ) {
466         selection.append(AlarmsColumns.HOUR).append("=?")
467         args.add(hour.toString())
468         selection.append(" AND ").append(AlarmsColumns.MINUTES).append("=?")
469         args.add(minutes.toString())
470         if (intent.hasExtra(AlarmClock.EXTRA_MESSAGE)) {
471             selection.append(" AND ").append(AlarmSettingColumns.LABEL).append("=?")
472             args.add(getLabelFromIntent(intent, ""))
473         }
474 
475         // Days is treated differently than other fields because if days is not specified, it
476         // explicitly means "not recurring".
477         selection.append(" AND ").append(AlarmsColumns.DAYS_OF_WEEK).append("=?")
478         args.add(getDaysFromIntent(intent, Weekdays.NONE).bits.toString())
479         if (intent.hasExtra(AlarmClock.EXTRA_VIBRATE)) {
480             selection.append(" AND ").append(AlarmSettingColumns.VIBRATE).append("=?")
481             args.add(if (intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, false)) "1" else "0")
482         }
483         if (intent.hasExtra(AlarmClock.EXTRA_RINGTONE)) {
484             selection.append(" AND ").append(AlarmSettingColumns.RINGTONE).append("=?")
485 
486             // If the intent explicitly specified a NULL ringtone, treat it as the default ringtone.
487             val defaultRingtone: Uri = DataModel.dataModel.defaultAlarmRingtoneUri
488             val ringtone = getAlertFromIntent(intent, defaultRingtone)
489             args.add(ringtone.toString())
490         }
491     }
492 
493     companion object {
494         private val LOGGER = LogUtils.Logger("HandleApiCalls")
495 
dismissAlarmnull496         fun dismissAlarm(alarm: Alarm, activity: Activity) {
497             val context = activity.applicationContext
498             val instance = AlarmInstance.getNextUpcomingInstanceByAlarmId(
499                     context.contentResolver, alarm.id)
500             if (instance == null) {
501                 val reason = context.getString(R.string.no_alarm_scheduled_for_this_time)
502                 Controller.getController().notifyVoiceFailure(activity, reason)
503                 LOGGER.i("No alarm instance to dismiss")
504                 return
505             }
506 
507             dismissAlarmInstance(instance, activity)
508         }
509 
dismissAlarmInstancenull510         private fun dismissAlarmInstance(instance: AlarmInstance, activity: Activity) {
511             Utils.enforceNotMainLooper()
512 
513             val context = activity.applicationContext
514             val alarmTime: Date = instance.alarmTime.time
515             val time = DateFormat.getTimeFormat(context).format(alarmTime)
516 
517             if (instance.mAlarmState == ClockContract.InstancesColumns.FIRED_STATE ||
518                     instance.mAlarmState == ClockContract.InstancesColumns.SNOOZE_STATE) {
519                 // Always dismiss alarms that are fired or snoozed.
520                 AlarmStateManager.deleteInstanceAndUpdateParent(context, instance)
521             } else if (Utils.isAlarmWithin24Hours(instance)) {
522                 // Upcoming alarms are always predismissed.
523                 AlarmStateManager.setPreDismissState(context, instance)
524             } else {
525                 // Otherwise the alarm cannot be dismissed at this time.
526                 val reason = context.getString(
527                         R.string.alarm_cant_be_dismissed_still_more_than_24_hours_away, time)
528                 Controller.getController().notifyVoiceFailure(activity, reason)
529                 LOGGER.i("Can't dismiss alarm more than 24 hours in advance")
530             }
531 
532             // Log the successful dismissal.
533             val reason = context.getString(R.string.alarm_is_dismissed, time)
534             Controller.getController().notifyVoiceSuccess(activity, reason)
535             LOGGER.i("Alarm dismissed: $instance")
536             Events.sendAlarmEvent(R.string.action_dismiss, R.string.label_intent)
537         }
538 
snoozeAlarmnull539         fun snoozeAlarm(alarmInstance: AlarmInstance, context: Context, activity: Activity) {
540             Utils.enforceNotMainLooper()
541 
542             val time = DateFormat.getTimeFormat(context).format(
543                     alarmInstance.alarmTime.time)
544             val reason = context.getString(R.string.alarm_is_snoozed, time)
545             AlarmStateManager.setSnoozeState(context, alarmInstance, true)
546 
547             Controller.getController().notifyVoiceSuccess(activity, reason)
548             LOGGER.i("Alarm snoozed: $alarmInstance")
549             Events.sendAlarmEvent(R.string.action_snooze, R.string.label_intent)
550         }
551 
552         /**
553          * @param alarm the alarm to be updated
554          * @param intent the intent containing new alarm field values to merge into the `alarm`
555          */
updateAlarmFromIntentnull556         private fun updateAlarmFromIntent(alarm: Alarm, intent: Intent) {
557             alarm.enabled = true
558             alarm.hour = intent.getIntExtra(AlarmClock.EXTRA_HOUR, alarm.hour)
559             alarm.minutes = intent.getIntExtra(AlarmClock.EXTRA_MINUTES, alarm.minutes)
560             alarm.vibrate = intent.getBooleanExtra(AlarmClock.EXTRA_VIBRATE, alarm.vibrate)
561             alarm.alert = getAlertFromIntent(intent, alarm.alert!!)
562             alarm.label = getLabelFromIntent(intent, alarm.label)
563             alarm.daysOfWeek = getDaysFromIntent(intent, alarm.daysOfWeek)
564         }
565 
getLabelFromIntentnull566         private fun getLabelFromIntent(intent: Intent?, defaultLabel: String?): String {
567             val message = intent!!.extras!!.getString(AlarmClock.EXTRA_MESSAGE, defaultLabel)
568             return message ?: ""
569         }
570 
getDaysFromIntentnull571         private fun getDaysFromIntent(intent: Intent, defaultWeekdays: Weekdays): Weekdays {
572             if (!intent.hasExtra(AlarmClock.EXTRA_DAYS)) {
573                 return defaultWeekdays
574             }
575 
576             val days: List<Int>? = intent.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS)
577             if (days != null) {
578                 val daysArray = IntArray(days.size)
579                 for (i in days.indices) {
580                     daysArray[i] = days[i]
581                 }
582                 return Weekdays.fromCalendarDays(*daysArray)
583             } else {
584                 // API says to use an ArrayList<Integer> but we allow the user to use a int[] too.
585                 val daysArray = intent.getIntArrayExtra(AlarmClock.EXTRA_DAYS)
586                 if (daysArray != null) {
587                     return Weekdays.fromCalendarDays(*daysArray)
588                 }
589             }
590             return defaultWeekdays
591         }
592 
getAlertFromIntentnull593         private fun getAlertFromIntent(intent: Intent, defaultUri: Uri): Uri {
594             val alert = intent.getStringExtra(AlarmClock.EXTRA_RINGTONE)
595             if (alert == null) {
596                 return defaultUri
597             } else if (AlarmClock.VALUE_RINGTONE_SILENT == alert || alert.isEmpty()) {
598                 return AlarmSettingColumns.NO_RINGTONE_URI
599             }
600 
601             return Uri.parse(alert)
602         }
603     }
604 }