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