Home | History | Annotate | Download | only in provider
      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.provider;
     18 
     19 import android.annotation.SdkConstant;
     20 import android.annotation.SdkConstant.SdkConstantType;
     21 import android.content.ContentProviderClient;
     22 import android.content.ContentResolver;
     23 import android.content.ContentUris;
     24 import android.content.ContentValues;
     25 import android.content.Context;
     26 import android.content.Intent;
     27 import android.content.UriPermission;
     28 import android.database.Cursor;
     29 import android.database.DatabaseUtils;
     30 import android.database.sqlite.SQLiteException;
     31 import android.graphics.Bitmap;
     32 import android.graphics.BitmapFactory;
     33 import android.graphics.Matrix;
     34 import android.media.MiniThumbFile;
     35 import android.media.ThumbnailUtils;
     36 import android.net.Uri;
     37 import android.os.Bundle;
     38 import android.os.Environment;
     39 import android.os.ParcelFileDescriptor;
     40 import android.os.RemoteException;
     41 import android.service.media.CameraPrewarmService;
     42 import android.util.Log;
     43 
     44 import libcore.io.IoUtils;
     45 
     46 import java.io.File;
     47 import java.io.FileInputStream;
     48 import java.io.FileNotFoundException;
     49 import java.io.IOException;
     50 import java.io.InputStream;
     51 import java.io.OutputStream;
     52 import java.util.Arrays;
     53 import java.util.List;
     54 
     55 /**
     56  * The Media provider contains meta data for all available media on both internal
     57  * and external storage devices.
     58  */
     59 public final class MediaStore {
     60     private final static String TAG = "MediaStore";
     61 
     62     public static final String AUTHORITY = "media";
     63 
     64     private static final String CONTENT_AUTHORITY_SLASH = "content://" + AUTHORITY + "/";
     65 
     66     /**
     67      * The method name used by the media scanner and mtp to tell the media provider to
     68      * rescan and reclassify that have become unhidden because of renaming folders or
     69      * removing nomedia files
     70      * @hide
     71      */
     72     public static final String UNHIDE_CALL = "unhide";
     73 
     74     /**
     75      * The method name used by the media scanner service to reload all localized ringtone titles due
     76      * to a locale change.
     77      * @hide
     78      */
     79     public static final String RETRANSLATE_CALL = "update_titles";
     80 
     81     /**
     82      * This is for internal use by the media scanner only.
     83      * Name of the (optional) Uri parameter that determines whether to skip deleting
     84      * the file pointed to by the _data column, when deleting the database entry.
     85      * The only appropriate value for this parameter is "false", in which case the
     86      * delete will be skipped. Note especially that setting this to true, or omitting
     87      * the parameter altogether, will perform the default action, which is different
     88      * for different types of media.
     89      * @hide
     90      */
     91     public static final String PARAM_DELETE_DATA = "deletedata";
     92 
     93     /**
     94      * Activity Action: Launch a music player.
     95      * The activity should be able to play, browse, or manipulate music files stored on the device.
     96      *
     97      * @deprecated Use {@link android.content.Intent#CATEGORY_APP_MUSIC} instead.
     98      */
     99     @Deprecated
    100     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    101     public static final String INTENT_ACTION_MUSIC_PLAYER = "android.intent.action.MUSIC_PLAYER";
    102 
    103     /**
    104      * Activity Action: Perform a search for media.
    105      * Contains at least the {@link android.app.SearchManager#QUERY} extra.
    106      * May also contain any combination of the following extras:
    107      * EXTRA_MEDIA_ARTIST, EXTRA_MEDIA_ALBUM, EXTRA_MEDIA_TITLE, EXTRA_MEDIA_FOCUS
    108      *
    109      * @see android.provider.MediaStore#EXTRA_MEDIA_ARTIST
    110      * @see android.provider.MediaStore#EXTRA_MEDIA_ALBUM
    111      * @see android.provider.MediaStore#EXTRA_MEDIA_TITLE
    112      * @see android.provider.MediaStore#EXTRA_MEDIA_FOCUS
    113      */
    114     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    115     public static final String INTENT_ACTION_MEDIA_SEARCH = "android.intent.action.MEDIA_SEARCH";
    116 
    117     /**
    118      * An intent to perform a search for music media and automatically play content from the
    119      * result when possible. This can be fired, for example, by the result of a voice recognition
    120      * command to listen to music.
    121      * <p>This intent always includes the {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS}
    122      * and {@link android.app.SearchManager#QUERY} extras. The
    123      * {@link android.provider.MediaStore#EXTRA_MEDIA_FOCUS} extra determines the search mode, and
    124      * the value of the {@link android.app.SearchManager#QUERY} extra depends on the search mode.
    125      * For more information about the search modes for this intent, see
    126      * <a href="{@docRoot}guide/components/intents-common.html#PlaySearch">Play music based
    127      * on a search query</a> in <a href="{@docRoot}guide/components/intents-common.html">Common
    128      * Intents</a>.</p>
    129      *
    130      * <p>This intent makes the most sense for apps that can support large-scale search of music,
    131      * such as services connected to an online database of music which can be streamed and played
    132      * on the device.</p>
    133      */
    134     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    135     public static final String INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH =
    136             "android.media.action.MEDIA_PLAY_FROM_SEARCH";
    137 
    138     /**
    139      * An intent to perform a search for readable media and automatically play content from the
    140      * result when possible. This can be fired, for example, by the result of a voice recognition
    141      * command to read a book or magazine.
    142      * <p>
    143      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
    144      * contain any type of unstructured text search, like the name of a book or magazine, an author
    145      * a genre, a publisher, or any combination of these.
    146      * <p>
    147      * Because this intent includes an open-ended unstructured search string, it makes the most
    148      * sense for apps that can support large-scale search of text media, such as services connected
    149      * to an online database of books and/or magazines which can be read on the device.
    150      */
    151     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    152     public static final String INTENT_ACTION_TEXT_OPEN_FROM_SEARCH =
    153             "android.media.action.TEXT_OPEN_FROM_SEARCH";
    154 
    155     /**
    156      * An intent to perform a search for video media and automatically play content from the
    157      * result when possible. This can be fired, for example, by the result of a voice recognition
    158      * command to play movies.
    159      * <p>
    160      * Contains the {@link android.app.SearchManager#QUERY} extra, which is a string that can
    161      * contain any type of unstructured video search, like the name of a movie, one or more actors,
    162      * a genre, or any combination of these.
    163      * <p>
    164      * Because this intent includes an open-ended unstructured search string, it makes the most
    165      * sense for apps that can support large-scale search of video, such as services connected to an
    166      * online database of videos which can be streamed and played on the device.
    167      */
    168     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    169     public static final String INTENT_ACTION_VIDEO_PLAY_FROM_SEARCH =
    170             "android.media.action.VIDEO_PLAY_FROM_SEARCH";
    171 
    172     /**
    173      * The name of the Intent-extra used to define the artist
    174      */
    175     public static final String EXTRA_MEDIA_ARTIST = "android.intent.extra.artist";
    176     /**
    177      * The name of the Intent-extra used to define the album
    178      */
    179     public static final String EXTRA_MEDIA_ALBUM = "android.intent.extra.album";
    180     /**
    181      * The name of the Intent-extra used to define the song title
    182      */
    183     public static final String EXTRA_MEDIA_TITLE = "android.intent.extra.title";
    184     /**
    185      * The name of the Intent-extra used to define the genre.
    186      */
    187     public static final String EXTRA_MEDIA_GENRE = "android.intent.extra.genre";
    188     /**
    189      * The name of the Intent-extra used to define the playlist.
    190      */
    191     public static final String EXTRA_MEDIA_PLAYLIST = "android.intent.extra.playlist";
    192     /**
    193      * The name of the Intent-extra used to define the radio channel.
    194      */
    195     public static final String EXTRA_MEDIA_RADIO_CHANNEL = "android.intent.extra.radio_channel";
    196     /**
    197      * The name of the Intent-extra used to define the search focus. The search focus
    198      * indicates whether the search should be for things related to the artist, album
    199      * or song that is identified by the other extras.
    200      */
    201     public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus";
    202 
    203     /**
    204      * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView.
    205      * This is an int property that overrides the activity's requestedOrientation.
    206      * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
    207      */
    208     public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation";
    209 
    210     /**
    211      * The name of an Intent-extra used to control the UI of a ViewImage.
    212      * This is a boolean property that overrides the activity's default fullscreen state.
    213      */
    214     public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen";
    215 
    216     /**
    217      * The name of an Intent-extra used to control the UI of a ViewImage.
    218      * This is a boolean property that specifies whether or not to show action icons.
    219      */
    220     public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons";
    221 
    222     /**
    223      * The name of the Intent-extra used to control the onCompletion behavior of a MovieView.
    224      * This is a boolean property that specifies whether or not to finish the MovieView activity
    225      * when the movie completes playing. The default value is true, which means to automatically
    226      * exit the movie player activity when the movie completes playing.
    227      */
    228     public static final String EXTRA_FINISH_ON_COMPLETION = "android.intent.extra.finishOnCompletion";
    229 
    230     /**
    231      * The name of the Intent action used to launch a camera in still image mode.
    232      */
    233     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    234     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA = "android.media.action.STILL_IMAGE_CAMERA";
    235 
    236     /**
    237      * Name under which an activity handling {@link #INTENT_ACTION_STILL_IMAGE_CAMERA} or
    238      * {@link #INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE} publishes the service name for its prewarm
    239      * service.
    240      * <p>
    241      * This meta-data should reference the fully qualified class name of the prewarm service
    242      * extending {@link CameraPrewarmService}.
    243      * <p>
    244      * The prewarm service will get bound and receive a prewarm signal
    245      * {@link CameraPrewarmService#onPrewarm()} when a camera launch intent fire might be imminent.
    246      * An application implementing a prewarm service should do the absolute minimum amount of work
    247      * to initialize the camera in order to reduce startup time in likely case that shortly after a
    248      * camera launch intent would be sent.
    249      */
    250     public static final String META_DATA_STILL_IMAGE_CAMERA_PREWARM_SERVICE =
    251             "android.media.still_image_camera_preview_service";
    252 
    253     /**
    254      * The name of the Intent action used to launch a camera in still image mode
    255      * for use when the device is secured (e.g. with a pin, password, pattern,
    256      * or face unlock). Applications responding to this intent must not expose
    257      * any personal content like existing photos or videos on the device. The
    258      * applications should be careful not to share any photo or video with other
    259      * applications or internet. The activity should use {@link
    260      * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display
    261      * on top of the lock screen while secured. There is no activity stack when
    262      * this flag is used, so launching more than one activity is strongly
    263      * discouraged.
    264      */
    265     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    266     public static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
    267             "android.media.action.STILL_IMAGE_CAMERA_SECURE";
    268 
    269     /**
    270      * The name of the Intent action used to launch a camera in video mode.
    271      */
    272     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    273     public static final String INTENT_ACTION_VIDEO_CAMERA = "android.media.action.VIDEO_CAMERA";
    274 
    275     /**
    276      * Standard Intent action that can be sent to have the camera application
    277      * capture an image and return it.
    278      * <p>
    279      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
    280      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
    281      * object in the extra field. This is useful for applications that only need a small image.
    282      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
    283      * value of EXTRA_OUTPUT.
    284      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
    285      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
    286      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
    287      * If you don't set a ClipData, it will be copied there for you when calling
    288      * {@link Context#startActivity(Intent)}.
    289      *
    290      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
    291      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
    292      * is not granted, then attempting to use this action will result in a {@link
    293      * java.lang.SecurityException}.
    294      *
    295      *  @see #EXTRA_OUTPUT
    296      */
    297     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    298     public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE";
    299 
    300     /**
    301      * Intent action that can be sent to have the camera application capture an image and return
    302      * it when the device is secured (e.g. with a pin, password, pattern, or face unlock).
    303      * Applications responding to this intent must not expose any personal content like existing
    304      * photos or videos on the device. The applications should be careful not to share any photo
    305      * or video with other applications or internet. The activity should use {@link
    306      * android.view.WindowManager.LayoutParams#FLAG_SHOW_WHEN_LOCKED} to display on top of the
    307      * lock screen while secured. There is no activity stack when this flag is used, so
    308      * launching more than one activity is strongly discouraged.
    309      * <p>
    310      * The caller may pass an extra EXTRA_OUTPUT to control where this image will be written.
    311      * If the EXTRA_OUTPUT is not present, then a small sized image is returned as a Bitmap
    312      * object in the extra field. This is useful for applications that only need a small image.
    313      * If the EXTRA_OUTPUT is present, then the full-sized image will be written to the Uri
    314      * value of EXTRA_OUTPUT.
    315      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
    316      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
    317      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
    318      * If you don't set a ClipData, it will be copied there for you when calling
    319      * {@link Context#startActivity(Intent)}.
    320      *
    321      * @see #ACTION_IMAGE_CAPTURE
    322      * @see #EXTRA_OUTPUT
    323      */
    324     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    325     public static final String ACTION_IMAGE_CAPTURE_SECURE =
    326             "android.media.action.IMAGE_CAPTURE_SECURE";
    327 
    328     /**
    329      * Standard Intent action that can be sent to have the camera application
    330      * capture a video and return it.
    331      * <p>
    332      * The caller may pass in an extra EXTRA_VIDEO_QUALITY to control the video quality.
    333      * <p>
    334      * The caller may pass in an extra EXTRA_OUTPUT to control
    335      * where the video is written. If EXTRA_OUTPUT is not present the video will be
    336      * written to the standard location for videos, and the Uri of that location will be
    337      * returned in the data field of the Uri.
    338      * As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this uri can also be supplied through
    339      * {@link android.content.Intent#setClipData(ClipData)}. If using this approach, you still must
    340      * supply the uri through the EXTRA_OUTPUT field for compatibility with old applications.
    341      * If you don't set a ClipData, it will be copied there for you when calling
    342      * {@link Context#startActivity(Intent)}.
    343      *
    344      * <p>Note: if you app targets {@link android.os.Build.VERSION_CODES#M M} and above
    345      * and declares as using the {@link android.Manifest.permission#CAMERA} permission which
    346      * is not granted, then atempting to use this action will result in a {@link
    347      * java.lang.SecurityException}.
    348      *
    349      * @see #EXTRA_OUTPUT
    350      * @see #EXTRA_VIDEO_QUALITY
    351      * @see #EXTRA_SIZE_LIMIT
    352      * @see #EXTRA_DURATION_LIMIT
    353      */
    354     @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
    355     public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE";
    356 
    357     /**
    358      * The name of the Intent-extra used to control the quality of a recorded video. This is an
    359      * integer property. Currently value 0 means low quality, suitable for MMS messages, and
    360      * value 1 means high quality. In the future other quality levels may be added.
    361      */
    362     public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality";
    363 
    364     /**
    365      * Specify the maximum allowed size.
    366      */
    367     public final static String EXTRA_SIZE_LIMIT = "android.intent.extra.sizeLimit";
    368 
    369     /**
    370      * Specify the maximum allowed recording duration in seconds.
    371      */
    372     public final static String EXTRA_DURATION_LIMIT = "android.intent.extra.durationLimit";
    373 
    374     /**
    375      * The name of the Intent-extra used to indicate a content resolver Uri to be used to
    376      * store the requested image or video.
    377      */
    378     public final static String EXTRA_OUTPUT = "output";
    379 
    380     /**
    381       * The string that is used when a media attribute is not known. For example,
    382       * if an audio file does not have any meta data, the artist and album columns
    383       * will be set to this value.
    384       */
    385     public static final String UNKNOWN_STRING = "<unknown>";
    386 
    387     /**
    388      * Common fields for most MediaProvider tables
    389      */
    390 
    391     public interface MediaColumns extends BaseColumns {
    392         /**
    393          * Path to the file on disk.
    394          * <p>
    395          * Note that apps may not have filesystem permissions to directly access
    396          * this path. Instead of trying to open this path directly, apps should
    397          * use {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
    398          * access.
    399          * <p>
    400          * Type: TEXT
    401          */
    402         public static final String DATA = "_data";
    403 
    404         /**
    405          * The size of the file in bytes
    406          * <P>Type: INTEGER (long)</P>
    407          */
    408         public static final String SIZE = "_size";
    409 
    410         /**
    411          * The display name of the file
    412          * <P>Type: TEXT</P>
    413          */
    414         public static final String DISPLAY_NAME = "_display_name";
    415 
    416         /**
    417          * The title of the content
    418          * <P>Type: TEXT</P>
    419          */
    420         public static final String TITLE = "title";
    421 
    422         /**
    423          * The time the file was added to the media provider
    424          * Units are seconds since 1970.
    425          * <P>Type: INTEGER (long)</P>
    426          */
    427         public static final String DATE_ADDED = "date_added";
    428 
    429         /**
    430          * The time the file was last modified
    431          * Units are seconds since 1970.
    432          * NOTE: This is for internal use by the media scanner.  Do not modify this field.
    433          * <P>Type: INTEGER (long)</P>
    434          */
    435         public static final String DATE_MODIFIED = "date_modified";
    436 
    437         /**
    438          * The MIME type of the file
    439          * <P>Type: TEXT</P>
    440          */
    441         public static final String MIME_TYPE = "mime_type";
    442 
    443         /**
    444          * The MTP object handle of a newly transfered file.
    445          * Used to pass the new file's object handle through the media scanner
    446          * from MTP to the media provider
    447          * For internal use only by MTP, media scanner and media provider.
    448          * <P>Type: INTEGER</P>
    449          * @hide
    450          */
    451         public static final String MEDIA_SCANNER_NEW_OBJECT_ID = "media_scanner_new_object_id";
    452 
    453         /**
    454          * Non-zero if the media file is drm-protected
    455          * <P>Type: INTEGER (boolean)</P>
    456          * @hide
    457          */
    458         public static final String IS_DRM = "is_drm";
    459 
    460         /**
    461          * The width of the image/video in pixels.
    462          */
    463         public static final String WIDTH = "width";
    464 
    465         /**
    466          * The height of the image/video in pixels.
    467          */
    468         public static final String HEIGHT = "height";
    469      }
    470 
    471     /**
    472      * Media provider table containing an index of all files in the media storage,
    473      * including non-media files.  This should be used by applications that work with
    474      * non-media file types (text, HTML, PDF, etc) as well as applications that need to
    475      * work with multiple media file types in a single query.
    476      */
    477     public static final class Files {
    478 
    479         /**
    480          * Get the content:// style URI for the files table on the
    481          * given volume.
    482          *
    483          * @param volumeName the name of the volume to get the URI for
    484          * @return the URI to the files table on the given volume
    485          */
    486         public static Uri getContentUri(String volumeName) {
    487             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
    488                     "/file");
    489         }
    490 
    491         /**
    492          * Get the content:// style URI for a single row in the files table on the
    493          * given volume.
    494          *
    495          * @param volumeName the name of the volume to get the URI for
    496          * @param rowId the file to get the URI for
    497          * @return the URI to the files table on the given volume
    498          */
    499         public static final Uri getContentUri(String volumeName,
    500                 long rowId) {
    501             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
    502                     + "/file/" + rowId);
    503         }
    504 
    505         /**
    506          * For use only by the MTP implementation.
    507          * @hide
    508          */
    509         public static Uri getMtpObjectsUri(String volumeName) {
    510             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
    511                     "/object");
    512         }
    513 
    514         /**
    515          * For use only by the MTP implementation.
    516          * @hide
    517          */
    518         public static final Uri getMtpObjectsUri(String volumeName,
    519                 long fileId) {
    520             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
    521                     + "/object/" + fileId);
    522         }
    523 
    524         /**
    525          * Used to implement the MTP GetObjectReferences and SetObjectReferences commands.
    526          * @hide
    527          */
    528         public static final Uri getMtpReferencesUri(String volumeName,
    529                 long fileId) {
    530             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
    531                     + "/object/" + fileId + "/references");
    532         }
    533 
    534         /**
    535          * Used to trigger special logic for directories.
    536          * @hide
    537          */
    538         public static final Uri getDirectoryUri(String volumeName) {
    539             return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName + "/dir");
    540         }
    541 
    542         /**
    543          * Fields for master table for all media files.
    544          * Table also contains MediaColumns._ID, DATA, SIZE and DATE_MODIFIED.
    545          */
    546         public interface FileColumns extends MediaColumns {
    547             /**
    548              * The MTP storage ID of the file
    549              * <P>Type: INTEGER</P>
    550              * @hide
    551              */
    552             public static final String STORAGE_ID = "storage_id";
    553 
    554             /**
    555              * The MTP format code of the file
    556              * <P>Type: INTEGER</P>
    557              * @hide
    558              */
    559             public static final String FORMAT = "format";
    560 
    561             /**
    562              * The index of the parent directory of the file
    563              * <P>Type: INTEGER</P>
    564              */
    565             public static final String PARENT = "parent";
    566 
    567             /**
    568              * The MIME type of the file
    569              * <P>Type: TEXT</P>
    570              */
    571             public static final String MIME_TYPE = "mime_type";
    572 
    573             /**
    574              * The title of the content
    575              * <P>Type: TEXT</P>
    576              */
    577             public static final String TITLE = "title";
    578 
    579             /**
    580              * The media type (audio, video, image or playlist)
    581              * of the file, or 0 for not a media file
    582              * <P>Type: TEXT</P>
    583              */
    584             public static final String MEDIA_TYPE = "media_type";
    585 
    586             /**
    587              * Constant for the {@link #MEDIA_TYPE} column indicating that file
    588              * is not an audio, image, video or playlist file.
    589              */
    590             public static final int MEDIA_TYPE_NONE = 0;
    591 
    592             /**
    593              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an image file.
    594              */
    595             public static final int MEDIA_TYPE_IMAGE = 1;
    596 
    597             /**
    598              * Constant for the {@link #MEDIA_TYPE} column indicating that file is an audio file.
    599              */
    600             public static final int MEDIA_TYPE_AUDIO = 2;
    601 
    602             /**
    603              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a video file.
    604              */
    605             public static final int MEDIA_TYPE_VIDEO = 3;
    606 
    607             /**
    608              * Constant for the {@link #MEDIA_TYPE} column indicating that file is a playlist file.
    609              */
    610             public static final int MEDIA_TYPE_PLAYLIST = 4;
    611         }
    612     }
    613 
    614     /**
    615      * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
    616      * to be accessed elsewhere.
    617      */
    618     private static class InternalThumbnails implements BaseColumns {
    619         private static final int MINI_KIND = 1;
    620         private static final int FULL_SCREEN_KIND = 2;
    621         private static final int MICRO_KIND = 3;
    622         private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
    623         static final int DEFAULT_GROUP_ID = 0;
    624         private static final Object sThumbBufLock = new Object();
    625         private static byte[] sThumbBuf;
    626 
    627         private static Bitmap getMiniThumbFromFile(
    628                 Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options) {
    629             Bitmap bitmap = null;
    630             Uri thumbUri = null;
    631             try {
    632                 long thumbId = c.getLong(0);
    633                 String filePath = c.getString(1);
    634                 thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
    635                 ParcelFileDescriptor pfdInput = cr.openFileDescriptor(thumbUri, "r");
    636                 bitmap = BitmapFactory.decodeFileDescriptor(
    637                         pfdInput.getFileDescriptor(), null, options);
    638                 pfdInput.close();
    639             } catch (FileNotFoundException ex) {
    640                 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
    641             } catch (IOException ex) {
    642                 Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
    643             } catch (OutOfMemoryError ex) {
    644                 Log.e(TAG, "failed to allocate memory for thumbnail "
    645                         + thumbUri + "; " + ex);
    646             }
    647             return bitmap;
    648         }
    649 
    650         /**
    651          * This method cancels the thumbnail request so clients waiting for getThumbnail will be
    652          * interrupted and return immediately. Only the original process which made the getThumbnail
    653          * requests can cancel their own requests.
    654          *
    655          * @param cr ContentResolver
    656          * @param origId original image or video id. use -1 to cancel all requests.
    657          * @param groupId the same groupId used in getThumbnail
    658          * @param baseUri the base URI of requested thumbnails
    659          */
    660         static void cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri,
    661                 long groupId) {
    662             Uri cancelUri = baseUri.buildUpon().appendQueryParameter("cancel", "1")
    663                     .appendQueryParameter("orig_id", String.valueOf(origId))
    664                     .appendQueryParameter("group_id", String.valueOf(groupId)).build();
    665             Cursor c = null;
    666             try {
    667                 c = cr.query(cancelUri, PROJECTION, null, null, null);
    668             }
    669             finally {
    670                 if (c != null) c.close();
    671             }
    672         }
    673 
    674         /**
    675          * This method ensure thumbnails associated with origId are generated and decode the byte
    676          * stream from database (MICRO_KIND) or file (MINI_KIND).
    677          *
    678          * Special optimization has been done to avoid further IPC communication for MICRO_KIND
    679          * thumbnails.
    680          *
    681          * @param cr ContentResolver
    682          * @param origId original image or video id
    683          * @param kind could be MINI_KIND or MICRO_KIND
    684          * @param options this is only used for MINI_KIND when decoding the Bitmap
    685          * @param baseUri the base URI of requested thumbnails
    686          * @param groupId the id of group to which this request belongs
    687          * @return Bitmap bitmap of specified thumbnail kind
    688          */
    689         static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId, int kind,
    690                 BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
    691             Bitmap bitmap = null;
    692             // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
    693             // If the magic is non-zero, we simply return thumbnail if it does exist.
    694             // querying MediaProvider and simply return thumbnail.
    695             MiniThumbFile thumbFile = MiniThumbFile.instance(
    696                     isVideo ? Video.Media.EXTERNAL_CONTENT_URI : Images.Media.EXTERNAL_CONTENT_URI);
    697             Cursor c = null;
    698             try {
    699                 long magic = thumbFile.getMagic(origId);
    700                 if (magic != 0) {
    701                     if (kind == MICRO_KIND) {
    702                         synchronized (sThumbBufLock) {
    703                             if (sThumbBuf == null) {
    704                                 sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
    705                             }
    706                             if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
    707                                 bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
    708                                 if (bitmap == null) {
    709                                     Log.w(TAG, "couldn't decode byte array.");
    710                                 }
    711                             }
    712                         }
    713                         return bitmap;
    714                     } else if (kind == MINI_KIND) {
    715                         String column = isVideo ? "video_id=" : "image_id=";
    716                         c = cr.query(baseUri, PROJECTION, column + origId, null, null);
    717                         if (c != null && c.moveToFirst()) {
    718                             bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
    719                             if (bitmap != null) {
    720                                 return bitmap;
    721                             }
    722                         }
    723                     }
    724                 }
    725 
    726                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
    727                         .appendQueryParameter("orig_id", String.valueOf(origId))
    728                         .appendQueryParameter("group_id", String.valueOf(groupId)).build();
    729                 if (c != null) c.close();
    730                 c = cr.query(blockingUri, PROJECTION, null, null, null);
    731                 // This happens when original image/video doesn't exist.
    732                 if (c == null) return null;
    733 
    734                 // Assuming thumbnail has been generated, at least original image exists.
    735                 if (kind == MICRO_KIND) {
    736                     synchronized (sThumbBufLock) {
    737                         if (sThumbBuf == null) {
    738                             sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
    739                         }
    740                         Arrays.fill(sThumbBuf, (byte)0);
    741                         if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
    742                             bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
    743                             if (bitmap == null) {
    744                                 Log.w(TAG, "couldn't decode byte array.");
    745                             }
    746                         }
    747                     }
    748                 } else if (kind == MINI_KIND) {
    749                     if (c.moveToFirst()) {
    750                         bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
    751                     }
    752                 } else {
    753                     throw new IllegalArgumentException("Unsupported kind: " + kind);
    754                 }
    755 
    756                 // We probably run out of space, so create the thumbnail in memory.
    757                 if (bitmap == null) {
    758                     Log.v(TAG, "Create the thumbnail in memory: origId=" + origId
    759                             + ", kind=" + kind + ", isVideo="+isVideo);
    760                     Uri uri = Uri.parse(
    761                             baseUri.buildUpon().appendPath(String.valueOf(origId))
    762                                     .toString().replaceFirst("thumbnails", "media"));
    763                     if (c != null) c.close();
    764                     c = cr.query(uri, PROJECTION, null, null, null);
    765                     if (c == null || !c.moveToFirst()) {
    766                         return null;
    767                     }
    768                     String filePath = c.getString(1);
    769                     if (filePath != null) {
    770                         if (isVideo) {
    771                             bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
    772                         } else {
    773                             bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
    774                         }
    775                     }
    776                 }
    777             } catch (SQLiteException ex) {
    778                 Log.w(TAG, ex);
    779             } finally {
    780                 if (c != null) c.close();
    781                 // To avoid file descriptor leak in application process.
    782                 thumbFile.deactivate();
    783                 thumbFile = null;
    784             }
    785             return bitmap;
    786         }
    787     }
    788 
    789     /**
    790      * Contains meta data for all available images.
    791      */
    792     public static final class Images {
    793         public interface ImageColumns extends MediaColumns {
    794             /**
    795              * The description of the image
    796              * <P>Type: TEXT</P>
    797              */
    798             public static final String DESCRIPTION = "description";
    799 
    800             /**
    801              * The picasa id of the image
    802              * <P>Type: TEXT</P>
    803              */
    804             public static final String PICASA_ID = "picasa_id";
    805 
    806             /**
    807              * Whether the video should be published as public or private
    808              * <P>Type: INTEGER</P>
    809              */
    810             public static final String IS_PRIVATE = "isprivate";
    811 
    812             /**
    813              * The latitude where the image was captured.
    814              * <P>Type: DOUBLE</P>
    815              */
    816             public static final String LATITUDE = "latitude";
    817 
    818             /**
    819              * The longitude where the image was captured.
    820              * <P>Type: DOUBLE</P>
    821              */
    822             public static final String LONGITUDE = "longitude";
    823 
    824             /**
    825              * The date & time that the image was taken in units
    826              * of milliseconds since jan 1, 1970.
    827              * <P>Type: INTEGER</P>
    828              */
    829             public static final String DATE_TAKEN = "datetaken";
    830 
    831             /**
    832              * The orientation for the image expressed as degrees.
    833              * Only degrees 0, 90, 180, 270 will work.
    834              * <P>Type: INTEGER</P>
    835              */
    836             public static final String ORIENTATION = "orientation";
    837 
    838             /**
    839              * The mini thumb id.
    840              * <P>Type: INTEGER</P>
    841              */
    842             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
    843 
    844             /**
    845              * The bucket id of the image. This is a read-only property that
    846              * is automatically computed from the DATA column.
    847              * <P>Type: TEXT</P>
    848              */
    849             public static final String BUCKET_ID = "bucket_id";
    850 
    851             /**
    852              * The bucket display name of the image. This is a read-only property that
    853              * is automatically computed from the DATA column.
    854              * <P>Type: TEXT</P>
    855              */
    856             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
    857         }
    858 
    859         public static final class Media implements ImageColumns {
    860             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
    861                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
    862             }
    863 
    864             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
    865                     String where, String orderBy) {
    866                 return cr.query(uri, projection, where,
    867                                              null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
    868             }
    869 
    870             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
    871                     String selection, String [] selectionArgs, String orderBy) {
    872                 return cr.query(uri, projection, selection,
    873                         selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
    874             }
    875 
    876             /**
    877              * Retrieves an image for the given url as a {@link Bitmap}.
    878              *
    879              * @param cr The content resolver to use
    880              * @param url The url of the image
    881              * @throws FileNotFoundException
    882              * @throws IOException
    883              */
    884             public static final Bitmap getBitmap(ContentResolver cr, Uri url)
    885                     throws FileNotFoundException, IOException {
    886                 InputStream input = cr.openInputStream(url);
    887                 Bitmap bitmap = BitmapFactory.decodeStream(input);
    888                 input.close();
    889                 return bitmap;
    890             }
    891 
    892             /**
    893              * Insert an image and create a thumbnail for it.
    894              *
    895              * @param cr The content resolver to use
    896              * @param imagePath The path to the image to insert
    897              * @param name The name of the image
    898              * @param description The description of the image
    899              * @return The URL to the newly created image
    900              * @throws FileNotFoundException
    901              */
    902             public static final String insertImage(ContentResolver cr, String imagePath,
    903                     String name, String description) throws FileNotFoundException {
    904                 // Check if file exists with a FileInputStream
    905                 FileInputStream stream = new FileInputStream(imagePath);
    906                 try {
    907                     Bitmap bm = BitmapFactory.decodeFile(imagePath);
    908                     String ret = insertImage(cr, bm, name, description);
    909                     bm.recycle();
    910                     return ret;
    911                 } finally {
    912                     try {
    913                         stream.close();
    914                     } catch (IOException e) {
    915                     }
    916                 }
    917             }
    918 
    919             private static final Bitmap StoreThumbnail(
    920                     ContentResolver cr,
    921                     Bitmap source,
    922                     long id,
    923                     float width, float height,
    924                     int kind) {
    925                 // create the matrix to scale it
    926                 Matrix matrix = new Matrix();
    927 
    928                 float scaleX = width / source.getWidth();
    929                 float scaleY = height / source.getHeight();
    930 
    931                 matrix.setScale(scaleX, scaleY);
    932 
    933                 Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
    934                                                    source.getWidth(),
    935                                                    source.getHeight(), matrix,
    936                                                    true);
    937 
    938                 ContentValues values = new ContentValues(4);
    939                 values.put(Images.Thumbnails.KIND,     kind);
    940                 values.put(Images.Thumbnails.IMAGE_ID, (int)id);
    941                 values.put(Images.Thumbnails.HEIGHT,   thumb.getHeight());
    942                 values.put(Images.Thumbnails.WIDTH,    thumb.getWidth());
    943 
    944                 Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
    945 
    946                 try {
    947                     OutputStream thumbOut = cr.openOutputStream(url);
    948 
    949                     thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
    950                     thumbOut.close();
    951                     return thumb;
    952                 }
    953                 catch (FileNotFoundException ex) {
    954                     return null;
    955                 }
    956                 catch (IOException ex) {
    957                     return null;
    958                 }
    959             }
    960 
    961             /**
    962              * Insert an image and create a thumbnail for it.
    963              *
    964              * @param cr The content resolver to use
    965              * @param source The stream to use for the image
    966              * @param title The name of the image
    967              * @param description The description of the image
    968              * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
    969              *              for any reason.
    970              */
    971             public static final String insertImage(ContentResolver cr, Bitmap source,
    972                                                    String title, String description) {
    973                 ContentValues values = new ContentValues();
    974                 values.put(Images.Media.TITLE, title);
    975                 values.put(Images.Media.DESCRIPTION, description);
    976                 values.put(Images.Media.MIME_TYPE, "image/jpeg");
    977 
    978                 Uri url = null;
    979                 String stringUrl = null;    /* value to be returned */
    980 
    981                 try {
    982                     url = cr.insert(EXTERNAL_CONTENT_URI, values);
    983 
    984                     if (source != null) {
    985                         OutputStream imageOut = cr.openOutputStream(url);
    986                         try {
    987                             source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
    988                         } finally {
    989                             imageOut.close();
    990                         }
    991 
    992                         long id = ContentUris.parseId(url);
    993                         // Wait until MINI_KIND thumbnail is generated.
    994                         Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id,
    995                                 Images.Thumbnails.MINI_KIND, null);
    996                         // This is for backward compatibility.
    997                         Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F,
    998                                 Images.Thumbnails.MICRO_KIND);
    999                     } else {
   1000                         Log.e(TAG, "Failed to create thumbnail, removing original");
   1001                         cr.delete(url, null, null);
   1002                         url = null;
   1003                     }
   1004                 } catch (Exception e) {
   1005                     Log.e(TAG, "Failed to insert image", e);
   1006                     if (url != null) {
   1007                         cr.delete(url, null, null);
   1008                         url = null;
   1009                     }
   1010                 }
   1011 
   1012                 if (url != null) {
   1013                     stringUrl = url.toString();
   1014                 }
   1015 
   1016                 return stringUrl;
   1017             }
   1018 
   1019             /**
   1020              * Get the content:// style URI for the image media table on the
   1021              * given volume.
   1022              *
   1023              * @param volumeName the name of the volume to get the URI for
   1024              * @return the URI to the image media table on the given volume
   1025              */
   1026             public static Uri getContentUri(String volumeName) {
   1027                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1028                         "/images/media");
   1029             }
   1030 
   1031             /**
   1032              * The content:// style URI for the internal storage.
   1033              */
   1034             public static final Uri INTERNAL_CONTENT_URI =
   1035                     getContentUri("internal");
   1036 
   1037             /**
   1038              * The content:// style URI for the "primary" external storage
   1039              * volume.
   1040              */
   1041             public static final Uri EXTERNAL_CONTENT_URI =
   1042                     getContentUri("external");
   1043 
   1044             /**
   1045              * The MIME type of of this directory of
   1046              * images.  Note that each entry in this directory will have a standard
   1047              * image MIME type as appropriate -- for example, image/jpeg.
   1048              */
   1049             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
   1050 
   1051             /**
   1052              * The default sort order for this table
   1053              */
   1054             public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
   1055         }
   1056 
   1057         /**
   1058          * This class allows developers to query and get two kinds of thumbnails:
   1059          * MINI_KIND: 512 x 384 thumbnail
   1060          * MICRO_KIND: 96 x 96 thumbnail
   1061          */
   1062         public static class Thumbnails implements BaseColumns {
   1063             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
   1064                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
   1065             }
   1066 
   1067             public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
   1068                     String[] projection) {
   1069                 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
   1070             }
   1071 
   1072             public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
   1073                     String[] projection) {
   1074                 return cr.query(EXTERNAL_CONTENT_URI, projection,
   1075                         IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
   1076                         kind, null, null);
   1077             }
   1078 
   1079             /**
   1080              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
   1081              * interrupted and return immediately. Only the original process which made the getThumbnail
   1082              * requests can cancel their own requests.
   1083              *
   1084              * @param cr ContentResolver
   1085              * @param origId original image id
   1086              */
   1087             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
   1088                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
   1089                         InternalThumbnails.DEFAULT_GROUP_ID);
   1090             }
   1091 
   1092             /**
   1093              * This method checks if the thumbnails of the specified image (origId) has been created.
   1094              * It will be blocked until the thumbnails are generated.
   1095              *
   1096              * @param cr ContentResolver used to dispatch queries to MediaProvider.
   1097              * @param origId Original image id associated with thumbnail of interest.
   1098              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
   1099              * @param options this is only used for MINI_KIND when decoding the Bitmap
   1100              * @return A Bitmap instance. It could be null if the original image
   1101              *         associated with origId doesn't exist or memory is not enough.
   1102              */
   1103             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
   1104                     BitmapFactory.Options options) {
   1105                 return InternalThumbnails.getThumbnail(cr, origId,
   1106                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
   1107                         EXTERNAL_CONTENT_URI, false);
   1108             }
   1109 
   1110             /**
   1111              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
   1112              * interrupted and return immediately. Only the original process which made the getThumbnail
   1113              * requests can cancel their own requests.
   1114              *
   1115              * @param cr ContentResolver
   1116              * @param origId original image id
   1117              * @param groupId the same groupId used in getThumbnail.
   1118              */
   1119             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
   1120                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
   1121             }
   1122 
   1123             /**
   1124              * This method checks if the thumbnails of the specified image (origId) has been created.
   1125              * It will be blocked until the thumbnails are generated.
   1126              *
   1127              * @param cr ContentResolver used to dispatch queries to MediaProvider.
   1128              * @param origId Original image id associated with thumbnail of interest.
   1129              * @param groupId the id of group to which this request belongs
   1130              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
   1131              * @param options this is only used for MINI_KIND when decoding the Bitmap
   1132              * @return A Bitmap instance. It could be null if the original image
   1133              *         associated with origId doesn't exist or memory is not enough.
   1134              */
   1135             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
   1136                     int kind, BitmapFactory.Options options) {
   1137                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
   1138                         EXTERNAL_CONTENT_URI, false);
   1139             }
   1140 
   1141             /**
   1142              * Get the content:// style URI for the image media table on the
   1143              * given volume.
   1144              *
   1145              * @param volumeName the name of the volume to get the URI for
   1146              * @return the URI to the image media table on the given volume
   1147              */
   1148             public static Uri getContentUri(String volumeName) {
   1149                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1150                         "/images/thumbnails");
   1151             }
   1152 
   1153             /**
   1154              * The content:// style URI for the internal storage.
   1155              */
   1156             public static final Uri INTERNAL_CONTENT_URI =
   1157                     getContentUri("internal");
   1158 
   1159             /**
   1160              * The content:// style URI for the "primary" external storage
   1161              * volume.
   1162              */
   1163             public static final Uri EXTERNAL_CONTENT_URI =
   1164                     getContentUri("external");
   1165 
   1166             /**
   1167              * The default sort order for this table
   1168              */
   1169             public static final String DEFAULT_SORT_ORDER = "image_id ASC";
   1170 
   1171             /**
   1172              * Path to the thumbnail file on disk.
   1173              * <p>
   1174              * Note that apps may not have filesystem permissions to directly
   1175              * access this path. Instead of trying to open this path directly,
   1176              * apps should use
   1177              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
   1178              * access.
   1179              * <p>
   1180              * Type: TEXT
   1181              */
   1182             public static final String DATA = "_data";
   1183 
   1184             /**
   1185              * The original image for the thumbnal
   1186              * <P>Type: INTEGER (ID from Images table)</P>
   1187              */
   1188             public static final String IMAGE_ID = "image_id";
   1189 
   1190             /**
   1191              * The kind of the thumbnail
   1192              * <P>Type: INTEGER (One of the values below)</P>
   1193              */
   1194             public static final String KIND = "kind";
   1195 
   1196             public static final int MINI_KIND = 1;
   1197             public static final int FULL_SCREEN_KIND = 2;
   1198             public static final int MICRO_KIND = 3;
   1199             /**
   1200              * The blob raw data of thumbnail
   1201              * <P>Type: DATA STREAM</P>
   1202              */
   1203             public static final String THUMB_DATA = "thumb_data";
   1204 
   1205             /**
   1206              * The width of the thumbnal
   1207              * <P>Type: INTEGER (long)</P>
   1208              */
   1209             public static final String WIDTH = "width";
   1210 
   1211             /**
   1212              * The height of the thumbnail
   1213              * <P>Type: INTEGER (long)</P>
   1214              */
   1215             public static final String HEIGHT = "height";
   1216         }
   1217     }
   1218 
   1219     /**
   1220      * Container for all audio content.
   1221      */
   1222     public static final class Audio {
   1223         /**
   1224          * Columns for audio file that show up in multiple tables.
   1225          */
   1226         public interface AudioColumns extends MediaColumns {
   1227 
   1228             /**
   1229              * A non human readable key calculated from the TITLE, used for
   1230              * searching, sorting and grouping
   1231              * <P>Type: TEXT</P>
   1232              */
   1233             public static final String TITLE_KEY = "title_key";
   1234 
   1235             /**
   1236              * The duration of the audio file, in ms
   1237              * <P>Type: INTEGER (long)</P>
   1238              */
   1239             public static final String DURATION = "duration";
   1240 
   1241             /**
   1242              * The position, in ms, playback was at when playback for this file
   1243              * was last stopped.
   1244              * <P>Type: INTEGER (long)</P>
   1245              */
   1246             public static final String BOOKMARK = "bookmark";
   1247 
   1248             /**
   1249              * The id of the artist who created the audio file, if any
   1250              * <P>Type: INTEGER (long)</P>
   1251              */
   1252             public static final String ARTIST_ID = "artist_id";
   1253 
   1254             /**
   1255              * The artist who created the audio file, if any
   1256              * <P>Type: TEXT</P>
   1257              */
   1258             public static final String ARTIST = "artist";
   1259 
   1260             /**
   1261              * The artist credited for the album that contains the audio file
   1262              * <P>Type: TEXT</P>
   1263              * @hide
   1264              */
   1265             public static final String ALBUM_ARTIST = "album_artist";
   1266 
   1267             /**
   1268              * Whether the song is part of a compilation
   1269              * <P>Type: TEXT</P>
   1270              * @hide
   1271              */
   1272             public static final String COMPILATION = "compilation";
   1273 
   1274             /**
   1275              * A non human readable key calculated from the ARTIST, used for
   1276              * searching, sorting and grouping
   1277              * <P>Type: TEXT</P>
   1278              */
   1279             public static final String ARTIST_KEY = "artist_key";
   1280 
   1281             /**
   1282              * The composer of the audio file, if any
   1283              * <P>Type: TEXT</P>
   1284              */
   1285             public static final String COMPOSER = "composer";
   1286 
   1287             /**
   1288              * The id of the album the audio file is from, if any
   1289              * <P>Type: INTEGER (long)</P>
   1290              */
   1291             public static final String ALBUM_ID = "album_id";
   1292 
   1293             /**
   1294              * The album the audio file is from, if any
   1295              * <P>Type: TEXT</P>
   1296              */
   1297             public static final String ALBUM = "album";
   1298 
   1299             /**
   1300              * A non human readable key calculated from the ALBUM, used for
   1301              * searching, sorting and grouping
   1302              * <P>Type: TEXT</P>
   1303              */
   1304             public static final String ALBUM_KEY = "album_key";
   1305 
   1306             /**
   1307              * The track number of this song on the album, if any.
   1308              * This number encodes both the track number and the
   1309              * disc number. For multi-disc sets, this number will
   1310              * be 1xxx for tracks on the first disc, 2xxx for tracks
   1311              * on the second disc, etc.
   1312              * <P>Type: INTEGER</P>
   1313              */
   1314             public static final String TRACK = "track";
   1315 
   1316             /**
   1317              * The year the audio file was recorded, if any
   1318              * <P>Type: INTEGER</P>
   1319              */
   1320             public static final String YEAR = "year";
   1321 
   1322             /**
   1323              * Non-zero if the audio file is music
   1324              * <P>Type: INTEGER (boolean)</P>
   1325              */
   1326             public static final String IS_MUSIC = "is_music";
   1327 
   1328             /**
   1329              * Non-zero if the audio file is a podcast
   1330              * <P>Type: INTEGER (boolean)</P>
   1331              */
   1332             public static final String IS_PODCAST = "is_podcast";
   1333 
   1334             /**
   1335              * Non-zero if the audio file may be a ringtone
   1336              * <P>Type: INTEGER (boolean)</P>
   1337              */
   1338             public static final String IS_RINGTONE = "is_ringtone";
   1339 
   1340             /**
   1341              * Non-zero if the audio file may be an alarm
   1342              * <P>Type: INTEGER (boolean)</P>
   1343              */
   1344             public static final String IS_ALARM = "is_alarm";
   1345 
   1346             /**
   1347              * Non-zero if the audio file may be a notification sound
   1348              * <P>Type: INTEGER (boolean)</P>
   1349              */
   1350             public static final String IS_NOTIFICATION = "is_notification";
   1351 
   1352             /**
   1353              * The genre of the audio file, if any
   1354              * <P>Type: TEXT</P>
   1355              * Does not exist in the database - only used by the media scanner for inserts.
   1356              * @hide
   1357              */
   1358             public static final String GENRE = "genre";
   1359 
   1360             /**
   1361              * The resource URI of a localized title, if any
   1362              * <P>Type: TEXT</P>
   1363              * Conforms to this pattern:
   1364              *   Scheme: {@link ContentResolver.SCHEME_ANDROID_RESOURCE}
   1365              *   Authority: Package Name of ringtone title provider
   1366              *   First Path Segment: Type of resource (must be "string")
   1367              *   Second Path Segment: Resource ID of title
   1368              * @hide
   1369              */
   1370             public static final String TITLE_RESOURCE_URI = "title_resource_uri";
   1371         }
   1372 
   1373         /**
   1374          * Converts a name to a "key" that can be used for grouping, sorting
   1375          * and searching.
   1376          * The rules that govern this conversion are:
   1377          * - remove 'special' characters like ()[]'!?.,
   1378          * - remove leading/trailing spaces
   1379          * - convert everything to lowercase
   1380          * - remove leading "the ", "an " and "a "
   1381          * - remove trailing ", the|an|a"
   1382          * - remove accents. This step leaves us with CollationKey data,
   1383          *   which is not human readable
   1384          *
   1385          * @param name The artist or album name to convert
   1386          * @return The "key" for the given name.
   1387          */
   1388         public static String keyFor(String name) {
   1389             if (name != null)  {
   1390                 boolean sortfirst = false;
   1391                 if (name.equals(UNKNOWN_STRING)) {
   1392                     return "\001";
   1393                 }
   1394                 // Check if the first character is \001. We use this to
   1395                 // force sorting of certain special files, like the silent ringtone.
   1396                 if (name.startsWith("\001")) {
   1397                     sortfirst = true;
   1398                 }
   1399                 name = name.trim().toLowerCase();
   1400                 if (name.startsWith("the ")) {
   1401                     name = name.substring(4);
   1402                 }
   1403                 if (name.startsWith("an ")) {
   1404                     name = name.substring(3);
   1405                 }
   1406                 if (name.startsWith("a ")) {
   1407                     name = name.substring(2);
   1408                 }
   1409                 if (name.endsWith(", the") || name.endsWith(",the") ||
   1410                     name.endsWith(", an") || name.endsWith(",an") ||
   1411                     name.endsWith(", a") || name.endsWith(",a")) {
   1412                     name = name.substring(0, name.lastIndexOf(','));
   1413                 }
   1414                 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
   1415                 if (name.length() > 0) {
   1416                     // Insert a separator between the characters to avoid
   1417                     // matches on a partial character. If we ever change
   1418                     // to start-of-word-only matches, this can be removed.
   1419                     StringBuilder b = new StringBuilder();
   1420                     b.append('.');
   1421                     int nl = name.length();
   1422                     for (int i = 0; i < nl; i++) {
   1423                         b.append(name.charAt(i));
   1424                         b.append('.');
   1425                     }
   1426                     name = b.toString();
   1427                     String key = DatabaseUtils.getCollationKey(name);
   1428                     if (sortfirst) {
   1429                         key = "\001" + key;
   1430                     }
   1431                     return key;
   1432                } else {
   1433                     return "";
   1434                 }
   1435             }
   1436             return null;
   1437         }
   1438 
   1439         public static final class Media implements AudioColumns {
   1440 
   1441             private static final String[] EXTERNAL_PATHS;
   1442 
   1443             static {
   1444                 String secondary_storage = System.getenv("SECONDARY_STORAGE");
   1445                 if (secondary_storage != null) {
   1446                     EXTERNAL_PATHS = secondary_storage.split(":");
   1447                 } else {
   1448                     EXTERNAL_PATHS = new String[0];
   1449                 }
   1450             }
   1451 
   1452             /**
   1453              * Get the content:// style URI for the audio media table on the
   1454              * given volume.
   1455              *
   1456              * @param volumeName the name of the volume to get the URI for
   1457              * @return the URI to the audio media table on the given volume
   1458              */
   1459             public static Uri getContentUri(String volumeName) {
   1460                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1461                         "/audio/media");
   1462             }
   1463 
   1464             public static Uri getContentUriForPath(String path) {
   1465                 for (String ep : EXTERNAL_PATHS) {
   1466                     if (path.startsWith(ep)) {
   1467                         return EXTERNAL_CONTENT_URI;
   1468                     }
   1469                 }
   1470 
   1471                 return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
   1472                         EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
   1473             }
   1474 
   1475             /**
   1476              * The content:// style URI for the internal storage.
   1477              */
   1478             public static final Uri INTERNAL_CONTENT_URI =
   1479                     getContentUri("internal");
   1480 
   1481             /**
   1482              * The content:// style URI for the "primary" external storage
   1483              * volume.
   1484              */
   1485             public static final Uri EXTERNAL_CONTENT_URI =
   1486                     getContentUri("external");
   1487 
   1488             /**
   1489              * The MIME type for this table.
   1490              */
   1491             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
   1492 
   1493             /**
   1494              * The MIME type for an audio track.
   1495              */
   1496             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
   1497 
   1498             /**
   1499              * The default sort order for this table
   1500              */
   1501             public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
   1502 
   1503             /**
   1504              * Activity Action: Start SoundRecorder application.
   1505              * <p>Input: nothing.
   1506              * <p>Output: An uri to the recorded sound stored in the Media Library
   1507              * if the recording was successful.
   1508              * May also contain the extra EXTRA_MAX_BYTES.
   1509              * @see #EXTRA_MAX_BYTES
   1510              */
   1511             @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
   1512             public static final String RECORD_SOUND_ACTION =
   1513                     "android.provider.MediaStore.RECORD_SOUND";
   1514 
   1515             /**
   1516              * The name of the Intent-extra used to define a maximum file size for
   1517              * a recording made by the SoundRecorder application.
   1518              *
   1519              * @see #RECORD_SOUND_ACTION
   1520              */
   1521              public static final String EXTRA_MAX_BYTES =
   1522                     "android.provider.MediaStore.extra.MAX_BYTES";
   1523         }
   1524 
   1525         /**
   1526          * Columns representing an audio genre
   1527          */
   1528         public interface GenresColumns {
   1529             /**
   1530              * The name of the genre
   1531              * <P>Type: TEXT</P>
   1532              */
   1533             public static final String NAME = "name";
   1534         }
   1535 
   1536         /**
   1537          * Contains all genres for audio files
   1538          */
   1539         public static final class Genres implements BaseColumns, GenresColumns {
   1540             /**
   1541              * Get the content:// style URI for the audio genres table on the
   1542              * given volume.
   1543              *
   1544              * @param volumeName the name of the volume to get the URI for
   1545              * @return the URI to the audio genres table on the given volume
   1546              */
   1547             public static Uri getContentUri(String volumeName) {
   1548                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1549                         "/audio/genres");
   1550             }
   1551 
   1552             /**
   1553              * Get the content:// style URI for querying the genres of an audio file.
   1554              *
   1555              * @param volumeName the name of the volume to get the URI for
   1556              * @param audioId the ID of the audio file for which to retrieve the genres
   1557              * @return the URI to for querying the genres for the audio file
   1558              * with the given the volume and audioID
   1559              */
   1560             public static Uri getContentUriForAudioId(String volumeName, int audioId) {
   1561                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1562                         "/audio/media/" + audioId + "/genres");
   1563             }
   1564 
   1565             /**
   1566              * The content:// style URI for the internal storage.
   1567              */
   1568             public static final Uri INTERNAL_CONTENT_URI =
   1569                     getContentUri("internal");
   1570 
   1571             /**
   1572              * The content:// style URI for the "primary" external storage
   1573              * volume.
   1574              */
   1575             public static final Uri EXTERNAL_CONTENT_URI =
   1576                     getContentUri("external");
   1577 
   1578             /**
   1579              * The MIME type for this table.
   1580              */
   1581             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
   1582 
   1583             /**
   1584              * The MIME type for entries in this table.
   1585              */
   1586             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
   1587 
   1588             /**
   1589              * The default sort order for this table
   1590              */
   1591             public static final String DEFAULT_SORT_ORDER = NAME;
   1592 
   1593             /**
   1594              * Sub-directory of each genre containing all members.
   1595              */
   1596             public static final class Members implements AudioColumns {
   1597 
   1598                 public static final Uri getContentUri(String volumeName,
   1599                         long genreId) {
   1600                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
   1601                             + "/audio/genres/" + genreId + "/members");
   1602                 }
   1603 
   1604                 /**
   1605                  * A subdirectory of each genre containing all member audio files.
   1606                  */
   1607                 public static final String CONTENT_DIRECTORY = "members";
   1608 
   1609                 /**
   1610                  * The default sort order for this table
   1611                  */
   1612                 public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
   1613 
   1614                 /**
   1615                  * The ID of the audio file
   1616                  * <P>Type: INTEGER (long)</P>
   1617                  */
   1618                 public static final String AUDIO_ID = "audio_id";
   1619 
   1620                 /**
   1621                  * The ID of the genre
   1622                  * <P>Type: INTEGER (long)</P>
   1623                  */
   1624                 public static final String GENRE_ID = "genre_id";
   1625             }
   1626         }
   1627 
   1628         /**
   1629          * Columns representing a playlist
   1630          */
   1631         public interface PlaylistsColumns {
   1632             /**
   1633              * The name of the playlist
   1634              * <P>Type: TEXT</P>
   1635              */
   1636             public static final String NAME = "name";
   1637 
   1638             /**
   1639              * Path to the playlist file on disk.
   1640              * <p>
   1641              * Note that apps may not have filesystem permissions to directly
   1642              * access this path. Instead of trying to open this path directly,
   1643              * apps should use
   1644              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
   1645              * access.
   1646              * <p>
   1647              * Type: TEXT
   1648              */
   1649             public static final String DATA = "_data";
   1650 
   1651             /**
   1652              * The time the file was added to the media provider
   1653              * Units are seconds since 1970.
   1654              * <P>Type: INTEGER (long)</P>
   1655              */
   1656             public static final String DATE_ADDED = "date_added";
   1657 
   1658             /**
   1659              * The time the file was last modified
   1660              * Units are seconds since 1970.
   1661              * NOTE: This is for internal use by the media scanner.  Do not modify this field.
   1662              * <P>Type: INTEGER (long)</P>
   1663              */
   1664             public static final String DATE_MODIFIED = "date_modified";
   1665         }
   1666 
   1667         /**
   1668          * Contains playlists for audio files
   1669          */
   1670         public static final class Playlists implements BaseColumns,
   1671                 PlaylistsColumns {
   1672             /**
   1673              * Get the content:// style URI for the audio playlists table on the
   1674              * given volume.
   1675              *
   1676              * @param volumeName the name of the volume to get the URI for
   1677              * @return the URI to the audio playlists table on the given volume
   1678              */
   1679             public static Uri getContentUri(String volumeName) {
   1680                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1681                         "/audio/playlists");
   1682             }
   1683 
   1684             /**
   1685              * The content:// style URI for the internal storage.
   1686              */
   1687             public static final Uri INTERNAL_CONTENT_URI =
   1688                     getContentUri("internal");
   1689 
   1690             /**
   1691              * The content:// style URI for the "primary" external storage
   1692              * volume.
   1693              */
   1694             public static final Uri EXTERNAL_CONTENT_URI =
   1695                     getContentUri("external");
   1696 
   1697             /**
   1698              * The MIME type for this table.
   1699              */
   1700             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
   1701 
   1702             /**
   1703              * The MIME type for entries in this table.
   1704              */
   1705             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
   1706 
   1707             /**
   1708              * The default sort order for this table
   1709              */
   1710             public static final String DEFAULT_SORT_ORDER = NAME;
   1711 
   1712             /**
   1713              * Sub-directory of each playlist containing all members.
   1714              */
   1715             public static final class Members implements AudioColumns {
   1716                 public static final Uri getContentUri(String volumeName,
   1717                         long playlistId) {
   1718                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
   1719                             + "/audio/playlists/" + playlistId + "/members");
   1720                 }
   1721 
   1722                 /**
   1723                  * Convenience method to move a playlist item to a new location
   1724                  * @param res The content resolver to use
   1725                  * @param playlistId The numeric id of the playlist
   1726                  * @param from The position of the item to move
   1727                  * @param to The position to move the item to
   1728                  * @return true on success
   1729                  */
   1730                 public static final boolean moveItem(ContentResolver res,
   1731                         long playlistId, int from, int to) {
   1732                     Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
   1733                             playlistId)
   1734                             .buildUpon()
   1735                             .appendEncodedPath(String.valueOf(from))
   1736                             .appendQueryParameter("move", "true")
   1737                             .build();
   1738                     ContentValues values = new ContentValues();
   1739                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
   1740                     return res.update(uri, values, null, null) != 0;
   1741                 }
   1742 
   1743                 /**
   1744                  * The ID within the playlist.
   1745                  */
   1746                 public static final String _ID = "_id";
   1747 
   1748                 /**
   1749                  * A subdirectory of each playlist containing all member audio
   1750                  * files.
   1751                  */
   1752                 public static final String CONTENT_DIRECTORY = "members";
   1753 
   1754                 /**
   1755                  * The ID of the audio file
   1756                  * <P>Type: INTEGER (long)</P>
   1757                  */
   1758                 public static final String AUDIO_ID = "audio_id";
   1759 
   1760                 /**
   1761                  * The ID of the playlist
   1762                  * <P>Type: INTEGER (long)</P>
   1763                  */
   1764                 public static final String PLAYLIST_ID = "playlist_id";
   1765 
   1766                 /**
   1767                  * The order of the songs in the playlist
   1768                  * <P>Type: INTEGER (long)></P>
   1769                  */
   1770                 public static final String PLAY_ORDER = "play_order";
   1771 
   1772                 /**
   1773                  * The default sort order for this table
   1774                  */
   1775                 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
   1776             }
   1777         }
   1778 
   1779         /**
   1780          * Columns representing an artist
   1781          */
   1782         public interface ArtistColumns {
   1783             /**
   1784              * The artist who created the audio file, if any
   1785              * <P>Type: TEXT</P>
   1786              */
   1787             public static final String ARTIST = "artist";
   1788 
   1789             /**
   1790              * A non human readable key calculated from the ARTIST, used for
   1791              * searching, sorting and grouping
   1792              * <P>Type: TEXT</P>
   1793              */
   1794             public static final String ARTIST_KEY = "artist_key";
   1795 
   1796             /**
   1797              * The number of albums in the database for this artist
   1798              */
   1799             public static final String NUMBER_OF_ALBUMS = "number_of_albums";
   1800 
   1801             /**
   1802              * The number of albums in the database for this artist
   1803              */
   1804             public static final String NUMBER_OF_TRACKS = "number_of_tracks";
   1805         }
   1806 
   1807         /**
   1808          * Contains artists for audio files
   1809          */
   1810         public static final class Artists implements BaseColumns, ArtistColumns {
   1811             /**
   1812              * Get the content:// style URI for the artists table on the
   1813              * given volume.
   1814              *
   1815              * @param volumeName the name of the volume to get the URI for
   1816              * @return the URI to the audio artists table on the given volume
   1817              */
   1818             public static Uri getContentUri(String volumeName) {
   1819                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1820                         "/audio/artists");
   1821             }
   1822 
   1823             /**
   1824              * The content:// style URI for the internal storage.
   1825              */
   1826             public static final Uri INTERNAL_CONTENT_URI =
   1827                     getContentUri("internal");
   1828 
   1829             /**
   1830              * The content:// style URI for the "primary" external storage
   1831              * volume.
   1832              */
   1833             public static final Uri EXTERNAL_CONTENT_URI =
   1834                     getContentUri("external");
   1835 
   1836             /**
   1837              * The MIME type for this table.
   1838              */
   1839             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
   1840 
   1841             /**
   1842              * The MIME type for entries in this table.
   1843              */
   1844             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
   1845 
   1846             /**
   1847              * The default sort order for this table
   1848              */
   1849             public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
   1850 
   1851             /**
   1852              * Sub-directory of each artist containing all albums on which
   1853              * a song by the artist appears.
   1854              */
   1855             public static final class Albums implements AlbumColumns {
   1856                 public static final Uri getContentUri(String volumeName,
   1857                         long artistId) {
   1858                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
   1859                             + "/audio/artists/" + artistId + "/albums");
   1860                 }
   1861             }
   1862         }
   1863 
   1864         /**
   1865          * Columns representing an album
   1866          */
   1867         public interface AlbumColumns {
   1868 
   1869             /**
   1870              * The id for the album
   1871              * <P>Type: INTEGER</P>
   1872              */
   1873             public static final String ALBUM_ID = "album_id";
   1874 
   1875             /**
   1876              * The album on which the audio file appears, if any
   1877              * <P>Type: TEXT</P>
   1878              */
   1879             public static final String ALBUM = "album";
   1880 
   1881             /**
   1882              * The artist whose songs appear on this album
   1883              * <P>Type: TEXT</P>
   1884              */
   1885             public static final String ARTIST = "artist";
   1886 
   1887             /**
   1888              * The number of songs on this album
   1889              * <P>Type: INTEGER</P>
   1890              */
   1891             public static final String NUMBER_OF_SONGS = "numsongs";
   1892 
   1893             /**
   1894              * This column is available when getting album info via artist,
   1895              * and indicates the number of songs on the album by the given
   1896              * artist.
   1897              * <P>Type: INTEGER</P>
   1898              */
   1899             public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
   1900 
   1901             /**
   1902              * The year in which the earliest songs
   1903              * on this album were released. This will often
   1904              * be the same as {@link #LAST_YEAR}, but for compilation albums
   1905              * they might differ.
   1906              * <P>Type: INTEGER</P>
   1907              */
   1908             public static final String FIRST_YEAR = "minyear";
   1909 
   1910             /**
   1911              * The year in which the latest songs
   1912              * on this album were released. This will often
   1913              * be the same as {@link #FIRST_YEAR}, but for compilation albums
   1914              * they might differ.
   1915              * <P>Type: INTEGER</P>
   1916              */
   1917             public static final String LAST_YEAR = "maxyear";
   1918 
   1919             /**
   1920              * A non human readable key calculated from the ALBUM, used for
   1921              * searching, sorting and grouping
   1922              * <P>Type: TEXT</P>
   1923              */
   1924             public static final String ALBUM_KEY = "album_key";
   1925 
   1926             /**
   1927              * Cached album art.
   1928              * <P>Type: TEXT</P>
   1929              */
   1930             public static final String ALBUM_ART = "album_art";
   1931         }
   1932 
   1933         /**
   1934          * Contains artists for audio files
   1935          */
   1936         public static final class Albums implements BaseColumns, AlbumColumns {
   1937             /**
   1938              * Get the content:// style URI for the albums table on the
   1939              * given volume.
   1940              *
   1941              * @param volumeName the name of the volume to get the URI for
   1942              * @return the URI to the audio albums table on the given volume
   1943              */
   1944             public static Uri getContentUri(String volumeName) {
   1945                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   1946                         "/audio/albums");
   1947             }
   1948 
   1949             /**
   1950              * The content:// style URI for the internal storage.
   1951              */
   1952             public static final Uri INTERNAL_CONTENT_URI =
   1953                     getContentUri("internal");
   1954 
   1955             /**
   1956              * The content:// style URI for the "primary" external storage
   1957              * volume.
   1958              */
   1959             public static final Uri EXTERNAL_CONTENT_URI =
   1960                     getContentUri("external");
   1961 
   1962             /**
   1963              * The MIME type for this table.
   1964              */
   1965             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
   1966 
   1967             /**
   1968              * The MIME type for entries in this table.
   1969              */
   1970             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
   1971 
   1972             /**
   1973              * The default sort order for this table
   1974              */
   1975             public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
   1976         }
   1977 
   1978         public static final class Radio {
   1979             /**
   1980              * The MIME type for entries in this table.
   1981              */
   1982             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
   1983 
   1984             // Not instantiable.
   1985             private Radio() { }
   1986         }
   1987     }
   1988 
   1989     public static final class Video {
   1990 
   1991         /**
   1992          * The default sort order for this table.
   1993          */
   1994         public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
   1995 
   1996         public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
   1997             return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
   1998         }
   1999 
   2000         public interface VideoColumns extends MediaColumns {
   2001 
   2002             /**
   2003              * The duration of the video file, in ms
   2004              * <P>Type: INTEGER (long)</P>
   2005              */
   2006             public static final String DURATION = "duration";
   2007 
   2008             /**
   2009              * The artist who created the video file, if any
   2010              * <P>Type: TEXT</P>
   2011              */
   2012             public static final String ARTIST = "artist";
   2013 
   2014             /**
   2015              * The album the video file is from, if any
   2016              * <P>Type: TEXT</P>
   2017              */
   2018             public static final String ALBUM = "album";
   2019 
   2020             /**
   2021              * The resolution of the video file, formatted as "XxY"
   2022              * <P>Type: TEXT</P>
   2023              */
   2024             public static final String RESOLUTION = "resolution";
   2025 
   2026             /**
   2027              * The description of the video recording
   2028              * <P>Type: TEXT</P>
   2029              */
   2030             public static final String DESCRIPTION = "description";
   2031 
   2032             /**
   2033              * Whether the video should be published as public or private
   2034              * <P>Type: INTEGER</P>
   2035              */
   2036             public static final String IS_PRIVATE = "isprivate";
   2037 
   2038             /**
   2039              * The user-added tags associated with a video
   2040              * <P>Type: TEXT</P>
   2041              */
   2042             public static final String TAGS = "tags";
   2043 
   2044             /**
   2045              * The YouTube category of the video
   2046              * <P>Type: TEXT</P>
   2047              */
   2048             public static final String CATEGORY = "category";
   2049 
   2050             /**
   2051              * The language of the video
   2052              * <P>Type: TEXT</P>
   2053              */
   2054             public static final String LANGUAGE = "language";
   2055 
   2056             /**
   2057              * The latitude where the video was captured.
   2058              * <P>Type: DOUBLE</P>
   2059              */
   2060             public static final String LATITUDE = "latitude";
   2061 
   2062             /**
   2063              * The longitude where the video was captured.
   2064              * <P>Type: DOUBLE</P>
   2065              */
   2066             public static final String LONGITUDE = "longitude";
   2067 
   2068             /**
   2069              * The date & time that the video was taken in units
   2070              * of milliseconds since jan 1, 1970.
   2071              * <P>Type: INTEGER</P>
   2072              */
   2073             public static final String DATE_TAKEN = "datetaken";
   2074 
   2075             /**
   2076              * The mini thumb id.
   2077              * <P>Type: INTEGER</P>
   2078              */
   2079             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
   2080 
   2081             /**
   2082              * The bucket id of the video. This is a read-only property that
   2083              * is automatically computed from the DATA column.
   2084              * <P>Type: TEXT</P>
   2085              */
   2086             public static final String BUCKET_ID = "bucket_id";
   2087 
   2088             /**
   2089              * The bucket display name of the video. This is a read-only property that
   2090              * is automatically computed from the DATA column.
   2091              * <P>Type: TEXT</P>
   2092              */
   2093             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
   2094 
   2095             /**
   2096              * The bookmark for the video. Time in ms. Represents the location in the video that the
   2097              * video should start playing at the next time it is opened. If the value is null or
   2098              * out of the range 0..DURATION-1 then the video should start playing from the
   2099              * beginning.
   2100              * <P>Type: INTEGER</P>
   2101              */
   2102             public static final String BOOKMARK = "bookmark";
   2103         }
   2104 
   2105         public static final class Media implements VideoColumns {
   2106             /**
   2107              * Get the content:// style URI for the video media table on the
   2108              * given volume.
   2109              *
   2110              * @param volumeName the name of the volume to get the URI for
   2111              * @return the URI to the video media table on the given volume
   2112              */
   2113             public static Uri getContentUri(String volumeName) {
   2114                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   2115                         "/video/media");
   2116             }
   2117 
   2118             /**
   2119              * The content:// style URI for the internal storage.
   2120              */
   2121             public static final Uri INTERNAL_CONTENT_URI =
   2122                     getContentUri("internal");
   2123 
   2124             /**
   2125              * The content:// style URI for the "primary" external storage
   2126              * volume.
   2127              */
   2128             public static final Uri EXTERNAL_CONTENT_URI =
   2129                     getContentUri("external");
   2130 
   2131             /**
   2132              * The MIME type for this table.
   2133              */
   2134             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
   2135 
   2136             /**
   2137              * The default sort order for this table
   2138              */
   2139             public static final String DEFAULT_SORT_ORDER = TITLE;
   2140         }
   2141 
   2142         /**
   2143          * This class allows developers to query and get two kinds of thumbnails:
   2144          * MINI_KIND: 512 x 384 thumbnail
   2145          * MICRO_KIND: 96 x 96 thumbnail
   2146          *
   2147          */
   2148         public static class Thumbnails implements BaseColumns {
   2149             /**
   2150              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
   2151              * interrupted and return immediately. Only the original process which made the getThumbnail
   2152              * requests can cancel their own requests.
   2153              *
   2154              * @param cr ContentResolver
   2155              * @param origId original video id
   2156              */
   2157             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
   2158                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
   2159                         InternalThumbnails.DEFAULT_GROUP_ID);
   2160             }
   2161 
   2162             /**
   2163              * This method checks if the thumbnails of the specified image (origId) has been created.
   2164              * It will be blocked until the thumbnails are generated.
   2165              *
   2166              * @param cr ContentResolver used to dispatch queries to MediaProvider.
   2167              * @param origId Original image id associated with thumbnail of interest.
   2168              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
   2169              * @param options this is only used for MINI_KIND when decoding the Bitmap
   2170              * @return A Bitmap instance. It could be null if the original image
   2171              *         associated with origId doesn't exist or memory is not enough.
   2172              */
   2173             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
   2174                     BitmapFactory.Options options) {
   2175                 return InternalThumbnails.getThumbnail(cr, origId,
   2176                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
   2177                         EXTERNAL_CONTENT_URI, true);
   2178             }
   2179 
   2180             /**
   2181              * This method checks if the thumbnails of the specified image (origId) has been created.
   2182              * It will be blocked until the thumbnails are generated.
   2183              *
   2184              * @param cr ContentResolver used to dispatch queries to MediaProvider.
   2185              * @param origId Original image id associated with thumbnail of interest.
   2186              * @param groupId the id of group to which this request belongs
   2187              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
   2188              * @param options this is only used for MINI_KIND when decoding the Bitmap
   2189              * @return A Bitmap instance. It could be null if the original image associated with
   2190              *         origId doesn't exist or memory is not enough.
   2191              */
   2192             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
   2193                     int kind, BitmapFactory.Options options) {
   2194                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
   2195                         EXTERNAL_CONTENT_URI, true);
   2196             }
   2197 
   2198             /**
   2199              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
   2200              * interrupted and return immediately. Only the original process which made the getThumbnail
   2201              * requests can cancel their own requests.
   2202              *
   2203              * @param cr ContentResolver
   2204              * @param origId original video id
   2205              * @param groupId the same groupId used in getThumbnail.
   2206              */
   2207             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
   2208                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
   2209             }
   2210 
   2211             /**
   2212              * Get the content:// style URI for the image media table on the
   2213              * given volume.
   2214              *
   2215              * @param volumeName the name of the volume to get the URI for
   2216              * @return the URI to the image media table on the given volume
   2217              */
   2218             public static Uri getContentUri(String volumeName) {
   2219                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
   2220                         "/video/thumbnails");
   2221             }
   2222 
   2223             /**
   2224              * The content:// style URI for the internal storage.
   2225              */
   2226             public static final Uri INTERNAL_CONTENT_URI =
   2227                     getContentUri("internal");
   2228 
   2229             /**
   2230              * The content:// style URI for the "primary" external storage
   2231              * volume.
   2232              */
   2233             public static final Uri EXTERNAL_CONTENT_URI =
   2234                     getContentUri("external");
   2235 
   2236             /**
   2237              * The default sort order for this table
   2238              */
   2239             public static final String DEFAULT_SORT_ORDER = "video_id ASC";
   2240 
   2241             /**
   2242              * Path to the thumbnail file on disk.
   2243              * <p>
   2244              * Note that apps may not have filesystem permissions to directly
   2245              * access this path. Instead of trying to open this path directly,
   2246              * apps should use
   2247              * {@link ContentResolver#openFileDescriptor(Uri, String)} to gain
   2248              * access.
   2249              * <p>
   2250              * Type: TEXT
   2251              */
   2252             public static final String DATA = "_data";
   2253 
   2254             /**
   2255              * The original image for the thumbnal
   2256              * <P>Type: INTEGER (ID from Video table)</P>
   2257              */
   2258             public static final String VIDEO_ID = "video_id";
   2259 
   2260             /**
   2261              * The kind of the thumbnail
   2262              * <P>Type: INTEGER (One of the values below)</P>
   2263              */
   2264             public static final String KIND = "kind";
   2265 
   2266             public static final int MINI_KIND = 1;
   2267             public static final int FULL_SCREEN_KIND = 2;
   2268             public static final int MICRO_KIND = 3;
   2269 
   2270             /**
   2271              * The width of the thumbnal
   2272              * <P>Type: INTEGER (long)</P>
   2273              */
   2274             public static final String WIDTH = "width";
   2275 
   2276             /**
   2277              * The height of the thumbnail
   2278              * <P>Type: INTEGER (long)</P>
   2279              */
   2280             public static final String HEIGHT = "height";
   2281         }
   2282     }
   2283 
   2284     /**
   2285      * Uri for querying the state of the media scanner.
   2286      */
   2287     public static Uri getMediaScannerUri() {
   2288         return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner");
   2289     }
   2290 
   2291     /**
   2292      * Name of current volume being scanned by the media scanner.
   2293      */
   2294     public static final String MEDIA_SCANNER_VOLUME = "volume";
   2295 
   2296     /**
   2297      * Name of the file signaling the media scanner to ignore media in the containing directory
   2298      * and its subdirectories. Developers should use this to avoid application graphics showing
   2299      * up in the Gallery and likewise prevent application sounds and music from showing up in
   2300      * the Music app.
   2301      */
   2302     public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
   2303 
   2304     /**
   2305      * Get the media provider's version.
   2306      * Applications that import data from the media provider into their own caches
   2307      * can use this to detect that the media provider changed, and reimport data
   2308      * as needed. No other assumptions should be made about the meaning of the version.
   2309      * @param context Context to use for performing the query.
   2310      * @return A version string, or null if the version could not be determined.
   2311      */
   2312     public static String getVersion(Context context) {
   2313         Cursor c = context.getContentResolver().query(
   2314                 Uri.parse(CONTENT_AUTHORITY_SLASH + "none/version"),
   2315                 null, null, null, null);
   2316         if (c != null) {
   2317             try {
   2318                 if (c.moveToFirst()) {
   2319                     return c.getString(0);
   2320                 }
   2321             } finally {
   2322                 c.close();
   2323             }
   2324         }
   2325         return null;
   2326     }
   2327 
   2328     /**
   2329      * Gets a URI backed by a {@link DocumentsProvider} that points to the same media
   2330      * file as the specified mediaUri. This allows apps who have permissions to access
   2331      * media files in Storage Access Framework to perform file operations through that
   2332      * on media files.
   2333      * <p>
   2334      * Note: this method doesn't grant any URI permission. Callers need to obtain
   2335      * permission before calling this method. One way to obtain permission is through
   2336      * a 3-step process:
   2337      * <ol>
   2338      *     <li>Call {@link android.os.storage.StorageManager#getStorageVolume(File)} to
   2339      *     obtain the {@link android.os.storage.StorageVolume} of a media file;</li>
   2340      *
   2341      *     <li>Invoke the intent returned by
   2342      *     {@link android.os.storage.StorageVolume#createAccessIntent(String)} to
   2343      *     obtain the access of the volume or one of its specific subdirectories;</li>
   2344      *
   2345      *     <li>Check whether permission is granted and take persistent permission.</li>
   2346      * </ol>
   2347      * @param mediaUri the media URI which document URI is requested
   2348      * @return the document URI
   2349      */
   2350     public static Uri getDocumentUri(Context context, Uri mediaUri) {
   2351 
   2352         try {
   2353             final ContentResolver resolver = context.getContentResolver();
   2354 
   2355             final String path = getFilePath(resolver, mediaUri);
   2356             final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
   2357 
   2358             return getDocumentUri(resolver, path, uriPermissions);
   2359         } catch (RemoteException e) {
   2360             throw e.rethrowAsRuntimeException();
   2361         }
   2362     }
   2363 
   2364     private static String getFilePath(ContentResolver resolver, Uri mediaUri)
   2365             throws RemoteException {
   2366 
   2367         try (ContentProviderClient client =
   2368                      resolver.acquireUnstableContentProviderClient(AUTHORITY)) {
   2369             final Cursor c = client.query(
   2370                     mediaUri,
   2371                     new String[]{ MediaColumns.DATA },
   2372                     null, /* selection */
   2373                     null, /* selectionArg */
   2374                     null /* sortOrder */);
   2375 
   2376             final String path;
   2377             try {
   2378                 if (c.getCount() == 0) {
   2379                     throw new IllegalStateException("Not found media file under URI: " + mediaUri);
   2380                 }
   2381 
   2382                 if (!c.moveToFirst()) {
   2383                     throw new IllegalStateException("Failed to move cursor to the first item.");
   2384                 }
   2385 
   2386                 path = c.getString(0);
   2387             } finally {
   2388                 IoUtils.closeQuietly(c);
   2389             }
   2390 
   2391             return path;
   2392         }
   2393     }
   2394 
   2395     private static Uri getDocumentUri(
   2396             ContentResolver resolver, String path, List<UriPermission> uriPermissions)
   2397             throws RemoteException {
   2398 
   2399         try (ContentProviderClient client = resolver.acquireUnstableContentProviderClient(
   2400                 DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY)) {
   2401             final Bundle in = new Bundle();
   2402             in.putParcelableList(
   2403                     DocumentsContract.EXTERNAL_STORAGE_PROVIDER_AUTHORITY + ".extra.uriPermissions",
   2404                     uriPermissions);
   2405             final Bundle out = client.call("getDocumentId", path, in);
   2406             return out.getParcelable(DocumentsContract.EXTRA_URI);
   2407         }
   2408     }
   2409 }
   2410