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
17package android.media;
18
19import android.annotation.SdkConstant;
20import android.annotation.SdkConstant.SdkConstantType;
21import android.app.Activity;
22import android.content.ContentResolver;
23import android.content.ContentUris;
24import android.content.Context;
25import android.content.pm.PackageManager;
26import android.database.Cursor;
27import android.net.Uri;
28import android.os.Environment;
29import android.os.ParcelFileDescriptor;
30import android.os.Process;
31import android.provider.MediaStore;
32import android.provider.Settings;
33import android.provider.Settings.System;
34import android.util.Log;
35
36import com.android.internal.database.SortCursor;
37
38import libcore.io.Streams;
39
40import java.io.IOException;
41import java.io.InputStream;
42import java.io.OutputStream;
43import java.util.ArrayList;
44import 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 */
59public 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