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