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