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 android.media; 18 19 import com.android.internal.database.SortCursor; 20 21 import android.annotation.SdkConstant; 22 import android.annotation.SdkConstant.SdkConstantType; 23 import android.app.Activity; 24 import android.content.ContentUris; 25 import android.content.Context; 26 import android.content.pm.PackageManager; 27 import android.database.Cursor; 28 import android.net.Uri; 29 import android.os.Environment; 30 import android.os.Process; 31 import android.provider.MediaStore; 32 import android.provider.Settings; 33 import android.provider.Settings.System; 34 import android.util.Log; 35 36 import java.util.ArrayList; 37 import java.util.List; 38 39 /** 40 * RingtoneManager provides access to ringtones, notification, and other types 41 * of sounds. It manages querying the different media providers and combines the 42 * results into a single cursor. It also provides a {@link Ringtone} for each 43 * ringtone. We generically call these sounds ringtones, however the 44 * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the 45 * phone ringer. 46 * <p> 47 * To show a ringtone picker to the user, use the 48 * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity. 49 * 50 * @see Ringtone 51 */ 52 public class RingtoneManager { 53 54 private static final String TAG = "RingtoneManager"; 55 56 // Make sure these are in sync with attrs.xml: 57 // <attr name="ringtoneType"> 58 59 /** 60 * Type that refers to sounds that are used for the phone ringer. 61 */ 62 public static final int TYPE_RINGTONE = 1; 63 64 /** 65 * Type that refers to sounds that are used for notifications. 66 */ 67 public static final int TYPE_NOTIFICATION = 2; 68 69 /** 70 * Type that refers to sounds that are used for the alarm. 71 */ 72 public static final int TYPE_ALARM = 4; 73 74 /** 75 * All types of sounds. 76 */ 77 public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM; 78 79 // </attr> 80 81 /** 82 * Activity Action: Shows a ringtone picker. 83 * <p> 84 * Input: {@link #EXTRA_RINGTONE_EXISTING_URI}, 85 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}, 86 * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE}, 87 * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE}, 88 * <p> 89 * Output: {@link #EXTRA_RINGTONE_PICKED_URI}. 90 */ 91 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 92 public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER"; 93 94 /** 95 * Given to the ringtone picker as a boolean. Whether to show an item for 96 * "Default". 97 * 98 * @see #ACTION_RINGTONE_PICKER 99 */ 100 public static final String EXTRA_RINGTONE_SHOW_DEFAULT = 101 "android.intent.extra.ringtone.SHOW_DEFAULT"; 102 103 /** 104 * Given to the ringtone picker as a boolean. Whether to show an item for 105 * "Silent". If the "Silent" item is picked, 106 * {@link #EXTRA_RINGTONE_PICKED_URI} will be null. 107 * 108 * @see #ACTION_RINGTONE_PICKER 109 */ 110 public static final String EXTRA_RINGTONE_SHOW_SILENT = 111 "android.intent.extra.ringtone.SHOW_SILENT"; 112 113 /** 114 * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. 115 * @deprecated DRM ringtones are no longer supported 116 */ 117 @Deprecated 118 public static final String EXTRA_RINGTONE_INCLUDE_DRM = 119 "android.intent.extra.ringtone.INCLUDE_DRM"; 120 121 /** 122 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 123 * current ringtone, which will be used to show a checkmark next to the item 124 * for this {@link Uri}. If showing an item for "Default" (@see 125 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of 126 * {@link System#DEFAULT_RINGTONE_URI}, 127 * {@link System#DEFAULT_NOTIFICATION_URI}, or 128 * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" item 129 * checked. 130 * 131 * @see #ACTION_RINGTONE_PICKER 132 */ 133 public static final String EXTRA_RINGTONE_EXISTING_URI = 134 "android.intent.extra.ringtone.EXISTING_URI"; 135 136 /** 137 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 138 * ringtone to play when the user attempts to preview the "Default" 139 * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI}, 140 * {@link System#DEFAULT_NOTIFICATION_URI}, or 141 * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" point to 142 * the current sound for the given default sound type. If you are showing a 143 * ringtone picker for some other type of sound, you are free to provide any 144 * {@link Uri} here. 145 */ 146 public static final String EXTRA_RINGTONE_DEFAULT_URI = 147 "android.intent.extra.ringtone.DEFAULT_URI"; 148 149 /** 150 * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be 151 * shown in the picker. One or more of {@link #TYPE_RINGTONE}, 152 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL} 153 * (bitwise-ored together). 154 */ 155 public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE"; 156 157 /** 158 * Given to the ringtone picker as a {@link CharSequence}. The title to 159 * show for the ringtone picker. This has a default value that is suitable 160 * in most cases. 161 */ 162 public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE"; 163 164 /** 165 * @hide 166 * Given to the ringtone picker as an int. Additional AudioAttributes flags to use 167 * when playing the ringtone in the picker. 168 * @see #ACTION_RINGTONE_PICKER 169 */ 170 public static final String EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS = 171 "android.intent.extra.ringtone.AUDIO_ATTRIBUTES_FLAGS"; 172 173 /** 174 * Returned from the ringtone picker as a {@link Uri}. 175 * <p> 176 * It will be one of: 177 * <li> the picked ringtone, 178 * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI}, 179 * {@link System#DEFAULT_NOTIFICATION_URI}, or 180 * {@link System#DEFAULT_ALARM_ALERT_URI} if the default was chosen, 181 * <li> null if the "Silent" item was picked. 182 * 183 * @see #ACTION_RINGTONE_PICKER 184 */ 185 public static final String EXTRA_RINGTONE_PICKED_URI = 186 "android.intent.extra.ringtone.PICKED_URI"; 187 188 // Make sure the column ordering and then ..._COLUMN_INDEX are in sync 189 190 private static final String[] INTERNAL_COLUMNS = new String[] { 191 MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, 192 "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"", 193 MediaStore.Audio.Media.TITLE_KEY 194 }; 195 196 private static final String[] MEDIA_COLUMNS = new String[] { 197 MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE, 198 "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"", 199 MediaStore.Audio.Media.TITLE_KEY 200 }; 201 202 /** 203 * The column index (in the cursor returned by {@link #getCursor()} for the 204 * row ID. 205 */ 206 public static final int ID_COLUMN_INDEX = 0; 207 208 /** 209 * The column index (in the cursor returned by {@link #getCursor()} for the 210 * title. 211 */ 212 public static final int TITLE_COLUMN_INDEX = 1; 213 214 /** 215 * The column index (in the cursor returned by {@link #getCursor()} for the 216 * media provider's URI. 217 */ 218 public static final int URI_COLUMN_INDEX = 2; 219 220 private Activity mActivity; 221 private Context mContext; 222 223 private Cursor mCursor; 224 225 private int mType = TYPE_RINGTONE; 226 227 /** 228 * If a column (item from this list) exists in the Cursor, its value must 229 * be true (value of 1) for the row to be returned. 230 */ 231 private final List<String> mFilterColumns = new ArrayList<String>(); 232 233 private boolean mStopPreviousRingtone = true; 234 private Ringtone mPreviousRingtone; 235 236 /** 237 * Constructs a RingtoneManager. This constructor is recommended as its 238 * constructed instance manages cursor(s). 239 * 240 * @param activity The activity used to get a managed cursor. 241 */ RingtoneManager(Activity activity)242 public RingtoneManager(Activity activity) { 243 mContext = mActivity = activity; 244 setType(mType); 245 } 246 247 /** 248 * Constructs a RingtoneManager. The instance constructed by this 249 * constructor will not manage the cursor(s), so the client should handle 250 * this itself. 251 * 252 * @param context The context to used to get a cursor. 253 */ RingtoneManager(Context context)254 public RingtoneManager(Context context) { 255 mContext = context; 256 setType(mType); 257 } 258 259 /** 260 * Sets which type(s) of ringtones will be listed by this. 261 * 262 * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, 263 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, 264 * {@link #TYPE_ALL}. 265 * @see #EXTRA_RINGTONE_TYPE 266 */ setType(int type)267 public void setType(int type) { 268 269 if (mCursor != null) { 270 throw new IllegalStateException( 271 "Setting filter columns should be done before querying for ringtones."); 272 } 273 274 mType = type; 275 setFilterColumnsList(type); 276 } 277 278 /** 279 * Infers the playback stream type based on what type of ringtones this 280 * manager is returning. 281 * 282 * @return The stream type. 283 */ inferStreamType()284 public int inferStreamType() { 285 switch (mType) { 286 287 case TYPE_ALARM: 288 return AudioManager.STREAM_ALARM; 289 290 case TYPE_NOTIFICATION: 291 return AudioManager.STREAM_NOTIFICATION; 292 293 default: 294 return AudioManager.STREAM_RING; 295 } 296 } 297 298 /** 299 * Whether retrieving another {@link Ringtone} will stop playing the 300 * previously retrieved {@link Ringtone}. 301 * <p> 302 * If this is false, make sure to {@link Ringtone#stop()} any previous 303 * ringtones to free resources. 304 * 305 * @param stopPreviousRingtone If true, the previously retrieved 306 * {@link Ringtone} will be stopped. 307 */ setStopPreviousRingtone(boolean stopPreviousRingtone)308 public void setStopPreviousRingtone(boolean stopPreviousRingtone) { 309 mStopPreviousRingtone = stopPreviousRingtone; 310 } 311 312 /** 313 * @see #setStopPreviousRingtone(boolean) 314 */ getStopPreviousRingtone()315 public boolean getStopPreviousRingtone() { 316 return mStopPreviousRingtone; 317 } 318 319 /** 320 * Stops playing the last {@link Ringtone} retrieved from this. 321 */ stopPreviousRingtone()322 public void stopPreviousRingtone() { 323 if (mPreviousRingtone != null) { 324 mPreviousRingtone.stop(); 325 } 326 } 327 328 /** 329 * Returns whether DRM ringtones will be included. 330 * 331 * @return Whether DRM ringtones will be included. 332 * @see #setIncludeDrm(boolean) 333 * Obsolete - always returns false 334 * @deprecated DRM ringtones are no longer supported 335 */ 336 @Deprecated getIncludeDrm()337 public boolean getIncludeDrm() { 338 return false; 339 } 340 341 /** 342 * Sets whether to include DRM ringtones. 343 * 344 * @param includeDrm Whether to include DRM ringtones. 345 * Obsolete - no longer has any effect 346 * @deprecated DRM ringtones are no longer supported 347 */ 348 @Deprecated setIncludeDrm(boolean includeDrm)349 public void setIncludeDrm(boolean includeDrm) { 350 if (includeDrm) { 351 Log.w(TAG, "setIncludeDrm no longer supported"); 352 } 353 } 354 355 /** 356 * Returns a {@link Cursor} of all the ringtones available. The returned 357 * cursor will be the same cursor returned each time this method is called, 358 * so do not {@link Cursor#close()} the cursor. The cursor can be 359 * {@link Cursor#deactivate()} safely. 360 * <p> 361 * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the 362 * caller should manage the returned cursor through its activity's life 363 * cycle to prevent leaking the cursor. 364 * <p> 365 * Note that the list of ringtones available will differ depending on whether the caller 366 * has the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission. 367 * 368 * @return A {@link Cursor} of all the ringtones available. 369 * @see #ID_COLUMN_INDEX 370 * @see #TITLE_COLUMN_INDEX 371 * @see #URI_COLUMN_INDEX 372 */ getCursor()373 public Cursor getCursor() { 374 if (mCursor != null && mCursor.requery()) { 375 return mCursor; 376 } 377 378 final Cursor internalCursor = getInternalRingtones(); 379 final Cursor mediaCursor = getMediaRingtones(); 380 381 return mCursor = new SortCursor(new Cursor[] { internalCursor, mediaCursor }, 382 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 383 } 384 385 /** 386 * Gets a {@link Ringtone} for the ringtone at the given position in the 387 * {@link Cursor}. 388 * 389 * @param position The position (in the {@link Cursor}) of the ringtone. 390 * @return A {@link Ringtone} pointing to the ringtone. 391 */ getRingtone(int position)392 public Ringtone getRingtone(int position) { 393 if (mStopPreviousRingtone && mPreviousRingtone != null) { 394 mPreviousRingtone.stop(); 395 } 396 397 mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType()); 398 return mPreviousRingtone; 399 } 400 401 /** 402 * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}. 403 * 404 * @param position The position (in the {@link Cursor}) of the ringtone. 405 * @return A {@link Uri} pointing to the ringtone. 406 */ getRingtoneUri(int position)407 public Uri getRingtoneUri(int position) { 408 // use cursor directly instead of requerying it, which could easily 409 // cause position to shuffle. 410 if (mCursor == null || !mCursor.moveToPosition(position)) { 411 return null; 412 } 413 414 return getUriFromCursor(mCursor); 415 } 416 getUriFromCursor(Cursor cursor)417 private static Uri getUriFromCursor(Cursor cursor) { 418 return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor 419 .getLong(ID_COLUMN_INDEX)); 420 } 421 422 /** 423 * Gets the position of a {@link Uri} within this {@link RingtoneManager}. 424 * 425 * @param ringtoneUri The {@link Uri} to retreive the position of. 426 * @return The position of the {@link Uri}, or -1 if it cannot be found. 427 */ getRingtonePosition(Uri ringtoneUri)428 public int getRingtonePosition(Uri ringtoneUri) { 429 430 if (ringtoneUri == null) return -1; 431 432 final Cursor cursor = getCursor(); 433 final int cursorCount = cursor.getCount(); 434 435 if (!cursor.moveToFirst()) { 436 return -1; 437 } 438 439 // Only create Uri objects when the actual URI changes 440 Uri currentUri = null; 441 String previousUriString = null; 442 for (int i = 0; i < cursorCount; i++) { 443 String uriString = cursor.getString(URI_COLUMN_INDEX); 444 if (currentUri == null || !uriString.equals(previousUriString)) { 445 currentUri = Uri.parse(uriString); 446 } 447 448 if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor 449 .getLong(ID_COLUMN_INDEX)))) { 450 return i; 451 } 452 453 cursor.move(1); 454 455 previousUriString = uriString; 456 } 457 458 return -1; 459 } 460 461 /** 462 * Returns a valid ringtone URI. No guarantees on which it returns. If it 463 * cannot find one, returns null. If it can only find one on external storage and the caller 464 * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission, 465 * returns null. 466 * 467 * @param context The context to use for querying. 468 * @return A ringtone URI, or null if one cannot be found. 469 */ getValidRingtoneUri(Context context)470 public static Uri getValidRingtoneUri(Context context) { 471 final RingtoneManager rm = new RingtoneManager(context); 472 473 Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); 474 475 if (uri == null) { 476 uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); 477 } 478 479 return uri; 480 } 481 getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor)482 private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) { 483 if (cursor != null) { 484 Uri uri = null; 485 486 if (cursor.moveToFirst()) { 487 uri = getUriFromCursor(cursor); 488 } 489 cursor.close(); 490 491 return uri; 492 } else { 493 return null; 494 } 495 } 496 getInternalRingtones()497 private Cursor getInternalRingtones() { 498 return query( 499 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, 500 constructBooleanTrueWhereClause(mFilterColumns), 501 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 502 } 503 getMediaRingtones()504 private Cursor getMediaRingtones() { 505 if (PackageManager.PERMISSION_GRANTED != mContext.checkPermission( 506 android.Manifest.permission.READ_EXTERNAL_STORAGE, 507 Process.myPid(), Process.myUid())) { 508 Log.w(TAG, "No READ_EXTERNAL_STORAGE permission, ignoring ringtones on ext storage"); 509 return null; 510 } 511 // Get the external media cursor. First check to see if it is mounted. 512 final String status = Environment.getExternalStorageState(); 513 514 return (status.equals(Environment.MEDIA_MOUNTED) || 515 status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) 516 ? query( 517 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, 518 constructBooleanTrueWhereClause(mFilterColumns), null, 519 MediaStore.Audio.Media.DEFAULT_SORT_ORDER) 520 : null; 521 } 522 setFilterColumnsList(int type)523 private void setFilterColumnsList(int type) { 524 List<String> columns = mFilterColumns; 525 columns.clear(); 526 527 if ((type & TYPE_RINGTONE) != 0) { 528 columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE); 529 } 530 531 if ((type & TYPE_NOTIFICATION) != 0) { 532 columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION); 533 } 534 535 if ((type & TYPE_ALARM) != 0) { 536 columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); 537 } 538 } 539 540 /** 541 * Constructs a where clause that consists of at least one column being 1 542 * (true). This is used to find all matching sounds for the given sound 543 * types (ringtone, notifications, etc.) 544 * 545 * @param columns The columns that must be true. 546 * @return The where clause. 547 */ constructBooleanTrueWhereClause(List<String> columns)548 private static String constructBooleanTrueWhereClause(List<String> columns) { 549 550 if (columns == null) return null; 551 552 StringBuilder sb = new StringBuilder(); 553 sb.append("("); 554 555 for (int i = columns.size() - 1; i >= 0; i--) { 556 sb.append(columns.get(i)).append("=1 or "); 557 } 558 559 if (columns.size() > 0) { 560 // Remove last ' or ' 561 sb.setLength(sb.length() - 4); 562 } 563 564 sb.append(")"); 565 566 return sb.toString(); 567 } 568 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)569 private Cursor query(Uri uri, 570 String[] projection, 571 String selection, 572 String[] selectionArgs, 573 String sortOrder) { 574 if (mActivity != null) { 575 return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder); 576 } else { 577 return mContext.getContentResolver().query(uri, projection, selection, selectionArgs, 578 sortOrder); 579 } 580 } 581 582 /** 583 * Returns a {@link Ringtone} for a given sound URI. 584 * <p> 585 * If the given URI cannot be opened for any reason, this method will 586 * attempt to fallback on another sound. If it cannot find any, it will 587 * return null. 588 * 589 * @param context A context used to query. 590 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 591 * @return A {@link Ringtone} for the given URI, or null. 592 */ getRingtone(final Context context, Uri ringtoneUri)593 public static Ringtone getRingtone(final Context context, Uri ringtoneUri) { 594 // Don't set the stream type 595 return getRingtone(context, ringtoneUri, -1); 596 } 597 598 /** 599 * Returns a {@link Ringtone} for a given sound URI on the given stream 600 * type. Normally, if you change the stream type on the returned 601 * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just 602 * an optimized route to avoid that. 603 * 604 * @param streamType The stream type for the ringtone, or -1 if it should 605 * not be set (and the default used instead). 606 * @see #getRingtone(Context, Uri) 607 */ getRingtone(final Context context, Uri ringtoneUri, int streamType)608 private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) { 609 try { 610 final Ringtone r = new Ringtone(context, true); 611 if (streamType >= 0) { 612 r.setStreamType(streamType); 613 } 614 r.setUri(ringtoneUri); 615 return r; 616 } catch (Exception ex) { 617 Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex); 618 } 619 620 return null; 621 } 622 623 /** 624 * Gets the current default sound's {@link Uri}. This will give the actual 625 * sound {@link Uri}, instead of using this, most clients can use 626 * {@link System#DEFAULT_RINGTONE_URI}. 627 * 628 * @param context A context used for querying. 629 * @param type The type whose default sound should be returned. One of 630 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 631 * {@link #TYPE_ALARM}. 632 * @return A {@link Uri} pointing to the default sound for the sound type. 633 * @see #setActualDefaultRingtoneUri(Context, int, Uri) 634 */ getActualDefaultRingtoneUri(Context context, int type)635 public static Uri getActualDefaultRingtoneUri(Context context, int type) { 636 String setting = getSettingForType(type); 637 if (setting == null) return null; 638 final String uriString = Settings.System.getString(context.getContentResolver(), setting); 639 return uriString != null ? Uri.parse(uriString) : null; 640 } 641 642 /** 643 * Sets the {@link Uri} of the default sound for a given sound type. 644 * 645 * @param context A context used for querying. 646 * @param type The type whose default sound should be set. One of 647 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 648 * {@link #TYPE_ALARM}. 649 * @param ringtoneUri A {@link Uri} pointing to the default sound to set. 650 * @see #getActualDefaultRingtoneUri(Context, int) 651 */ setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)652 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { 653 String setting = getSettingForType(type); 654 if (setting == null) return; 655 Settings.System.putString(context.getContentResolver(), setting, 656 ringtoneUri != null ? ringtoneUri.toString() : null); 657 } 658 getSettingForType(int type)659 private static String getSettingForType(int type) { 660 if ((type & TYPE_RINGTONE) != 0) { 661 return Settings.System.RINGTONE; 662 } else if ((type & TYPE_NOTIFICATION) != 0) { 663 return Settings.System.NOTIFICATION_SOUND; 664 } else if ((type & TYPE_ALARM) != 0) { 665 return Settings.System.ALARM_ALERT; 666 } else { 667 return null; 668 } 669 } 670 671 /** 672 * Returns whether the given {@link Uri} is one of the default ringtones. 673 * 674 * @param ringtoneUri The ringtone {@link Uri} to be checked. 675 * @return Whether the {@link Uri} is a default. 676 */ isDefault(Uri ringtoneUri)677 public static boolean isDefault(Uri ringtoneUri) { 678 return getDefaultType(ringtoneUri) != -1; 679 } 680 681 /** 682 * Returns the type of a default {@link Uri}. 683 * 684 * @param defaultRingtoneUri The default {@link Uri}. For example, 685 * {@link System#DEFAULT_RINGTONE_URI}, 686 * {@link System#DEFAULT_NOTIFICATION_URI}, or 687 * {@link System#DEFAULT_ALARM_ALERT_URI}. 688 * @return The type of the defaultRingtoneUri, or -1. 689 */ getDefaultType(Uri defaultRingtoneUri)690 public static int getDefaultType(Uri defaultRingtoneUri) { 691 if (defaultRingtoneUri == null) { 692 return -1; 693 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) { 694 return TYPE_RINGTONE; 695 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { 696 return TYPE_NOTIFICATION; 697 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) { 698 return TYPE_ALARM; 699 } else { 700 return -1; 701 } 702 } 703 704 /** 705 * Returns the {@link Uri} for the default ringtone of a particular type. 706 * Rather than returning the actual ringtone's sound {@link Uri}, this will 707 * return the symbolic {@link Uri} which will resolved to the actual sound 708 * when played. 709 * 710 * @param type The ringtone type whose default should be returned. 711 * @return The {@link Uri} of the default ringtone for the given type. 712 */ getDefaultUri(int type)713 public static Uri getDefaultUri(int type) { 714 if ((type & TYPE_RINGTONE) != 0) { 715 return Settings.System.DEFAULT_RINGTONE_URI; 716 } else if ((type & TYPE_NOTIFICATION) != 0) { 717 return Settings.System.DEFAULT_NOTIFICATION_URI; 718 } else if ((type & TYPE_ALARM) != 0) { 719 return Settings.System.DEFAULT_ALARM_ALERT_URI; 720 } else { 721 return null; 722 } 723 } 724 725 } 726