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          */
getContentUri(String volumeName)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          */
getContentUri(String volumeName, long rowId)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          */
getMtpObjectsUri(String volumeName)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          */
getMtpObjectsUri(String volumeName, long fileId)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          */
getMtpReferencesUri(String volumeName, long fileId)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 
getMiniThumbFromFile( Cursor c, Uri baseUri, ContentResolver cr, BitmapFactory.Options options)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          */
cancelThumbnailRequest(ContentResolver cr, long origId, Uri baseUri, long groupId)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          */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options, Uri baseUri, boolean isVideo)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             // Log.v(TAG, "getThumbnail: origId="+origId+", kind="+kind+", isVideo="+isVideo);
642             // If the magic is non-zero, we simply return thumbnail if it does exist.
643             // querying MediaProvider and simply return thumbnail.
644             MiniThumbFile thumbFile = new MiniThumbFile(isVideo ? Video.Media.EXTERNAL_CONTENT_URI
645                     : Images.Media.EXTERNAL_CONTENT_URI);
646             Cursor c = null;
647             try {
648                 long magic = thumbFile.getMagic(origId);
649                 if (magic != 0) {
650                     if (kind == MICRO_KIND) {
651                         synchronized (sThumbBufLock) {
652                             if (sThumbBuf == null) {
653                                 sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
654                             }
655                             if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
656                                 bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
657                                 if (bitmap == null) {
658                                     Log.w(TAG, "couldn't decode byte array.");
659                                 }
660                             }
661                         }
662                         return bitmap;
663                     } else if (kind == MINI_KIND) {
664                         String column = isVideo ? "video_id=" : "image_id=";
665                         c = cr.query(baseUri, PROJECTION, column + origId, null, null);
666                         if (c != null && c.moveToFirst()) {
667                             bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
668                             if (bitmap != null) {
669                                 return bitmap;
670                             }
671                         }
672                     }
673                 }
674 
675                 Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
676                         .appendQueryParameter("orig_id", String.valueOf(origId))
677                         .appendQueryParameter("group_id", String.valueOf(groupId)).build();
678                 if (c != null) c.close();
679                 c = cr.query(blockingUri, PROJECTION, null, null, null);
680                 // This happens when original image/video doesn't exist.
681                 if (c == null) return null;
682 
683                 // Assuming thumbnail has been generated, at least original image exists.
684                 if (kind == MICRO_KIND) {
685                     synchronized (sThumbBufLock) {
686                         if (sThumbBuf == null) {
687                             sThumbBuf = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
688                         }
689                         Arrays.fill(sThumbBuf, (byte)0);
690                         if (thumbFile.getMiniThumbFromFile(origId, sThumbBuf) != null) {
691                             bitmap = BitmapFactory.decodeByteArray(sThumbBuf, 0, sThumbBuf.length);
692                             if (bitmap == null) {
693                                 Log.w(TAG, "couldn't decode byte array.");
694                             }
695                         }
696                     }
697                 } else if (kind == MINI_KIND) {
698                     if (c.moveToFirst()) {
699                         bitmap = getMiniThumbFromFile(c, baseUri, cr, options);
700                     }
701                 } else {
702                     throw new IllegalArgumentException("Unsupported kind: " + kind);
703                 }
704 
705                 // We probably run out of space, so create the thumbnail in memory.
706                 if (bitmap == null) {
707                     Log.v(TAG, "Create the thumbnail in memory: origId=" + origId
708                             + ", kind=" + kind + ", isVideo="+isVideo);
709                     Uri uri = Uri.parse(
710                             baseUri.buildUpon().appendPath(String.valueOf(origId))
711                                     .toString().replaceFirst("thumbnails", "media"));
712                     if (c != null) c.close();
713                     c = cr.query(uri, PROJECTION, null, null, null);
714                     if (c == null || !c.moveToFirst()) {
715                         return null;
716                     }
717                     String filePath = c.getString(1);
718                     if (filePath != null) {
719                         if (isVideo) {
720                             bitmap = ThumbnailUtils.createVideoThumbnail(filePath, kind);
721                         } else {
722                             bitmap = ThumbnailUtils.createImageThumbnail(filePath, kind);
723                         }
724                     }
725                 }
726             } catch (SQLiteException ex) {
727                 Log.w(TAG, ex);
728             } finally {
729                 if (c != null) c.close();
730                 // To avoid file descriptor leak in application process.
731                 thumbFile.deactivate();
732                 thumbFile = null;
733             }
734             return bitmap;
735         }
736     }
737 
738     /**
739      * Contains meta data for all available images.
740      */
741     public static final class Images {
742         public interface ImageColumns extends MediaColumns {
743             /**
744              * The description of the image
745              * <P>Type: TEXT</P>
746              */
747             public static final String DESCRIPTION = "description";
748 
749             /**
750              * The picasa id of the image
751              * <P>Type: TEXT</P>
752              */
753             public static final String PICASA_ID = "picasa_id";
754 
755             /**
756              * Whether the video should be published as public or private
757              * <P>Type: INTEGER</P>
758              */
759             public static final String IS_PRIVATE = "isprivate";
760 
761             /**
762              * The latitude where the image was captured.
763              * <P>Type: DOUBLE</P>
764              */
765             public static final String LATITUDE = "latitude";
766 
767             /**
768              * The longitude where the image was captured.
769              * <P>Type: DOUBLE</P>
770              */
771             public static final String LONGITUDE = "longitude";
772 
773             /**
774              * The date & time that the image was taken in units
775              * of milliseconds since jan 1, 1970.
776              * <P>Type: INTEGER</P>
777              */
778             public static final String DATE_TAKEN = "datetaken";
779 
780             /**
781              * The orientation for the image expressed as degrees.
782              * Only degrees 0, 90, 180, 270 will work.
783              * <P>Type: INTEGER</P>
784              */
785             public static final String ORIENTATION = "orientation";
786 
787             /**
788              * The mini thumb id.
789              * <P>Type: INTEGER</P>
790              */
791             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
792 
793             /**
794              * The bucket id of the image. This is a read-only property that
795              * is automatically computed from the DATA column.
796              * <P>Type: TEXT</P>
797              */
798             public static final String BUCKET_ID = "bucket_id";
799 
800             /**
801              * The bucket display name of the image. This is a read-only property that
802              * is automatically computed from the DATA column.
803              * <P>Type: TEXT</P>
804              */
805             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
806         }
807 
808         public static final class Media implements ImageColumns {
query(ContentResolver cr, Uri uri, String[] projection)809             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
810                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
811             }
812 
query(ContentResolver cr, Uri uri, String[] projection, String where, String orderBy)813             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
814                     String where, String orderBy) {
815                 return cr.query(uri, projection, where,
816                                              null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
817             }
818 
query(ContentResolver cr, Uri uri, String[] projection, String selection, String [] selectionArgs, String orderBy)819             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
820                     String selection, String [] selectionArgs, String orderBy) {
821                 return cr.query(uri, projection, selection,
822                         selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
823             }
824 
825             /**
826              * Retrieves an image for the given url as a {@link Bitmap}.
827              *
828              * @param cr The content resolver to use
829              * @param url The url of the image
830              * @throws FileNotFoundException
831              * @throws IOException
832              */
getBitmap(ContentResolver cr, Uri url)833             public static final Bitmap getBitmap(ContentResolver cr, Uri url)
834                     throws FileNotFoundException, IOException {
835                 InputStream input = cr.openInputStream(url);
836                 Bitmap bitmap = BitmapFactory.decodeStream(input);
837                 input.close();
838                 return bitmap;
839             }
840 
841             /**
842              * Insert an image and create a thumbnail for it.
843              *
844              * @param cr The content resolver to use
845              * @param imagePath The path to the image to insert
846              * @param name The name of the image
847              * @param description The description of the image
848              * @return The URL to the newly created image
849              * @throws FileNotFoundException
850              */
insertImage(ContentResolver cr, String imagePath, String name, String description)851             public static final String insertImage(ContentResolver cr, String imagePath,
852                     String name, String description) throws FileNotFoundException {
853                 // Check if file exists with a FileInputStream
854                 FileInputStream stream = new FileInputStream(imagePath);
855                 try {
856                     Bitmap bm = BitmapFactory.decodeFile(imagePath);
857                     String ret = insertImage(cr, bm, name, description);
858                     bm.recycle();
859                     return ret;
860                 } finally {
861                     try {
862                         stream.close();
863                     } catch (IOException e) {
864                     }
865                 }
866             }
867 
StoreThumbnail( ContentResolver cr, Bitmap source, long id, float width, float height, int kind)868             private static final Bitmap StoreThumbnail(
869                     ContentResolver cr,
870                     Bitmap source,
871                     long id,
872                     float width, float height,
873                     int kind) {
874                 // create the matrix to scale it
875                 Matrix matrix = new Matrix();
876 
877                 float scaleX = width / source.getWidth();
878                 float scaleY = height / source.getHeight();
879 
880                 matrix.setScale(scaleX, scaleY);
881 
882                 Bitmap thumb = Bitmap.createBitmap(source, 0, 0,
883                                                    source.getWidth(),
884                                                    source.getHeight(), matrix,
885                                                    true);
886 
887                 ContentValues values = new ContentValues(4);
888                 values.put(Images.Thumbnails.KIND,     kind);
889                 values.put(Images.Thumbnails.IMAGE_ID, (int)id);
890                 values.put(Images.Thumbnails.HEIGHT,   thumb.getHeight());
891                 values.put(Images.Thumbnails.WIDTH,    thumb.getWidth());
892 
893                 Uri url = cr.insert(Images.Thumbnails.EXTERNAL_CONTENT_URI, values);
894 
895                 try {
896                     OutputStream thumbOut = cr.openOutputStream(url);
897 
898                     thumb.compress(Bitmap.CompressFormat.JPEG, 100, thumbOut);
899                     thumbOut.close();
900                     return thumb;
901                 }
902                 catch (FileNotFoundException ex) {
903                     return null;
904                 }
905                 catch (IOException ex) {
906                     return null;
907                 }
908             }
909 
910             /**
911              * Insert an image and create a thumbnail for it.
912              *
913              * @param cr The content resolver to use
914              * @param source The stream to use for the image
915              * @param title The name of the image
916              * @param description The description of the image
917              * @return The URL to the newly created image, or <code>null</code> if the image failed to be stored
918              *              for any reason.
919              */
insertImage(ContentResolver cr, Bitmap source, String title, String description)920             public static final String insertImage(ContentResolver cr, Bitmap source,
921                                                    String title, String description) {
922                 ContentValues values = new ContentValues();
923                 values.put(Images.Media.TITLE, title);
924                 values.put(Images.Media.DESCRIPTION, description);
925                 values.put(Images.Media.MIME_TYPE, "image/jpeg");
926 
927                 Uri url = null;
928                 String stringUrl = null;    /* value to be returned */
929 
930                 try {
931                     url = cr.insert(EXTERNAL_CONTENT_URI, values);
932 
933                     if (source != null) {
934                         OutputStream imageOut = cr.openOutputStream(url);
935                         try {
936                             source.compress(Bitmap.CompressFormat.JPEG, 50, imageOut);
937                         } finally {
938                             imageOut.close();
939                         }
940 
941                         long id = ContentUris.parseId(url);
942                         // Wait until MINI_KIND thumbnail is generated.
943                         Bitmap miniThumb = Images.Thumbnails.getThumbnail(cr, id,
944                                 Images.Thumbnails.MINI_KIND, null);
945                         // This is for backward compatibility.
946                         Bitmap microThumb = StoreThumbnail(cr, miniThumb, id, 50F, 50F,
947                                 Images.Thumbnails.MICRO_KIND);
948                     } else {
949                         Log.e(TAG, "Failed to create thumbnail, removing original");
950                         cr.delete(url, null, null);
951                         url = null;
952                     }
953                 } catch (Exception e) {
954                     Log.e(TAG, "Failed to insert image", e);
955                     if (url != null) {
956                         cr.delete(url, null, null);
957                         url = null;
958                     }
959                 }
960 
961                 if (url != null) {
962                     stringUrl = url.toString();
963                 }
964 
965                 return stringUrl;
966             }
967 
968             /**
969              * Get the content:// style URI for the image media table on the
970              * given volume.
971              *
972              * @param volumeName the name of the volume to get the URI for
973              * @return the URI to the image media table on the given volume
974              */
getContentUri(String volumeName)975             public static Uri getContentUri(String volumeName) {
976                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
977                         "/images/media");
978             }
979 
980             /**
981              * The content:// style URI for the internal storage.
982              */
983             public static final Uri INTERNAL_CONTENT_URI =
984                     getContentUri("internal");
985 
986             /**
987              * The content:// style URI for the "primary" external storage
988              * volume.
989              */
990             public static final Uri EXTERNAL_CONTENT_URI =
991                     getContentUri("external");
992 
993             /**
994              * The MIME type of of this directory of
995              * images.  Note that each entry in this directory will have a standard
996              * image MIME type as appropriate -- for example, image/jpeg.
997              */
998             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/image";
999 
1000             /**
1001              * The default sort order for this table
1002              */
1003             public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
1004         }
1005 
1006         /**
1007          * This class allows developers to query and get two kinds of thumbnails:
1008          * MINI_KIND: 512 x 384 thumbnail
1009          * MICRO_KIND: 96 x 96 thumbnail
1010          */
1011         public static class Thumbnails implements BaseColumns {
query(ContentResolver cr, Uri uri, String[] projection)1012             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1013                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1014             }
1015 
queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)1016             public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
1017                     String[] projection) {
1018                 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
1019             }
1020 
queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)1021             public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
1022                     String[] projection) {
1023                 return cr.query(EXTERNAL_CONTENT_URI, projection,
1024                         IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
1025                         kind, null, null);
1026             }
1027 
1028             /**
1029              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1030              * interrupted and return immediately. Only the original process which made the getThumbnail
1031              * requests can cancel their own requests.
1032              *
1033              * @param cr ContentResolver
1034              * @param origId original image id
1035              */
cancelThumbnailRequest(ContentResolver cr, long origId)1036             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
1037                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
1038                         InternalThumbnails.DEFAULT_GROUP_ID);
1039             }
1040 
1041             /**
1042              * This method checks if the thumbnails of the specified image (origId) has been created.
1043              * It will be blocked until the thumbnails are generated.
1044              *
1045              * @param cr ContentResolver used to dispatch queries to MediaProvider.
1046              * @param origId Original image id associated with thumbnail of interest.
1047              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1048              * @param options this is only used for MINI_KIND when decoding the Bitmap
1049              * @return A Bitmap instance. It could be null if the original image
1050              *         associated with origId doesn't exist or memory is not enough.
1051              */
getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options)1052             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
1053                     BitmapFactory.Options options) {
1054                 return InternalThumbnails.getThumbnail(cr, origId,
1055                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
1056                         EXTERNAL_CONTENT_URI, false);
1057             }
1058 
1059             /**
1060              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
1061              * interrupted and return immediately. Only the original process which made the getThumbnail
1062              * requests can cancel their own requests.
1063              *
1064              * @param cr ContentResolver
1065              * @param origId original image id
1066              * @param groupId the same groupId used in getThumbnail.
1067              */
cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)1068             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
1069                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
1070             }
1071 
1072             /**
1073              * This method checks if the thumbnails of the specified image (origId) has been created.
1074              * It will be blocked until the thumbnails are generated.
1075              *
1076              * @param cr ContentResolver used to dispatch queries to MediaProvider.
1077              * @param origId Original image id associated with thumbnail of interest.
1078              * @param groupId the id of group to which this request belongs
1079              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
1080              * @param options this is only used for MINI_KIND when decoding the Bitmap
1081              * @return A Bitmap instance. It could be null if the original image
1082              *         associated with origId doesn't exist or memory is not enough.
1083              */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options)1084             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
1085                     int kind, BitmapFactory.Options options) {
1086                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
1087                         EXTERNAL_CONTENT_URI, false);
1088             }
1089 
1090             /**
1091              * Get the content:// style URI for the image media table on the
1092              * given volume.
1093              *
1094              * @param volumeName the name of the volume to get the URI for
1095              * @return the URI to the image media table on the given volume
1096              */
getContentUri(String volumeName)1097             public static Uri getContentUri(String volumeName) {
1098                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1099                         "/images/thumbnails");
1100             }
1101 
1102             /**
1103              * The content:// style URI for the internal storage.
1104              */
1105             public static final Uri INTERNAL_CONTENT_URI =
1106                     getContentUri("internal");
1107 
1108             /**
1109              * The content:// style URI for the "primary" external storage
1110              * volume.
1111              */
1112             public static final Uri EXTERNAL_CONTENT_URI =
1113                     getContentUri("external");
1114 
1115             /**
1116              * The default sort order for this table
1117              */
1118             public static final String DEFAULT_SORT_ORDER = "image_id ASC";
1119 
1120             /**
1121              * The data stream for the thumbnail
1122              * <P>Type: DATA STREAM</P>
1123              */
1124             public static final String DATA = "_data";
1125 
1126             /**
1127              * The original image for the thumbnal
1128              * <P>Type: INTEGER (ID from Images table)</P>
1129              */
1130             public static final String IMAGE_ID = "image_id";
1131 
1132             /**
1133              * The kind of the thumbnail
1134              * <P>Type: INTEGER (One of the values below)</P>
1135              */
1136             public static final String KIND = "kind";
1137 
1138             public static final int MINI_KIND = 1;
1139             public static final int FULL_SCREEN_KIND = 2;
1140             public static final int MICRO_KIND = 3;
1141             /**
1142              * The blob raw data of thumbnail
1143              * <P>Type: DATA STREAM</P>
1144              */
1145             public static final String THUMB_DATA = "thumb_data";
1146 
1147             /**
1148              * The width of the thumbnal
1149              * <P>Type: INTEGER (long)</P>
1150              */
1151             public static final String WIDTH = "width";
1152 
1153             /**
1154              * The height of the thumbnail
1155              * <P>Type: INTEGER (long)</P>
1156              */
1157             public static final String HEIGHT = "height";
1158         }
1159     }
1160 
1161     /**
1162      * Container for all audio content.
1163      */
1164     public static final class Audio {
1165         /**
1166          * Columns for audio file that show up in multiple tables.
1167          */
1168         public interface AudioColumns extends MediaColumns {
1169 
1170             /**
1171              * A non human readable key calculated from the TITLE, used for
1172              * searching, sorting and grouping
1173              * <P>Type: TEXT</P>
1174              */
1175             public static final String TITLE_KEY = "title_key";
1176 
1177             /**
1178              * The duration of the audio file, in ms
1179              * <P>Type: INTEGER (long)</P>
1180              */
1181             public static final String DURATION = "duration";
1182 
1183             /**
1184              * The position, in ms, playback was at when playback for this file
1185              * was last stopped.
1186              * <P>Type: INTEGER (long)</P>
1187              */
1188             public static final String BOOKMARK = "bookmark";
1189 
1190             /**
1191              * The id of the artist who created the audio file, if any
1192              * <P>Type: INTEGER (long)</P>
1193              */
1194             public static final String ARTIST_ID = "artist_id";
1195 
1196             /**
1197              * The artist who created the audio file, if any
1198              * <P>Type: TEXT</P>
1199              */
1200             public static final String ARTIST = "artist";
1201 
1202             /**
1203              * The artist credited for the album that contains the audio file
1204              * <P>Type: TEXT</P>
1205              * @hide
1206              */
1207             public static final String ALBUM_ARTIST = "album_artist";
1208 
1209             /**
1210              * Whether the song is part of a compilation
1211              * <P>Type: TEXT</P>
1212              * @hide
1213              */
1214             public static final String COMPILATION = "compilation";
1215 
1216             /**
1217              * A non human readable key calculated from the ARTIST, used for
1218              * searching, sorting and grouping
1219              * <P>Type: TEXT</P>
1220              */
1221             public static final String ARTIST_KEY = "artist_key";
1222 
1223             /**
1224              * The composer of the audio file, if any
1225              * <P>Type: TEXT</P>
1226              */
1227             public static final String COMPOSER = "composer";
1228 
1229             /**
1230              * The id of the album the audio file is from, if any
1231              * <P>Type: INTEGER (long)</P>
1232              */
1233             public static final String ALBUM_ID = "album_id";
1234 
1235             /**
1236              * The album the audio file is from, if any
1237              * <P>Type: TEXT</P>
1238              */
1239             public static final String ALBUM = "album";
1240 
1241             /**
1242              * A non human readable key calculated from the ALBUM, used for
1243              * searching, sorting and grouping
1244              * <P>Type: TEXT</P>
1245              */
1246             public static final String ALBUM_KEY = "album_key";
1247 
1248             /**
1249              * The track number of this song on the album, if any.
1250              * This number encodes both the track number and the
1251              * disc number. For multi-disc sets, this number will
1252              * be 1xxx for tracks on the first disc, 2xxx for tracks
1253              * on the second disc, etc.
1254              * <P>Type: INTEGER</P>
1255              */
1256             public static final String TRACK = "track";
1257 
1258             /**
1259              * The year the audio file was recorded, if any
1260              * <P>Type: INTEGER</P>
1261              */
1262             public static final String YEAR = "year";
1263 
1264             /**
1265              * Non-zero if the audio file is music
1266              * <P>Type: INTEGER (boolean)</P>
1267              */
1268             public static final String IS_MUSIC = "is_music";
1269 
1270             /**
1271              * Non-zero if the audio file is a podcast
1272              * <P>Type: INTEGER (boolean)</P>
1273              */
1274             public static final String IS_PODCAST = "is_podcast";
1275 
1276             /**
1277              * Non-zero if the audio file may be a ringtone
1278              * <P>Type: INTEGER (boolean)</P>
1279              */
1280             public static final String IS_RINGTONE = "is_ringtone";
1281 
1282             /**
1283              * Non-zero if the audio file may be an alarm
1284              * <P>Type: INTEGER (boolean)</P>
1285              */
1286             public static final String IS_ALARM = "is_alarm";
1287 
1288             /**
1289              * Non-zero if the audio file may be a notification sound
1290              * <P>Type: INTEGER (boolean)</P>
1291              */
1292             public static final String IS_NOTIFICATION = "is_notification";
1293 
1294             /**
1295              * The genre of the audio file, if any
1296              * <P>Type: TEXT</P>
1297              * Does not exist in the database - only used by the media scanner for inserts.
1298              * @hide
1299              */
1300             public static final String GENRE = "genre";
1301         }
1302 
1303         /**
1304          * Converts a name to a "key" that can be used for grouping, sorting
1305          * and searching.
1306          * The rules that govern this conversion are:
1307          * - remove 'special' characters like ()[]'!?.,
1308          * - remove leading/trailing spaces
1309          * - convert everything to lowercase
1310          * - remove leading "the ", "an " and "a "
1311          * - remove trailing ", the|an|a"
1312          * - remove accents. This step leaves us with CollationKey data,
1313          *   which is not human readable
1314          *
1315          * @param name The artist or album name to convert
1316          * @return The "key" for the given name.
1317          */
keyFor(String name)1318         public static String keyFor(String name) {
1319             if (name != null)  {
1320                 boolean sortfirst = false;
1321                 if (name.equals(UNKNOWN_STRING)) {
1322                     return "\001";
1323                 }
1324                 // Check if the first character is \001. We use this to
1325                 // force sorting of certain special files, like the silent ringtone.
1326                 if (name.startsWith("\001")) {
1327                     sortfirst = true;
1328                 }
1329                 name = name.trim().toLowerCase();
1330                 if (name.startsWith("the ")) {
1331                     name = name.substring(4);
1332                 }
1333                 if (name.startsWith("an ")) {
1334                     name = name.substring(3);
1335                 }
1336                 if (name.startsWith("a ")) {
1337                     name = name.substring(2);
1338                 }
1339                 if (name.endsWith(", the") || name.endsWith(",the") ||
1340                     name.endsWith(", an") || name.endsWith(",an") ||
1341                     name.endsWith(", a") || name.endsWith(",a")) {
1342                     name = name.substring(0, name.lastIndexOf(','));
1343                 }
1344                 name = name.replaceAll("[\\[\\]\\(\\)\"'.,?!]", "").trim();
1345                 if (name.length() > 0) {
1346                     // Insert a separator between the characters to avoid
1347                     // matches on a partial character. If we ever change
1348                     // to start-of-word-only matches, this can be removed.
1349                     StringBuilder b = new StringBuilder();
1350                     b.append('.');
1351                     int nl = name.length();
1352                     for (int i = 0; i < nl; i++) {
1353                         b.append(name.charAt(i));
1354                         b.append('.');
1355                     }
1356                     name = b.toString();
1357                     String key = DatabaseUtils.getCollationKey(name);
1358                     if (sortfirst) {
1359                         key = "\001" + key;
1360                     }
1361                     return key;
1362                } else {
1363                     return "";
1364                 }
1365             }
1366             return null;
1367         }
1368 
1369         public static final class Media implements AudioColumns {
1370 
1371             private static final String[] EXTERNAL_PATHS;
1372 
1373             static {
1374                 String secondary_storage = System.getenv("SECONDARY_STORAGE");
1375                 if (secondary_storage != null) {
1376                     EXTERNAL_PATHS = secondary_storage.split(":");
1377                 } else {
1378                     EXTERNAL_PATHS = new String[0];
1379                 }
1380             }
1381 
1382             /**
1383              * Get the content:// style URI for the audio media table on the
1384              * given volume.
1385              *
1386              * @param volumeName the name of the volume to get the URI for
1387              * @return the URI to the audio media table on the given volume
1388              */
getContentUri(String volumeName)1389             public static Uri getContentUri(String volumeName) {
1390                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1391                         "/audio/media");
1392             }
1393 
getContentUriForPath(String path)1394             public static Uri getContentUriForPath(String path) {
1395                 for (String ep : EXTERNAL_PATHS) {
1396                     if (path.startsWith(ep)) {
1397                         return EXTERNAL_CONTENT_URI;
1398                     }
1399                 }
1400 
1401                 return (path.startsWith(Environment.getExternalStorageDirectory().getPath()) ?
1402                         EXTERNAL_CONTENT_URI : INTERNAL_CONTENT_URI);
1403             }
1404 
1405             /**
1406              * The content:// style URI for the internal storage.
1407              */
1408             public static final Uri INTERNAL_CONTENT_URI =
1409                     getContentUri("internal");
1410 
1411             /**
1412              * The content:// style URI for the "primary" external storage
1413              * volume.
1414              */
1415             public static final Uri EXTERNAL_CONTENT_URI =
1416                     getContentUri("external");
1417 
1418             /**
1419              * The MIME type for this table.
1420              */
1421             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/audio";
1422 
1423             /**
1424              * The MIME type for an audio track.
1425              */
1426             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/audio";
1427 
1428             /**
1429              * The default sort order for this table
1430              */
1431             public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1432 
1433             /**
1434              * Activity Action: Start SoundRecorder application.
1435              * <p>Input: nothing.
1436              * <p>Output: An uri to the recorded sound stored in the Media Library
1437              * if the recording was successful.
1438              * May also contain the extra EXTRA_MAX_BYTES.
1439              * @see #EXTRA_MAX_BYTES
1440              */
1441             public static final String RECORD_SOUND_ACTION =
1442                     "android.provider.MediaStore.RECORD_SOUND";
1443 
1444             /**
1445              * The name of the Intent-extra used to define a maximum file size for
1446              * a recording made by the SoundRecorder application.
1447              *
1448              * @see #RECORD_SOUND_ACTION
1449              */
1450              public static final String EXTRA_MAX_BYTES =
1451                     "android.provider.MediaStore.extra.MAX_BYTES";
1452         }
1453 
1454         /**
1455          * Columns representing an audio genre
1456          */
1457         public interface GenresColumns {
1458             /**
1459              * The name of the genre
1460              * <P>Type: TEXT</P>
1461              */
1462             public static final String NAME = "name";
1463         }
1464 
1465         /**
1466          * Contains all genres for audio files
1467          */
1468         public static final class Genres implements BaseColumns, GenresColumns {
1469             /**
1470              * Get the content:// style URI for the audio genres table on the
1471              * given volume.
1472              *
1473              * @param volumeName the name of the volume to get the URI for
1474              * @return the URI to the audio genres table on the given volume
1475              */
getContentUri(String volumeName)1476             public static Uri getContentUri(String volumeName) {
1477                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1478                         "/audio/genres");
1479             }
1480 
1481             /**
1482              * Get the content:// style URI for querying the genres of an audio file.
1483              *
1484              * @param volumeName the name of the volume to get the URI for
1485              * @param audioId the ID of the audio file for which to retrieve the genres
1486              * @return the URI to for querying the genres for the audio file
1487              * with the given the volume and audioID
1488              */
getContentUriForAudioId(String volumeName, int audioId)1489             public static Uri getContentUriForAudioId(String volumeName, int audioId) {
1490                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1491                         "/audio/media/" + audioId + "/genres");
1492             }
1493 
1494             /**
1495              * The content:// style URI for the internal storage.
1496              */
1497             public static final Uri INTERNAL_CONTENT_URI =
1498                     getContentUri("internal");
1499 
1500             /**
1501              * The content:// style URI for the "primary" external storage
1502              * volume.
1503              */
1504             public static final Uri EXTERNAL_CONTENT_URI =
1505                     getContentUri("external");
1506 
1507             /**
1508              * The MIME type for this table.
1509              */
1510             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/genre";
1511 
1512             /**
1513              * The MIME type for entries in this table.
1514              */
1515             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/genre";
1516 
1517             /**
1518              * The default sort order for this table
1519              */
1520             public static final String DEFAULT_SORT_ORDER = NAME;
1521 
1522             /**
1523              * Sub-directory of each genre containing all members.
1524              */
1525             public static final class Members implements AudioColumns {
1526 
getContentUri(String volumeName, long genreId)1527                 public static final Uri getContentUri(String volumeName,
1528                         long genreId) {
1529                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1530                             + "/audio/genres/" + genreId + "/members");
1531                 }
1532 
1533                 /**
1534                  * A subdirectory of each genre containing all member audio files.
1535                  */
1536                 public static final String CONTENT_DIRECTORY = "members";
1537 
1538                 /**
1539                  * The default sort order for this table
1540                  */
1541                 public static final String DEFAULT_SORT_ORDER = TITLE_KEY;
1542 
1543                 /**
1544                  * The ID of the audio file
1545                  * <P>Type: INTEGER (long)</P>
1546                  */
1547                 public static final String AUDIO_ID = "audio_id";
1548 
1549                 /**
1550                  * The ID of the genre
1551                  * <P>Type: INTEGER (long)</P>
1552                  */
1553                 public static final String GENRE_ID = "genre_id";
1554             }
1555         }
1556 
1557         /**
1558          * Columns representing a playlist
1559          */
1560         public interface PlaylistsColumns {
1561             /**
1562              * The name of the playlist
1563              * <P>Type: TEXT</P>
1564              */
1565             public static final String NAME = "name";
1566 
1567             /**
1568              * The data stream for the playlist file
1569              * <P>Type: DATA STREAM</P>
1570              */
1571             public static final String DATA = "_data";
1572 
1573             /**
1574              * The time the file was added to the media provider
1575              * Units are seconds since 1970.
1576              * <P>Type: INTEGER (long)</P>
1577              */
1578             public static final String DATE_ADDED = "date_added";
1579 
1580             /**
1581              * The time the file was last modified
1582              * Units are seconds since 1970.
1583              * NOTE: This is for internal use by the media scanner.  Do not modify this field.
1584              * <P>Type: INTEGER (long)</P>
1585              */
1586             public static final String DATE_MODIFIED = "date_modified";
1587         }
1588 
1589         /**
1590          * Contains playlists for audio files
1591          */
1592         public static final class Playlists implements BaseColumns,
1593                 PlaylistsColumns {
1594             /**
1595              * Get the content:// style URI for the audio playlists table on the
1596              * given volume.
1597              *
1598              * @param volumeName the name of the volume to get the URI for
1599              * @return the URI to the audio playlists table on the given volume
1600              */
getContentUri(String volumeName)1601             public static Uri getContentUri(String volumeName) {
1602                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1603                         "/audio/playlists");
1604             }
1605 
1606             /**
1607              * The content:// style URI for the internal storage.
1608              */
1609             public static final Uri INTERNAL_CONTENT_URI =
1610                     getContentUri("internal");
1611 
1612             /**
1613              * The content:// style URI for the "primary" external storage
1614              * volume.
1615              */
1616             public static final Uri EXTERNAL_CONTENT_URI =
1617                     getContentUri("external");
1618 
1619             /**
1620              * The MIME type for this table.
1621              */
1622             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/playlist";
1623 
1624             /**
1625              * The MIME type for entries in this table.
1626              */
1627             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/playlist";
1628 
1629             /**
1630              * The default sort order for this table
1631              */
1632             public static final String DEFAULT_SORT_ORDER = NAME;
1633 
1634             /**
1635              * Sub-directory of each playlist containing all members.
1636              */
1637             public static final class Members implements AudioColumns {
getContentUri(String volumeName, long playlistId)1638                 public static final Uri getContentUri(String volumeName,
1639                         long playlistId) {
1640                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1641                             + "/audio/playlists/" + playlistId + "/members");
1642                 }
1643 
1644                 /**
1645                  * Convenience method to move a playlist item to a new location
1646                  * @param res The content resolver to use
1647                  * @param playlistId The numeric id of the playlist
1648                  * @param from The position of the item to move
1649                  * @param to The position to move the item to
1650                  * @return true on success
1651                  */
moveItem(ContentResolver res, long playlistId, int from, int to)1652                 public static final boolean moveItem(ContentResolver res,
1653                         long playlistId, int from, int to) {
1654                     Uri uri = MediaStore.Audio.Playlists.Members.getContentUri("external",
1655                             playlistId)
1656                             .buildUpon()
1657                             .appendEncodedPath(String.valueOf(from))
1658                             .appendQueryParameter("move", "true")
1659                             .build();
1660                     ContentValues values = new ContentValues();
1661                     values.put(MediaStore.Audio.Playlists.Members.PLAY_ORDER, to);
1662                     return res.update(uri, values, null, null) != 0;
1663                 }
1664 
1665                 /**
1666                  * The ID within the playlist.
1667                  */
1668                 public static final String _ID = "_id";
1669 
1670                 /**
1671                  * A subdirectory of each playlist containing all member audio
1672                  * files.
1673                  */
1674                 public static final String CONTENT_DIRECTORY = "members";
1675 
1676                 /**
1677                  * The ID of the audio file
1678                  * <P>Type: INTEGER (long)</P>
1679                  */
1680                 public static final String AUDIO_ID = "audio_id";
1681 
1682                 /**
1683                  * The ID of the playlist
1684                  * <P>Type: INTEGER (long)</P>
1685                  */
1686                 public static final String PLAYLIST_ID = "playlist_id";
1687 
1688                 /**
1689                  * The order of the songs in the playlist
1690                  * <P>Type: INTEGER (long)></P>
1691                  */
1692                 public static final String PLAY_ORDER = "play_order";
1693 
1694                 /**
1695                  * The default sort order for this table
1696                  */
1697                 public static final String DEFAULT_SORT_ORDER = PLAY_ORDER;
1698             }
1699         }
1700 
1701         /**
1702          * Columns representing an artist
1703          */
1704         public interface ArtistColumns {
1705             /**
1706              * The artist who created the audio file, if any
1707              * <P>Type: TEXT</P>
1708              */
1709             public static final String ARTIST = "artist";
1710 
1711             /**
1712              * A non human readable key calculated from the ARTIST, used for
1713              * searching, sorting and grouping
1714              * <P>Type: TEXT</P>
1715              */
1716             public static final String ARTIST_KEY = "artist_key";
1717 
1718             /**
1719              * The number of albums in the database for this artist
1720              */
1721             public static final String NUMBER_OF_ALBUMS = "number_of_albums";
1722 
1723             /**
1724              * The number of albums in the database for this artist
1725              */
1726             public static final String NUMBER_OF_TRACKS = "number_of_tracks";
1727         }
1728 
1729         /**
1730          * Contains artists for audio files
1731          */
1732         public static final class Artists implements BaseColumns, ArtistColumns {
1733             /**
1734              * Get the content:// style URI for the artists table on the
1735              * given volume.
1736              *
1737              * @param volumeName the name of the volume to get the URI for
1738              * @return the URI to the audio artists table on the given volume
1739              */
getContentUri(String volumeName)1740             public static Uri getContentUri(String volumeName) {
1741                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1742                         "/audio/artists");
1743             }
1744 
1745             /**
1746              * The content:// style URI for the internal storage.
1747              */
1748             public static final Uri INTERNAL_CONTENT_URI =
1749                     getContentUri("internal");
1750 
1751             /**
1752              * The content:// style URI for the "primary" external storage
1753              * volume.
1754              */
1755             public static final Uri EXTERNAL_CONTENT_URI =
1756                     getContentUri("external");
1757 
1758             /**
1759              * The MIME type for this table.
1760              */
1761             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/artists";
1762 
1763             /**
1764              * The MIME type for entries in this table.
1765              */
1766             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/artist";
1767 
1768             /**
1769              * The default sort order for this table
1770              */
1771             public static final String DEFAULT_SORT_ORDER = ARTIST_KEY;
1772 
1773             /**
1774              * Sub-directory of each artist containing all albums on which
1775              * a song by the artist appears.
1776              */
1777             public static final class Albums implements AlbumColumns {
getContentUri(String volumeName, long artistId)1778                 public static final Uri getContentUri(String volumeName,
1779                         long artistId) {
1780                     return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName
1781                             + "/audio/artists/" + artistId + "/albums");
1782                 }
1783             }
1784         }
1785 
1786         /**
1787          * Columns representing an album
1788          */
1789         public interface AlbumColumns {
1790 
1791             /**
1792              * The id for the album
1793              * <P>Type: INTEGER</P>
1794              */
1795             public static final String ALBUM_ID = "album_id";
1796 
1797             /**
1798              * The album on which the audio file appears, if any
1799              * <P>Type: TEXT</P>
1800              */
1801             public static final String ALBUM = "album";
1802 
1803             /**
1804              * The artist whose songs appear on this album
1805              * <P>Type: TEXT</P>
1806              */
1807             public static final String ARTIST = "artist";
1808 
1809             /**
1810              * The number of songs on this album
1811              * <P>Type: INTEGER</P>
1812              */
1813             public static final String NUMBER_OF_SONGS = "numsongs";
1814 
1815             /**
1816              * This column is available when getting album info via artist,
1817              * and indicates the number of songs on the album by the given
1818              * artist.
1819              * <P>Type: INTEGER</P>
1820              */
1821             public static final String NUMBER_OF_SONGS_FOR_ARTIST = "numsongs_by_artist";
1822 
1823             /**
1824              * The year in which the earliest songs
1825              * on this album were released. This will often
1826              * be the same as {@link #LAST_YEAR}, but for compilation albums
1827              * they might differ.
1828              * <P>Type: INTEGER</P>
1829              */
1830             public static final String FIRST_YEAR = "minyear";
1831 
1832             /**
1833              * The year in which the latest songs
1834              * on this album were released. This will often
1835              * be the same as {@link #FIRST_YEAR}, but for compilation albums
1836              * they might differ.
1837              * <P>Type: INTEGER</P>
1838              */
1839             public static final String LAST_YEAR = "maxyear";
1840 
1841             /**
1842              * A non human readable key calculated from the ALBUM, used for
1843              * searching, sorting and grouping
1844              * <P>Type: TEXT</P>
1845              */
1846             public static final String ALBUM_KEY = "album_key";
1847 
1848             /**
1849              * Cached album art.
1850              * <P>Type: TEXT</P>
1851              */
1852             public static final String ALBUM_ART = "album_art";
1853         }
1854 
1855         /**
1856          * Contains artists for audio files
1857          */
1858         public static final class Albums implements BaseColumns, AlbumColumns {
1859             /**
1860              * Get the content:// style URI for the albums table on the
1861              * given volume.
1862              *
1863              * @param volumeName the name of the volume to get the URI for
1864              * @return the URI to the audio albums table on the given volume
1865              */
getContentUri(String volumeName)1866             public static Uri getContentUri(String volumeName) {
1867                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
1868                         "/audio/albums");
1869             }
1870 
1871             /**
1872              * The content:// style URI for the internal storage.
1873              */
1874             public static final Uri INTERNAL_CONTENT_URI =
1875                     getContentUri("internal");
1876 
1877             /**
1878              * The content:// style URI for the "primary" external storage
1879              * volume.
1880              */
1881             public static final Uri EXTERNAL_CONTENT_URI =
1882                     getContentUri("external");
1883 
1884             /**
1885              * The MIME type for this table.
1886              */
1887             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/albums";
1888 
1889             /**
1890              * The MIME type for entries in this table.
1891              */
1892             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/album";
1893 
1894             /**
1895              * The default sort order for this table
1896              */
1897             public static final String DEFAULT_SORT_ORDER = ALBUM_KEY;
1898         }
1899 
1900         public static final class Radio {
1901             /**
1902              * The MIME type for entries in this table.
1903              */
1904             public static final String ENTRY_CONTENT_TYPE = "vnd.android.cursor.item/radio";
1905 
1906             // Not instantiable.
Radio()1907             private Radio() { }
1908         }
1909     }
1910 
1911     public static final class Video {
1912 
1913         /**
1914          * The default sort order for this table.
1915          */
1916         public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
1917 
query(ContentResolver cr, Uri uri, String[] projection)1918         public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
1919             return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
1920         }
1921 
1922         public interface VideoColumns extends MediaColumns {
1923 
1924             /**
1925              * The duration of the video file, in ms
1926              * <P>Type: INTEGER (long)</P>
1927              */
1928             public static final String DURATION = "duration";
1929 
1930             /**
1931              * The artist who created the video file, if any
1932              * <P>Type: TEXT</P>
1933              */
1934             public static final String ARTIST = "artist";
1935 
1936             /**
1937              * The album the video file is from, if any
1938              * <P>Type: TEXT</P>
1939              */
1940             public static final String ALBUM = "album";
1941 
1942             /**
1943              * The resolution of the video file, formatted as "XxY"
1944              * <P>Type: TEXT</P>
1945              */
1946             public static final String RESOLUTION = "resolution";
1947 
1948             /**
1949              * The description of the video recording
1950              * <P>Type: TEXT</P>
1951              */
1952             public static final String DESCRIPTION = "description";
1953 
1954             /**
1955              * Whether the video should be published as public or private
1956              * <P>Type: INTEGER</P>
1957              */
1958             public static final String IS_PRIVATE = "isprivate";
1959 
1960             /**
1961              * The user-added tags associated with a video
1962              * <P>Type: TEXT</P>
1963              */
1964             public static final String TAGS = "tags";
1965 
1966             /**
1967              * The YouTube category of the video
1968              * <P>Type: TEXT</P>
1969              */
1970             public static final String CATEGORY = "category";
1971 
1972             /**
1973              * The language of the video
1974              * <P>Type: TEXT</P>
1975              */
1976             public static final String LANGUAGE = "language";
1977 
1978             /**
1979              * The latitude where the video was captured.
1980              * <P>Type: DOUBLE</P>
1981              */
1982             public static final String LATITUDE = "latitude";
1983 
1984             /**
1985              * The longitude where the video was captured.
1986              * <P>Type: DOUBLE</P>
1987              */
1988             public static final String LONGITUDE = "longitude";
1989 
1990             /**
1991              * The date & time that the video was taken in units
1992              * of milliseconds since jan 1, 1970.
1993              * <P>Type: INTEGER</P>
1994              */
1995             public static final String DATE_TAKEN = "datetaken";
1996 
1997             /**
1998              * The mini thumb id.
1999              * <P>Type: INTEGER</P>
2000              */
2001             public static final String MINI_THUMB_MAGIC = "mini_thumb_magic";
2002 
2003             /**
2004              * The bucket id of the video. This is a read-only property that
2005              * is automatically computed from the DATA column.
2006              * <P>Type: TEXT</P>
2007              */
2008             public static final String BUCKET_ID = "bucket_id";
2009 
2010             /**
2011              * The bucket display name of the video. This is a read-only property that
2012              * is automatically computed from the DATA column.
2013              * <P>Type: TEXT</P>
2014              */
2015             public static final String BUCKET_DISPLAY_NAME = "bucket_display_name";
2016 
2017             /**
2018              * The bookmark for the video. Time in ms. Represents the location in the video that the
2019              * video should start playing at the next time it is opened. If the value is null or
2020              * out of the range 0..DURATION-1 then the video should start playing from the
2021              * beginning.
2022              * <P>Type: INTEGER</P>
2023              */
2024             public static final String BOOKMARK = "bookmark";
2025         }
2026 
2027         public static final class Media implements VideoColumns {
2028             /**
2029              * Get the content:// style URI for the video media table on the
2030              * given volume.
2031              *
2032              * @param volumeName the name of the volume to get the URI for
2033              * @return the URI to the video media table on the given volume
2034              */
getContentUri(String volumeName)2035             public static Uri getContentUri(String volumeName) {
2036                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2037                         "/video/media");
2038             }
2039 
2040             /**
2041              * The content:// style URI for the internal storage.
2042              */
2043             public static final Uri INTERNAL_CONTENT_URI =
2044                     getContentUri("internal");
2045 
2046             /**
2047              * The content:// style URI for the "primary" external storage
2048              * volume.
2049              */
2050             public static final Uri EXTERNAL_CONTENT_URI =
2051                     getContentUri("external");
2052 
2053             /**
2054              * The MIME type for this table.
2055              */
2056             public static final String CONTENT_TYPE = "vnd.android.cursor.dir/video";
2057 
2058             /**
2059              * The default sort order for this table
2060              */
2061             public static final String DEFAULT_SORT_ORDER = TITLE;
2062         }
2063 
2064         /**
2065          * This class allows developers to query and get two kinds of thumbnails:
2066          * MINI_KIND: 512 x 384 thumbnail
2067          * MICRO_KIND: 96 x 96 thumbnail
2068          *
2069          */
2070         public static class Thumbnails implements BaseColumns {
2071             /**
2072              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2073              * interrupted and return immediately. Only the original process which made the getThumbnail
2074              * requests can cancel their own requests.
2075              *
2076              * @param cr ContentResolver
2077              * @param origId original video id
2078              */
cancelThumbnailRequest(ContentResolver cr, long origId)2079             public static void cancelThumbnailRequest(ContentResolver cr, long origId) {
2080                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI,
2081                         InternalThumbnails.DEFAULT_GROUP_ID);
2082             }
2083 
2084             /**
2085              * This method checks if the thumbnails of the specified image (origId) has been created.
2086              * It will be blocked until the thumbnails are generated.
2087              *
2088              * @param cr ContentResolver used to dispatch queries to MediaProvider.
2089              * @param origId Original image id associated with thumbnail of interest.
2090              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
2091              * @param options this is only used for MINI_KIND when decoding the Bitmap
2092              * @return A Bitmap instance. It could be null if the original image
2093              *         associated with origId doesn't exist or memory is not enough.
2094              */
getThumbnail(ContentResolver cr, long origId, int kind, BitmapFactory.Options options)2095             public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
2096                     BitmapFactory.Options options) {
2097                 return InternalThumbnails.getThumbnail(cr, origId,
2098                         InternalThumbnails.DEFAULT_GROUP_ID, kind, options,
2099                         EXTERNAL_CONTENT_URI, true);
2100             }
2101 
2102             /**
2103              * This method checks if the thumbnails of the specified image (origId) has been created.
2104              * It will be blocked until the thumbnails are generated.
2105              *
2106              * @param cr ContentResolver used to dispatch queries to MediaProvider.
2107              * @param origId Original image id associated with thumbnail of interest.
2108              * @param groupId the id of group to which this request belongs
2109              * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
2110              * @param options this is only used for MINI_KIND when decoding the Bitmap
2111              * @return A Bitmap instance. It could be null if the original image associated with
2112              *         origId doesn't exist or memory is not enough.
2113              */
getThumbnail(ContentResolver cr, long origId, long groupId, int kind, BitmapFactory.Options options)2114             public static Bitmap getThumbnail(ContentResolver cr, long origId, long groupId,
2115                     int kind, BitmapFactory.Options options) {
2116                 return InternalThumbnails.getThumbnail(cr, origId, groupId, kind, options,
2117                         EXTERNAL_CONTENT_URI, true);
2118             }
2119 
2120             /**
2121              * This method cancels the thumbnail request so clients waiting for getThumbnail will be
2122              * interrupted and return immediately. Only the original process which made the getThumbnail
2123              * requests can cancel their own requests.
2124              *
2125              * @param cr ContentResolver
2126              * @param origId original video id
2127              * @param groupId the same groupId used in getThumbnail.
2128              */
cancelThumbnailRequest(ContentResolver cr, long origId, long groupId)2129             public static void cancelThumbnailRequest(ContentResolver cr, long origId, long groupId) {
2130                 InternalThumbnails.cancelThumbnailRequest(cr, origId, EXTERNAL_CONTENT_URI, groupId);
2131             }
2132 
2133             /**
2134              * Get the content:// style URI for the image media table on the
2135              * given volume.
2136              *
2137              * @param volumeName the name of the volume to get the URI for
2138              * @return the URI to the image media table on the given volume
2139              */
getContentUri(String volumeName)2140             public static Uri getContentUri(String volumeName) {
2141                 return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
2142                         "/video/thumbnails");
2143             }
2144 
2145             /**
2146              * The content:// style URI for the internal storage.
2147              */
2148             public static final Uri INTERNAL_CONTENT_URI =
2149                     getContentUri("internal");
2150 
2151             /**
2152              * The content:// style URI for the "primary" external storage
2153              * volume.
2154              */
2155             public static final Uri EXTERNAL_CONTENT_URI =
2156                     getContentUri("external");
2157 
2158             /**
2159              * The default sort order for this table
2160              */
2161             public static final String DEFAULT_SORT_ORDER = "video_id ASC";
2162 
2163             /**
2164              * The data stream for the thumbnail
2165              * <P>Type: DATA STREAM</P>
2166              */
2167             public static final String DATA = "_data";
2168 
2169             /**
2170              * The original image for the thumbnal
2171              * <P>Type: INTEGER (ID from Video table)</P>
2172              */
2173             public static final String VIDEO_ID = "video_id";
2174 
2175             /**
2176              * The kind of the thumbnail
2177              * <P>Type: INTEGER (One of the values below)</P>
2178              */
2179             public static final String KIND = "kind";
2180 
2181             public static final int MINI_KIND = 1;
2182             public static final int FULL_SCREEN_KIND = 2;
2183             public static final int MICRO_KIND = 3;
2184 
2185             /**
2186              * The width of the thumbnal
2187              * <P>Type: INTEGER (long)</P>
2188              */
2189             public static final String WIDTH = "width";
2190 
2191             /**
2192              * The height of the thumbnail
2193              * <P>Type: INTEGER (long)</P>
2194              */
2195             public static final String HEIGHT = "height";
2196         }
2197     }
2198 
2199     /**
2200      * Uri for querying the state of the media scanner.
2201      */
getMediaScannerUri()2202     public static Uri getMediaScannerUri() {
2203         return Uri.parse(CONTENT_AUTHORITY_SLASH + "none/media_scanner");
2204     }
2205 
2206     /**
2207      * Name of current volume being scanned by the media scanner.
2208      */
2209     public static final String MEDIA_SCANNER_VOLUME = "volume";
2210 
2211     /**
2212      * Name of the file signaling the media scanner to ignore media in the containing directory
2213      * and its subdirectories. Developers should use this to avoid application graphics showing
2214      * up in the Gallery and likewise prevent application sounds and music from showing up in
2215      * the Music app.
2216      */
2217     public static final String MEDIA_IGNORE_FILENAME = ".nomedia";
2218 
2219     /**
2220      * Get the media provider's version.
2221      * Applications that import data from the media provider into their own caches
2222      * can use this to detect that the media provider changed, and reimport data
2223      * as needed. No other assumptions should be made about the meaning of the version.
2224      * @param context Context to use for performing the query.
2225      * @return A version string, or null if the version could not be determined.
2226      */
getVersion(Context context)2227     public static String getVersion(Context context) {
2228         Cursor c = context.getContentResolver().query(
2229                 Uri.parse(CONTENT_AUTHORITY_SLASH + "none/version"),
2230                 null, null, null, null);
2231         if (c != null) {
2232             try {
2233                 if (c.moveToFirst()) {
2234                     return c.getString(0);
2235                 }
2236             } finally {
2237                 c.close();
2238             }
2239         }
2240         return null;
2241     }
2242 
2243 }
2244