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