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