• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.media;
18 
19 import android.Manifest;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.annotation.RequiresPermission;
23 import android.annotation.SdkConstant;
24 import android.annotation.SdkConstant.SdkConstantType;
25 import android.annotation.UnsupportedAppUsage;
26 import android.annotation.WorkerThread;
27 import android.app.Activity;
28 import android.content.ContentProvider;
29 import android.content.ContentResolver;
30 import android.content.ContentUris;
31 import android.content.Context;
32 import android.content.pm.PackageManager.NameNotFoundException;
33 import android.content.pm.UserInfo;
34 import android.content.res.AssetFileDescriptor;
35 import android.database.Cursor;
36 import android.net.Uri;
37 import android.os.Environment;
38 import android.os.FileUtils;
39 import android.os.IBinder;
40 import android.os.ParcelFileDescriptor;
41 import android.os.RemoteException;
42 import android.os.ServiceManager;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.provider.MediaStore;
46 import android.provider.Settings;
47 import android.provider.Settings.System;
48 import android.util.Log;
49 
50 import com.android.internal.database.SortCursor;
51 
52 import java.io.File;
53 import java.io.FileNotFoundException;
54 import java.io.FileOutputStream;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.OutputStream;
58 import java.util.ArrayList;
59 import java.util.List;
60 
61 /**
62  * RingtoneManager provides access to ringtones, notification, and other types
63  * of sounds. It manages querying the different media providers and combines the
64  * results into a single cursor. It also provides a {@link Ringtone} for each
65  * ringtone. We generically call these sounds ringtones, however the
66  * {@link #TYPE_RINGTONE} refers to the type of sounds that are suitable for the
67  * phone ringer.
68  * <p>
69  * To show a ringtone picker to the user, use the
70  * {@link #ACTION_RINGTONE_PICKER} intent to launch the picker as a subactivity.
71  *
72  * @see Ringtone
73  */
74 public class RingtoneManager {
75 
76     private static final String TAG = "RingtoneManager";
77 
78     // Make sure these are in sync with attrs.xml:
79     // <attr name="ringtoneType">
80 
81     /**
82      * Type that refers to sounds that are used for the phone ringer.
83      */
84     public static final int TYPE_RINGTONE = 1;
85 
86     /**
87      * Type that refers to sounds that are used for notifications.
88      */
89     public static final int TYPE_NOTIFICATION = 2;
90 
91     /**
92      * Type that refers to sounds that are used for the alarm.
93      */
94     public static final int TYPE_ALARM = 4;
95 
96     /**
97      * All types of sounds.
98      */
99     public static final int TYPE_ALL = TYPE_RINGTONE | TYPE_NOTIFICATION | TYPE_ALARM;
100 
101     // </attr>
102 
103     /**
104      * Activity Action: Shows a ringtone picker.
105      * <p>
106      * Input: {@link #EXTRA_RINGTONE_EXISTING_URI},
107      * {@link #EXTRA_RINGTONE_SHOW_DEFAULT},
108      * {@link #EXTRA_RINGTONE_SHOW_SILENT}, {@link #EXTRA_RINGTONE_TYPE},
109      * {@link #EXTRA_RINGTONE_DEFAULT_URI}, {@link #EXTRA_RINGTONE_TITLE},
110      * <p>
111      * Output: {@link #EXTRA_RINGTONE_PICKED_URI}.
112      */
113     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
114     public static final String ACTION_RINGTONE_PICKER = "android.intent.action.RINGTONE_PICKER";
115 
116     /**
117      * Given to the ringtone picker as a boolean. Whether to show an item for
118      * "Default".
119      *
120      * @see #ACTION_RINGTONE_PICKER
121      */
122     public static final String EXTRA_RINGTONE_SHOW_DEFAULT =
123             "android.intent.extra.ringtone.SHOW_DEFAULT";
124 
125     /**
126      * Given to the ringtone picker as a boolean. Whether to show an item for
127      * "Silent". If the "Silent" item is picked,
128      * {@link #EXTRA_RINGTONE_PICKED_URI} will be null.
129      *
130      * @see #ACTION_RINGTONE_PICKER
131      */
132     public static final String EXTRA_RINGTONE_SHOW_SILENT =
133             "android.intent.extra.ringtone.SHOW_SILENT";
134 
135     /**
136      * Given to the ringtone picker as a boolean. Whether to include DRM ringtones.
137      * @deprecated DRM ringtones are no longer supported
138      */
139     @Deprecated
140     public static final String EXTRA_RINGTONE_INCLUDE_DRM =
141             "android.intent.extra.ringtone.INCLUDE_DRM";
142 
143     /**
144      * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
145      * current ringtone, which will be used to show a checkmark next to the item
146      * for this {@link Uri}. If showing an item for "Default" (@see
147      * {@link #EXTRA_RINGTONE_SHOW_DEFAULT}), this can also be one of
148      * {@link System#DEFAULT_RINGTONE_URI},
149      * {@link System#DEFAULT_NOTIFICATION_URI}, or
150      * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" item
151      * checked.
152      *
153      * @see #ACTION_RINGTONE_PICKER
154      */
155     public static final String EXTRA_RINGTONE_EXISTING_URI =
156             "android.intent.extra.ringtone.EXISTING_URI";
157 
158     /**
159      * Given to the ringtone picker as a {@link Uri}. The {@link Uri} of the
160      * ringtone to play when the user attempts to preview the "Default"
161      * ringtone. This can be one of {@link System#DEFAULT_RINGTONE_URI},
162      * {@link System#DEFAULT_NOTIFICATION_URI}, or
163      * {@link System#DEFAULT_ALARM_ALERT_URI} to have the "Default" point to
164      * the current sound for the given default sound type. If you are showing a
165      * ringtone picker for some other type of sound, you are free to provide any
166      * {@link Uri} here.
167      */
168     public static final String EXTRA_RINGTONE_DEFAULT_URI =
169             "android.intent.extra.ringtone.DEFAULT_URI";
170 
171     /**
172      * Given to the ringtone picker as an int. Specifies which ringtone type(s) should be
173      * shown in the picker. One or more of {@link #TYPE_RINGTONE},
174      * {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM}, or {@link #TYPE_ALL}
175      * (bitwise-ored together).
176      */
177     public static final String EXTRA_RINGTONE_TYPE = "android.intent.extra.ringtone.TYPE";
178 
179     /**
180      * Given to the ringtone picker as a {@link CharSequence}. The title to
181      * show for the ringtone picker. This has a default value that is suitable
182      * in most cases.
183      */
184     public static final String EXTRA_RINGTONE_TITLE = "android.intent.extra.ringtone.TITLE";
185 
186     /**
187      * @hide
188      * Given to the ringtone picker as an int. Additional AudioAttributes flags to use
189      * when playing the ringtone in the picker.
190      * @see #ACTION_RINGTONE_PICKER
191      */
192     public static final String EXTRA_RINGTONE_AUDIO_ATTRIBUTES_FLAGS =
193             "android.intent.extra.ringtone.AUDIO_ATTRIBUTES_FLAGS";
194 
195     /**
196      * Returned from the ringtone picker as a {@link Uri}.
197      * <p>
198      * It will be one of:
199      * <li> the picked ringtone,
200      * <li> a {@link Uri} that equals {@link System#DEFAULT_RINGTONE_URI},
201      * {@link System#DEFAULT_NOTIFICATION_URI}, or
202      * {@link System#DEFAULT_ALARM_ALERT_URI} if the default was chosen,
203      * <li> null if the "Silent" item was picked.
204      *
205      * @see #ACTION_RINGTONE_PICKER
206      */
207     public static final String EXTRA_RINGTONE_PICKED_URI =
208             "android.intent.extra.ringtone.PICKED_URI";
209 
210     // Make sure the column ordering and then ..._COLUMN_INDEX are in sync
211 
212     private static final String[] INTERNAL_COLUMNS = new String[] {
213         MediaStore.Audio.Media._ID,
214         MediaStore.Audio.Media.TITLE,
215         MediaStore.Audio.Media.TITLE,
216         MediaStore.Audio.Media.TITLE_KEY,
217     };
218 
219     private static final String[] MEDIA_COLUMNS = new String[] {
220         MediaStore.Audio.Media._ID,
221         MediaStore.Audio.Media.TITLE,
222         MediaStore.Audio.Media.TITLE,
223         MediaStore.Audio.Media.TITLE_KEY,
224     };
225 
226     /**
227      * The column index (in the cursor returned by {@link #getCursor()} for the
228      * row ID.
229      */
230     public static final int ID_COLUMN_INDEX = 0;
231 
232     /**
233      * The column index (in the cursor returned by {@link #getCursor()} for the
234      * title.
235      */
236     public static final int TITLE_COLUMN_INDEX = 1;
237 
238     /**
239      * The column index (in the cursor returned by {@link #getCursor()} for the
240      * media provider's URI.
241      */
242     public static final int URI_COLUMN_INDEX = 2;
243 
244     private final Activity mActivity;
245     private final Context mContext;
246 
247     @UnsupportedAppUsage
248     private Cursor mCursor;
249 
250     private int mType = TYPE_RINGTONE;
251 
252     /**
253      * If a column (item from this list) exists in the Cursor, its value must
254      * be true (value of 1) for the row to be returned.
255      */
256     private final List<String> mFilterColumns = new ArrayList<String>();
257 
258     private boolean mStopPreviousRingtone = true;
259     private Ringtone mPreviousRingtone;
260 
261     private boolean mIncludeParentRingtones;
262 
263     /**
264      * Constructs a RingtoneManager. This constructor is recommended as its
265      * constructed instance manages cursor(s).
266      *
267      * @param activity The activity used to get a managed cursor.
268      */
RingtoneManager(Activity activity)269     public RingtoneManager(Activity activity) {
270         this(activity, /* includeParentRingtones */ false);
271     }
272 
273     /**
274      * Constructs a RingtoneManager. This constructor is recommended if there's the need to also
275      * list ringtones from the user's parent.
276      *
277      * @param activity The activity used to get a managed cursor.
278      * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve
279      *            ringtones from the parent of the user specified in the given activity
280      *
281      * @hide
282      */
RingtoneManager(Activity activity, boolean includeParentRingtones)283     public RingtoneManager(Activity activity, boolean includeParentRingtones) {
284         mActivity = activity;
285         mContext = activity;
286         setType(mType);
287         mIncludeParentRingtones = includeParentRingtones;
288     }
289 
290     /**
291      * Constructs a RingtoneManager. The instance constructed by this
292      * constructor will not manage the cursor(s), so the client should handle
293      * this itself.
294      *
295      * @param context The context to used to get a cursor.
296      */
RingtoneManager(Context context)297     public RingtoneManager(Context context) {
298         this(context, /* includeParentRingtones */ false);
299     }
300 
301     /**
302      * Constructs a RingtoneManager.
303      *
304      * @param context The context to used to get a cursor.
305      * @param includeParentRingtones if true, this ringtone manager's cursor will also retrieve
306      *            ringtones from the parent of the user specified in the given context
307      *
308      * @hide
309      */
RingtoneManager(Context context, boolean includeParentRingtones)310     public RingtoneManager(Context context, boolean includeParentRingtones) {
311         mActivity = null;
312         mContext = context;
313         setType(mType);
314         mIncludeParentRingtones = includeParentRingtones;
315     }
316 
317     /**
318      * Sets which type(s) of ringtones will be listed by this.
319      *
320      * @param type The type(s), one or more of {@link #TYPE_RINGTONE},
321      *            {@link #TYPE_NOTIFICATION}, {@link #TYPE_ALARM},
322      *            {@link #TYPE_ALL}.
323      * @see #EXTRA_RINGTONE_TYPE
324      */
setType(int type)325     public void setType(int type) {
326         if (mCursor != null) {
327             throw new IllegalStateException(
328                     "Setting filter columns should be done before querying for ringtones.");
329         }
330 
331         mType = type;
332         setFilterColumnsList(type);
333     }
334 
335     /**
336      * Infers the volume stream type based on what type of ringtones this
337      * manager is returning.
338      *
339      * @return The stream type.
340      */
inferStreamType()341     public int inferStreamType() {
342         switch (mType) {
343 
344             case TYPE_ALARM:
345                 return AudioManager.STREAM_ALARM;
346 
347             case TYPE_NOTIFICATION:
348                 return AudioManager.STREAM_NOTIFICATION;
349 
350             default:
351                 return AudioManager.STREAM_RING;
352         }
353     }
354 
355     /**
356      * Whether retrieving another {@link Ringtone} will stop playing the
357      * previously retrieved {@link Ringtone}.
358      * <p>
359      * If this is false, make sure to {@link Ringtone#stop()} any previous
360      * ringtones to free resources.
361      *
362      * @param stopPreviousRingtone If true, the previously retrieved
363      *            {@link Ringtone} will be stopped.
364      */
setStopPreviousRingtone(boolean stopPreviousRingtone)365     public void setStopPreviousRingtone(boolean stopPreviousRingtone) {
366         mStopPreviousRingtone = stopPreviousRingtone;
367     }
368 
369     /**
370      * @see #setStopPreviousRingtone(boolean)
371      */
getStopPreviousRingtone()372     public boolean getStopPreviousRingtone() {
373         return mStopPreviousRingtone;
374     }
375 
376     /**
377      * Stops playing the last {@link Ringtone} retrieved from this.
378      */
stopPreviousRingtone()379     public void stopPreviousRingtone() {
380         if (mPreviousRingtone != null) {
381             mPreviousRingtone.stop();
382         }
383     }
384 
385     /**
386      * Returns whether DRM ringtones will be included.
387      *
388      * @return Whether DRM ringtones will be included.
389      * @see #setIncludeDrm(boolean)
390      * Obsolete - always returns false
391      * @deprecated DRM ringtones are no longer supported
392      */
393     @Deprecated
getIncludeDrm()394     public boolean getIncludeDrm() {
395         return false;
396     }
397 
398     /**
399      * Sets whether to include DRM ringtones.
400      *
401      * @param includeDrm Whether to include DRM ringtones.
402      * Obsolete - no longer has any effect
403      * @deprecated DRM ringtones are no longer supported
404      */
405     @Deprecated
setIncludeDrm(boolean includeDrm)406     public void setIncludeDrm(boolean includeDrm) {
407         if (includeDrm) {
408             Log.w(TAG, "setIncludeDrm no longer supported");
409         }
410     }
411 
412     /**
413      * Returns a {@link Cursor} of all the ringtones available. The returned
414      * cursor will be the same cursor returned each time this method is called,
415      * so do not {@link Cursor#close()} the cursor. The cursor can be
416      * {@link Cursor#deactivate()} safely.
417      * <p>
418      * If {@link RingtoneManager#RingtoneManager(Activity)} was not used, the
419      * caller should manage the returned cursor through its activity's life
420      * cycle to prevent leaking the cursor.
421      * <p>
422      * Note that the list of ringtones available will differ depending on whether the caller
423      * has the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.
424      *
425      * @return A {@link Cursor} of all the ringtones available.
426      * @see #ID_COLUMN_INDEX
427      * @see #TITLE_COLUMN_INDEX
428      * @see #URI_COLUMN_INDEX
429      */
getCursor()430     public Cursor getCursor() {
431         if (mCursor != null && mCursor.requery()) {
432             return mCursor;
433         }
434 
435         ArrayList<Cursor> ringtoneCursors = new ArrayList<Cursor>();
436         ringtoneCursors.add(getInternalRingtones());
437         ringtoneCursors.add(getMediaRingtones());
438 
439         if (mIncludeParentRingtones) {
440             Cursor parentRingtonesCursor = getParentProfileRingtones();
441             if (parentRingtonesCursor != null) {
442                 ringtoneCursors.add(parentRingtonesCursor);
443             }
444         }
445 
446         return mCursor = new SortCursor(ringtoneCursors.toArray(new Cursor[ringtoneCursors.size()]),
447                 MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
448     }
449 
getParentProfileRingtones()450     private Cursor getParentProfileRingtones() {
451         final UserManager um = UserManager.get(mContext);
452         final UserInfo parentInfo = um.getProfileParent(mContext.getUserId());
453         if (parentInfo != null && parentInfo.id != mContext.getUserId()) {
454             final Context parentContext = createPackageContextAsUser(mContext, parentInfo.id);
455             if (parentContext != null) {
456                 // We don't need to re-add the internal ringtones for the work profile since
457                 // they are the same as the personal profile. We just need the external
458                 // ringtones.
459                 final Cursor res = getMediaRingtones(parentContext);
460                 return new ExternalRingtonesCursorWrapper(res, ContentProvider.maybeAddUserId(
461                         MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, parentInfo.id));
462             }
463         }
464         return null;
465     }
466 
467     /**
468      * Gets a {@link Ringtone} for the ringtone at the given position in the
469      * {@link Cursor}.
470      *
471      * @param position The position (in the {@link Cursor}) of the ringtone.
472      * @return A {@link Ringtone} pointing to the ringtone.
473      */
getRingtone(int position)474     public Ringtone getRingtone(int position) {
475         if (mStopPreviousRingtone && mPreviousRingtone != null) {
476             mPreviousRingtone.stop();
477         }
478 
479         mPreviousRingtone = getRingtone(mContext, getRingtoneUri(position), inferStreamType());
480         return mPreviousRingtone;
481     }
482 
483     /**
484      * Gets a {@link Uri} for the ringtone at the given position in the {@link Cursor}.
485      *
486      * @param position The position (in the {@link Cursor}) of the ringtone.
487      * @return A {@link Uri} pointing to the ringtone.
488      */
getRingtoneUri(int position)489     public Uri getRingtoneUri(int position) {
490         // use cursor directly instead of requerying it, which could easily
491         // cause position to shuffle.
492         if (mCursor == null || !mCursor.moveToPosition(position)) {
493             return null;
494         }
495 
496         return getUriFromCursor(mContext, mCursor);
497     }
498 
getUriFromCursor(Context context, Cursor cursor)499     private static Uri getUriFromCursor(Context context, Cursor cursor) {
500         final Uri uri = ContentUris.withAppendedId(Uri.parse(cursor.getString(URI_COLUMN_INDEX)),
501                 cursor.getLong(ID_COLUMN_INDEX));
502         return context.getContentResolver().canonicalizeOrElse(uri);
503     }
504 
505     /**
506      * Gets the position of a {@link Uri} within this {@link RingtoneManager}.
507      *
508      * @param ringtoneUri The {@link Uri} to retreive the position of.
509      * @return The position of the {@link Uri}, or -1 if it cannot be found.
510      */
getRingtonePosition(Uri ringtoneUri)511     public int getRingtonePosition(Uri ringtoneUri) {
512         if (ringtoneUri == null) return -1;
513         final long ringtoneId = ContentUris.parseId(ringtoneUri);
514 
515         final Cursor cursor = getCursor();
516         cursor.moveToPosition(-1);
517         while (cursor.moveToNext()) {
518             if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) {
519                 return cursor.getPosition();
520             }
521         }
522         return -1;
523     }
524 
525     /**
526      * Returns a valid ringtone URI. No guarantees on which it returns. If it
527      * cannot find one, returns null. If it can only find one on external storage and the caller
528      * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
529      * returns null.
530      *
531      * @param context The context to use for querying.
532      * @return A ringtone URI, or null if one cannot be found.
533      */
getValidRingtoneUri(Context context)534     public static Uri getValidRingtoneUri(Context context) {
535         final RingtoneManager rm = new RingtoneManager(context);
536 
537         Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
538 
539         if (uri == null) {
540             uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
541         }
542 
543         return uri;
544     }
545 
getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor)546     private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) {
547         if (cursor != null) {
548             Uri uri = null;
549 
550             if (cursor.moveToFirst()) {
551                 uri = getUriFromCursor(context, cursor);
552             }
553             cursor.close();
554 
555             return uri;
556         } else {
557             return null;
558         }
559     }
560 
561     @UnsupportedAppUsage
getInternalRingtones()562     private Cursor getInternalRingtones() {
563         final Cursor res = query(
564                 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
565                 constructBooleanTrueWhereClause(mFilterColumns),
566                 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
567         return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
568     }
569 
getMediaRingtones()570     private Cursor getMediaRingtones() {
571         final Cursor res = getMediaRingtones(mContext);
572         return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
573     }
574 
575     @UnsupportedAppUsage
getMediaRingtones(Context context)576     private Cursor getMediaRingtones(Context context) {
577         // MediaStore now returns ringtones on other storage devices, even when
578         // we don't have storage or audio permissions
579         return query(
580                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
581                 constructBooleanTrueWhereClause(mFilterColumns), null,
582                 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context);
583     }
584 
setFilterColumnsList(int type)585     private void setFilterColumnsList(int type) {
586         List<String> columns = mFilterColumns;
587         columns.clear();
588 
589         if ((type & TYPE_RINGTONE) != 0) {
590             columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE);
591         }
592 
593         if ((type & TYPE_NOTIFICATION) != 0) {
594             columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION);
595         }
596 
597         if ((type & TYPE_ALARM) != 0) {
598             columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
599         }
600     }
601 
602     /**
603      * Constructs a where clause that consists of at least one column being 1
604      * (true). This is used to find all matching sounds for the given sound
605      * types (ringtone, notifications, etc.)
606      *
607      * @param columns The columns that must be true.
608      * @return The where clause.
609      */
constructBooleanTrueWhereClause(List<String> columns)610     private static String constructBooleanTrueWhereClause(List<String> columns) {
611 
612         if (columns == null) return null;
613 
614         StringBuilder sb = new StringBuilder();
615         sb.append("(");
616 
617         for (int i = columns.size() - 1; i >= 0; i--) {
618             sb.append(columns.get(i)).append("=1 or ");
619         }
620 
621         if (columns.size() > 0) {
622             // Remove last ' or '
623             sb.setLength(sb.length() - 4);
624         }
625 
626         sb.append(")");
627 
628         return sb.toString();
629     }
630 
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)631     private Cursor query(Uri uri,
632             String[] projection,
633             String selection,
634             String[] selectionArgs,
635             String sortOrder) {
636         return query(uri, projection, selection, selectionArgs, sortOrder, mContext);
637     }
638 
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, Context context)639     private Cursor query(Uri uri,
640             String[] projection,
641             String selection,
642             String[] selectionArgs,
643             String sortOrder,
644             Context context) {
645         if (mActivity != null) {
646             return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder);
647         } else {
648             return context.getContentResolver().query(uri, projection, selection, selectionArgs,
649                     sortOrder);
650         }
651     }
652 
653     /**
654      * Returns a {@link Ringtone} for a given sound URI.
655      * <p>
656      * If the given URI cannot be opened for any reason, this method will
657      * attempt to fallback on another sound. If it cannot find any, it will
658      * return null.
659      *
660      * @param context A context used to query.
661      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
662      * @return A {@link Ringtone} for the given URI, or null.
663      */
getRingtone(final Context context, Uri ringtoneUri)664     public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
665         // Don't set the stream type
666         return getRingtone(context, ringtoneUri, -1);
667     }
668 
669     /**
670      * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
671      * <p>
672      * If the given URI cannot be opened for any reason, this method will
673      * attempt to fallback on another sound. If it cannot find any, it will
674      * return null.
675      *
676      * @param context A context used to query.
677      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
678      * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
679      * @return A {@link Ringtone} for the given URI, or null.
680      *
681      * @hide
682      */
getRingtone( final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig)683     public static Ringtone getRingtone(
684             final Context context, Uri ringtoneUri,
685             @Nullable VolumeShaper.Configuration volumeShaperConfig) {
686         // Don't set the stream type
687         return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig);
688     }
689 
690     //FIXME bypass the notion of stream types within the class
691     /**
692      * Returns a {@link Ringtone} for a given sound URI on the given stream
693      * type. Normally, if you change the stream type on the returned
694      * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
695      * an optimized route to avoid that.
696      *
697      * @param streamType The stream type for the ringtone, or -1 if it should
698      *            not be set (and the default used instead).
699      * @see #getRingtone(Context, Uri)
700      */
701     @UnsupportedAppUsage
getRingtone(final Context context, Uri ringtoneUri, int streamType)702     private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
703         return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */);
704     }
705 
706     //FIXME bypass the notion of stream types within the class
707     /**
708      * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on
709      * the given stream type. Normally, if you change the stream type on the returned
710      * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
711      * an optimized route to avoid that.
712      *
713      * @param streamType The stream type for the ringtone, or -1 if it should
714      *            not be set (and the default used instead).
715      * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
716      * @see #getRingtone(Context, Uri)
717      */
718     @UnsupportedAppUsage
getRingtone( final Context context, Uri ringtoneUri, int streamType, @Nullable VolumeShaper.Configuration volumeShaperConfig)719     private static Ringtone getRingtone(
720             final Context context, Uri ringtoneUri, int streamType,
721             @Nullable VolumeShaper.Configuration volumeShaperConfig) {
722         try {
723             final Ringtone r = new Ringtone(context, true);
724             if (streamType >= 0) {
725                 //FIXME deprecated call
726                 r.setStreamType(streamType);
727             }
728             r.setUri(ringtoneUri, volumeShaperConfig);
729             return r;
730         } catch (Exception ex) {
731             Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
732         }
733 
734         return null;
735     }
736 
737     /**
738      * Disables Settings.System.SYNC_PARENT_SOUNDS.
739      *
740      * @hide
741      */
disableSyncFromParent(Context userContext)742     public static void disableSyncFromParent(Context userContext) {
743         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
744         IAudioService audioService = IAudioService.Stub.asInterface(b);
745         try {
746             audioService.disableRingtoneSync(userContext.getUserId());
747         } catch (RemoteException e) {
748             Log.e(TAG, "Unable to disable ringtone sync.");
749         }
750     }
751 
752     /**
753      * Enables Settings.System.SYNC_PARENT_SOUNDS for the content's user
754      *
755      * @hide
756      */
757     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
enableSyncFromParent(Context userContext)758     public static void enableSyncFromParent(Context userContext) {
759         Settings.Secure.putIntForUser(userContext.getContentResolver(),
760                 Settings.Secure.SYNC_PARENT_SOUNDS, 1 /* true */, userContext.getUserId());
761     }
762 
763     /**
764      * Gets the current default sound's {@link Uri}. This will give the actual
765      * sound {@link Uri}, instead of using this, most clients can use
766      * {@link System#DEFAULT_RINGTONE_URI}.
767      *
768      * @param context A context used for querying.
769      * @param type The type whose default sound should be returned. One of
770      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
771      *            {@link #TYPE_ALARM}.
772      * @return A {@link Uri} pointing to the default sound for the sound type.
773      * @see #setActualDefaultRingtoneUri(Context, int, Uri)
774      */
getActualDefaultRingtoneUri(Context context, int type)775     public static Uri getActualDefaultRingtoneUri(Context context, int type) {
776         String setting = getSettingForType(type);
777         if (setting == null) return null;
778         final String uriString = Settings.System.getStringForUser(context.getContentResolver(),
779                 setting, context.getUserId());
780         Uri ringtoneUri = uriString != null ? Uri.parse(uriString) : null;
781 
782         // If this doesn't verify, the user id must be kept in the uri to ensure it resolves in the
783         // correct user storage
784         if (ringtoneUri != null
785                 && ContentProvider.getUserIdFromUri(ringtoneUri) == context.getUserId()) {
786             ringtoneUri = ContentProvider.getUriWithoutUserId(ringtoneUri);
787         }
788 
789         return ringtoneUri;
790     }
791 
792     /**
793      * Sets the {@link Uri} of the default sound for a given sound type.
794      *
795      * @param context A context used for querying.
796      * @param type The type whose default sound should be set. One of
797      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
798      *            {@link #TYPE_ALARM}.
799      * @param ringtoneUri A {@link Uri} pointing to the default sound to set.
800      * @see #getActualDefaultRingtoneUri(Context, int)
801      */
setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)802     public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
803         String setting = getSettingForType(type);
804         if (setting == null) return;
805 
806         final ContentResolver resolver = context.getContentResolver();
807         if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0,
808                     context.getUserId()) == 1) {
809             // Parent sound override is enabled. Disable it using the audio service.
810             disableSyncFromParent(context);
811         }
812         if(!isInternalRingtoneUri(ringtoneUri)) {
813             ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
814         }
815         Settings.System.putStringForUser(resolver, setting,
816                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
817 
818         // Stream selected ringtone into cache so it's available for playback
819         // when CE storage is still locked
820         if (ringtoneUri != null) {
821             final Uri cacheUri = getCacheForType(type, context.getUserId());
822             try (InputStream in = openRingtone(context, ringtoneUri);
823                     OutputStream out = resolver.openOutputStream(cacheUri)) {
824                 FileUtils.copy(in, out);
825             } catch (IOException e) {
826                 Log.w(TAG, "Failed to cache ringtone: " + e);
827             }
828         }
829     }
830 
isInternalRingtoneUri(Uri uri)831     private static boolean isInternalRingtoneUri(Uri uri) {
832         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
833     }
834 
isExternalRingtoneUri(Uri uri)835     private static boolean isExternalRingtoneUri(Uri uri) {
836         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
837     }
838 
isRingtoneUriInStorage(Uri ringtone, Uri storage)839     private static boolean isRingtoneUriInStorage(Uri ringtone, Uri storage) {
840         Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(ringtone);
841         return uriWithoutUserId == null ? false
842                 : uriWithoutUserId.toString().startsWith(storage.toString());
843     }
844 
845     /**
846      * Adds an audio file to the list of ringtones.
847      *
848      * After making sure the given file is an audio file, copies the file to the ringtone storage,
849      * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until
850      * the scan is completed.
851      *
852      * The directory where the copied file is stored is the directory that matches the ringtone's
853      * type, which is one of: {@link android.is.Environment#DIRECTORY_RINGTONES};
854      * {@link android.is.Environment#DIRECTORY_NOTIFICATIONS};
855      * {@link android.is.Environment#DIRECTORY_ALARMS}.
856      *
857      * This does not allow modifying the type of an existing ringtone file. To change type, use the
858      * APIs in {@link android.content.ContentResolver} to update the corresponding columns.
859      *
860      * @param fileUri Uri of the file to be added as ringtone. Must be a media file.
861      * @param type The type of the ringtone to be added. Must be one of {@link #TYPE_RINGTONE},
862      *            {@link #TYPE_NOTIFICATION}, or {@link #TYPE_ALARM}.
863      *
864      * @return The Uri of the installed ringtone, which may be the Uri of {@param fileUri} if it is
865      *         already in ringtone storage.
866      *
867      * @throws FileNotFoundexception if an appropriate unique filename to save the new ringtone file
868      *         as cannot be found, for example if the unique name is too long.
869      * @throws IllegalArgumentException if {@param fileUri} does not point to an existing audio
870      *         file, or if the {@param type} is not one of the accepted ringtone types.
871      * @throws IOException if the audio file failed to copy to ringtone storage; for example, if
872      *         external storage was not available, or if the file was copied but the media scanner
873      *         did not recognize it as a ringtone.
874      *
875      * @hide
876      */
877     @WorkerThread
addCustomExternalRingtone(@onNull final Uri fileUri, final int type)878     public Uri addCustomExternalRingtone(@NonNull final Uri fileUri, final int type)
879             throws FileNotFoundException, IllegalArgumentException, IOException {
880         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
881             throw new IOException("External storage is not mounted. Unable to install ringtones.");
882         }
883 
884         // Sanity-check: are we actually being asked to install an audio file?
885         final String mimeType = mContext.getContentResolver().getType(fileUri);
886         if(mimeType == null ||
887                 !(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
888             throw new IllegalArgumentException("Ringtone file must have MIME type \"audio/*\"."
889                     + " Given file has MIME type \"" + mimeType + "\"");
890         }
891 
892         // Choose a directory to save the ringtone. Only one type of installation at a time is
893         // allowed. Throws IllegalArgumentException if anything else is given.
894         final String subdirectory = getExternalDirectoryForType(type);
895 
896         // Find a filename. Throws FileNotFoundException if none can be found.
897         final File outFile = Utils.getUniqueExternalFile(mContext, subdirectory,
898                 FileUtils.buildValidFatFilename(Utils.getFileDisplayNameFromUri(mContext, fileUri)),
899                         mimeType);
900 
901         // Copy contents to external ringtone storage. Throws IOException if the copy fails.
902         try (final InputStream input = mContext.getContentResolver().openInputStream(fileUri);
903                 final OutputStream output = new FileOutputStream(outFile)) {
904             FileUtils.copy(input, output);
905         }
906 
907         // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
908         return MediaStore.scanFile(mContext, outFile);
909     }
910 
getExternalDirectoryForType(final int type)911     private static final String getExternalDirectoryForType(final int type) {
912         switch (type) {
913             case TYPE_RINGTONE:
914                 return Environment.DIRECTORY_RINGTONES;
915             case TYPE_NOTIFICATION:
916                 return Environment.DIRECTORY_NOTIFICATIONS;
917             case TYPE_ALARM:
918                 return Environment.DIRECTORY_ALARMS;
919             default:
920                 throw new IllegalArgumentException("Unsupported ringtone type: " + type);
921         }
922     }
923 
924     /**
925      * Try opening the given ringtone locally first, but failover to
926      * {@link IRingtonePlayer} if we can't access it directly. Typically happens
927      * when process doesn't hold
928      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
929      */
openRingtone(Context context, Uri uri)930     private static InputStream openRingtone(Context context, Uri uri) throws IOException {
931         final ContentResolver resolver = context.getContentResolver();
932         try {
933             return resolver.openInputStream(uri);
934         } catch (SecurityException | IOException e) {
935             Log.w(TAG, "Failed to open directly; attempting failover: " + e);
936             final IRingtonePlayer player = context.getSystemService(AudioManager.class)
937                     .getRingtonePlayer();
938             try {
939                 return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri));
940             } catch (Exception e2) {
941                 throw new IOException(e2);
942             }
943         }
944     }
945 
getSettingForType(int type)946     private static String getSettingForType(int type) {
947         if ((type & TYPE_RINGTONE) != 0) {
948             return Settings.System.RINGTONE;
949         } else if ((type & TYPE_NOTIFICATION) != 0) {
950             return Settings.System.NOTIFICATION_SOUND;
951         } else if ((type & TYPE_ALARM) != 0) {
952             return Settings.System.ALARM_ALERT;
953         } else {
954             return null;
955         }
956     }
957 
958     /** {@hide} */
getCacheForType(int type)959     public static Uri getCacheForType(int type) {
960         return getCacheForType(type, UserHandle.getCallingUserId());
961     }
962 
963     /** {@hide} */
getCacheForType(int type, int userId)964     public static Uri getCacheForType(int type, int userId) {
965         if ((type & TYPE_RINGTONE) != 0) {
966             return ContentProvider.maybeAddUserId(Settings.System.RINGTONE_CACHE_URI, userId);
967         } else if ((type & TYPE_NOTIFICATION) != 0) {
968             return ContentProvider.maybeAddUserId(Settings.System.NOTIFICATION_SOUND_CACHE_URI,
969                     userId);
970         } else if ((type & TYPE_ALARM) != 0) {
971             return ContentProvider.maybeAddUserId(Settings.System.ALARM_ALERT_CACHE_URI, userId);
972         }
973         return null;
974     }
975 
976     /**
977      * Returns whether the given {@link Uri} is one of the default ringtones.
978      *
979      * @param ringtoneUri The ringtone {@link Uri} to be checked.
980      * @return Whether the {@link Uri} is a default.
981      */
isDefault(Uri ringtoneUri)982     public static boolean isDefault(Uri ringtoneUri) {
983         return getDefaultType(ringtoneUri) != -1;
984     }
985 
986     /**
987      * Returns the type of a default {@link Uri}.
988      *
989      * @param defaultRingtoneUri The default {@link Uri}. For example,
990      *            {@link System#DEFAULT_RINGTONE_URI},
991      *            {@link System#DEFAULT_NOTIFICATION_URI}, or
992      *            {@link System#DEFAULT_ALARM_ALERT_URI}.
993      * @return The type of the defaultRingtoneUri, or -1.
994      */
getDefaultType(Uri defaultRingtoneUri)995     public static int getDefaultType(Uri defaultRingtoneUri) {
996         defaultRingtoneUri = ContentProvider.getUriWithoutUserId(defaultRingtoneUri);
997         if (defaultRingtoneUri == null) {
998             return -1;
999         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
1000             return TYPE_RINGTONE;
1001         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) {
1002             return TYPE_NOTIFICATION;
1003         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) {
1004             return TYPE_ALARM;
1005         } else {
1006             return -1;
1007         }
1008     }
1009 
1010     /**
1011      * Returns the {@link Uri} for the default ringtone of a particular type.
1012      * Rather than returning the actual ringtone's sound {@link Uri}, this will
1013      * return the symbolic {@link Uri} which will resolved to the actual sound
1014      * when played.
1015      *
1016      * @param type The ringtone type whose default should be returned.
1017      * @return The {@link Uri} of the default ringtone for the given type.
1018      */
getDefaultUri(int type)1019     public static Uri getDefaultUri(int type) {
1020         if ((type & TYPE_RINGTONE) != 0) {
1021             return Settings.System.DEFAULT_RINGTONE_URI;
1022         } else if ((type & TYPE_NOTIFICATION) != 0) {
1023             return Settings.System.DEFAULT_NOTIFICATION_URI;
1024         } else if ((type & TYPE_ALARM) != 0) {
1025             return Settings.System.DEFAULT_ALARM_ALERT_URI;
1026         } else {
1027             return null;
1028         }
1029     }
1030 
1031     /**
1032      * Opens a raw file descriptor to read the data under the given default URI.
1033      *
1034      * @param context the Context to use when resolving the Uri.
1035      * @param uri The desired default URI to open.
1036      * @return a new AssetFileDescriptor pointing to the file. You own this descriptor
1037      * and are responsible for closing it when done. This value may be {@code null}.
1038      * @throws FileNotFoundException if the provided URI could not be opened.
1039      * @see #getDefaultUri
1040      */
openDefaultRingtoneUri( @onNull Context context, @NonNull Uri uri)1041     public static @Nullable AssetFileDescriptor openDefaultRingtoneUri(
1042             @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException {
1043         // Try cached ringtone first since the actual provider may not be
1044         // encryption aware, or it may be stored on CE media storage
1045         final int type = getDefaultType(uri);
1046         final Uri cacheUri = getCacheForType(type, context.getUserId());
1047         final Uri actualUri = getActualDefaultRingtoneUri(context, type);
1048         final ContentResolver resolver = context.getContentResolver();
1049 
1050         AssetFileDescriptor afd = null;
1051         if (cacheUri != null) {
1052             afd = resolver.openAssetFileDescriptor(cacheUri, "r");
1053             if (afd != null) {
1054                 return afd;
1055             }
1056         }
1057         if (actualUri != null) {
1058             afd = resolver.openAssetFileDescriptor(actualUri, "r");
1059         }
1060         return afd;
1061     }
1062 
1063     /**
1064      * Returns if the {@link Ringtone} at the given position in the
1065      * {@link Cursor} contains haptic channels.
1066      *
1067      * @param position The position (in the {@link Cursor}) of the ringtone.
1068      * @return true if the ringtone contains haptic channels.
1069      */
hasHapticChannels(int position)1070     public boolean hasHapticChannels(int position) {
1071         return hasHapticChannels(getRingtoneUri(position));
1072     }
1073 
1074     /**
1075      * Returns if the {@link Ringtone} from a given sound URI contains
1076      * haptic channels or not.
1077      *
1078      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
1079      * @return true if the ringtone contains haptic channels.
1080      */
hasHapticChannels(@onNull Uri ringtoneUri)1081     public static boolean hasHapticChannels(@NonNull Uri ringtoneUri) {
1082         return AudioManager.hasHapticChannels(ringtoneUri);
1083     }
1084 
1085     /**
1086      * Attempts to create a context for the given user.
1087      *
1088      * @return created context, or null if package does not exist
1089      * @hide
1090      */
createPackageContextAsUser(Context context, int userId)1091     private static Context createPackageContextAsUser(Context context, int userId) {
1092         try {
1093             return context.createPackageContextAsUser(context.getPackageName(), 0 /* flags */,
1094                     UserHandle.of(userId));
1095         } catch (NameNotFoundException e) {
1096             Log.e(TAG, "Unable to create package context", e);
1097             return null;
1098         }
1099     }
1100 }
1101