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