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