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