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