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 android.Manifest; 20 import android.annotation.NonNull; 21 import android.annotation.Nullable; 22 import android.annotation.RequiresPermission; 23 import android.annotation.SdkConstant; 24 import android.annotation.SdkConstant.SdkConstantType; 25 import android.annotation.SystemApi; 26 import android.annotation.WorkerThread; 27 import android.app.Activity; 28 import android.compat.annotation.UnsupportedAppUsage; 29 import android.content.ContentProvider; 30 import android.content.ContentResolver; 31 import android.content.ContentUris; 32 import android.content.Context; 33 import android.content.pm.PackageManager.NameNotFoundException; 34 import android.content.pm.UserInfo; 35 import android.content.res.AssetFileDescriptor; 36 import android.database.Cursor; 37 import android.database.StaleDataException; 38 import android.net.Uri; 39 import android.os.Build; 40 import android.os.Environment; 41 import android.os.FileUtils; 42 import android.os.IBinder; 43 import android.os.ParcelFileDescriptor; 44 import android.os.RemoteException; 45 import android.os.ServiceManager; 46 import android.os.SystemProperties; 47 import android.os.UserHandle; 48 import android.os.UserManager; 49 import android.provider.MediaStore; 50 import android.provider.MediaStore.MediaColumns; 51 import android.provider.Settings; 52 import android.provider.Settings.System; 53 import android.util.Log; 54 55 import com.android.internal.database.SortCursor; 56 57 import java.io.File; 58 import java.io.FileNotFoundException; 59 import java.io.FileOutputStream; 60 import java.io.IOException; 61 import java.io.InputStream; 62 import java.io.OutputStream; 63 import java.util.ArrayList; 64 import java.util.List; 65 66 /** 67 * RingtoneManager provides access to ringtones, notification, and other types 68 * of sounds. It manages querying the different media providers and combines the 69 * results into a single cursor. It also provides a {@link Ringtone} for each 70 * ringtone. We generically call these sounds ringtones, however the 71 * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the 72 * phone ringer. 73 * <p> 74 * To show a ringtone picker to the user, use the 75 * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity. 76 * 77 * @see Ringtone 78 */ 79 public class RingtoneManager { 80 81 private static final String TAG = "RingtoneManager"; 82 83 // Make sure these are in sync with attrs.xml: 84 // <attr name="ringtoneType"> 85 86 /** 87 * Type that refers to sounds that are used for the phone ringer. 88 */ 89 public static final int TYPE_RINGTONE = 1; 90 91 /** 92 * Type that refers to sounds that are used for notifications. 93 */ 94 public static final int TYPE_NOTIFICATION = 2; 95 96 /** 97 * Type that refers to sounds that are used for the alarm. 98 */ 99 public static final int TYPE_ALARM = 4; 100 101 /** 102 * All types of sounds. 103 */ 104 public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM; 105 106 // </attr> 107 108 /** 109 * Activity Action: Shows a ringtone picker. 110 * <p> 111 * Input: {@link #EXTRA_RINGTONE_EXISTING_URI}, 112 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}, 113 * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE}, 114 * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE}, 115 * <p> 116 * Output: {@link #EXTRA_RINGTONE_PICKED_URI}. 117 */ 118 @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) 119 public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER"; 120 121 /** 122 * Given to the ringtone picker as a boolean. Whether to show an item for 123 * "Default". 124 * 125 * @see #ACTION_RINGTONE_PICKER 126 */ 127 public static final String EXTRA_RINGTONE_SHOW_DEFAULT = 128 "android.intent.extra.ringtone.SHOW_DEFAULT"; 129 130 /** 131 * Given to the ringtone picker as a boolean. Whether to show an item for 132 * "Silent". If the "Silent" item is picked, 133 * {@link #EXTRA_RINGTONE_PICKED_URI} will be null. 134 * 135 * @see #ACTION_RINGTONE_PICKER 136 */ 137 public static final String EXTRA_RINGTONE_SHOW_SILENT = 138 "android.intent.extra.ringtone.SHOW_SILENT"; 139 140 /** 141 * Given to the ringtone picker as a boolean. Whether to include DRM ringtones. 142 * @deprecated DRM ringtones are no longer supported 143 */ 144 @Deprecated 145 public static final String EXTRA_RINGTONE_INCLUDE_DRM = 146 "android.intent.extra.ringtone.INCLUDE_DRM"; 147 148 /** 149 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 150 * current ringtone, which will be used to show a checkmark next to the item 151 * for this {@link Uri}. If showing an item for "Default" (@see 152 * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of 153 * {@link System#DEFAULT_RINGTONE_URI}, 154 * {@link System#DEFAULT_NOTIFICATION_URI}, or 155 * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" item 156 * checked. 157 * 158 * @see #ACTION_RINGTONE_PICKER 159 */ 160 public static final String EXTRA_RINGTONE_EXISTING_URI = 161 "android.intent.extra.ringtone.EXISTING_URI"; 162 163 /** 164 * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the 165 * ringtone to play when the user attempts to preview the "Default" 166 * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI}, 167 * {@link System#DEFAULT_NOTIFICATION_URI}, or 168 * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" point to 169 * the current sound for the given default sound type. If you are showing a 170 * ringtone picker for some other type of sound, you are free to provide any 171 * {@link Uri} here. 172 */ 173 public static final String EXTRA_RINGTONE_DEFAULT_URI = 174 "android.intent.extra.ringtone.DEFAULT_URI"; 175 176 /** 177 * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be 178 * shown in the picker. One or more of {@link #TYPE_RINGTONE}, 179 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL} 180 * (bitwise-ored together). 181 */ 182 public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE"; 183 184 /** 185 * Given to the ringtone picker as a {@link CharSequence}. The title to 186 * show for the ringtone picker. This has a default value that is suitable 187 * in most cases. 188 */ 189 public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE"; 190 191 /** 192 * @hide 193 * Given to the ringtone picker as an int. Additional AudioAttributes flags to use 194 * when playing the ringtone in the picker. 195 * @see #ACTION_RINGTONE_PICKER 196 */ 197 public static final String EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS = 198 "android.intent.extra.ringtone.AUDIO_ATTRIBUTES_FLAGS"; 199 200 /** 201 * Returned from the ringtone picker as a {@link Uri}. 202 * <p> 203 * It will be one of: 204 * <li> the picked ringtone, 205 * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI}, 206 * {@link System#DEFAULT_NOTIFICATION_URI}, or 207 * {@link System#DEFAULT_ALARM_ALERT_URI} if the default was chosen, 208 * <li> null if the "Silent" item was picked. 209 * 210 * @see #ACTION_RINGTONE_PICKER 211 */ 212 public static final String EXTRA_RINGTONE_PICKED_URI = 213 "android.intent.extra.ringtone.PICKED_URI"; 214 215 // Make sure the column ordering and then ..._COLUMN_INDEX are in sync 216 217 private static final String[] INTERNAL_COLUMNS = new String[] { 218 MediaStore.Audio.Media._ID, 219 MediaStore.Audio.Media.TITLE, 220 MediaStore.Audio.Media.TITLE, 221 MediaStore.Audio.Media.TITLE_KEY, 222 }; 223 224 private static final String[] MEDIA_COLUMNS = new String[] { 225 MediaStore.Audio.Media._ID, 226 MediaStore.Audio.Media.TITLE, 227 MediaStore.Audio.Media.TITLE, 228 MediaStore.Audio.Media.TITLE_KEY, 229 }; 230 231 /** 232 * The column index (in the cursor returned by {@link #getCursor()} for the 233 * row ID. 234 */ 235 public static final int ID_COLUMN_INDEX = 0; 236 237 /** 238 * The column index (in the cursor returned by {@link #getCursor()} for the 239 * title. 240 */ 241 public static final int TITLE_COLUMN_INDEX = 1; 242 243 /** 244 * The column index (in the cursor returned by {@link #getCursor()} for the 245 * media provider's URI. 246 */ 247 public static final int URI_COLUMN_INDEX = 2; 248 249 private final Activity mActivity; 250 private final Context mContext; 251 252 @UnsupportedAppUsage 253 private Cursor mCursor; 254 255 private int mType = TYPE_RINGTONE; 256 257 /** 258 * If a column (item from this list) exists in the Cursor, its value must 259 * be true (value of 1) for the row to be returned. 260 */ 261 private final List<String> mFilterColumns = new ArrayList<String>(); 262 263 private boolean mStopPreviousRingtone = true; 264 private Ringtone mPreviousRingtone; 265 266 private boolean mIncludeParentRingtones; 267 268 /** 269 * Constructs a RingtoneManager. This constructor is recommended as its 270 * constructed instance manages cursor(s). 271 * 272 * @param activity The activity used to get a managed cursor. 273 */ RingtoneManager(Activity activity)274 public RingtoneManager(Activity activity) { 275 this(activity, /* includeParentRingtones */ false); 276 } 277 278 /** 279 * Constructs a RingtoneManager. This constructor is recommended if there's the need to also 280 * list ringtones from the user's parent. 281 * 282 * @param activity The activity used to get a managed cursor. 283 * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve 284 * ringtones from the parent of the user specified in the given activity 285 * 286 * @hide 287 */ RingtoneManager(Activity activity, boolean includeParentRingtones)288 public RingtoneManager(Activity activity, boolean includeParentRingtones) { 289 mActivity = activity; 290 mContext = activity; 291 setType(mType); 292 mIncludeParentRingtones = includeParentRingtones; 293 } 294 295 /** 296 * Constructs a RingtoneManager. The instance constructed by this 297 * constructor will not manage the cursor(s), so the client should handle 298 * this itself. 299 * 300 * @param context The context to used to get a cursor. 301 */ RingtoneManager(Context context)302 public RingtoneManager(Context context) { 303 this(context, /* includeParentRingtones */ false); 304 } 305 306 /** 307 * Constructs a RingtoneManager. 308 * 309 * @param context The context to used to get a cursor. 310 * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve 311 * ringtones from the parent of the user specified in the given context 312 * 313 * @hide 314 */ RingtoneManager(Context context, boolean includeParentRingtones)315 public RingtoneManager(Context context, boolean includeParentRingtones) { 316 mActivity = null; 317 mContext = context; 318 setType(mType); 319 mIncludeParentRingtones = includeParentRingtones; 320 } 321 322 /** 323 * Sets which type(s) of ringtones will be listed by this. 324 * 325 * @param type The type(s), one or more of {@link #TYPE_RINGTONE}, 326 * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, 327 * {@link #TYPE_ALL}. 328 * @see #EXTRA_RINGTONE_TYPE 329 */ setType(int type)330 public void setType(int type) { 331 if (mCursor != null) { 332 throw new IllegalStateException( 333 "Setting filter columns should be done before querying for ringtones."); 334 } 335 336 mType = type; 337 setFilterColumnsList(type); 338 } 339 340 /** 341 * Infers the volume stream type based on what type of ringtones this 342 * manager is returning. 343 * 344 * @return The stream type. 345 */ inferStreamType()346 public int inferStreamType() { 347 switch (mType) { 348 349 case TYPE_ALARM: 350 return AudioManager.STREAM_ALARM; 351 352 case TYPE_NOTIFICATION: 353 return AudioManager.STREAM_NOTIFICATION; 354 355 default: 356 return AudioManager.STREAM_RING; 357 } 358 } 359 360 /** 361 * Whether retrieving another {@link Ringtone} will stop playing the 362 * previously retrieved {@link Ringtone}. 363 * <p> 364 * If this is false, make sure to {@link Ringtone#stop()} any previous 365 * ringtones to free resources. 366 * 367 * @param stopPreviousRingtone If true, the previously retrieved 368 * {@link Ringtone} will be stopped. 369 */ setStopPreviousRingtone(boolean stopPreviousRingtone)370 public void setStopPreviousRingtone(boolean stopPreviousRingtone) { 371 mStopPreviousRingtone = stopPreviousRingtone; 372 } 373 374 /** 375 * @see #setStopPreviousRingtone(boolean) 376 */ getStopPreviousRingtone()377 public boolean getStopPreviousRingtone() { 378 return mStopPreviousRingtone; 379 } 380 381 /** 382 * Stops playing the last {@link Ringtone} retrieved from this. 383 */ stopPreviousRingtone()384 public void stopPreviousRingtone() { 385 if (mPreviousRingtone != null) { 386 mPreviousRingtone.stop(); 387 } 388 } 389 390 /** 391 * Returns whether DRM ringtones will be included. 392 * 393 * @return Whether DRM ringtones will be included. 394 * @see #setIncludeDrm(boolean) 395 * Obsolete - always returns false 396 * @deprecated DRM ringtones are no longer supported 397 */ 398 @Deprecated getIncludeDrm()399 public boolean getIncludeDrm() { 400 return false; 401 } 402 403 /** 404 * Sets whether to include DRM ringtones. 405 * 406 * @param includeDrm Whether to include DRM ringtones. 407 * Obsolete - no longer has any effect 408 * @deprecated DRM ringtones are no longer supported 409 */ 410 @Deprecated setIncludeDrm(boolean includeDrm)411 public void setIncludeDrm(boolean includeDrm) { 412 if (includeDrm) { 413 Log.w(TAG, "setIncludeDrm no longer supported"); 414 } 415 } 416 417 /** 418 * Returns a {@link Cursor} of all the ringtones available. The returned 419 * cursor will be the same cursor returned each time this method is called, 420 * so do not {@link Cursor#close()} the cursor. The cursor can be 421 * {@link Cursor#deactivate()} safely. 422 * <p> 423 * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the 424 * caller should manage the returned cursor through its activity's life 425 * cycle to prevent leaking the cursor. 426 * <p> 427 * Note that the list of ringtones available will differ depending on whether the caller 428 * has the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission. 429 * 430 * @return A {@link Cursor} of all the ringtones available. 431 * @see #ID_COLUMN_INDEX 432 * @see #TITLE_COLUMN_INDEX 433 * @see #URI_COLUMN_INDEX 434 */ getCursor()435 public Cursor getCursor() { 436 if (mCursor != null && mCursor.requery()) { 437 return mCursor; 438 } 439 440 ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>(); 441 ringtoneCursors.add(getInternalRingtones()); 442 ringtoneCursors.add(getMediaRingtones()); 443 444 if (mIncludeParentRingtones) { 445 Cursor parentRingtonesCursor = getParentProfileRingtones(); 446 if (parentRingtonesCursor != null) { 447 ringtoneCursors.add(parentRingtonesCursor); 448 } 449 } 450 451 return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]), 452 MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 453 } 454 getParentProfileRingtones()455 private Cursor getParentProfileRingtones() { 456 final UserManager um = UserManager.get(mContext); 457 final UserInfo parentInfo = um.getProfileParent(mContext.getUserId()); 458 if (parentInfo != null && parentInfo.id != mContext.getUserId()) { 459 final Context parentContext = createPackageContextAsUser(mContext, parentInfo.id); 460 if (parentContext != null) { 461 // We don't need to re-add the internal ringtones for the work profile since 462 // they are the same as the personal profile. We just need the external 463 // ringtones. 464 final Cursor res = getMediaRingtones(parentContext); 465 return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId( 466 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id)); 467 } 468 } 469 return null; 470 } 471 472 /** 473 * Gets a {@link Ringtone} for the ringtone at the given position in the 474 * {@link Cursor}. 475 * 476 * @param position The position (in the {@link Cursor}) of the ringtone. 477 * @return A {@link Ringtone} pointing to the ringtone. 478 */ getRingtone(int position)479 public Ringtone getRingtone(int position) { 480 if (mStopPreviousRingtone && mPreviousRingtone != null) { 481 mPreviousRingtone.stop(); 482 } 483 484 mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType()); 485 return mPreviousRingtone; 486 } 487 488 /** 489 * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}. 490 * 491 * @param position The position (in the {@link Cursor}) of the ringtone. 492 * @return A {@link Uri} pointing to the ringtone. 493 */ getRingtoneUri(int position)494 public Uri getRingtoneUri(int position) { 495 // use cursor directly instead of requerying it, which could easily 496 // cause position to shuffle. 497 try { 498 if (mCursor == null || !mCursor.moveToPosition(position)) { 499 return null; 500 } 501 } catch (StaleDataException | IllegalStateException e) { 502 Log.e(TAG, "Unexpected Exception has been catched.", e); 503 return null; 504 } 505 506 return getUriFromCursor(mContext, mCursor); 507 } 508 getUriFromCursor(Context context, Cursor cursor)509 private static Uri getUriFromCursor(Context context, Cursor cursor) { 510 final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), 511 cursor.getLong(ID_COLUMN_INDEX)); 512 return context.getContentResolver().canonicalizeOrElse(uri); 513 } 514 515 /** 516 * Gets the position of a {@link Uri} within this {@link RingtoneManager}. 517 * 518 * @param ringtoneUri The {@link Uri} to retreive the position of. 519 * @return The position of the {@link Uri}, or -1 if it cannot be found. 520 */ getRingtonePosition(Uri ringtoneUri)521 public int getRingtonePosition(Uri ringtoneUri) { 522 try { 523 if (ringtoneUri == null) return -1; 524 525 final Cursor cursor = getCursor(); 526 cursor.moveToPosition(-1); 527 while (cursor.moveToNext()) { 528 Uri uriFromCursor = getUriFromCursor(mContext, cursor); 529 if (ringtoneUri.equals(uriFromCursor)) { 530 return cursor.getPosition(); 531 } 532 } 533 } catch (NumberFormatException e) { 534 Log.e(TAG, "NumberFormatException while getting ringtone position, returning -1", e); 535 } 536 return -1; 537 } 538 539 /** 540 * Returns a valid ringtone URI. No guarantees on which it returns. If it 541 * cannot find one, returns null. If it can only find one on external storage and the caller 542 * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission, 543 * returns null. 544 * 545 * @param context The context to use for querying. 546 * @return A ringtone URI, or null if one cannot be found. 547 */ getValidRingtoneUri(Context context)548 public static Uri getValidRingtoneUri(Context context) { 549 final RingtoneManager rm = new RingtoneManager(context); 550 551 Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); 552 553 if (uri == null) { 554 uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); 555 } 556 557 return uri; 558 } 559 getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor)560 private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) { 561 if (cursor != null) { 562 Uri uri = null; 563 564 if (cursor.moveToFirst()) { 565 uri = getUriFromCursor(context, cursor); 566 } 567 cursor.close(); 568 569 return uri; 570 } else { 571 return null; 572 } 573 } 574 575 @UnsupportedAppUsage getInternalRingtones()576 private Cursor getInternalRingtones() { 577 final Cursor res = query( 578 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, 579 constructBooleanTrueWhereClause(mFilterColumns), 580 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 581 return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); 582 } 583 getMediaRingtones()584 private Cursor getMediaRingtones() { 585 final Cursor res = getMediaRingtones(mContext); 586 return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); 587 } 588 589 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getMediaRingtones(Context context)590 private Cursor getMediaRingtones(Context context) { 591 // MediaStore now returns ringtones on other storage devices, even when 592 // we don't have storage or audio permissions 593 return query( 594 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, 595 constructBooleanTrueWhereClause(mFilterColumns), null, 596 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); 597 } 598 setFilterColumnsList(int type)599 private void setFilterColumnsList(int type) { 600 List<String> columns = mFilterColumns; 601 columns.clear(); 602 603 if ((type & TYPE_RINGTONE) != 0) { 604 columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE); 605 } 606 607 if ((type & TYPE_NOTIFICATION) != 0) { 608 columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION); 609 } 610 611 if ((type & TYPE_ALARM) != 0) { 612 columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); 613 } 614 } 615 616 /** 617 * Constructs a where clause that consists of at least one column being 1 618 * (true). This is used to find all matching sounds for the given sound 619 * types (ringtone, notifications, etc.) 620 * 621 * @param columns The columns that must be true. 622 * @return The where clause. 623 */ constructBooleanTrueWhereClause(List<String> columns)624 private static String constructBooleanTrueWhereClause(List<String> columns) { 625 626 if (columns == null) return null; 627 628 StringBuilder sb = new StringBuilder(); 629 sb.append("("); 630 631 for (int i = columns.size() - 1; i >= 0; i--) { 632 sb.append(columns.get(i)).append("=1 or "); 633 } 634 635 if (columns.size() > 0) { 636 // Remove last ' or ' 637 sb.setLength(sb.length() - 4); 638 } 639 640 sb.append(")"); 641 642 return sb.toString(); 643 } 644 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)645 private Cursor query(Uri uri, 646 String[] projection, 647 String selection, 648 String[] selectionArgs, 649 String sortOrder) { 650 return query(uri, projection, selection, selectionArgs, sortOrder, mContext); 651 } 652 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, Context context)653 private Cursor query(Uri uri, 654 String[] projection, 655 String selection, 656 String[] selectionArgs, 657 String sortOrder, 658 Context context) { 659 if (mActivity != null) { 660 return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder); 661 } else { 662 return context.getContentResolver().query(uri, projection, selection, selectionArgs, 663 sortOrder); 664 } 665 } 666 667 /** 668 * Returns a {@link Ringtone} for a given sound URI. 669 * <p> 670 * If the given URI cannot be opened for any reason, this method will 671 * attempt to fallback on another sound. If it cannot find any, it will 672 * return null. 673 * 674 * @param context A context used to query. 675 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 676 * @return A {@link Ringtone} for the given URI, or null. 677 */ getRingtone(final Context context, Uri ringtoneUri)678 public static Ringtone getRingtone(final Context context, Uri ringtoneUri) { 679 // Don't set the stream type 680 return getRingtone(context, ringtoneUri, -1); 681 } 682 683 /** 684 * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI. 685 * <p> 686 * If the given URI cannot be opened for any reason, this method will 687 * attempt to fallback on another sound. If it cannot find any, it will 688 * return null. 689 * 690 * @param context A context used to query. 691 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 692 * @param volumeShaperConfig config for volume shaper of the ringtone if applied. 693 * @return A {@link Ringtone} for the given URI, or null. 694 * 695 * @hide 696 */ getRingtone( final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig)697 public static Ringtone getRingtone( 698 final Context context, Uri ringtoneUri, 699 @Nullable VolumeShaper.Configuration volumeShaperConfig) { 700 // Don't set the stream type 701 return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig); 702 } 703 704 //FIXME bypass the notion of stream types within the class 705 /** 706 * Returns a {@link Ringtone} for a given sound URI on the given stream 707 * type. Normally, if you change the stream type on the returned 708 * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just 709 * an optimized route to avoid that. 710 * 711 * @param streamType The stream type for the ringtone, or -1 if it should 712 * not be set (and the default used instead). 713 * @see #getRingtone(Context, Uri) 714 */ 715 @UnsupportedAppUsage getRingtone(final Context context, Uri ringtoneUri, int streamType)716 private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) { 717 return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */); 718 } 719 720 //FIXME bypass the notion of stream types within the class 721 /** 722 * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on 723 * the given stream type. Normally, if you change the stream type on the returned 724 * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just 725 * an optimized route to avoid that. 726 * 727 * @param streamType The stream type for the ringtone, or -1 if it should 728 * not be set (and the default used instead). 729 * @param volumeShaperConfig config for volume shaper of the ringtone if applied. 730 * @see #getRingtone(Context, Uri) 731 */ 732 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getRingtone( final Context context, Uri ringtoneUri, int streamType, @Nullable VolumeShaper.Configuration volumeShaperConfig)733 private static Ringtone getRingtone( 734 final Context context, Uri ringtoneUri, int streamType, 735 @Nullable VolumeShaper.Configuration volumeShaperConfig) { 736 try { 737 final Ringtone r = new Ringtone(context, true); 738 if (streamType >= 0) { 739 //FIXME deprecated call 740 r.setStreamType(streamType); 741 } 742 r.setUri(ringtoneUri, volumeShaperConfig); 743 return r; 744 } catch (Exception ex) { 745 Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex); 746 } 747 748 return null; 749 } 750 751 /** 752 * Disables Settings.System.SYNC_PARENT_SOUNDS. 753 * 754 * @hide 755 */ disableSyncFromParent(Context userContext)756 public static void disableSyncFromParent(Context userContext) { 757 IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE); 758 IAudioService audioService = IAudioService.Stub.asInterface(b); 759 try { 760 audioService.disableRingtoneSync(userContext.getUserId()); 761 } catch (RemoteException e) { 762 Log.e(TAG, "Unable to disable ringtone sync."); 763 } 764 } 765 766 /** 767 * Enables Settings.System.SYNC_PARENT_SOUNDS for the content's user 768 * 769 * @hide 770 */ 771 @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS) enableSyncFromParent(Context userContext)772 public static void enableSyncFromParent(Context userContext) { 773 Settings.Secure.putIntForUser(userContext.getContentResolver(), 774 Settings.Secure.SYNC_PARENT_SOUNDS, 1 /* true */, userContext.getUserId()); 775 } 776 777 /** 778 * Gets the current default sound's {@link Uri}. This will give the actual 779 * sound {@link Uri}, instead of using this, most clients can use 780 * {@link System#DEFAULT_RINGTONE_URI}. 781 * 782 * @param context A context used for querying. 783 * @param type The type whose default sound should be returned. One of 784 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 785 * {@link #TYPE_ALARM}. 786 * @return A {@link Uri} pointing to the default sound for the sound type. 787 * @see #setActualDefaultRingtoneUri(Context, int, Uri) 788 */ getActualDefaultRingtoneUri(Context context, int type)789 public static Uri getActualDefaultRingtoneUri(Context context, int type) { 790 String setting = getSettingForType(type); 791 if (setting == null) return null; 792 final String uriString = Settings.System.getStringForUser(context.getContentResolver(), 793 setting, context.getUserId()); 794 Uri ringtoneUri = uriString != null ? Uri.parse(uriString) : null; 795 796 // If this doesn't verify, the user id must be kept in the uri to ensure it resolves in the 797 // correct user storage 798 if (ringtoneUri != null 799 && ContentProvider.getUserIdFromUri(ringtoneUri) == context.getUserId()) { 800 ringtoneUri = ContentProvider.getUriWithoutUserId(ringtoneUri); 801 } 802 803 return ringtoneUri; 804 } 805 806 /** 807 * Sets the {@link Uri} of the default sound for a given sound type. 808 * 809 * @param context A context used for querying. 810 * @param type The type whose default sound should be set. One of 811 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 812 * {@link #TYPE_ALARM}. 813 * @param ringtoneUri A {@link Uri} pointing to the default sound to set. 814 * @see #getActualDefaultRingtoneUri(Context, int) 815 */ setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)816 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { 817 String setting = getSettingForType(type); 818 if (setting == null) return; 819 820 final ContentResolver resolver = context.getContentResolver(); 821 if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0, 822 context.getUserId()) == 1) { 823 // Parent sound override is enabled. Disable it using the audio service. 824 disableSyncFromParent(context); 825 } 826 if(!isInternalRingtoneUri(ringtoneUri)) { 827 ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); 828 } 829 Settings.System.putStringForUser(resolver, setting, 830 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); 831 832 // Stream selected ringtone into cache so it's available for playback 833 // when CE storage is still locked 834 if (ringtoneUri != null) { 835 final Uri cacheUri = getCacheForType(type, context.getUserId()); 836 try (InputStream in = openRingtone(context, ringtoneUri); 837 OutputStream out = resolver.openOutputStream(cacheUri)) { 838 FileUtils.copy(in, out); 839 } catch (IOException e) { 840 Log.w(TAG, "Failed to cache ringtone: " + e); 841 } 842 } 843 } 844 isInternalRingtoneUri(Uri uri)845 private static boolean isInternalRingtoneUri(Uri uri) { 846 return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); 847 } 848 isExternalRingtoneUri(Uri uri)849 private static boolean isExternalRingtoneUri(Uri uri) { 850 return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); 851 } 852 isRingtoneUriInStorage(Uri ringtone, Uri storage)853 private static boolean isRingtoneUriInStorage(Uri ringtone, Uri storage) { 854 Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(ringtone); 855 return uriWithoutUserId == null ? false 856 : uriWithoutUserId.toString().startsWith(storage.toString()); 857 } 858 859 /** 860 * Adds an audio file to the list of ringtones. 861 * 862 * After making sure the given file is an audio file, copies the file to the ringtone storage, 863 * and asks the system to scan that file. This call will block until 864 * the scan is completed. 865 * 866 * The directory where the copied file is stored is the directory that matches the ringtone's 867 * type, which is one of: {@link android.is.Environment#DIRECTORY_RINGTONES}; 868 * {@link android.is.Environment#DIRECTORY_NOTIFICATIONS}; 869 * {@link android.is.Environment#DIRECTORY_ALARMS}. 870 * 871 * This does not allow modifying the type of an existing ringtone file. To change type, use the 872 * APIs in {@link android.content.ContentResolver} to update the corresponding columns. 873 * 874 * @param fileUri Uri of the file to be added as ringtone. Must be a media file. 875 * @param type The type of the ringtone to be added. Must be one of {@link #TYPE_RINGTONE}, 876 * {@link #TYPE_NOTIFICATION}, or {@link #TYPE_ALARM}. 877 * 878 * @return The Uri of the installed ringtone, which may be the Uri of {@param fileUri} if it is 879 * already in ringtone storage. 880 * 881 * @throws FileNotFoundexception if an appropriate unique filename to save the new ringtone file 882 * as cannot be found, for example if the unique name is too long. 883 * @throws IllegalArgumentException if {@param fileUri} does not point to an existing audio 884 * file, or if the {@param type} is not one of the accepted ringtone types. 885 * @throws IOException if the audio file failed to copy to ringtone storage; for example, if 886 * external storage was not available, or if the file was copied but the media scanner 887 * did not recognize it as a ringtone. 888 * 889 * @hide 890 */ 891 @WorkerThread addCustomExternalRingtone(@onNull final Uri fileUri, final int type)892 public Uri addCustomExternalRingtone(@NonNull final Uri fileUri, final int type) 893 throws FileNotFoundException, IllegalArgumentException, IOException { 894 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 895 throw new IOException("External storage is not mounted. Unable to install ringtones."); 896 } 897 898 // Sanity-check: are we actually being asked to install an audio file? 899 final String mimeType = mContext.getContentResolver().getType(fileUri); 900 if(mimeType == null || 901 !(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { 902 throw new IllegalArgumentException("Ringtone file must have MIME type \"audio/*\"." 903 + " Given file has MIME type \"" + mimeType + "\""); 904 } 905 906 // Choose a directory to save the ringtone. Only one type of installation at a time is 907 // allowed. Throws IllegalArgumentException if anything else is given. 908 final String subdirectory = getExternalDirectoryForType(type); 909 910 // Find a filename. Throws FileNotFoundException if none can be found. 911 final File outFile = Utils.getUniqueExternalFile(mContext, subdirectory, 912 FileUtils.buildValidFatFilename(Utils.getFileDisplayNameFromUri(mContext, fileUri)), 913 mimeType); 914 915 // Copy contents to external ringtone storage. Throws IOException if the copy fails. 916 try (final InputStream input = mContext.getContentResolver().openInputStream(fileUri); 917 final OutputStream output = new FileOutputStream(outFile)) { 918 FileUtils.copy(input, output); 919 } 920 921 // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}. 922 return MediaStore.scanFile(mContext.getContentResolver(), outFile); 923 } 924 getExternalDirectoryForType(final int type)925 private static final String getExternalDirectoryForType(final int type) { 926 switch (type) { 927 case TYPE_RINGTONE: 928 return Environment.DIRECTORY_RINGTONES; 929 case TYPE_NOTIFICATION: 930 return Environment.DIRECTORY_NOTIFICATIONS; 931 case TYPE_ALARM: 932 return Environment.DIRECTORY_ALARMS; 933 default: 934 throw new IllegalArgumentException("Unsupported ringtone type: " + type); 935 } 936 } 937 938 /** 939 * Try opening the given ringtone locally first, but failover to 940 * {@link IRingtonePlayer} if we can't access it directly. Typically happens 941 * when process doesn't hold 942 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. 943 */ openRingtone(Context context, Uri uri)944 private static InputStream openRingtone(Context context, Uri uri) throws IOException { 945 final ContentResolver resolver = context.getContentResolver(); 946 try { 947 return resolver.openInputStream(uri); 948 } catch (SecurityException | IOException e) { 949 Log.w(TAG, "Failed to open directly; attempting failover: " + e); 950 final IRingtonePlayer player = context.getSystemService(AudioManager.class) 951 .getRingtonePlayer(); 952 try { 953 return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri)); 954 } catch (Exception e2) { 955 throw new IOException(e2); 956 } 957 } 958 } 959 getSettingForType(int type)960 private static String getSettingForType(int type) { 961 if ((type & TYPE_RINGTONE) != 0) { 962 return Settings.System.RINGTONE; 963 } else if ((type & TYPE_NOTIFICATION) != 0) { 964 return Settings.System.NOTIFICATION_SOUND; 965 } else if ((type & TYPE_ALARM) != 0) { 966 return Settings.System.ALARM_ALERT; 967 } else { 968 return null; 969 } 970 } 971 972 /** {@hide} */ getCacheForType(int type)973 public static Uri getCacheForType(int type) { 974 return getCacheForType(type, UserHandle.getCallingUserId()); 975 } 976 977 /** {@hide} */ getCacheForType(int type, int userId)978 public static Uri getCacheForType(int type, int userId) { 979 if ((type & TYPE_RINGTONE) != 0) { 980 return ContentProvider.maybeAddUserId(Settings.System.RINGTONE_CACHE_URI, userId); 981 } else if ((type & TYPE_NOTIFICATION) != 0) { 982 return ContentProvider.maybeAddUserId(Settings.System.NOTIFICATION_SOUND_CACHE_URI, 983 userId); 984 } else if ((type & TYPE_ALARM) != 0) { 985 return ContentProvider.maybeAddUserId(Settings.System.ALARM_ALERT_CACHE_URI, userId); 986 } 987 return null; 988 } 989 990 /** 991 * Returns whether the given {@link Uri} is one of the default ringtones. 992 * 993 * @param ringtoneUri The ringtone {@link Uri} to be checked. 994 * @return Whether the {@link Uri} is a default. 995 */ isDefault(Uri ringtoneUri)996 public static boolean isDefault(Uri ringtoneUri) { 997 return getDefaultType(ringtoneUri) != -1; 998 } 999 1000 /** 1001 * Returns the type of a default {@link Uri}. 1002 * 1003 * @param defaultRingtoneUri The default {@link Uri}. For example, 1004 * {@link System#DEFAULT_RINGTONE_URI}, 1005 * {@link System#DEFAULT_NOTIFICATION_URI}, or 1006 * {@link System#DEFAULT_ALARM_ALERT_URI}. 1007 * @return The type of the defaultRingtoneUri, or -1. 1008 */ getDefaultType(Uri defaultRingtoneUri)1009 public static int getDefaultType(Uri defaultRingtoneUri) { 1010 defaultRingtoneUri = ContentProvider.getUriWithoutUserId(defaultRingtoneUri); 1011 if (defaultRingtoneUri == null) { 1012 return -1; 1013 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) { 1014 return TYPE_RINGTONE; 1015 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { 1016 return TYPE_NOTIFICATION; 1017 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) { 1018 return TYPE_ALARM; 1019 } else { 1020 return -1; 1021 } 1022 } 1023 1024 /** 1025 * Returns the {@link Uri} for the default ringtone of a particular type. 1026 * Rather than returning the actual ringtone's sound {@link Uri}, this will 1027 * return the symbolic {@link Uri} which will resolved to the actual sound 1028 * when played. 1029 * 1030 * @param type The ringtone type whose default should be returned. 1031 * @return The {@link Uri} of the default ringtone for the given type. 1032 */ getDefaultUri(int type)1033 public static Uri getDefaultUri(int type) { 1034 if ((type & TYPE_RINGTONE) != 0) { 1035 return Settings.System.DEFAULT_RINGTONE_URI; 1036 } else if ((type & TYPE_NOTIFICATION) != 0) { 1037 return Settings.System.DEFAULT_NOTIFICATION_URI; 1038 } else if ((type & TYPE_ALARM) != 0) { 1039 return Settings.System.DEFAULT_ALARM_ALERT_URI; 1040 } else { 1041 return null; 1042 } 1043 } 1044 1045 /** 1046 * Opens a raw file descriptor to read the data under the given default URI. 1047 * 1048 * @param context the Context to use when resolving the Uri. 1049 * @param uri The desired default URI to open. 1050 * @return a new AssetFileDescriptor pointing to the file. You own this descriptor 1051 * and are responsible for closing it when done. This value may be {@code null}. 1052 * @throws FileNotFoundException if the provided URI could not be opened. 1053 * @see #getDefaultUri 1054 */ openDefaultRingtoneUri( @onNull Context context, @NonNull Uri uri)1055 public static @Nullable AssetFileDescriptor openDefaultRingtoneUri( 1056 @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { 1057 // Try cached ringtone first since the actual provider may not be 1058 // encryption aware, or it may be stored on CE media storage 1059 final int type = getDefaultType(uri); 1060 final Uri cacheUri = getCacheForType(type, context.getUserId()); 1061 final Uri actualUri = getActualDefaultRingtoneUri(context, type); 1062 final ContentResolver resolver = context.getContentResolver(); 1063 1064 AssetFileDescriptor afd = null; 1065 if (cacheUri != null) { 1066 afd = resolver.openAssetFileDescriptor(cacheUri, "r"); 1067 if (afd != null) { 1068 return afd; 1069 } 1070 } 1071 if (actualUri != null) { 1072 afd = resolver.openAssetFileDescriptor(actualUri, "r"); 1073 } 1074 return afd; 1075 } 1076 1077 /** 1078 * Returns if the {@link Ringtone} at the given position in the 1079 * {@link Cursor} contains haptic channels. 1080 * 1081 * @param position The position (in the {@link Cursor}) of the ringtone. 1082 * @return true if the ringtone contains haptic channels. 1083 */ hasHapticChannels(int position)1084 public boolean hasHapticChannels(int position) { 1085 return AudioManager.hasHapticChannels(mContext, getRingtoneUri(position)); 1086 } 1087 1088 /** 1089 * Returns if the {@link Ringtone} from a given sound URI contains 1090 * haptic channels or not. As this function doesn't has a context 1091 * to resolve the uri, the result may be wrong if the uri cannot be 1092 * resolved correctly. 1093 * Use {@link #hasHapticChannels(int)} instead when possible. 1094 * 1095 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 1096 * @return true if the ringtone contains haptic channels. 1097 */ hasHapticChannels(@onNull Uri ringtoneUri)1098 public static boolean hasHapticChannels(@NonNull Uri ringtoneUri) { 1099 return AudioManager.hasHapticChannels(null, ringtoneUri); 1100 } 1101 1102 /** 1103 * Attempts to create a context for the given user. 1104 * 1105 * @return created context, or null if package does not exist 1106 * @hide 1107 */ createPackageContextAsUser(Context context, int userId)1108 private static Context createPackageContextAsUser(Context context, int userId) { 1109 try { 1110 return context.createPackageContextAsUser(context.getPackageName(), 0 /* flags */, 1111 UserHandle.of(userId)); 1112 } catch (NameNotFoundException e) { 1113 Log.e(TAG, "Unable to create package context", e); 1114 return null; 1115 } 1116 } 1117 1118 /** 1119 * Ensure that ringtones have been set at least once on this device. This 1120 * should be called after the device has finished scanned all media on 1121 * {@link MediaStore#VOLUME_INTERNAL}, so that default ringtones can be 1122 * configured. 1123 * 1124 * @hide 1125 */ 1126 @SystemApi 1127 @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) ensureDefaultRingtones(@onNull Context context)1128 public static void ensureDefaultRingtones(@NonNull Context context) { 1129 for (int type : new int[] { 1130 TYPE_RINGTONE, 1131 TYPE_NOTIFICATION, 1132 TYPE_ALARM, 1133 }) { 1134 // Skip if we've already defined it at least once, so we don't 1135 // overwrite the user changing to null 1136 final String setting = getDefaultRingtoneSetting(type); 1137 if (Settings.System.getInt(context.getContentResolver(), setting, 0) != 0) { 1138 continue; 1139 } 1140 1141 // Try finding the scanned ringtone 1142 final String filename = getDefaultRingtoneFilename(type); 1143 final String whichAudio = getQueryStringForType(type); 1144 final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?"; 1145 final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; 1146 try (Cursor cursor = context.getContentResolver().query(baseUri, 1147 new String[] { MediaColumns._ID }, 1148 where, 1149 new String[] { filename, "1" }, null)) { 1150 if (cursor.moveToFirst()) { 1151 final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( 1152 ContentUris.withAppendedId(baseUri, cursor.getLong(0))); 1153 RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri); 1154 Settings.System.putInt(context.getContentResolver(), setting, 1); 1155 } 1156 } 1157 } 1158 } 1159 getDefaultRingtoneSetting(int type)1160 private static String getDefaultRingtoneSetting(int type) { 1161 switch (type) { 1162 case TYPE_RINGTONE: return "ringtone_set"; 1163 case TYPE_NOTIFICATION: return "notification_sound_set"; 1164 case TYPE_ALARM: return "alarm_alert_set"; 1165 default: throw new IllegalArgumentException(); 1166 } 1167 } 1168 getDefaultRingtoneFilename(int type)1169 private static String getDefaultRingtoneFilename(int type) { 1170 switch (type) { 1171 case TYPE_RINGTONE: return SystemProperties.get("ro.config.ringtone"); 1172 case TYPE_NOTIFICATION: return SystemProperties.get("ro.config.notification_sound"); 1173 case TYPE_ALARM: return SystemProperties.get("ro.config.alarm_alert"); 1174 default: throw new IllegalArgumentException(); 1175 } 1176 } 1177 getQueryStringForType(int type)1178 private static String getQueryStringForType(int type) { 1179 switch (type) { 1180 case TYPE_RINGTONE: return MediaStore.Audio.AudioColumns.IS_RINGTONE; 1181 case TYPE_NOTIFICATION: return MediaStore.Audio.AudioColumns.IS_NOTIFICATION; 1182 case TYPE_ALARM: return MediaStore.Audio.AudioColumns.IS_ALARM; 1183 default: throw new IllegalArgumentException(); 1184 } 1185 } 1186 } 1187