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