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.WorkerThread;
26 import android.app.Activity;
27 import android.compat.annotation.UnsupportedAppUsage;
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         try {
513             if (ringtoneUri == null) return -1;
514             final long ringtoneId = ContentUris.parseId(ringtoneUri);
515 
516             final Cursor cursor = getCursor();
517             cursor.moveToPosition(-1);
518             while (cursor.moveToNext()) {
519                 if (ringtoneId == cursor.getLong(ID_COLUMN_INDEX)) {
520                     return cursor.getPosition();
521                 }
522             }
523         } catch (NumberFormatException e) {
524             Log.e(TAG, "NumberFormatException while getting ringtone position, returning -1", e);
525         }
526         return -1;
527     }
528 
529     /**
530      * Returns a valid ringtone URI. No guarantees on which it returns. If it
531      * cannot find one, returns null. If it can only find one on external storage and the caller
532      * doesn't have the {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission,
533      * returns null.
534      *
535      * @param context The context to use for querying.
536      * @return A ringtone URI, or null if one cannot be found.
537      */
getValidRingtoneUri(Context context)538     public static Uri getValidRingtoneUri(Context context) {
539         final RingtoneManager rm = new RingtoneManager(context);
540 
541         Uri uri = getValidRingtoneUriFromCursorAndClose(context, rm.getInternalRingtones());
542 
543         if (uri == null) {
544             uri = getValidRingtoneUriFromCursorAndClose(context, rm.getMediaRingtones());
545         }
546 
547         return uri;
548     }
549 
getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor)550     private static Uri getValidRingtoneUriFromCursorAndClose(Context context, Cursor cursor) {
551         if (cursor != null) {
552             Uri uri = null;
553 
554             if (cursor.moveToFirst()) {
555                 uri = getUriFromCursor(context, cursor);
556             }
557             cursor.close();
558 
559             return uri;
560         } else {
561             return null;
562         }
563     }
564 
565     @UnsupportedAppUsage
getInternalRingtones()566     private Cursor getInternalRingtones() {
567         final Cursor res = query(
568                 MediaStore.Audio.Media.INTERNAL_CONTENT_URI, INTERNAL_COLUMNS,
569                 constructBooleanTrueWhereClause(mFilterColumns),
570                 null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);
571         return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
572     }
573 
getMediaRingtones()574     private Cursor getMediaRingtones() {
575         final Cursor res = getMediaRingtones(mContext);
576         return new ExternalRingtonesCursorWrapper(res, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
577     }
578 
579     @UnsupportedAppUsage
getMediaRingtones(Context context)580     private Cursor getMediaRingtones(Context context) {
581         // MediaStore now returns ringtones on other storage devices, even when
582         // we don't have storage or audio permissions
583         return query(
584                 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, MEDIA_COLUMNS,
585                 constructBooleanTrueWhereClause(mFilterColumns), null,
586                 MediaStore.Audio.Media.DEFAULT_SORT_ORDER, context);
587     }
588 
setFilterColumnsList(int type)589     private void setFilterColumnsList(int type) {
590         List<String> columns = mFilterColumns;
591         columns.clear();
592 
593         if ((type & TYPE_RINGTONE) != 0) {
594             columns.add(MediaStore.Audio.AudioColumns.IS_RINGTONE);
595         }
596 
597         if ((type & TYPE_NOTIFICATION) != 0) {
598             columns.add(MediaStore.Audio.AudioColumns.IS_NOTIFICATION);
599         }
600 
601         if ((type & TYPE_ALARM) != 0) {
602             columns.add(MediaStore.Audio.AudioColumns.IS_ALARM);
603         }
604     }
605 
606     /**
607      * Constructs a where clause that consists of at least one column being 1
608      * (true). This is used to find all matching sounds for the given sound
609      * types (ringtone, notifications, etc.)
610      *
611      * @param columns The columns that must be true.
612      * @return The where clause.
613      */
constructBooleanTrueWhereClause(List<String> columns)614     private static String constructBooleanTrueWhereClause(List<String> columns) {
615 
616         if (columns == null) return null;
617 
618         StringBuilder sb = new StringBuilder();
619         sb.append("(");
620 
621         for (int i = columns.size() - 1; i >= 0; i--) {
622             sb.append(columns.get(i)).append("=1 or ");
623         }
624 
625         if (columns.size() > 0) {
626             // Remove last ' or '
627             sb.setLength(sb.length() - 4);
628         }
629 
630         sb.append(")");
631 
632         return sb.toString();
633     }
634 
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)635     private Cursor query(Uri uri,
636             String[] projection,
637             String selection,
638             String[] selectionArgs,
639             String sortOrder) {
640         return query(uri, projection, selection, selectionArgs, sortOrder, mContext);
641     }
642 
query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, Context context)643     private Cursor query(Uri uri,
644             String[] projection,
645             String selection,
646             String[] selectionArgs,
647             String sortOrder,
648             Context context) {
649         if (mActivity != null) {
650             return mActivity.managedQuery(uri, projection, selection, selectionArgs, sortOrder);
651         } else {
652             return context.getContentResolver().query(uri, projection, selection, selectionArgs,
653                     sortOrder);
654         }
655     }
656 
657     /**
658      * Returns a {@link Ringtone} for a given sound URI.
659      * <p>
660      * If the given URI cannot be opened for any reason, this method will
661      * attempt to fallback on another sound. If it cannot find any, it will
662      * return null.
663      *
664      * @param context A context used to query.
665      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
666      * @return A {@link Ringtone} for the given URI, or null.
667      */
getRingtone(final Context context, Uri ringtoneUri)668     public static Ringtone getRingtone(final Context context, Uri ringtoneUri) {
669         // Don't set the stream type
670         return getRingtone(context, ringtoneUri, -1);
671     }
672 
673     /**
674      * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI.
675      * <p>
676      * If the given URI cannot be opened for any reason, this method will
677      * attempt to fallback on another sound. If it cannot find any, it will
678      * return null.
679      *
680      * @param context A context used to query.
681      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
682      * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
683      * @return A {@link Ringtone} for the given URI, or null.
684      *
685      * @hide
686      */
getRingtone( final Context context, Uri ringtoneUri, @Nullable VolumeShaper.Configuration volumeShaperConfig)687     public static Ringtone getRingtone(
688             final Context context, Uri ringtoneUri,
689             @Nullable VolumeShaper.Configuration volumeShaperConfig) {
690         // Don't set the stream type
691         return getRingtone(context, ringtoneUri, -1 /* streamType */, volumeShaperConfig);
692     }
693 
694     //FIXME bypass the notion of stream types within the class
695     /**
696      * Returns a {@link Ringtone} for a given sound URI on the given stream
697      * type. Normally, if you change the stream type on the returned
698      * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
699      * an optimized route to avoid that.
700      *
701      * @param streamType The stream type for the ringtone, or -1 if it should
702      *            not be set (and the default used instead).
703      * @see #getRingtone(Context, Uri)
704      */
705     @UnsupportedAppUsage
getRingtone(final Context context, Uri ringtoneUri, int streamType)706     private static Ringtone getRingtone(final Context context, Uri ringtoneUri, int streamType) {
707         return getRingtone(context, ringtoneUri, streamType, null /* volumeShaperConfig */);
708     }
709 
710     //FIXME bypass the notion of stream types within the class
711     /**
712      * Returns a {@link Ringtone} with {@link VolumeShaper} if required for a given sound URI on
713      * the given stream type. Normally, if you change the stream type on the returned
714      * {@link Ringtone}, it will re-create the {@link MediaPlayer}. This is just
715      * an optimized route to avoid that.
716      *
717      * @param streamType The stream type for the ringtone, or -1 if it should
718      *            not be set (and the default used instead).
719      * @param volumeShaperConfig config for volume shaper of the ringtone if applied.
720      * @see #getRingtone(Context, Uri)
721      */
722     @UnsupportedAppUsage
getRingtone( final Context context, Uri ringtoneUri, int streamType, @Nullable VolumeShaper.Configuration volumeShaperConfig)723     private static Ringtone getRingtone(
724             final Context context, Uri ringtoneUri, int streamType,
725             @Nullable VolumeShaper.Configuration volumeShaperConfig) {
726         try {
727             final Ringtone r = new Ringtone(context, true);
728             if (streamType >= 0) {
729                 //FIXME deprecated call
730                 r.setStreamType(streamType);
731             }
732             r.setUri(ringtoneUri, volumeShaperConfig);
733             return r;
734         } catch (Exception ex) {
735             Log.e(TAG, "Failed to open ringtone " + ringtoneUri + ": " + ex);
736         }
737 
738         return null;
739     }
740 
741     /**
742      * Disables Settings.System.SYNC_PARENT_SOUNDS.
743      *
744      * @hide
745      */
disableSyncFromParent(Context userContext)746     public static void disableSyncFromParent(Context userContext) {
747         IBinder b = ServiceManager.getService(Context.AUDIO_SERVICE);
748         IAudioService audioService = IAudioService.Stub.asInterface(b);
749         try {
750             audioService.disableRingtoneSync(userContext.getUserId());
751         } catch (RemoteException e) {
752             Log.e(TAG, "Unable to disable ringtone sync.");
753         }
754     }
755 
756     /**
757      * Enables Settings.System.SYNC_PARENT_SOUNDS for the content's user
758      *
759      * @hide
760      */
761     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
enableSyncFromParent(Context userContext)762     public static void enableSyncFromParent(Context userContext) {
763         Settings.Secure.putIntForUser(userContext.getContentResolver(),
764                 Settings.Secure.SYNC_PARENT_SOUNDS, 1 /* true */, userContext.getUserId());
765     }
766 
767     /**
768      * Gets the current default sound's {@link Uri}. This will give the actual
769      * sound {@link Uri}, instead of using this, most clients can use
770      * {@link System#DEFAULT_RINGTONE_URI}.
771      *
772      * @param context A context used for querying.
773      * @param type The type whose default sound should be returned. One of
774      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
775      *            {@link #TYPE_ALARM}.
776      * @return A {@link Uri} pointing to the default sound for the sound type.
777      * @see #setActualDefaultRingtoneUri(Context, int, Uri)
778      */
getActualDefaultRingtoneUri(Context context, int type)779     public static Uri getActualDefaultRingtoneUri(Context context, int type) {
780         String setting = getSettingForType(type);
781         if (setting == null) return null;
782         final String uriString = Settings.System.getStringForUser(context.getContentResolver(),
783                 setting, context.getUserId());
784         Uri ringtoneUri = uriString != null ? Uri.parse(uriString) : null;
785 
786         // If this doesn't verify, the user id must be kept in the uri to ensure it resolves in the
787         // correct user storage
788         if (ringtoneUri != null
789                 && ContentProvider.getUserIdFromUri(ringtoneUri) == context.getUserId()) {
790             ringtoneUri = ContentProvider.getUriWithoutUserId(ringtoneUri);
791         }
792 
793         return ringtoneUri;
794     }
795 
796     /**
797      * Sets the {@link Uri} of the default sound for a given sound type.
798      *
799      * @param context A context used for querying.
800      * @param type The type whose default sound should be set. One of
801      *            {@link #TYPE_RINGTONE}, {@link #TYPE_NOTIFICATION}, or
802      *            {@link #TYPE_ALARM}.
803      * @param ringtoneUri A {@link Uri} pointing to the default sound to set.
804      * @see #getActualDefaultRingtoneUri(Context, int)
805      */
setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri)806     public static void setActualDefaultRingtoneUri(Context context, int type, Uri ringtoneUri) {
807         String setting = getSettingForType(type);
808         if (setting == null) return;
809 
810         final ContentResolver resolver = context.getContentResolver();
811         if (Settings.Secure.getIntForUser(resolver, Settings.Secure.SYNC_PARENT_SOUNDS, 0,
812                     context.getUserId()) == 1) {
813             // Parent sound override is enabled. Disable it using the audio service.
814             disableSyncFromParent(context);
815         }
816         if(!isInternalRingtoneUri(ringtoneUri)) {
817             ringtoneUri = ContentProvider.maybeAddUserId(ringtoneUri, context.getUserId());
818         }
819         Settings.System.putStringForUser(resolver, setting,
820                 ringtoneUri != null ? ringtoneUri.toString() : null, context.getUserId());
821 
822         // Stream selected ringtone into cache so it's available for playback
823         // when CE storage is still locked
824         if (ringtoneUri != null) {
825             final Uri cacheUri = getCacheForType(type, context.getUserId());
826             try (InputStream in = openRingtone(context, ringtoneUri);
827                     OutputStream out = resolver.openOutputStream(cacheUri)) {
828                 FileUtils.copy(in, out);
829             } catch (IOException e) {
830                 Log.w(TAG, "Failed to cache ringtone: " + e);
831             }
832         }
833     }
834 
isInternalRingtoneUri(Uri uri)835     private static boolean isInternalRingtoneUri(Uri uri) {
836         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
837     }
838 
isExternalRingtoneUri(Uri uri)839     private static boolean isExternalRingtoneUri(Uri uri) {
840         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
841     }
842 
isRingtoneUriInStorage(Uri ringtone, Uri storage)843     private static boolean isRingtoneUriInStorage(Uri ringtone, Uri storage) {
844         Uri uriWithoutUserId = ContentProvider.getUriWithoutUserId(ringtone);
845         return uriWithoutUserId == null ? false
846                 : uriWithoutUserId.toString().startsWith(storage.toString());
847     }
848 
849     /**
850      * Adds an audio file to the list of ringtones.
851      *
852      * After making sure the given file is an audio file, copies the file to the ringtone storage,
853      * and asks the {@link android.media.MediaScanner} to scan that file. This call will block until
854      * the scan is completed.
855      *
856      * The directory where the copied file is stored is the directory that matches the ringtone's
857      * type, which is one of: {@link android.is.Environment#DIRECTORY_RINGTONES};
858      * {@link android.is.Environment#DIRECTORY_NOTIFICATIONS};
859      * {@link android.is.Environment#DIRECTORY_ALARMS}.
860      *
861      * This does not allow modifying the type of an existing ringtone file. To change type, use the
862      * APIs in {@link android.content.ContentResolver} to update the corresponding columns.
863      *
864      * @param fileUri Uri of the file to be added as ringtone. Must be a media file.
865      * @param type The type of the ringtone to be added. Must be one of {@link #TYPE_RINGTONE},
866      *            {@link #TYPE_NOTIFICATION}, or {@link #TYPE_ALARM}.
867      *
868      * @return The Uri of the installed ringtone, which may be the Uri of {@param fileUri} if it is
869      *         already in ringtone storage.
870      *
871      * @throws FileNotFoundexception if an appropriate unique filename to save the new ringtone file
872      *         as cannot be found, for example if the unique name is too long.
873      * @throws IllegalArgumentException if {@param fileUri} does not point to an existing audio
874      *         file, or if the {@param type} is not one of the accepted ringtone types.
875      * @throws IOException if the audio file failed to copy to ringtone storage; for example, if
876      *         external storage was not available, or if the file was copied but the media scanner
877      *         did not recognize it as a ringtone.
878      *
879      * @hide
880      */
881     @WorkerThread
addCustomExternalRingtone(@onNull final Uri fileUri, final int type)882     public Uri addCustomExternalRingtone(@NonNull final Uri fileUri, final int type)
883             throws FileNotFoundException, IllegalArgumentException, IOException {
884         if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
885             throw new IOException("External storage is not mounted. Unable to install ringtones.");
886         }
887 
888         // Sanity-check: are we actually being asked to install an audio file?
889         final String mimeType = mContext.getContentResolver().getType(fileUri);
890         if(mimeType == null ||
891                 !(mimeType.startsWith("audio/") || mimeType.equals("application/ogg"))) {
892             throw new IllegalArgumentException("Ringtone file must have MIME type \"audio/*\"."
893                     + " Given file has MIME type \"" + mimeType + "\"");
894         }
895 
896         // Choose a directory to save the ringtone. Only one type of installation at a time is
897         // allowed. Throws IllegalArgumentException if anything else is given.
898         final String subdirectory = getExternalDirectoryForType(type);
899 
900         // Find a filename. Throws FileNotFoundException if none can be found.
901         final File outFile = Utils.getUniqueExternalFile(mContext, subdirectory,
902                 FileUtils.buildValidFatFilename(Utils.getFileDisplayNameFromUri(mContext, fileUri)),
903                         mimeType);
904 
905         // Copy contents to external ringtone storage. Throws IOException if the copy fails.
906         try (final InputStream input = mContext.getContentResolver().openInputStream(fileUri);
907                 final OutputStream output = new FileOutputStream(outFile)) {
908             FileUtils.copy(input, output);
909         }
910 
911         // Tell MediaScanner about the new file. Wait for it to assign a {@link Uri}.
912         return MediaStore.scanFile(mContext, outFile);
913     }
914 
getExternalDirectoryForType(final int type)915     private static final String getExternalDirectoryForType(final int type) {
916         switch (type) {
917             case TYPE_RINGTONE:
918                 return Environment.DIRECTORY_RINGTONES;
919             case TYPE_NOTIFICATION:
920                 return Environment.DIRECTORY_NOTIFICATIONS;
921             case TYPE_ALARM:
922                 return Environment.DIRECTORY_ALARMS;
923             default:
924                 throw new IllegalArgumentException("Unsupported ringtone type: " + type);
925         }
926     }
927 
928     /**
929      * Try opening the given ringtone locally first, but failover to
930      * {@link IRingtonePlayer} if we can't access it directly. Typically happens
931      * when process doesn't hold
932      * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE}.
933      */
openRingtone(Context context, Uri uri)934     private static InputStream openRingtone(Context context, Uri uri) throws IOException {
935         final ContentResolver resolver = context.getContentResolver();
936         try {
937             return resolver.openInputStream(uri);
938         } catch (SecurityException | IOException e) {
939             Log.w(TAG, "Failed to open directly; attempting failover: " + e);
940             final IRingtonePlayer player = context.getSystemService(AudioManager.class)
941                     .getRingtonePlayer();
942             try {
943                 return new ParcelFileDescriptor.AutoCloseInputStream(player.openRingtone(uri));
944             } catch (Exception e2) {
945                 throw new IOException(e2);
946             }
947         }
948     }
949 
getSettingForType(int type)950     private static String getSettingForType(int type) {
951         if ((type & TYPE_RINGTONE) != 0) {
952             return Settings.System.RINGTONE;
953         } else if ((type & TYPE_NOTIFICATION) != 0) {
954             return Settings.System.NOTIFICATION_SOUND;
955         } else if ((type & TYPE_ALARM) != 0) {
956             return Settings.System.ALARM_ALERT;
957         } else {
958             return null;
959         }
960     }
961 
962     /** {@hide} */
getCacheForType(int type)963     public static Uri getCacheForType(int type) {
964         return getCacheForType(type, UserHandle.getCallingUserId());
965     }
966 
967     /** {@hide} */
getCacheForType(int type, int userId)968     public static Uri getCacheForType(int type, int userId) {
969         if ((type & TYPE_RINGTONE) != 0) {
970             return ContentProvider.maybeAddUserId(Settings.System.RINGTONE_CACHE_URI, userId);
971         } else if ((type & TYPE_NOTIFICATION) != 0) {
972             return ContentProvider.maybeAddUserId(Settings.System.NOTIFICATION_SOUND_CACHE_URI,
973                     userId);
974         } else if ((type & TYPE_ALARM) != 0) {
975             return ContentProvider.maybeAddUserId(Settings.System.ALARM_ALERT_CACHE_URI, userId);
976         }
977         return null;
978     }
979 
980     /**
981      * Returns whether the given {@link Uri} is one of the default ringtones.
982      *
983      * @param ringtoneUri The ringtone {@link Uri} to be checked.
984      * @return Whether the {@link Uri} is a default.
985      */
isDefault(Uri ringtoneUri)986     public static boolean isDefault(Uri ringtoneUri) {
987         return getDefaultType(ringtoneUri) != -1;
988     }
989 
990     /**
991      * Returns the type of a default {@link Uri}.
992      *
993      * @param defaultRingtoneUri The default {@link Uri}. For example,
994      *            {@link System#DEFAULT_RINGTONE_URI},
995      *            {@link System#DEFAULT_NOTIFICATION_URI}, or
996      *            {@link System#DEFAULT_ALARM_ALERT_URI}.
997      * @return The type of the defaultRingtoneUri, or -1.
998      */
getDefaultType(Uri defaultRingtoneUri)999     public static int getDefaultType(Uri defaultRingtoneUri) {
1000         defaultRingtoneUri = ContentProvider.getUriWithoutUserId(defaultRingtoneUri);
1001         if (defaultRingtoneUri == null) {
1002             return -1;
1003         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_RINGTONE_URI)) {
1004             return TYPE_RINGTONE;
1005         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_NOTIFICATION_URI)) {
1006             return TYPE_NOTIFICATION;
1007         } else if (defaultRingtoneUri.equals(Settings.System.DEFAULT_ALARM_ALERT_URI)) {
1008             return TYPE_ALARM;
1009         } else {
1010             return -1;
1011         }
1012     }
1013 
1014     /**
1015      * Returns the {@link Uri} for the default ringtone of a particular type.
1016      * Rather than returning the actual ringtone's sound {@link Uri}, this will
1017      * return the symbolic {@link Uri} which will resolved to the actual sound
1018      * when played.
1019      *
1020      * @param type The ringtone type whose default should be returned.
1021      * @return The {@link Uri} of the default ringtone for the given type.
1022      */
getDefaultUri(int type)1023     public static Uri getDefaultUri(int type) {
1024         if ((type & TYPE_RINGTONE) != 0) {
1025             return Settings.System.DEFAULT_RINGTONE_URI;
1026         } else if ((type & TYPE_NOTIFICATION) != 0) {
1027             return Settings.System.DEFAULT_NOTIFICATION_URI;
1028         } else if ((type & TYPE_ALARM) != 0) {
1029             return Settings.System.DEFAULT_ALARM_ALERT_URI;
1030         } else {
1031             return null;
1032         }
1033     }
1034 
1035     /**
1036      * Opens a raw file descriptor to read the data under the given default URI.
1037      *
1038      * @param context the Context to use when resolving the Uri.
1039      * @param uri The desired default URI to open.
1040      * @return a new AssetFileDescriptor pointing to the file. You own this descriptor
1041      * and are responsible for closing it when done. This value may be {@code null}.
1042      * @throws FileNotFoundException if the provided URI could not be opened.
1043      * @see #getDefaultUri
1044      */
openDefaultRingtoneUri( @onNull Context context, @NonNull Uri uri)1045     public static @Nullable AssetFileDescriptor openDefaultRingtoneUri(
1046             @NonNull Context context, @NonNull Uri uri) throws FileNotFoundException {
1047         // Try cached ringtone first since the actual provider may not be
1048         // encryption aware, or it may be stored on CE media storage
1049         final int type = getDefaultType(uri);
1050         final Uri cacheUri = getCacheForType(type, context.getUserId());
1051         final Uri actualUri = getActualDefaultRingtoneUri(context, type);
1052         final ContentResolver resolver = context.getContentResolver();
1053 
1054         AssetFileDescriptor afd = null;
1055         if (cacheUri != null) {
1056             afd = resolver.openAssetFileDescriptor(cacheUri, "r");
1057             if (afd != null) {
1058                 return afd;
1059             }
1060         }
1061         if (actualUri != null) {
1062             afd = resolver.openAssetFileDescriptor(actualUri, "r");
1063         }
1064         return afd;
1065     }
1066 
1067     /**
1068      * Returns if the {@link Ringtone} at the given position in the
1069      * {@link Cursor} contains haptic channels.
1070      *
1071      * @param position The position (in the {@link Cursor}) of the ringtone.
1072      * @return true if the ringtone contains haptic channels.
1073      */
hasHapticChannels(int position)1074     public boolean hasHapticChannels(int position) {
1075         return hasHapticChannels(getRingtoneUri(position));
1076     }
1077 
1078     /**
1079      * Returns if the {@link Ringtone} from a given sound URI contains
1080      * haptic channels or not.
1081      *
1082      * @param ringtoneUri The {@link Uri} of a sound or ringtone.
1083      * @return true if the ringtone contains haptic channels.
1084      */
hasHapticChannels(@onNull Uri ringtoneUri)1085     public static boolean hasHapticChannels(@NonNull Uri ringtoneUri) {
1086         return AudioManager.hasHapticChannels(ringtoneUri);
1087     }
1088 
1089     /**
1090      * Attempts to create a context for the given user.
1091      *
1092      * @return created context, or null if package does not exist
1093      * @hide
1094      */
createPackageContextAsUser(Context context, int userId)1095     private static Context createPackageContextAsUser(Context context, int userId) {
1096         try {
1097             return context.createPackageContextAsUser(context.getPackageName(), 0 /* flags */,
1098                     UserHandle.of(userId));
1099         } catch (NameNotFoundException e) {
1100             Log.e(TAG, "Unable to create package context", e);
1101             return null;
1102         }
1103     }
1104 }
1105