Home | History | Annotate | Download | only in media
      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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    364     public void setStopPreviousRingtone(boolean stopPreviousRingtone) {
    365         mStopPreviousRingtone = stopPreviousRingtone;
    366     }
    367 
    368     /**
    369      * @see #setStopPreviousRingtone(boolean)
    370      */
    371     public boolean getStopPreviousRingtone() {
    372         return mStopPreviousRingtone;
    373     }
    374 
    375     /**
    376      * Stops playing the last {@link Ringtone} retrieved from this.
    377      */
    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
    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
    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      */
    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 
    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      */
    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      */
    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      */
    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 
    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      */
    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      */
    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 
    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 
    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 
    612     private Cursor getMediaRingtones() {
    613         return getMediaRingtones(mContext);
    614     }
    615 
    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 
    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      */
    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 
    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 
    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      */
    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      */
    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      */
    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      */
    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)
    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      */
    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      */
    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 
    862     private static boolean isInternalRingtoneUri(Uri uri) {
    863         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.INTERNAL_CONTENT_URI);
    864     }
    865 
    866     private static boolean isExternalRingtoneUri(Uri uri) {
    867         return isRingtoneUriInStorage(uri, MediaStore.Audio.Media.EXTERNAL_CONTENT_URI);
    868     }
    869 
    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 */
    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
    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 
    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      */
    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      */
   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 
   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} */
   1052     public static Uri getCacheForType(int type) {
   1053         return getCacheForType(type, UserHandle.getCallingUserId());
   1054     }
   1055 
   1056     /** {@hide} */
   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      */
   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      */
   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      */
   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 
   1136         public NewRingtoneScanner(File file) {
   1137             mFile = file;
   1138             mMediaScannerConnection = new MediaScannerConnection(mContext, this);
   1139             mMediaScannerConnection.connect();
   1140         }
   1141 
   1142         @Override
   1143         public void close() {
   1144             mMediaScannerConnection.disconnect();
   1145         }
   1146 
   1147         @Override
   1148         public void onMediaScannerConnected() {
   1149             mMediaScannerConnection.scanFile(mFile.getAbsolutePath(), null);
   1150         }
   1151 
   1152         @Override
   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 
   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      */
   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