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