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 = 485 getRingtone(mContext, getRingtoneUri(position), inferStreamType(), true); 486 return mPreviousRingtone; 487 } 488 489 /** 490 * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}. 491 * 492 * @param position The position (in the {@link Cursor}) of the ringtone. 493 * @return A {@link Uri} pointing to the ringtone. 494 */ getRingtoneUri(int position)495 public Uri getRingtoneUri(int position) { 496 // use cursor directly instead of requerying it, which could easily 497 // cause position to shuffle. 498 try { 499 if (mCursor == null || !mCursor.moveToPosition(position)) { 500 return null; 501 } 502 } catch (StaleDataException | IllegalStateException e) { 503 Log.e(TAG, "Unexpected Exception has been catched.", e); 504 return null; 505 } 506 507 return getUriFromCursor(mContext, mCursor); 508 } 509 getUriFromCursor(Context context, Cursor cursor)510 private static Uri getUriFromCursor(Context context, Cursor cursor) { 511 final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), 512 cursor.getLong(ID_COLUMN_INDEX)); 513 return context.getContentResolver().canonicalizeOrElse(uri); 514 } 515 516 /** 517 * Gets the position of a {@link Uri} within this {@link RingtoneManager}. 518 * 519 * @param ringtoneUri The {@link Uri} to retreive the position of. 520 * @return The position of the {@link Uri}, or -1 if it cannot be found. 521 */ getRingtonePosition(Uri ringtoneUri)522 public int getRingtonePosition(Uri ringtoneUri) { 523 try { 524 if (ringtoneUri == null) return -1; 525 526 final Cursor cursor = getCursor(); 527 cursor.moveToPosition(-1); 528 while (cursor.moveToNext()) { 529 Uri uriFromCursor = getUriFromCursor(mContext, cursor); 530 if (ringtoneUri.equals(uriFromCursor)) { 531 return cursor.getPosition(); 532 } 533 } 534 } catch (NumberFormatException e) { 535 Log.e(TAG, "NumberFormatException while getting ringtone position, returning -1", e); 536 } 537 return -1; 538 } 539 540 /** 541 * Returns a valid ringtone URI. No guarantees on which it returns. If it 542 * cannot find one, returns null. If it can only find one on external storage and the caller 543 * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission, 544 * returns null. 545 * 546 * @param context The context to use for querying. 547 * @return A ringtone URI, or null if one cannot be found. 548 */ getValidRingtoneUri(Context context)549 public static Uri getValidRingtoneUri(Context context) { 550 final RingtoneManager rm = new RingtoneManager(context); 551 552 Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones()); 553 554 if (uri == null) { 555 uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones()); 556 } 557 558 return uri; 559 } 560 getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor)561 private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) { 562 if (cursor != null) { 563 Uri uri = null; 564 565 if (cursor.moveToFirst()) { 566 uri = getUriFromCursor(context, cursor); 567 } 568 cursor.close(); 569 570 return uri; 571 } else { 572 return null; 573 } 574 } 575 576 @UnsupportedAppUsage getInternalRingtones()577 private Cursor getInternalRingtones() { 578 final Cursor res = query( 579 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS, 580 constructBooleanTrueWhereClause(mFilterColumns), 581 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 582 return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); 583 } 584 getMediaRingtones()585 private Cursor getMediaRingtones() { 586 final Cursor res = getMediaRingtones(mContext); 587 return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); 588 } 589 590 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getMediaRingtones(Context context)591 private Cursor getMediaRingtones(Context context) { 592 // MediaStore now returns ringtones on other storage devices, even when 593 // we don't have storage or audio permissions 594 return query( 595 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS, 596 constructBooleanTrueWhereClause(mFilterColumns), null, 597 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context); 598 } 599 setFilterColumnsList(int type)600 private void setFilterColumnsList(int type) { 601 List<String> columns = mFilterColumns; 602 columns.clear(); 603 604 if ((type & TYPE_RINGTONE) != 0) { 605 columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE); 606 } 607 608 if ((type & TYPE_NOTIFICATION) != 0) { 609 columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION); 610 } 611 612 if ((type & TYPE_ALARM) != 0) { 613 columns.add(MediaStore.Audio.AudioColumns.IS_ALARM); 614 } 615 } 616 617 /** 618 * Constructs a where clause that consists of at least one column being 1 619 * (true). This is used to find all matching sounds for the given sound 620 * types (ringtone, notifications, etc.) 621 * 622 * @param columns The columns that must be true. 623 * @return The where clause. 624 */ constructBooleanTrueWhereClause(List<String> columns)625 private static String constructBooleanTrueWhereClause(List<String> columns) { 626 627 if (columns == null) return null; 628 629 StringBuilder sb = new StringBuilder(); 630 sb.append("("); 631 632 for (int i = columns.size() - 1; i >= 0; i--) { 633 sb.append(columns.get(i)).append("=1 or "); 634 } 635 636 if (columns.size() > 0) { 637 // Remove last ' or ' 638 sb.setLength(sb.length() - 4); 639 } 640 641 sb.append(")"); 642 643 return sb.toString(); 644 } 645 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)646 private Cursor query(Uri uri, 647 String[] projection, 648 String selection, 649 String[] selectionArgs, 650 String sortOrder) { 651 return query(uri, projection, selection, selectionArgs, sortOrder, mContext); 652 } 653 query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, Context context)654 private Cursor query(Uri uri, 655 String[] projection, 656 String selection, 657 String[] selectionArgs, 658 String sortOrder, 659 Context context) { 660 if (mActivity != null) { 661 return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder); 662 } else { 663 return context.getContentResolver().query(uri, projection, selection, selectionArgs, 664 sortOrder); 665 } 666 } 667 668 /** 669 * Returns a {@link Ringtone} for a given sound URI. 670 * <p> 671 * If the given URI cannot be opened for any reason, this method will 672 * attempt to fallback on another sound. If it cannot find any, it will 673 * return null. 674 * 675 * @param context A context used to query. 676 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 677 * @return A {@link Ringtone} for the given URI, or null. 678 */ getRingtone(final Context context, Uri ringtoneUri)679 public static Ringtone getRingtone(final Context context, Uri ringtoneUri) { 680 // Don't set the stream type 681 return getRingtone(context, ringtoneUri, -1, true); 682 } 683 684 /** 685 * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI. 686 * <p> 687 * If the given URI cannot be opened for any reason, this method will 688 * attempt to fallback on another sound. If it cannot find any, it will 689 * return null. 690 * 691 * @param context A context used to query. 692 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 693 * @param volumeShaperConfig config for volume shaper of the ringtone if applied. 694 * @return A {@link Ringtone} for the given URI, or null. 695 * 696 * @hide 697 */ getRingtone( final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig)698 public static Ringtone getRingtone( 699 final Context context, Uri ringtoneUri, 700 @Nullable VolumeShaper.Configuration volumeShaperConfig) { 701 // Don't set the stream type 702 return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, true); 703 } 704 705 /** 706 * @hide 707 */ getRingtone(final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean createLocalMediaPlayer)708 public static Ringtone getRingtone(final Context context, Uri ringtoneUri, 709 @Nullable VolumeShaper.Configuration volumeShaperConfig, 710 boolean createLocalMediaPlayer) { 711 // Don't set the stream type 712 return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, 713 createLocalMediaPlayer); 714 } 715 716 /** 717 * @hide 718 */ getRingtone(final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig, AudioAttributes audioAttributes)719 public static Ringtone getRingtone(final Context context, Uri ringtoneUri, 720 @Nullable VolumeShaper.Configuration volumeShaperConfig, 721 AudioAttributes audioAttributes) { 722 // Don't set the stream type 723 Ringtone ringtone = 724 getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig, false); 725 if (ringtone != null) { 726 ringtone.setAudioAttributesField(audioAttributes); 727 ringtone.createLocalMediaPlayer(); 728 } 729 return ringtone; 730 } 731 732 //FIXME bypass the notion of stream types within the class 733 /** 734 * Returns a {@link Ringtone} for a given sound URI on the given stream 735 * type. Normally, if you change the stream type on the returned 736 * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just 737 * an optimized route to avoid that. 738 * 739 * @param streamType The stream type for the ringtone, or -1 if it should 740 * not be set (and the default used instead). 741 * @param createLocalMediaPlayer when true, the ringtone returned will be fully 742 * created otherwise, it will require the caller to create the media player manually 743 * {@link Ringtone#createLocalMediaPlayer()} in order to play the Ringtone. 744 * @see #getRingtone(Context, Uri) 745 */ 746 @UnsupportedAppUsage getRingtone(final Context context, Uri ringtoneUri, int streamType, boolean createLocalMediaPlayer)747 private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType, 748 boolean createLocalMediaPlayer) { 749 return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */, 750 createLocalMediaPlayer); 751 } 752 753 //FIXME bypass the notion of stream types within the class 754 /** 755 * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on 756 * the given stream type. Normally, if you change the stream type on the returned 757 * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just 758 * an optimized route to avoid that. 759 * 760 * @param streamType The stream type for the ringtone, or -1 if it should 761 * not be set (and the default used instead). 762 * @param volumeShaperConfig config for volume shaper of the ringtone if applied. 763 * @see #getRingtone(Context, Uri) 764 */ 765 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) getRingtone(final Context context, Uri ringtoneUri, int streamType, @Nullable VolumeShaper.Configuration volumeShaperConfig, boolean createLocalMediaPlayer)766 private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType, 767 @Nullable VolumeShaper.Configuration volumeShaperConfig, 768 boolean createLocalMediaPlayer) { 769 try { 770 final Ringtone r = new Ringtone(context, true); 771 if (streamType >= 0) { 772 //FIXME deprecated call 773 r.setStreamType(streamType); 774 } 775 776 r.setVolumeShaperConfig(volumeShaperConfig); 777 r.setUri(ringtoneUri, volumeShaperConfig); 778 if (createLocalMediaPlayer) { 779 r.createLocalMediaPlayer(); 780 } 781 return r; 782 } catch (Exception ex) { 783 Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex); 784 } 785 786 return null; 787 } 788 789 /** 790 * Gets the current default sound's {@link Uri}. This will give the actual 791 * sound {@link Uri}, instead of using this, most clients can use 792 * {@link System#DEFAULT_RINGTONE_URI}. 793 * 794 * @param context A context used for querying. 795 * @param type The type whose default sound should be returned. One of 796 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 797 * {@link #TYPE_ALARM}. 798 * @return A {@link Uri} pointing to the default sound for the sound type. 799 * @see #setActualDefaultRingtoneUri(Context, int, Uri) 800 */ getActualDefaultRingtoneUri(Context context, int type)801 public static Uri getActualDefaultRingtoneUri(Context context, int type) { 802 String setting = getSettingForType(type); 803 if (setting == null) return null; 804 final String uriString = Settings.System.getStringForUser(context.getContentResolver(), 805 setting, context.getUserId()); 806 Uri ringtoneUri = uriString != null ? Uri.parse(uriString) : null; 807 808 // If this doesn't verify, the user id must be kept in the uri to ensure it resolves in the 809 // correct user storage 810 if (ringtoneUri != null 811 && ContentProvider.getUserIdFromUri(ringtoneUri) == context.getUserId()) { 812 ringtoneUri = ContentProvider.getUriWithoutUserId(ringtoneUri); 813 } 814 815 return ringtoneUri; 816 } 817 818 /** 819 * Sets the {@link Uri} of the default sound for a given sound type. 820 * 821 * @param context A context used for querying. 822 * @param type The type whose default sound should be set. One of 823 * {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or 824 * {@link #TYPE_ALARM}. 825 * @param ringtoneUri A {@link Uri} pointing to the default sound to set. 826 * @see #getActualDefaultRingtoneUri(Context, int) 827 */ setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)828 public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) { 829 String setting = getSettingForType(type); 830 if (setting == null) return; 831 832 final ContentResolver resolver = context.getContentResolver(); 833 if(!isInternalRingtoneUri(ringtoneUri)) { 834 ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId()); 835 } 836 Settings.System.putStringForUser(resolver, setting, 837 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId()); 838 839 // Stream selected ringtone into cache so it's available for playback 840 // when CE storage is still locked 841 if (ringtoneUri != null) { 842 final Uri cacheUri = getCacheForType(type, context.getUserId()); 843 try (InputStream in = openRingtone(context, ringtoneUri); 844 OutputStream out = resolver.openOutputStream(cacheUri)) { 845 FileUtils.copy(in, out); 846 } catch (IOException e) { 847 Log.w(TAG, "Failed to cache ringtone: " + e); 848 } 849 } 850 } 851 isInternalRingtoneUri(Uri uri)852 private static boolean isInternalRingtoneUri(Uri uri) { 853 return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.INTERNAL_CONTENT_URI); 854 } 855 isExternalRingtoneUri(Uri uri)856 private static boolean isExternalRingtoneUri(Uri uri) { 857 return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI); 858 } 859 isRingtoneUriInStorage(Uri ringtone, Uri storage)860 private static boolean isRingtoneUriInStorage(Uri ringtone, Uri storage) { 861 Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(ringtone); 862 return uriWithoutUserId == null ? false 863 : uriWithoutUserId.toString().startsWith(storage.toString()); 864 } 865 866 /** 867 * Adds an audio file to the list of ringtones. 868 * 869 * After making sure the given file is an audio file, copies the file to the ringtone storage, 870 * and asks the system to scan that file. This call will block until 871 * the scan is completed. 872 * 873 * The directory where the copied file is stored is the directory that matches the ringtone's 874 * type, which is one of: {@link android.is.Environment#DIRECTORY_RINGTONES}; 875 * {@link android.is.Environment#DIRECTORY_NOTIFICATIONS}; 876 * {@link android.is.Environment#DIRECTORY_ALARMS}. 877 * 878 * This does not allow modifying the type of an existing ringtone file. To change type, use the 879 * APIs in {@link android.content.ContentResolver} to update the corresponding columns. 880 * 881 * @param fileUri Uri of the file to be added as ringtone. Must be a media file. 882 * @param type The type of the ringtone to be added. Must be one of {@link #TYPE_RINGTONE}, 883 * {@link #TYPE_NOTIFICATION}, or {@link #TYPE_ALARM}. 884 * 885 * @return The Uri of the installed ringtone, which may be the Uri of {@param fileUri} if it is 886 * already in ringtone storage. 887 * 888 * @throws FileNotFoundexception if an appropriate unique filename to save the new ringtone file 889 * as cannot be found, for example if the unique name is too long. 890 * @throws IllegalArgumentException if {@param fileUri} does not point to an existing audio 891 * file, or if the {@param type} is not one of the accepted ringtone types. 892 * @throws IOException if the audio file failed to copy to ringtone storage; for example, if 893 * external storage was not available, or if the file was copied but the media scanner 894 * did not recognize it as a ringtone. 895 * 896 * @hide 897 */ 898 @WorkerThread addCustomExternalRingtone(@onNull final Uri fileUri, final int type)899 public Uri addCustomExternalRingtone(@NonNull final Uri fileUri, final int type) 900 throws FileNotFoundException, IllegalArgumentException, IOException { 901 if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { 902 throw new IOException("External storage is not mounted. Unable to install ringtones."); 903 } 904 905 // Consistency-check: are we actually being asked to install an audio file? 906 final String mimeType = mContext.getContentResolver().getType(fileUri); 907 if(mimeType == null || 908 !(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) { 909 throw new IllegalArgumentException("Ringtone file must have MIME type \"audio/*\"." 910 + " Given file has MIME type \"" + mimeType + "\""); 911 } 912 913 // Choose a directory to save the ringtone. Only one type of installation at a time is 914 // allowed. Throws IllegalArgumentException if anything else is given. 915 final String subdirectory = getExternalDirectoryForType(type); 916 917 // Find a filename. Throws FileNotFoundException if none can be found. 918 final File outFile = Utils.getUniqueExternalFile(mContext, subdirectory, 919 FileUtils.buildValidFatFilename(Utils.getFileDisplayNameFromUri(mContext, fileUri)), 920 mimeType); 921 922 // Copy contents to external ringtone storage. Throws IOException if the copy fails. 923 try (final InputStream input = mContext.getContentResolver().openInputStream(fileUri); 924 final OutputStream output = new FileOutputStream(outFile)) { 925 FileUtils.copy(input, output); 926 } 927 928 // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}. 929 return MediaStore.scanFile(mContext.getContentResolver(), outFile); 930 } 931 getExternalDirectoryForType(final int type)932 private static final String getExternalDirectoryForType(final int type) { 933 switch (type) { 934 case TYPE_RINGTONE: 935 return Environment.DIRECTORY_RINGTONES; 936 case TYPE_NOTIFICATION: 937 return Environment.DIRECTORY_NOTIFICATIONS; 938 case TYPE_ALARM: 939 return Environment.DIRECTORY_ALARMS; 940 default: 941 throw new IllegalArgumentException("Unsupported ringtone type: " + type); 942 } 943 } 944 945 /** 946 * Try opening the given ringtone locally first, but failover to 947 * {@link IRingtonePlayer} if we can't access it directly. Typically happens 948 * when process doesn't hold 949 * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}. 950 */ openRingtone(Context context, Uri uri)951 private static InputStream openRingtone(Context context, Uri uri) throws IOException { 952 final ContentResolver resolver = context.getContentResolver(); 953 try { 954 return resolver.openInputStream(uri); 955 } catch (SecurityException | IOException e) { 956 Log.w(TAG, "Failed to open directly; attempting failover: " + e); 957 final IRingtonePlayer player = context.getSystemService(AudioManager.class) 958 .getRingtonePlayer(); 959 try { 960 return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri)); 961 } catch (Exception e2) { 962 throw new IOException(e2); 963 } 964 } 965 } 966 getSettingForType(int type)967 private static String getSettingForType(int type) { 968 if ((type & TYPE_RINGTONE) != 0) { 969 return Settings.System.RINGTONE; 970 } else if ((type & TYPE_NOTIFICATION) != 0) { 971 return Settings.System.NOTIFICATION_SOUND; 972 } else if ((type & TYPE_ALARM) != 0) { 973 return Settings.System.ALARM_ALERT; 974 } else { 975 return null; 976 } 977 } 978 979 /** {@hide} */ getCacheForType(int type)980 public static Uri getCacheForType(int type) { 981 return getCacheForType(type, UserHandle.getCallingUserId()); 982 } 983 984 /** {@hide} */ getCacheForType(int type, int userId)985 public static Uri getCacheForType(int type, int userId) { 986 if ((type & TYPE_RINGTONE) != 0) { 987 return ContentProvider.maybeAddUserId(Settings.System.RINGTONE_CACHE_URI, userId); 988 } else if ((type & TYPE_NOTIFICATION) != 0) { 989 return ContentProvider.maybeAddUserId(Settings.System.NOTIFICATION_SOUND_CACHE_URI, 990 userId); 991 } else if ((type & TYPE_ALARM) != 0) { 992 return ContentProvider.maybeAddUserId(Settings.System.ALARM_ALERT_CACHE_URI, userId); 993 } 994 return null; 995 } 996 997 /** 998 * Returns whether the given {@link Uri} is one of the default ringtones. 999 * 1000 * @param ringtoneUri The ringtone {@link Uri} to be checked. 1001 * @return Whether the {@link Uri} is a default. 1002 */ isDefault(Uri ringtoneUri)1003 public static boolean isDefault(Uri ringtoneUri) { 1004 return getDefaultType(ringtoneUri) != -1; 1005 } 1006 1007 /** 1008 * Returns the type of a default {@link Uri}. 1009 * 1010 * @param defaultRingtoneUri The default {@link Uri}. For example, 1011 * {@link System#DEFAULT_RINGTONE_URI}, 1012 * {@link System#DEFAULT_NOTIFICATION_URI}, or 1013 * {@link System#DEFAULT_ALARM_ALERT_URI}. 1014 * @return The type of the defaultRingtoneUri, or -1. 1015 */ getDefaultType(Uri defaultRingtoneUri)1016 public static int getDefaultType(Uri defaultRingtoneUri) { 1017 defaultRingtoneUri = ContentProvider.getUriWithoutUserId(defaultRingtoneUri); 1018 if (defaultRingtoneUri == null) { 1019 return -1; 1020 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) { 1021 return TYPE_RINGTONE; 1022 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) { 1023 return TYPE_NOTIFICATION; 1024 } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) { 1025 return TYPE_ALARM; 1026 } else { 1027 return -1; 1028 } 1029 } 1030 1031 /** 1032 * Returns the {@link Uri} for the default ringtone of a particular type. 1033 * Rather than returning the actual ringtone's sound {@link Uri}, this will 1034 * return the symbolic {@link Uri} which will resolved to the actual sound 1035 * when played. 1036 * 1037 * @param type The ringtone type whose default should be returned. 1038 * @return The {@link Uri} of the default ringtone for the given type. 1039 */ getDefaultUri(int type)1040 public static Uri getDefaultUri(int type) { 1041 if ((type & TYPE_RINGTONE) != 0) { 1042 return Settings.System.DEFAULT_RINGTONE_URI; 1043 } else if ((type & TYPE_NOTIFICATION) != 0) { 1044 return Settings.System.DEFAULT_NOTIFICATION_URI; 1045 } else if ((type & TYPE_ALARM) != 0) { 1046 return Settings.System.DEFAULT_ALARM_ALERT_URI; 1047 } else { 1048 return null; 1049 } 1050 } 1051 1052 /** 1053 * Opens a raw file descriptor to read the data under the given default URI. 1054 * 1055 * @param context the Context to use when resolving the Uri. 1056 * @param uri The desired default URI to open. 1057 * @return a new AssetFileDescriptor pointing to the file. You own this descriptor 1058 * and are responsible for closing it when done. This value may be {@code null}. 1059 * @throws FileNotFoundException if the provided URI could not be opened. 1060 * @see #getDefaultUri 1061 */ openDefaultRingtoneUri( @onNull Context context, @NonNull Uri uri)1062 public static @Nullable AssetFileDescriptor openDefaultRingtoneUri( 1063 @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException { 1064 // Try cached ringtone first since the actual provider may not be 1065 // encryption aware, or it may be stored on CE media storage 1066 final int type = getDefaultType(uri); 1067 final Uri cacheUri = getCacheForType(type, context.getUserId()); 1068 final Uri actualUri = getActualDefaultRingtoneUri(context, type); 1069 final ContentResolver resolver = context.getContentResolver(); 1070 1071 AssetFileDescriptor afd = null; 1072 if (cacheUri != null) { 1073 afd = resolver.openAssetFileDescriptor(cacheUri, "r"); 1074 if (afd != null) { 1075 return afd; 1076 } 1077 } 1078 if (actualUri != null) { 1079 afd = resolver.openAssetFileDescriptor(actualUri, "r"); 1080 } 1081 return afd; 1082 } 1083 1084 /** 1085 * Returns if the {@link Ringtone} at the given position in the 1086 * {@link Cursor} contains haptic channels. 1087 * 1088 * @param position The position (in the {@link Cursor}) of the ringtone. 1089 * @return true if the ringtone contains haptic channels. 1090 */ hasHapticChannels(int position)1091 public boolean hasHapticChannels(int position) { 1092 return AudioManager.hasHapticChannels(mContext, getRingtoneUri(position)); 1093 } 1094 1095 /** 1096 * Returns if the {@link Ringtone} from a given sound URI contains 1097 * haptic channels or not. As this function doesn't has a context 1098 * to resolve the uri, the result may be wrong if the uri cannot be 1099 * resolved correctly. 1100 * Use {@link #hasHapticChannels(int)} or {@link #hasHapticChannels(Context, Uri)} 1101 * instead when possible. 1102 * 1103 * @param ringtoneUri The {@link Uri} of a sound or ringtone. 1104 * @return true if the ringtone contains haptic channels. 1105 */ hasHapticChannels(@onNull Uri ringtoneUri)1106 public static boolean hasHapticChannels(@NonNull Uri ringtoneUri) { 1107 return AudioManager.hasHapticChannels(null, ringtoneUri); 1108 } 1109 1110 /** 1111 * Returns if the {@link Ringtone} from a given sound URI contains haptics channels or not. 1112 * 1113 * @param context the {@link android.content.Context} to use when resolving the Uri. 1114 * @param ringtoneUri the {@link Uri} of a sound or ringtone. 1115 * @return true if the ringtone contains haptic channels. 1116 */ hasHapticChannels(@onNull Context context, @NonNull Uri ringtoneUri)1117 public static boolean hasHapticChannels(@NonNull Context context, @NonNull Uri ringtoneUri) { 1118 return AudioManager.hasHapticChannels(context, ringtoneUri); 1119 } 1120 1121 /** 1122 * Attempts to create a context for the given user. 1123 * 1124 * @return created context, or null if package does not exist 1125 * @hide 1126 */ createPackageContextAsUser(Context context, int userId)1127 private static Context createPackageContextAsUser(Context context, int userId) { 1128 try { 1129 return context.createPackageContextAsUser(context.getPackageName(), 0 /* flags */, 1130 UserHandle.of(userId)); 1131 } catch (NameNotFoundException e) { 1132 Log.e(TAG, "Unable to create package context", e); 1133 return null; 1134 } 1135 } 1136 1137 /** 1138 * Ensure that ringtones have been set at least once on this device. This 1139 * should be called after the device has finished scanned all media on 1140 * {@link MediaStore#VOLUME_INTERNAL}, so that default ringtones can be 1141 * configured. 1142 * 1143 * @hide 1144 */ 1145 @SystemApi 1146 @RequiresPermission(android.Manifest.permission.WRITE_SETTINGS) ensureDefaultRingtones(@onNull Context context)1147 public static void ensureDefaultRingtones(@NonNull Context context) { 1148 for (int type : new int[] { 1149 TYPE_RINGTONE, 1150 TYPE_NOTIFICATION, 1151 TYPE_ALARM, 1152 }) { 1153 // Skip if we've already defined it at least once, so we don't 1154 // overwrite the user changing to null 1155 final String setting = getDefaultRingtoneSetting(type); 1156 if (Settings.System.getInt(context.getContentResolver(), setting, 0) != 0) { 1157 continue; 1158 } 1159 1160 // Try finding the scanned ringtone 1161 final String filename = getDefaultRingtoneFilename(type); 1162 final String whichAudio = getQueryStringForType(type); 1163 final String where = MediaColumns.DISPLAY_NAME + "=? AND " + whichAudio + "=?"; 1164 final Uri baseUri = MediaStore.Audio.Media.INTERNAL_CONTENT_URI; 1165 try (Cursor cursor = context.getContentResolver().query(baseUri, 1166 new String[] { MediaColumns._ID }, 1167 where, 1168 new String[] { filename, "1" }, null)) { 1169 if (cursor.moveToFirst()) { 1170 final Uri ringtoneUri = context.getContentResolver().canonicalizeOrElse( 1171 ContentUris.withAppendedId(baseUri, cursor.getLong(0))); 1172 RingtoneManager.setActualDefaultRingtoneUri(context, type, ringtoneUri); 1173 Settings.System.putInt(context.getContentResolver(), setting, 1); 1174 } 1175 } 1176 } 1177 } 1178 getDefaultRingtoneSetting(int type)1179 private static String getDefaultRingtoneSetting(int type) { 1180 switch (type) { 1181 case TYPE_RINGTONE: return "ringtone_set"; 1182 case TYPE_NOTIFICATION: return "notification_sound_set"; 1183 case TYPE_ALARM: return "alarm_alert_set"; 1184 default: throw new IllegalArgumentException(); 1185 } 1186 } 1187 getDefaultRingtoneFilename(int type)1188 private static String getDefaultRingtoneFilename(int type) { 1189 switch (type) { 1190 case TYPE_RINGTONE: return SystemProperties.get("ro.config.ringtone"); 1191 case TYPE_NOTIFICATION: return SystemProperties.get("ro.config.notification_sound"); 1192 case TYPE_ALARM: return SystemProperties.get("ro.config.alarm_alert"); 1193 default: throw new IllegalArgumentException(); 1194 } 1195 } 1196 getQueryStringForType(int type)1197 private static String getQueryStringForType(int type) { 1198 switch (type) { 1199 case TYPE_RINGTONE: return MediaStore.Audio.AudioColumns.IS_RINGTONE; 1200 case TYPE_NOTIFICATION: return MediaStore.Audio.AudioColumns.IS_NOTIFICATION; 1201 case TYPE_ALARM: return MediaStore.Audio.AudioColumns.IS_ALARM; 1202 default: throw new IllegalArgumentException(); 1203 } 1204 } 1205 } 1206