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