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