Home | History | Annotate | Download | only in media
      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.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.app.Activity;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.Context;
     25 import android.content.pm.PackageManager;
     26 import android.database.Cursor;
     27 import android.net.Uri;
     28 import android.os.Environment;
     29 import android.os.ParcelFileDescriptor;
     30 import android.os.Process;
     31 import android.provider.MediaStore;
     32 import android.provider.Settings;
     33 import android.provider.Settings.System;
     34 import android.util.Log;
     35 
     36 import com.android.internal.database.SortCursor;
     37 
     38 import libcore.io.Streams;
     39 
     40 import java.io.IOException;
     41 import java.io.InputStream;
     42 import java.io.OutputStream;
     43 import java.util.ArrayList;
     44 import java.util.List;
     45 
     46 /**
     47  * RingtoneManager provides access to ringtones, notification, and other types
     48  * of sounds. It manages querying the different media providers and combines the
     49  * results into a single cursor. It also provides a {@link Ringtone} for each
     50  * ringtone. We generically call these sounds ringtones, however the
     51  * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the
     52  * phone ringer.
     53  * <p>
     54  * To show a ringtone picker to the user, use the
     55  * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity.
     56  *
     57  * @see Ringtone
     58  */
     59 public class RingtoneManager {
     60 
     61     private static final String TAG = "RingtoneManager";
     62 
     63     // Make sure these are in sync with attrs.xml:
     64     // <attr name="ringtoneType">
     65 
     66     /**
     67      * Type that refers to sounds that are used for the phone ringer.
     68      */
     69     public static final int TYPE_RINGTONE = 1;
     70 
     71     /**
     72      * Type that refers to sounds that are used for notifications.
     73      */
     74     public static final int TYPE_NOTIFICATION = 2;
     75 
     76     /**
     77      * Type that refers to sounds that are used for the alarm.
     78      */
     79     public static final int TYPE_ALARM = 4;
     80 
     81     /**
     82      * All types of sounds.
     83      */
     84     public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM;
     85 
     86     // </attr>
     87 
     88     /**
     89      * Activity Action: Shows a ringtone picker.
     90      * <p>
     91      * Input: {@link #EXTRA_RINGTONE_EXISTING_URI},
     92      * {@link #EXTRA_RINGTONE_SHOW_DEFAULT},
     93      * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE},
     94      * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE},
     95      * <p>
     96      * Output: {@link #EXTRA_RINGTONE_PICKED_URI}.
     97      */
     98     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
     99     public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER";
    100 
    101     /**
    102      * Given to the ringtone picker as a boolean. Whether to show an item for
    103      * "Default".
    104      *
    105      * @see #ACTION_RINGTONE_PICKER
    106      */
    107     public static final String EXTRA_RINGTONE_SHOW_DEFAULT =
    108             "android.intent.extra.ringtone.SHOW_DEFAULT";
    109 
    110     /**
    111      * Given to the ringtone picker as a boolean. Whether to show an item for
    112      * "Silent". If the "Silent" item is picked,
    113      * {@link #EXTRA_RINGTONE_PICKED_URI} will be null.
    114      *
    115      * @see #ACTION_RINGTONE_PICKER
    116      */
    117     public static final String EXTRA_RINGTONE_SHOW_SILENT =
    118             "android.intent.extra.ringtone.SHOW_SILENT";
    119 
    120     /**
    121      * Given to the ringtone picker as a boolean. Whether to include DRM ringtones.
    122      * @deprecated DRM ringtones are no longer supported
    123      */
    124     @Deprecated
    125     public static final String EXTRA_RINGTONE_INCLUDE_DRM =
    126             "android.intent.extra.ringtone.INCLUDE_DRM";
    127 
    128     /**
    129      * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
    130      * current ringtone, which will be used to show a checkmark next to the item
    131      * for this {@link Uri}. If showing an item for "Default" (@see
    132      * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of
    133      * {@link System#DEFAULT_RINGTONE_URI},
    134      * {@link System#DEFAULT_NOTIFICATION_URI}, or
    135      * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" item
    136      * checked.
    137      *
    138      * @see #ACTION_RINGTONE_PICKER
    139      */
    140     public static final String EXTRA_RINGTONE_EXISTING_URI =
    141             "android.intent.extra.ringtone.EXISTING_URI";
    142 
    143     /**
    144      * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
    145      * ringtone to play when the user attempts to preview the "Default"
    146      * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI},
    147      * {@link System#DEFAULT_NOTIFICATION_URI}, or
    148      * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" point to
    149      * the current sound for the given default sound type. If you are showing a
    150      * ringtone picker for some other type of sound, you are free to provide any
    151      * {@link Uri} here.
    152      */
    153     public static final String EXTRA_RINGTONE_DEFAULT_URI =
    154             "android.intent.extra.ringtone.DEFAULT_URI";
    155 
    156     /**
    157      * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be
    158      * shown in the picker. One or more of {@link #TYPE_RINGTONE},
    159      * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL}
    160      * (bitwise-ored together).
    161      */
    162     public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE";
    163 
    164     /**
    165      * Given to the ringtone picker as a {@link CharSequence}. The title to
    166      * show for the ringtone picker. This has a default value that is suitable
    167      * in most cases.
    168      */
    169     public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE";
    170 
    171     /**
    172      * @hide
    173      * Given to the ringtone picker as an int. Additional AudioAttributes flags to use
    174      * when playing the ringtone in the picker.
    175      * @see #ACTION_RINGTONE_PICKER
    176      */
    177     public static final String EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS =
    178             "android.intent.extra.ringtone.AUDIO_ATTRIBUTES_FLAGS";
    179 
    180     /**
    181      * Returned from the ringtone picker as a {@link Uri}.
    182      * <p>
    183      * It will be one of:
    184      * <li> the picked ringtone,
    185      * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI},
    186      * {@link System#DEFAULT_NOTIFICATION_URI}, or
    187      * {@link System#DEFAULT_ALARM_ALERT_URI} if the default was chosen,
    188      * <li> null if the "Silent" item was picked.
    189      *
    190      * @see #ACTION_RINGTONE_PICKER
    191      */
    192     public static final String EXTRA_RINGTONE_PICKED_URI =
    193             "android.intent.extra.ringtone.PICKED_URI";
    194 
    195     // Make sure the column ordering and then ..._COLUMN_INDEX are in sync
    196 
    197     private static final String[] INTERNAL_COLUMNS = new String[] {
    198         MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
    199         "\"" + MediaStore.Audio.Media.INTERNAL_CONTENT_URI + "\"",
    200         MediaStore.Audio.Media.TITLE_KEY
    201     };
    202 
    203     private static final String[] MEDIA_COLUMNS = new String[] {
    204         MediaStore.Audio.Media._ID, MediaStore.Audio.Media.TITLE,
    205         "\"" + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI + "\"",
    206         MediaStore.Audio.Media.TITLE_KEY
    207     };
    208 
    209     /**
    210      * The column index (in the cursor returned by {@link #getCursor()} for the
    211      * row ID.
    212      */
    213     public static final int ID_COLUMN_INDEX = 0;
    214 
    215     /**
    216      * The column index (in the cursor returned by {@link #getCursor()} for the
    217      * title.
    218      */
    219     public static final int TITLE_COLUMN_INDEX = 1;
    220 
    221     /**
    222      * The column index (in the cursor returned by {@link #getCursor()} for the
    223      * media provider's URI.
    224      */
    225     public static final int URI_COLUMN_INDEX = 2;
    226 
    227     private final Activity mActivity;
    228     private final Context mContext;
    229 
    230     private Cursor mCursor;
    231 
    232     private int mType = TYPE_RINGTONE;
    233 
    234     /**
    235      * If a column (item from this list) exists in the Cursor, its value must
    236      * be true (value of 1) for the row to be returned.
    237      */
    238     private final List<String> mFilterColumns = new ArrayList<String>();
    239 
    240     private boolean mStopPreviousRingtone = true;
    241     private Ringtone mPreviousRingtone;
    242 
    243     /**
    244      * Constructs a RingtoneManager. This constructor is recommended as its
    245      * constructed instance manages cursor(s).
    246      *
    247      * @param activity The activity used to get a managed cursor.
    248      */
    249     public RingtoneManager(Activity activity) {
    250         mActivity = activity;
    251         mContext = activity;
    252         setType(mType);
    253     }
    254 
    255     /**
    256      * Constructs a RingtoneManager. The instance constructed by this
    257      * constructor will not manage the cursor(s), so the client should handle
    258      * this itself.
    259      *
    260      * @param context The context to used to get a cursor.
    261      */
    262     public RingtoneManager(Context context) {
    263         mActivity = null;
    264         mContext = context;
    265         setType(mType);
    266     }
    267 
    268     /**
    269      * Sets which type(s) of ringtones will be listed by this.
    270      *
    271      * @param type The type(s), one or more of {@link #TYPE_RINGTONE},
    272      *            {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM},
    273      *            {@link #TYPE_ALL}.
    274      * @see #EXTRA_RINGTONE_TYPE
    275      */
    276     public void setType(int type) {
    277         if (mCursor != null) {
    278             throw new IllegalStateException(
    279                     "Setting filter columns should be done before querying for ringtones.");
    280         }
    281 
    282         mType = type;
    283         setFilterColumnsList(type);
    284     }
    285 
    286     /**
    287      * Infers the playback stream type based on what type of ringtones this
    288      * manager is returning.
    289      *
    290      * @return The stream type.
    291      */
    292     public int inferStreamType() {
    293         switch (mType) {
    294 
    295             case TYPE_ALARM:
    296                 return AudioManager.STREAM_ALARM;
    297 
    298             case TYPE_NOTIFICATION:
    299                 return AudioManager.STREAM_NOTIFICATION;
    300 
    301             default:
    302                 return AudioManager.STREAM_RING;
    303         }
    304     }
    305 
    306     /**
    307      * Whether retrieving another {@link Ringtone} will stop playing the
    308      * previously retrieved {@link Ringtone}.
    309      * <p>
    310      * If this is false, make sure to {@link Ringtone#stop()} any previous
    311      * ringtones to free resources.
    312      *
    313      * @param stopPreviousRingtone If true, the previously retrieved
    314      *            {@link Ringtone} will be stopped.
    315      */
    316     public void setStopPreviousRingtone(boolean stopPreviousRingtone) {
    317         mStopPreviousRingtone = stopPreviousRingtone;
    318     }
    319 
    320     /**
    321      * @see #setStopPreviousRingtone(boolean)
    322      */
    323     public boolean getStopPreviousRingtone() {
    324         return mStopPreviousRingtone;
    325     }
    326 
    327     /**
    328      * Stops playing the last {@link Ringtone} retrieved from this.
    329      */
    330     public void stopPreviousRingtone() {
    331         if (mPreviousRingtone != null) {
    332             mPreviousRingtone.stop();
    333         }
    334     }
    335 
    336     /**
    337      * Returns whether DRM ringtones will be included.
    338      *
    339      * @return Whether DRM ringtones will be included.
    340      * @see #setIncludeDrm(boolean)
    341      * Obsolete - always returns false
    342      * @deprecated DRM ringtones are no longer supported
    343      */
    344     @Deprecated
    345     public boolean getIncludeDrm() {
    346         return false;
    347     }
    348 
    349     /**
    350      * Sets whether to include DRM ringtones.
    351      *
    352      * @param includeDrm Whether to include DRM ringtones.
    353      * Obsolete - no longer has any effect
    354      * @deprecated DRM ringtones are no longer supported
    355      */
    356     @Deprecated
    357     public void setIncludeDrm(boolean includeDrm) {
    358         if (includeDrm) {
    359             Log.w(TAG, "setIncludeDrm no longer supported");
    360         }
    361     }
    362 
    363     /**
    364      * Returns a {@link Cursor} of all the ringtones available. The returned
    365      * cursor will be the same cursor returned each time this method is called,
    366      * so do not {@link Cursor#close()} the cursor. The cursor can be
    367      * {@link Cursor#deactivate()} safely.
    368      * <p>
    369      * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the
    370      * caller should manage the returned cursor through its activity's life
    371      * cycle to prevent leaking the cursor.
    372      * <p>
    373      * Note that the list of ringtones available will differ depending on whether the caller
    374      * has the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.
    375      *
    376      * @return A {@link Cursor} of all the ringtones available.
    377      * @see #ID_COLUMN_INDEX
    378      * @see #TITLE_COLUMN_INDEX
    379      * @see #URI_COLUMN_INDEX
    380      */
    381     public Cursor getCursor() {
    382         if (mCursor != null && mCursor.requery()) {
    383             return mCursor;
    384         }
    385 
    386         final Cursor internalCursor = getInternalRingtones();
    387         final Cursor mediaCursor = getMediaRingtones();
    388 
    389         return mCursor = new SortCursor(new Cursor[] { internalCursor, mediaCursor },
    390                 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
    391     }
    392 
    393     /**
    394      * Gets a {@link Ringtone} for the ringtone at the given position in the
    395      * {@link Cursor}.
    396      *
    397      * @param position The position (in the {@link Cursor}) of the ringtone.
    398      * @return A {@link Ringtone} pointing to the ringtone.
    399      */
    400     public Ringtone getRingtone(int position) {
    401         if (mStopPreviousRingtone && mPreviousRingtone != null) {
    402             mPreviousRingtone.stop();
    403         }
    404 
    405         mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType());
    406         return mPreviousRingtone;
    407     }
    408 
    409     /**
    410      * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}.
    411      *
    412      * @param position The position (in the {@link Cursor}) of the ringtone.
    413      * @return A {@link Uri} pointing to the ringtone.
    414      */
    415     public Uri getRingtoneUri(int position) {
    416         // use cursor directly instead of requerying it, which could easily
    417         // cause position to shuffle.
    418         if (mCursor == null || !mCursor.moveToPosition(position)) {
    419             return null;
    420         }
    421 
    422         return getUriFromCursor(mCursor);
    423     }
    424 
    425     private static Uri getUriFromCursor(Cursor cursor) {
    426         return ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)), cursor
    427                 .getLong(ID_COLUMN_INDEX));
    428     }
    429 
    430     /**
    431      * Gets the position of a {@link Uri} within this {@link RingtoneManager}.
    432      *
    433      * @param ringtoneUri The {@link Uri} to retreive the position of.
    434      * @return The position of the {@link Uri}, or -1 if it cannot be found.
    435      */
    436     public int getRingtonePosition(Uri ringtoneUri) {
    437 
    438         if (ringtoneUri == null) return -1;
    439 
    440         final Cursor cursor = getCursor();
    441         final int cursorCount = cursor.getCount();
    442 
    443         if (!cursor.moveToFirst()) {
    444             return -1;
    445         }
    446 
    447         // Only create Uri objects when the actual URI changes
    448         Uri currentUri = null;
    449         String previousUriString = null;
    450         for (int i = 0; i < cursorCount; i++) {
    451             String uriString = cursor.getString(URI_COLUMN_INDEX);
    452             if (currentUri == null || !uriString.equals(previousUriString)) {
    453                 currentUri = Uri.parse(uriString);
    454             }
    455 
    456             if (ringtoneUri.equals(ContentUris.withAppendedId(currentUri, cursor
    457                     .getLong(ID_COLUMN_INDEX)))) {
    458                 return i;
    459             }
    460 
    461             cursor.move(1);
    462 
    463             previousUriString = uriString;
    464         }
    465 
    466         return -1;
    467     }
    468 
    469     /**
    470      * Returns a valid ringtone URI. No guarantees on which it returns. If it
    471      * cannot find one, returns null. If it can only find one on external storage and the caller
    472      * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
    473      * returns null.
    474      *
    475      * @param context The context to use for querying.
    476      * @return A ringtone URI, or null if one cannot be found.
    477      */
    478     public static Uri getValidRingtoneUri(Context context) {
    479         final RingtoneManager rm = new RingtoneManager(context);
    480 
    481         Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
    482 
    483         if (uri == null) {
    484             uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
    485         }
    486 
    487         return uri;
    488     }
    489 
    490     private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) {
    491         if (cursor != null) {
    492             Uri uri = null;
    493 
    494             if (cursor.moveToFirst()) {
    495                 uri = getUriFromCursor(cursor);
    496             }
    497             cursor.close();
    498 
    499             return uri;
    500         } else {
    501             return null;
    502         }
    503     }
    504 
    505     private Cursor getInternalRingtones() {
    506         return query(
    507                 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
    508                 constructBooleanTrueWhereClause(mFilterColumns),
    509                 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
    510     }
    511 
    512     private Cursor getMediaRingtones() {
    513         if (PackageManager.PERMISSION_GRANTED != mContext.checkPermission(
    514                 android.Manifest.permission.READ_EXTERNAL_STORAGE,
    515                 Process.myPid(), Process.myUid())) {
    516             Log.w(TAG, "No READ_EXTERNAL_STORAGE permission, ignoring ringtones on ext storage");
    517             return null;
    518         }
    519          // Get the external media cursor. First check to see if it is mounted.
    520         final String status = Environment.getExternalStorageState();
    521 
    522         return (status.equals(Environment.MEDIA_MOUNTED) ||
    523                     status.equals(Environment.MEDIA_MOUNTED_READ_ONLY))
    524                 ? query(
    525                     MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
    526                     constructBooleanTrueWhereClause(mFilterColumns), null,
    527                     MediaStore.Audio.Media.DEFAULT_SORT_ORDER)
    528                 : null;
    529     }
    530 
    531     private void setFilterColumnsList(int type) {
    532         List<String> columns = mFilterColumns;
    533         columns.clear();
    534 
    535         if ((type & TYPE_RINGTONE) != 0) {
    536             columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE);
    537         }
    538 
    539         if ((type & TYPE_NOTIFICATION) != 0) {
    540             columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION);
    541         }
    542 
    543         if ((type & TYPE_ALARM) != 0) {
    544             columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
    545         }
    546     }
    547 
    548     /**
    549      * Constructs a where clause that consists of at least one column being 1
    550      * (true). This is used to find all matching sounds for the given sound
    551      * types (ringtone, notifications, etc.)
    552      *
    553      * @param columns The columns that must be true.
    554      * @return The where clause.
    555      */
    556     private static String constructBooleanTrueWhereClause(List<String> columns) {
    557 
    558         if (columns == null) return null;
    559 
    560         StringBuilder sb = new StringBuilder();
    561         sb.append("(");
    562 
    563         for (int i = columns.size() - 1; i >= 0; i--) {
    564             sb.append(columns.get(i)).append("=1 or ");
    565         }
    566 
    567         if (columns.size() > 0) {
    568             // Remove last ' or '
    569             sb.setLength(sb.length() - 4);
    570         }
    571 
    572         sb.append(")");
    573 
    574         return sb.toString();
    575     }
    576 
    577     private Cursor query(Uri uri,
    578             String[] projection,
    579             String selection,
    580             String[] selectionArgs,
    581             String sortOrder) {
    582         if (mActivity != null) {
    583             return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder);
    584         } else {
    585             return mContext.getContentResolver().query(uri, projection, selection, selectionArgs,
    586                     sortOrder);
    587         }
    588     }
    589 
    590     /**
    591      * Returns a {@link Ringtone} for a given sound URI.
    592      * <p>
    593      * If the given URI cannot be opened for any reason, this method will
    594      * attempt to fallback on another sound. If it cannot find any, it will
    595      * return null.
    596      *
    597      * @param context A context used to query.
    598      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
    599      * @return A {@link Ringtone} for the given URI, or null.
    600      */
    601     public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
    602         // Don't set the stream type
    603         return getRingtone(context, ringtoneUri, -1);
    604     }
    605 
    606     /**
    607      * Returns a {@link Ringtone} for a given sound URI on the given stream
    608      * type. Normally, if you change the stream type on the returned
    609      * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
    610      * an optimized route to avoid that.
    611      *
    612      * @param streamType The stream type for the ringtone, or -1 if it should
    613      *            not be set (and the default used instead).
    614      * @see #getRingtone(Context, Uri)
    615      */
    616     private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
    617         try {
    618             final Ringtone r = new Ringtone(context, true);
    619             if (streamType >= 0) {
    620                 r.setStreamType(streamType);
    621             }
    622             r.setUri(ringtoneUri);
    623             return r;
    624         } catch (Exception ex) {
    625             Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
    626         }
    627 
    628         return null;
    629     }
    630 
    631     /**
    632      * Gets the current default sound's {@link Uri}. This will give the actual
    633      * sound {@link Uri}, instead of using this, most clients can use
    634      * {@link System#DEFAULT_RINGTONE_URI}.
    635      *
    636      * @param context A context used for querying.
    637      * @param type The type whose default sound should be returned. One of
    638      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
    639      *            {@link #TYPE_ALARM}.
    640      * @return A {@link Uri} pointing to the default sound for the sound type.
    641      * @see #setActualDefaultRingtoneUri(Context, int, Uri)
    642      */
    643     public static Uri getActualDefaultRingtoneUri(Context context, int type) {
    644         String setting = getSettingForType(type);
    645         if (setting == null) return null;
    646         final String uriString = Settings.System.getStringForUser(context.getContentResolver(),
    647                 setting, context.getUserId());
    648         return uriString != null ? Uri.parse(uriString) : null;
    649     }
    650 
    651     /**
    652      * Sets the {@link Uri} of the default sound for a given sound type.
    653      *
    654      * @param context A context used for querying.
    655      * @param type The type whose default sound should be set. One of
    656      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
    657      *            {@link #TYPE_ALARM}.
    658      * @param ringtoneUri A {@link Uri} pointing to the default sound to set.
    659      * @see #getActualDefaultRingtoneUri(Context, int)
    660      */
    661     public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
    662         final ContentResolver resolver = context.getContentResolver();
    663 
    664         String setting = getSettingForType(type);
    665         if (setting == null) return;
    666         Settings.System.putStringForUser(resolver, setting,
    667                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
    668 
    669         // Stream selected ringtone into cache so it's available for playback
    670         // when CE storage is still locked
    671         if (ringtoneUri != null) {
    672             final Uri cacheUri = getCacheForType(type);
    673             try (InputStream in = openRingtone(context, ringtoneUri);
    674                     OutputStream out = resolver.openOutputStream(cacheUri)) {
    675                 Streams.copy(in, out);
    676             } catch (IOException e) {
    677                 Log.w(TAG, "Failed to cache ringtone: " + e);
    678             }
    679         }
    680     }
    681 
    682     /**
    683      * Try opening the given ringtone locally first, but failover to
    684      * {@link IRingtonePlayer} if we can't access it directly. Typically happens
    685      * when process doesn't hold
    686      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
    687      */
    688     private static InputStream openRingtone(Context context, Uri uri) throws IOException {
    689         final ContentResolver resolver = context.getContentResolver();
    690         try {
    691             return resolver.openInputStream(uri);
    692         } catch (SecurityException | IOException e) {
    693             Log.w(TAG, "Failed to open directly; attempting failover: " + e);
    694             final IRingtonePlayer player = context.getSystemService(AudioManager.class)
    695                     .getRingtonePlayer();
    696             try {
    697                 return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri));
    698             } catch (Exception e2) {
    699                 throw new IOException(e2);
    700             }
    701         }
    702     }
    703 
    704     private static String getSettingForType(int type) {
    705         if ((type & TYPE_RINGTONE) != 0) {
    706             return Settings.System.RINGTONE;
    707         } else if ((type & TYPE_NOTIFICATION) != 0) {
    708             return Settings.System.NOTIFICATION_SOUND;
    709         } else if ((type & TYPE_ALARM) != 0) {
    710             return Settings.System.ALARM_ALERT;
    711         } else {
    712             return null;
    713         }
    714     }
    715 
    716     /** {@hide} */
    717     public static Uri getCacheForType(int type) {
    718         if ((type & TYPE_RINGTONE) != 0) {
    719             return Settings.System.RINGTONE_CACHE_URI;
    720         } else if ((type & TYPE_NOTIFICATION) != 0) {
    721             return Settings.System.NOTIFICATION_SOUND_CACHE_URI;
    722         } else if ((type & TYPE_ALARM) != 0) {
    723             return Settings.System.ALARM_ALERT_CACHE_URI;
    724         } else {
    725             return null;
    726         }
    727     }
    728 
    729     /**
    730      * Returns whether the given {@link Uri} is one of the default ringtones.
    731      *
    732      * @param ringtoneUri The ringtone {@link Uri} to be checked.
    733      * @return Whether the {@link Uri} is a default.
    734      */
    735     public static boolean isDefault(Uri ringtoneUri) {
    736         return getDefaultType(ringtoneUri) != -1;
    737     }
    738 
    739     /**
    740      * Returns the type of a default {@link Uri}.
    741      *
    742      * @param defaultRingtoneUri The default {@link Uri}. For example,
    743      *            {@link System#DEFAULT_RINGTONE_URI},
    744      *            {@link System#DEFAULT_NOTIFICATION_URI}, or
    745      *            {@link System#DEFAULT_ALARM_ALERT_URI}.
    746      * @return The type of the defaultRingtoneUri, or -1.
    747      */
    748     public static int getDefaultType(Uri defaultRingtoneUri) {
    749         if (defaultRingtoneUri == null) {
    750             return -1;
    751         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
    752             return TYPE_RINGTONE;
    753         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) {
    754             return TYPE_NOTIFICATION;
    755         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) {
    756             return TYPE_ALARM;
    757         } else {
    758             return -1;
    759         }
    760     }
    761 
    762     /**
    763      * Returns the {@link Uri} for the default ringtone of a particular type.
    764      * Rather than returning the actual ringtone's sound {@link Uri}, this will
    765      * return the symbolic {@link Uri} which will resolved to the actual sound
    766      * when played.
    767      *
    768      * @param type The ringtone type whose default should be returned.
    769      * @return The {@link Uri} of the default ringtone for the given type.
    770      */
    771     public static Uri getDefaultUri(int type) {
    772         if ((type & TYPE_RINGTONE) != 0) {
    773             return Settings.System.DEFAULT_RINGTONE_URI;
    774         } else if ((type & TYPE_NOTIFICATION) != 0) {
    775             return Settings.System.DEFAULT_NOTIFICATION_URI;
    776         } else if ((type & TYPE_ALARM) != 0) {
    777             return Settings.System.DEFAULT_ALARM_ALERT_URI;
    778         } else {
    779             return null;
    780         }
    781     }
    782 
    783 }
    784