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