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