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