1 /* <lambda>null2 * 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.ringtone 18 19 import android.app.Dialog 20 import android.content.Context 21 import android.content.ContentResolver 22 import android.content.DialogInterface 23 import android.content.Intent 24 import android.media.AudioManager 25 import android.media.RingtoneManager 26 import android.net.Uri 27 import android.os.AsyncTask 28 import android.os.Bundle 29 import android.provider.MediaStore 30 import android.provider.OpenableColumns 31 import android.view.LayoutInflater 32 import android.view.Menu 33 import android.view.MenuItem 34 import android.view.View 35 import androidx.annotation.Keep 36 import androidx.annotation.VisibleForTesting 37 import androidx.appcompat.app.AlertDialog 38 import androidx.fragment.app.DialogFragment 39 import androidx.fragment.app.FragmentManager 40 import androidx.loader.app.LoaderManager 41 import androidx.loader.app.LoaderManager.LoaderCallbacks 42 import androidx.loader.content.Loader 43 import androidx.recyclerview.widget.LinearLayoutManager 44 import androidx.recyclerview.widget.RecyclerView 45 46 import com.android.deskclock.BaseActivity 47 import com.android.deskclock.DropShadowController 48 import com.android.deskclock.LogUtils 49 import com.android.deskclock.R 50 import com.android.deskclock.RingtonePreviewKlaxon 51 import com.android.deskclock.ItemAdapter 52 import com.android.deskclock.ItemAdapter.ItemHolder 53 import com.android.deskclock.ItemAdapter.ItemViewHolder 54 import com.android.deskclock.ItemAdapter.OnItemClickedListener 55 import com.android.deskclock.actionbarmenu.MenuItemControllerFactory 56 import com.android.deskclock.actionbarmenu.NavUpMenuItemController 57 import com.android.deskclock.actionbarmenu.OptionsMenuManager 58 import com.android.deskclock.alarms.AlarmUpdateHandler 59 import com.android.deskclock.data.DataModel 60 import com.android.deskclock.provider.Alarm 61 62 /** 63 * This activity presents a set of ringtones from which the user may select one. The set includes: 64 * 65 * * system ringtones from the Android framework 66 * * a ringtone representing pure silence 67 * * a ringtone representing a default ringtone 68 * * user-selected audio files available as ringtones 69 * 70 */ 71 // TODO(b/165664115) Replace deprecated AsyncTask calls 72 class RingtonePickerActivity : BaseActivity(), LoaderCallbacks<List<ItemHolder<Uri?>>> { 73 /** The controller that shows the drop shadow when content is not scrolled to the top. */ 74 private var mDropShadowController: DropShadowController? = null 75 76 /** Generates the items in the activity context menu. */ 77 private lateinit var mOptionsMenuManager: OptionsMenuManager 78 79 /** Displays a set of selectable ringtones. */ 80 private lateinit var mRecyclerView: RecyclerView 81 82 /** Stores the set of ItemHolders that wrap the selectable ringtones. */ 83 private lateinit var mRingtoneAdapter: ItemAdapter<ItemHolder<Uri?>> 84 85 /** The title of the default ringtone. */ 86 private var mDefaultRingtoneTitle: String? = null 87 88 /** The uri of the default ringtone. */ 89 private var mDefaultRingtoneUri: Uri? = null 90 91 /** The uri of the ringtone to select after data is loaded. */ 92 private var mSelectedRingtoneUri: Uri? = null 93 94 /** `true` indicates the [.mSelectedRingtoneUri] must be played after data load. */ 95 private var mIsPlaying = false 96 97 /** Identifies the alarm to receive the selected ringtone; -1 indicates there is no alarm. */ 98 private var mAlarmId: Long = -1 99 100 /** The location of the custom ringtone to be removed. */ 101 private var mIndexOfRingtoneToRemove: Int = RecyclerView.NO_POSITION 102 103 override fun onCreate(savedInstanceState: Bundle?) { 104 super.onCreate(savedInstanceState) 105 setContentView(R.layout.ringtone_picker) 106 setVolumeControlStream(AudioManager.STREAM_ALARM) 107 108 mOptionsMenuManager = OptionsMenuManager() 109 mOptionsMenuManager.addMenuItemController(NavUpMenuItemController(this)) 110 .addMenuItemController(*MenuItemControllerFactory.buildMenuItemControllers(this)) 111 112 val context: Context = getApplicationContext() 113 val intent: Intent = getIntent() 114 115 if (savedInstanceState != null) { 116 mIsPlaying = savedInstanceState.getBoolean(STATE_KEY_PLAYING) 117 mSelectedRingtoneUri = savedInstanceState.getParcelable(EXTRA_RINGTONE_URI) 118 } 119 120 if (mSelectedRingtoneUri == null) { 121 mSelectedRingtoneUri = intent.getParcelableExtra(EXTRA_RINGTONE_URI) 122 } 123 124 mAlarmId = intent.getLongExtra(EXTRA_ALARM_ID, -1) 125 mDefaultRingtoneUri = intent.getParcelableExtra(EXTRA_DEFAULT_RINGTONE_URI) 126 val defaultRingtoneTitleId = intent.getIntExtra(EXTRA_DEFAULT_RINGTONE_NAME, 0) 127 mDefaultRingtoneTitle = context.getString(defaultRingtoneTitleId) 128 129 val inflater: LayoutInflater = getLayoutInflater() 130 val listener: OnItemClickedListener = ItemClickWatcher() 131 val ringtoneFactory: ItemViewHolder.Factory = RingtoneViewHolder.Factory(inflater) 132 val headerFactory: ItemViewHolder.Factory = HeaderViewHolder.Factory(inflater) 133 val addNewFactory: ItemViewHolder.Factory = AddCustomRingtoneViewHolder.Factory(inflater) 134 mRingtoneAdapter = ItemAdapter() 135 mRingtoneAdapter 136 .withViewTypes(headerFactory, null, HeaderViewHolder.VIEW_TYPE_ITEM_HEADER) 137 .withViewTypes(addNewFactory, listener, 138 AddCustomRingtoneViewHolder.VIEW_TYPE_ADD_NEW) 139 .withViewTypes(ringtoneFactory, listener, RingtoneViewHolder.VIEW_TYPE_SYSTEM_SOUND) 140 .withViewTypes(ringtoneFactory, listener, RingtoneViewHolder.VIEW_TYPE_CUSTOM_SOUND) 141 142 mRecyclerView = findViewById(R.id.ringtone_content) as RecyclerView 143 mRecyclerView.setLayoutManager(LinearLayoutManager(context)) 144 mRecyclerView.setAdapter(mRingtoneAdapter) 145 mRecyclerView.setItemAnimator(null) 146 147 mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { 148 override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 149 if (mIndexOfRingtoneToRemove != RecyclerView.NO_POSITION) { 150 closeContextMenu() 151 } 152 } 153 }) 154 155 val titleResourceId = intent.getIntExtra(EXTRA_TITLE, 0) 156 setTitle(context.getString(titleResourceId)) 157 158 LoaderManager.getInstance(this).initLoader(0 /* id */, Bundle.EMPTY /* args */, 159 this /* callback */) 160 161 registerForContextMenu(mRecyclerView) 162 } 163 164 override fun onResume() { 165 super.onResume() 166 167 val dropShadow: View = findViewById(R.id.drop_shadow) 168 mDropShadowController = DropShadowController(dropShadow, mRecyclerView) 169 } 170 171 override fun onPause() { 172 mDropShadowController!!.stop() 173 mDropShadowController = null 174 175 mSelectedRingtoneUri?.let { 176 if (mAlarmId != -1L) { 177 val context: Context = getApplicationContext() 178 val cr: ContentResolver = getContentResolver() 179 180 // Start a background task to fetch the alarm whose ringtone must be updated. 181 object : AsyncTask<Void?, Void?, Alarm>() { 182 override fun doInBackground(vararg parameters: Void?): Alarm? { 183 val alarm = Alarm.getAlarm(cr, mAlarmId) 184 if (alarm != null) { 185 alarm.alert = it 186 } 187 return alarm 188 } 189 190 override fun onPostExecute(alarm: Alarm) { 191 // Update the default ringtone for future new alarms. 192 DataModel.dataModel.defaultAlarmRingtoneUri = alarm.alert!! 193 194 // Start a second background task to persist the updated alarm. 195 AlarmUpdateHandler(context, mScrollHandler = null, mSnackbarAnchor = null) 196 .asyncUpdateAlarm(alarm, popToast = false, minorUpdate = true) 197 } 198 }.execute() 199 } else { 200 DataModel.dataModel.timerRingtoneUri = it 201 } 202 } 203 204 super.onPause() 205 } 206 207 override fun onStop() { 208 if (!isChangingConfigurations()) { 209 stopPlayingRingtone(selectedRingtoneHolder, false) 210 } 211 super.onStop() 212 } 213 214 override fun onSaveInstanceState(outState: Bundle) { 215 super.onSaveInstanceState(outState) 216 217 outState.putBoolean(STATE_KEY_PLAYING, mIsPlaying) 218 outState.putParcelable(EXTRA_RINGTONE_URI, mSelectedRingtoneUri) 219 } 220 221 override fun onCreateOptionsMenu(menu: Menu): Boolean { 222 mOptionsMenuManager.onCreateOptionsMenu(menu) 223 return true 224 } 225 226 override fun onPrepareOptionsMenu(menu: Menu): Boolean { 227 mOptionsMenuManager.onPrepareOptionsMenu(menu) 228 return true 229 } 230 231 override fun onOptionsItemSelected(item: MenuItem): Boolean { 232 return mOptionsMenuManager.onOptionsItemSelected(item) || 233 super.onOptionsItemSelected(item) 234 } 235 236 override fun onCreateLoader(id: Int, args: Bundle?): Loader<List<ItemHolder<Uri?>>> { 237 return RingtoneLoader(getApplicationContext(), mDefaultRingtoneUri!!, 238 mDefaultRingtoneTitle!!) 239 } 240 241 override fun onLoadFinished( 242 loader: Loader<List<ItemHolder<Uri?>>>, 243 itemHolders: List<ItemHolder<Uri?>> 244 ) { 245 // Update the adapter with fresh data. 246 mRingtoneAdapter.setItems(itemHolders) 247 248 // Attempt to select the requested ringtone. 249 val toSelect = getRingtoneHolder(mSelectedRingtoneUri) 250 if (toSelect != null) { 251 toSelect.isSelected = true 252 mSelectedRingtoneUri = toSelect.uri 253 toSelect.notifyItemChanged() 254 255 // Start playing the ringtone if indicated. 256 if (mIsPlaying) { 257 startPlayingRingtone(toSelect) 258 } 259 } else { 260 // Clear the selection since it does not exist in the data. 261 RingtonePreviewKlaxon.stop(this) 262 mSelectedRingtoneUri = null 263 mIsPlaying = false 264 } 265 } 266 267 override fun onLoaderReset(loader: Loader<List<ItemHolder<Uri?>>>) { 268 } 269 270 override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { 271 if (resultCode != RESULT_OK) { 272 return 273 } 274 275 val uri = data?.data ?: return 276 277 // Bail if the permission to read (playback) the audio at the uri was not granted. 278 val flags = data.flags and Intent.FLAG_GRANT_READ_URI_PERMISSION 279 if (flags != Intent.FLAG_GRANT_READ_URI_PERMISSION) { 280 return 281 } 282 283 // Start a task to fetch the display name of the audio content and add the custom ringtone. 284 AddCustomRingtoneTask(uri).execute() 285 } 286 287 override fun onContextItemSelected(item: MenuItem): Boolean { 288 // Find the ringtone to be removed. 289 val items = mRingtoneAdapter.items 290 val toRemove = items!![mIndexOfRingtoneToRemove] as RingtoneHolder 291 mIndexOfRingtoneToRemove = RecyclerView.NO_POSITION 292 293 // Launch the confirmation dialog. 294 val manager: FragmentManager = supportFragmentManager 295 val hasPermissions = toRemove.hasPermissions() 296 ConfirmRemoveCustomRingtoneDialogFragment.show(manager, toRemove.uri, hasPermissions) 297 return true 298 } 299 300 private fun getRingtoneHolder(uri: Uri?): RingtoneHolder? { 301 for (itemHolder in mRingtoneAdapter.items!!) { 302 if (itemHolder is RingtoneHolder) { 303 if (itemHolder.uri == uri) { 304 return itemHolder 305 } 306 } 307 } 308 309 return null 310 } 311 312 @get:VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) 313 internal val selectedRingtoneHolder: RingtoneHolder? 314 get() = getRingtoneHolder(mSelectedRingtoneUri) 315 316 /** 317 * The given `ringtone` will be selected as a side-effect of playing the ringtone. 318 * 319 * @param ringtone the ringtone to be played 320 */ 321 private fun startPlayingRingtone(ringtone: RingtoneHolder) { 322 if (!ringtone.isPlaying && !ringtone.isSilent) { 323 RingtonePreviewKlaxon.start(getApplicationContext(), ringtone.uri) 324 ringtone.isPlaying = true 325 mIsPlaying = true 326 } 327 if (!ringtone.isSelected) { 328 ringtone.isSelected = true 329 mSelectedRingtoneUri = ringtone.uri 330 } 331 ringtone.notifyItemChanged() 332 } 333 334 /** 335 * @param ringtone the ringtone to stop playing 336 * @param deselect `true` indicates the ringtone should also be deselected; 337 * `false` indicates its selection state should remain unchanged 338 */ 339 private fun stopPlayingRingtone(ringtone: RingtoneHolder?, deselect: Boolean) { 340 if (ringtone == null) { 341 return 342 } 343 344 if (ringtone.isPlaying) { 345 RingtonePreviewKlaxon.stop(this) 346 ringtone.isPlaying = false 347 mIsPlaying = false 348 } 349 if (deselect && ringtone.isSelected) { 350 ringtone.isSelected = false 351 mSelectedRingtoneUri = null 352 } 353 ringtone.notifyItemChanged() 354 } 355 356 /** 357 * Proceeds with removing the custom ringtone with the given uri. 358 * 359 * @param toRemove identifies the custom ringtone to be removed 360 */ 361 private fun removeCustomRingtone(toRemove: Uri) { 362 RemoveCustomRingtoneTask(toRemove).execute() 363 } 364 365 /** 366 * This DialogFragment informs the user of the side-effects of removing a custom ringtone while 367 * it is in use by alarms and/or timers and prompts them to confirm the removal. 368 */ 369 class ConfirmRemoveCustomRingtoneDialogFragment : DialogFragment() { 370 override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 371 val arguments = requireArguments() 372 val toRemove = arguments.getParcelable<Uri>(ARG_RINGTONE_URI_TO_REMOVE) 373 374 val okListener = DialogInterface.OnClickListener { _, _ -> 375 (activity as RingtonePickerActivity).removeCustomRingtone(toRemove!!) 376 } 377 378 return if (arguments.getBoolean(ARG_RINGTONE_HAS_PERMISSIONS)) { 379 AlertDialog.Builder(requireActivity()) 380 .setPositiveButton(R.string.remove_sound, okListener) 381 .setNegativeButton(android.R.string.cancel, null /* listener */) 382 .setMessage(R.string.confirm_remove_custom_ringtone) 383 .create() 384 } else { 385 AlertDialog.Builder(requireActivity()) 386 .setPositiveButton(R.string.remove_sound, okListener) 387 .setMessage(R.string.custom_ringtone_lost_permissions) 388 .create() 389 } 390 } 391 392 companion object { 393 private const val ARG_RINGTONE_URI_TO_REMOVE = "arg_ringtone_uri_to_remove" 394 private const val ARG_RINGTONE_HAS_PERMISSIONS = "arg_ringtone_has_permissions" 395 396 fun show(manager: FragmentManager, toRemove: Uri?, hasPermissions: Boolean) { 397 if (manager.isDestroyed) { 398 return 399 } 400 401 val args = Bundle() 402 args.putParcelable(ARG_RINGTONE_URI_TO_REMOVE, toRemove) 403 args.putBoolean(ARG_RINGTONE_HAS_PERMISSIONS, hasPermissions) 404 405 val fragment: DialogFragment = ConfirmRemoveCustomRingtoneDialogFragment() 406 fragment.arguments = args 407 fragment.isCancelable = hasPermissions 408 fragment.show(manager, "confirm_ringtone_remove") 409 } 410 } 411 } 412 413 /** 414 * This click handler alters selection and playback of ringtones. It also launches the system 415 * file chooser to search for openable audio files that may serve as ringtones. 416 */ 417 private inner class ItemClickWatcher : OnItemClickedListener { 418 override fun onItemClicked(viewHolder: ItemViewHolder<*>, id: Int) { 419 when (id) { 420 AddCustomRingtoneViewHolder.CLICK_ADD_NEW -> { 421 stopPlayingRingtone(selectedRingtoneHolder, false) 422 startActivityForResult(Intent(Intent.ACTION_OPEN_DOCUMENT) 423 .addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) 424 .addCategory(Intent.CATEGORY_OPENABLE) 425 .setType("audio/*"), 0) 426 } 427 RingtoneViewHolder.CLICK_NORMAL -> { 428 val oldSelection = selectedRingtoneHolder 429 val newSelection = viewHolder.itemHolder as RingtoneHolder 430 431 // Tapping the existing selection toggles playback of the ringtone. 432 if (oldSelection === newSelection) { 433 if (newSelection.isPlaying) { 434 stopPlayingRingtone(newSelection, false) 435 } else { 436 startPlayingRingtone(newSelection) 437 } 438 } else { 439 // Tapping a new selection changes the selection and playback. 440 stopPlayingRingtone(oldSelection, true) 441 startPlayingRingtone(newSelection) 442 } 443 } 444 RingtoneViewHolder.CLICK_LONG_PRESS -> { 445 mIndexOfRingtoneToRemove = viewHolder.getAdapterPosition() 446 } 447 RingtoneViewHolder.CLICK_NO_PERMISSIONS -> { 448 ConfirmRemoveCustomRingtoneDialogFragment.show(supportFragmentManager, 449 (viewHolder.itemHolder as RingtoneHolder).uri, false) 450 } 451 } 452 } 453 } 454 455 /** 456 * This task locates a displayable string in the background that is fit for use as the title of 457 * the audio content. It adds a custom ringtone using the uri and title on the main thread. 458 */ 459 private inner class AddCustomRingtoneTask(private val mUri: Uri) 460 : AsyncTask<Void?, Void?, String>() { 461 private val mContext: Context = getApplicationContext() 462 463 override fun doInBackground(vararg voids: Void?): String { 464 val contentResolver = mContext.contentResolver 465 466 // Take the long-term permission to read (playback) the audio at the uri. 467 contentResolver 468 .takePersistableUriPermission(mUri, Intent.FLAG_GRANT_READ_URI_PERMISSION) 469 try { 470 contentResolver.query(mUri, null, null, null, null).use { cursor -> 471 if (cursor != null && cursor.moveToFirst()) { 472 // If the file was a media file, return its title. 473 val titleIndex = cursor.getColumnIndex(MediaStore.Audio.Media.TITLE) 474 if (titleIndex != -1) { 475 return cursor.getString(titleIndex) 476 } 477 478 // If the file was a simple openable, return its display name. 479 val displayNameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) 480 if (displayNameIndex != -1) { 481 var title = cursor.getString(displayNameIndex) 482 val dotIndex = title.lastIndexOf(".") 483 if (dotIndex > 0) { 484 title = title.substring(0, dotIndex) 485 } 486 return title 487 } 488 } else { 489 LogUtils.e("No ringtone for uri: %s", mUri) 490 } 491 } 492 } catch (e: Exception) { 493 LogUtils.e("Unable to locate title for custom ringtone: $mUri", e) 494 } 495 496 return mContext.getString(R.string.unknown_ringtone_title) 497 } 498 499 override fun onPostExecute(title: String) { 500 // Add the new custom ringtone to the data model. 501 DataModel.dataModel.addCustomRingtone(mUri, title) 502 503 // When the loader completes, it must play the new ringtone. 504 mSelectedRingtoneUri = mUri 505 mIsPlaying = true 506 507 // Reload the data to reflect the change in the UI. 508 LoaderManager.getInstance(this@RingtonePickerActivity).restartLoader(0 /* id */, 509 null /* args */, this@RingtonePickerActivity /* callback */) 510 } 511 } 512 513 /** 514 * Removes a custom ringtone with the given uri. Taking this action has side-effects because 515 * all alarms that use the custom ringtone are reassigned to the Android system default alarm 516 * ringtone. If the application's default alarm ringtone is being removed, it is reset to the 517 * Android system default alarm ringtone. If the application's timer ringtone is being removed, 518 * it is reset to the application's default timer ringtone. 519 */ 520 private inner class RemoveCustomRingtoneTask(private val mRemoveUri: Uri) 521 : AsyncTask<Void?, Void?, Void?>() { 522 private lateinit var mSystemDefaultRingtoneUri: Uri 523 524 override fun doInBackground(vararg voids: Void?): Void? { 525 mSystemDefaultRingtoneUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM) 526 527 // Update all alarms that use the custom ringtone to use the system default. 528 val cr: ContentResolver = getContentResolver() 529 val alarms = Alarm.getAlarms(cr, null) 530 for (alarm in alarms) { 531 if (mRemoveUri == alarm.alert) { 532 alarm.alert = mSystemDefaultRingtoneUri 533 // Start a second background task to persist the updated alarm. 534 AlarmUpdateHandler(this@RingtonePickerActivity, null, null) 535 .asyncUpdateAlarm(alarm, popToast = false, minorUpdate = true) 536 } 537 } 538 539 try { 540 // Release the permission to read (playback) the audio at the uri. 541 cr.releasePersistableUriPermission(mRemoveUri, 542 Intent.FLAG_GRANT_READ_URI_PERMISSION) 543 } catch (ignore: SecurityException) { 544 // If the file was already deleted from the file system, a SecurityException is 545 // thrown indicating this app did not hold the read permission being released. 546 LogUtils.w("SecurityException while releasing read permission for $mRemoveUri") 547 } 548 549 return null 550 } 551 552 override fun onPostExecute(v: Void?) { 553 // Reset the default alarm ringtone if it was just removed. 554 if (mRemoveUri == DataModel.dataModel.defaultAlarmRingtoneUri) { 555 DataModel.dataModel.defaultAlarmRingtoneUri = mSystemDefaultRingtoneUri 556 } 557 558 // Reset the timer ringtone if it was just removed. 559 if (mRemoveUri == DataModel.dataModel.timerRingtoneUri) { 560 val timerRingtoneUri = DataModel.dataModel.defaultTimerRingtoneUri 561 DataModel.dataModel.timerRingtoneUri = timerRingtoneUri 562 } 563 564 // Remove the corresponding custom ringtone. 565 DataModel.dataModel.removeCustomRingtone(mRemoveUri) 566 567 // Find the ringtone to be removed from the adapter. 568 val toRemove = getRingtoneHolder(mRemoveUri) ?: return 569 570 // If the ringtone to remove is also the selected ringtone, adjust the selection. 571 if (toRemove.isSelected) { 572 stopPlayingRingtone(toRemove, false) 573 val defaultRingtone = getRingtoneHolder(mDefaultRingtoneUri) 574 if (defaultRingtone != null) { 575 defaultRingtone.isSelected = true 576 mSelectedRingtoneUri = defaultRingtone.uri 577 defaultRingtone.notifyItemChanged() 578 } 579 } 580 581 // Remove the ringtone from the adapter. 582 mRingtoneAdapter.removeItem(toRemove) 583 } 584 } 585 586 companion object { 587 /** Key to an extra that defines resource id to the title of this activity. */ 588 private const val EXTRA_TITLE = "extra_title" 589 590 /** Key to an extra that identifies the alarm to which the selected ringtone is attached. */ 591 private const val EXTRA_ALARM_ID = "extra_alarm_id" 592 593 /** Key to an extra that identifies the selected ringtone. */ 594 private const val EXTRA_RINGTONE_URI = "extra_ringtone_uri" 595 596 /** Key to an extra that defines the uri representing the default ringtone. */ 597 private const val EXTRA_DEFAULT_RINGTONE_URI = "extra_default_ringtone_uri" 598 599 /** Key to an extra that defines the name of the default ringtone. */ 600 private const val EXTRA_DEFAULT_RINGTONE_NAME = "extra_default_ringtone_name" 601 602 /** Key to an instance state value indicating if the 603 * selected ringtone is currently playing. */ 604 private const val STATE_KEY_PLAYING = "extra_is_playing" 605 606 /** 607 * @return an intent that launches the ringtone picker to edit the ringtone of the given 608 * `alarm` 609 */ 610 @JvmStatic 611 @Keep 612 fun createAlarmRingtonePickerIntent(context: Context, alarm: Alarm): Intent { 613 return Intent(context, RingtonePickerActivity::class.java) 614 .putExtra(EXTRA_TITLE, R.string.alarm_sound) 615 .putExtra(EXTRA_ALARM_ID, alarm.id) 616 .putExtra(EXTRA_RINGTONE_URI, alarm.alert) 617 .putExtra(EXTRA_DEFAULT_RINGTONE_URI, 618 RingtoneManager.getDefaultUri(RingtoneManager.TYPE_ALARM)) 619 .putExtra(EXTRA_DEFAULT_RINGTONE_NAME, R.string.default_alarm_ringtone_title) 620 } 621 622 /** 623 * @return an intent that launches the ringtone picker to edit the ringtone of all timers 624 */ 625 @JvmStatic 626 @Keep 627 fun createTimerRingtonePickerIntent(context: Context): Intent { 628 val dataModel = DataModel.dataModel 629 return Intent(context, RingtonePickerActivity::class.java) 630 .putExtra(EXTRA_TITLE, R.string.timer_sound) 631 .putExtra(EXTRA_RINGTONE_URI, dataModel.timerRingtoneUri) 632 .putExtra(EXTRA_DEFAULT_RINGTONE_URI, dataModel.defaultTimerRingtoneUri) 633 .putExtra(EXTRA_DEFAULT_RINGTONE_NAME, R.string.default_timer_ringtone_title) 634 } 635 } 636 }