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