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