am 94cdc3d0: am 830207ca: Merge change 25635 into eclair

Merge commit '94cdc3d0e2c7f4f001177829f6bbf3b52ce07b98'

* commit '94cdc3d0e2c7f4f001177829f6bbf3b52ce07b98':
  Add new thumbnail API.
diff --git a/api/current.xml b/api/current.xml
index 29501b6..9bf0e75 100644
--- a/api/current.xml
+++ b/api/current.xml
@@ -114624,6 +114624,25 @@
 <parameter name="volumeName" type="java.lang.String">
 </parameter>
 </method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
 <method name="query"
  return="android.database.Cursor"
  abstract="false"
@@ -114787,6 +114806,17 @@
  visibility="public"
 >
 </field>
+<field name="THUMB_DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;thumb_data&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
 <field name="WIDTH"
  type="java.lang.String"
  transient="false"
@@ -115005,6 +115035,176 @@
 >
 </field>
 </class>
+<class name="MediaStore.Video.Thumbnails"
+ extends="java.lang.Object"
+ abstract="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<implements name="android.provider.BaseColumns">
+</implements>
+<constructor name="MediaStore.Video.Thumbnails"
+ type="android.provider.MediaStore.Video.Thumbnails"
+ static="false"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</constructor>
+<method name="getContentUri"
+ return="android.net.Uri"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="volumeName" type="java.lang.String">
+</parameter>
+</method>
+<method name="getThumbnail"
+ return="android.graphics.Bitmap"
+ abstract="false"
+ native="false"
+ synchronized="false"
+ static="true"
+ final="false"
+ deprecated="not deprecated"
+ visibility="public"
+>
+<parameter name="cr" type="android.content.ContentResolver">
+</parameter>
+<parameter name="origId" type="long">
+</parameter>
+<parameter name="kind" type="int">
+</parameter>
+<parameter name="options" type="android.graphics.BitmapFactory.Options">
+</parameter>
+</method>
+<field name="DATA"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;_data&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="DEFAULT_SORT_ORDER"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;video_id ASC&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="EXTERNAL_CONTENT_URI"
+ type="android.net.Uri"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="FULL_SCREEN_KIND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="2"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="HEIGHT"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;height&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="INTERNAL_CONTENT_URI"
+ type="android.net.Uri"
+ transient="false"
+ volatile="false"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="KIND"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;kind&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MICRO_KIND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="3"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="MINI_KIND"
+ type="int"
+ transient="false"
+ volatile="false"
+ value="1"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="VIDEO_ID"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;video_id&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+<field name="WIDTH"
+ type="java.lang.String"
+ transient="false"
+ volatile="false"
+ value="&quot;width&quot;"
+ static="true"
+ final="true"
+ deprecated="not deprecated"
+ visibility="public"
+>
+</field>
+</class>
 <interface name="MediaStore.Video.VideoColumns"
  abstract="true"
  static="true"
diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java
index 49b5bb1..106e833 100644
--- a/core/java/android/provider/MediaStore.java
+++ b/core/java/android/provider/MediaStore.java
@@ -23,11 +23,15 @@
 import android.content.ContentUris;
 import android.database.Cursor;
 import android.database.DatabaseUtils;
+import android.database.sqlite.SQLiteException;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Matrix;
+import android.media.MiniThumbFile;
+import android.media.ThumbnailUtil;
 import android.net.Uri;
 import android.os.Environment;
+import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
 import java.io.FileInputStream;
@@ -42,8 +46,7 @@
  * The Media provider contains meta data for all available media on both internal
  * and external storage devices.
  */
-public final class MediaStore
-{
+public final class MediaStore {
     private final static String TAG = "MediaStore";
 
     public static final String AUTHORITY = "media";
@@ -179,7 +182,7 @@
      * Common fields for most MediaProvider tables
      */
 
-     public interface MediaColumns extends BaseColumns {
+    public interface MediaColumns extends BaseColumns {
         /**
          * The data stream for the file
          * <P>Type: DATA STREAM</P>
@@ -227,10 +230,128 @@
      }
 
     /**
+     * This class is used internally by Images.Thumbnails and Video.Thumbnails, it's not intended
+     * to be accessed elsewhere.
+     */
+    private static class InternalThumbnails implements BaseColumns {
+        private static final int MINI_KIND = 1;
+        private static final int FULL_SCREEN_KIND = 2;
+        private static final int MICRO_KIND = 3;
+        private static final String[] PROJECTION = new String[] {_ID, MediaColumns.DATA};
+
+        /**
+         * This method ensure thumbnails associated with origId are generated and decode the byte
+         * stream from database (MICRO_KIND) or file (MINI_KIND).
+         *
+         * Special optimization has been done to avoid further IPC communication for MICRO_KIND
+         * thumbnails.
+         *
+         * @param cr ContentResolver
+         * @param origId original image or video id
+         * @param kind could be MINI_KIND or MICRO_KIND
+         * @param options this is only used for MINI_KIND when decoding the Bitmap
+         * @param baseUri the base URI of requested thumbnails
+         * @return Bitmap bitmap of specified thumbnail kind
+         */
+        static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+                BitmapFactory.Options options, Uri baseUri, boolean isVideo) {
+            Bitmap bitmap = null;
+            String filePath = null;
+            // some optimization for MICRO_KIND: if the magic is non-zero, we don't bother
+            // querying MediaProvider and simply return thumbnail.
+            if (kind == MICRO_KIND) {
+                MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
+                if (thumbFile.getMagic(origId) != 0) {
+                    byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+                    if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
+                        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                        if (bitmap == null) {
+                            Log.w(TAG, "couldn't decode byte array.");
+                        }
+                    }
+                    return bitmap;
+                }
+            }
+
+            Cursor c = null;
+            try {
+                Uri blockingUri = baseUri.buildUpon().appendQueryParameter("blocking", "1")
+                        .appendQueryParameter("orig_id", String.valueOf(origId)).build();
+                c = cr.query(blockingUri, PROJECTION, null, null, null);
+                // This happens when original image/video doesn't exist.
+                if (c == null) return null;
+
+                // Assuming thumbnail has been generated, at least original image exists.
+                if (kind == MICRO_KIND) {
+                    MiniThumbFile thumbFile = MiniThumbFile.instance(baseUri);
+                    byte[] data = new byte[MiniThumbFile.BYTES_PER_MINTHUMB];
+                    if (thumbFile.getMiniThumbFromFile(origId, data) != null) {
+                        bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+                        if (bitmap == null) {
+                            Log.w(TAG, "couldn't decode byte array.");
+                        }
+                    }
+                } else if (kind == MINI_KIND) {
+                    if (c.moveToFirst()) {
+                        ParcelFileDescriptor pfdInput;
+                        Uri thumbUri = null;
+                        try {
+                            long thumbId = c.getLong(0);
+                            filePath = c.getString(1);
+                            thumbUri = ContentUris.withAppendedId(baseUri, thumbId);
+                            pfdInput = cr.openFileDescriptor(thumbUri, "r");
+                            bitmap = BitmapFactory.decodeFileDescriptor(
+                                    pfdInput.getFileDescriptor(), null, options);
+                            pfdInput.close();
+                        } catch (FileNotFoundException ex) {
+                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+                        } catch (IOException ex) {
+                            Log.e(TAG, "couldn't open thumbnail " + thumbUri + "; " + ex);
+                        } catch (OutOfMemoryError ex) {
+                            Log.e(TAG, "failed to allocate memory for thumbnail "
+                                    + thumbUri + "; " + ex);
+                        }
+                    }
+                } else {
+                    throw new IllegalArgumentException("Unsupported kind: " + kind);
+                }
+
+                // We probably run out of space, so create the thumbnail in memory.
+                if (bitmap == null) {
+                    Log.v(TAG, "We probably run out of space, so create the thumbnail in memory.");
+                    int targetSize = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_TARGET_SIZE :
+                            ThumbnailUtil.MINI_THUMB_TARGET_SIZE;
+                    int maxPixelNum = kind == MINI_KIND ? ThumbnailUtil.THUMBNAIL_MAX_NUM_PIXELS :
+                            ThumbnailUtil.MINI_THUMB_MAX_NUM_PIXELS;
+                    Uri uri = Uri.parse(
+                            baseUri.buildUpon().appendPath(String.valueOf(origId))
+                                    .toString().replaceFirst("thumbnails", "media"));
+                    if (isVideo) {
+                        c = cr.query(uri, PROJECTION, null, null, null);
+                        if (c != null && c.moveToFirst()) {
+                            bitmap = ThumbnailUtil.createVideoThumbnail(c.getString(1));
+                            if (kind == MICRO_KIND) {
+                                bitmap = ThumbnailUtil.extractMiniThumb(bitmap,
+                                        targetSize, targetSize, ThumbnailUtil.RECYCLE_INPUT);
+                            }
+                        }
+                    } else {
+                        bitmap = ThumbnailUtil.makeBitmap(targetSize, maxPixelNum, uri, cr);
+                    }
+                }
+            } catch (SQLiteException ex) {
+                Log.w(TAG, ex);
+            } finally {
+                if (c != null) c.close();
+            }
+            return bitmap;
+        }
+    }
+
+    /**
      * Contains meta data for all available images.
      */
-    public static final class Images
-    {
+    public static final class Images {
         public interface ImageColumns extends MediaColumns {
             /**
              * The description of the image
@@ -298,21 +419,18 @@
         }
 
         public static final class Media implements ImageColumns {
-            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
-            {
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
             }
 
             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
-                                           String where, String orderBy)
-            {
+                    String where, String orderBy) {
                 return cr.query(uri, projection, where,
                                              null, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
             }
 
             public static final Cursor query(ContentResolver cr, Uri uri, String[] projection,
-                    String selection, String [] selectionArgs, String orderBy)
-            {
+                    String selection, String [] selectionArgs, String orderBy) {
                 return cr.query(uri, projection, selection,
                         selectionArgs, orderBy == null ? DEFAULT_SORT_ORDER : orderBy);
             }
@@ -326,8 +444,7 @@
              * @throws IOException
              */
             public static final Bitmap getBitmap(ContentResolver cr, Uri url)
-                    throws FileNotFoundException, IOException
-            {
+                    throws FileNotFoundException, IOException {
                 InputStream input = cr.openInputStream(url);
                 Bitmap bitmap = BitmapFactory.decodeStream(input);
                 input.close();
@@ -344,9 +461,8 @@
              * @return The URL to the newly created image
              * @throws FileNotFoundException
              */
-            public static final String insertImage(ContentResolver cr, String imagePath, String name,
-                                                   String description) throws FileNotFoundException
-            {
+            public static final String insertImage(ContentResolver cr, String imagePath,
+                    String name, String description) throws FileNotFoundException {
                 // Check if file exists with a FileInputStream
                 FileInputStream stream = new FileInputStream(imagePath);
                 try {
@@ -415,8 +531,7 @@
              *              for any reason.
              */
             public static final String insertImage(ContentResolver cr, Bitmap source,
-                                                   String title, String description)
-            {
+                                                   String title, String description) {
                 ContentValues values = new ContentValues();
                 values.put(Images.Media.TITLE, title);
                 values.put(Images.Media.DESCRIPTION, description);
@@ -425,8 +540,7 @@
                 Uri url = null;
                 String stringUrl = null;    /* value to be returned */
 
-                try
-                {
+                try {
                     url = cr.insert(EXTERNAL_CONTENT_URI, values);
 
                     if (source != null) {
@@ -496,28 +610,48 @@
              * The default sort order for this table
              */
             public static final String DEFAULT_SORT_ORDER = ImageColumns.BUCKET_DISPLAY_NAME;
-       }
+        }
 
-        public static class Thumbnails implements BaseColumns
-        {
-            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
-            {
+        /**
+         * This class allows developers to query and get two kinds of thumbnails:
+         * MINI_KIND: 512 x 384 thumbnail
+         * MICRO_KIND: 96 x 96 thumbnail
+         */
+        public static class Thumbnails implements BaseColumns {
+            public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
                 return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
             }
 
-            public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind, String[] projection)
-            {
+            public static final Cursor queryMiniThumbnails(ContentResolver cr, Uri uri, int kind,
+                    String[] projection) {
                 return cr.query(uri, projection, "kind = " + kind, null, DEFAULT_SORT_ORDER);
             }
 
-            public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind, String[] projection)
-            {
+            public static final Cursor queryMiniThumbnail(ContentResolver cr, long origId, int kind,
+                    String[] projection) {
                 return cr.query(EXTERNAL_CONTENT_URI, projection,
                         IMAGE_ID + " = " + origId + " AND " + KIND + " = " +
                         kind, null, null);
             }
 
             /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND.
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image
+             *         associated with origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+                    BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+                        EXTERNAL_CONTENT_URI, false);
+            }
+
+            /**
              * Get the content:// style URI for the image media table on the
              * given volume.
              *
@@ -568,6 +702,11 @@
             public static final int MINI_KIND = 1;
             public static final int FULL_SCREEN_KIND = 2;
             public static final int MICRO_KIND = 3;
+            /**
+             * The blob raw data of thumbnail
+             * <P>Type: DATA STREAM</P>
+             */
+            public static final String THUMB_DATA = "thumb_data";
 
             /**
              * The width of the thumbnal
@@ -1182,7 +1321,7 @@
              * <P>Type: INTEGER</P>
              */
             public static final String FIRST_YEAR = "minyear";
-            
+
             /**
              * The year in which the latest songs
              * on this album were released. This will often
@@ -1259,8 +1398,7 @@
          */
         public static final String DEFAULT_SORT_ORDER = MediaColumns.DISPLAY_NAME;
 
-        public static final Cursor query(ContentResolver cr, Uri uri, String[] projection)
-        {
+        public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) {
             return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER);
         }
 
@@ -1405,6 +1543,95 @@
              */
             public static final String DEFAULT_SORT_ORDER = TITLE;
         }
+
+        /**
+         * This class allows developers to query and get two kinds of thumbnails:
+         * MINI_KIND: 512 x 384 thumbnail
+         * MICRO_KIND: 96 x 96 thumbnail
+         *
+         */
+        public static class Thumbnails implements BaseColumns {
+            /**
+             * This method checks if the thumbnails of the specified image (origId) has been created.
+             * It will be blocked until the thumbnails are generated.
+             *
+             * @param cr ContentResolver used to dispatch queries to MediaProvider.
+             * @param origId Original image id associated with thumbnail of interest.
+             * @param kind The type of thumbnail to fetch. Should be either MINI_KIND or MICRO_KIND
+             * @param options this is only used for MINI_KIND when decoding the Bitmap
+             * @return A Bitmap instance. It could be null if the original image associated with
+             *         origId doesn't exist or memory is not enough.
+             */
+            public static Bitmap getThumbnail(ContentResolver cr, long origId, int kind,
+                    BitmapFactory.Options options) {
+                return InternalThumbnails.getThumbnail(cr, origId, kind, options,
+                        EXTERNAL_CONTENT_URI, true);
+            }
+
+            /**
+             * Get the content:// style URI for the image media table on the
+             * given volume.
+             *
+             * @param volumeName the name of the volume to get the URI for
+             * @return the URI to the image media table on the given volume
+             */
+            public static Uri getContentUri(String volumeName) {
+                return Uri.parse(CONTENT_AUTHORITY_SLASH + volumeName +
+                        "/video/thumbnails");
+            }
+
+            /**
+             * The content:// style URI for the internal storage.
+             */
+            public static final Uri INTERNAL_CONTENT_URI =
+                    getContentUri("internal");
+
+            /**
+             * The content:// style URI for the "primary" external storage
+             * volume.
+             */
+            public static final Uri EXTERNAL_CONTENT_URI =
+                    getContentUri("external");
+
+            /**
+             * The default sort order for this table
+             */
+            public static final String DEFAULT_SORT_ORDER = "video_id ASC";
+
+            /**
+             * The data stream for the thumbnail
+             * <P>Type: DATA STREAM</P>
+             */
+            public static final String DATA = "_data";
+
+            /**
+             * The original image for the thumbnal
+             * <P>Type: INTEGER (ID from Video table)</P>
+             */
+            public static final String VIDEO_ID = "video_id";
+
+            /**
+             * The kind of the thumbnail
+             * <P>Type: INTEGER (One of the values below)</P>
+             */
+            public static final String KIND = "kind";
+
+            public static final int MINI_KIND = 1;
+            public static final int FULL_SCREEN_KIND = 2;
+            public static final int MICRO_KIND = 3;
+
+            /**
+             * The width of the thumbnal
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String WIDTH = "width";
+
+            /**
+             * The height of the thumbnail
+             * <P>Type: INTEGER (long)</P>
+             */
+            public static final String HEIGHT = "height";
+        }
     }
 
     /**
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 3ac5df5..21ad82b 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -486,6 +486,8 @@
         }
 
         public void scanFile(String path, long lastModified, long fileSize) {
+            // This is the callback funtion from native codes.
+            // Log.v(TAG, "scanFile: "+path);
             doScanFile(path, null, lastModified, fileSize, false);
         }
 
diff --git a/media/java/android/media/MiniThumbFile.java b/media/java/android/media/MiniThumbFile.java
new file mode 100644
index 0000000..c607218
--- /dev/null
+++ b/media/java/android/media/MiniThumbFile.java
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.graphics.Bitmap;
+import android.media.ThumbnailUtil;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileLock;
+import java.util.Hashtable;
+
+/**
+ * This class handles the mini-thumb file. A mini-thumb file consists
+ * of blocks, indexed by id. Each block has BYTES_PER_MINTHUMB bytes in the
+ * following format:
+ *
+ * 1 byte status (0 = empty, 1 = mini-thumb available)
+ * 8 bytes magic (a magic number to match what's in the database)
+ * 4 bytes data length (LEN)
+ * LEN bytes jpeg data
+ * (the remaining bytes are unused)
+ *
+ * @hide This file is shared between MediaStore and MediaProvider and should remained internal use
+ *       only.
+ */
+public class MiniThumbFile {
+    public static final int THUMBNAIL_TARGET_SIZE = 320;
+    public static final int MINI_THUMB_TARGET_SIZE = 96;
+    public static final int THUMBNAIL_MAX_NUM_PIXELS = 512 * 384;
+    public static final int MINI_THUMB_MAX_NUM_PIXELS = 128 * 128;
+    public static final int UNCONSTRAINED = -1;
+
+    private static final String TAG = "MiniThumbFile";
+    private static final int MINI_THUMB_DATA_FILE_VERSION = 3;
+    public static final int BYTES_PER_MINTHUMB = 10000;
+    private static final int HEADER_SIZE = 1 + 8 + 4;
+    private Uri mUri;
+    private RandomAccessFile mMiniThumbFile;
+    private FileChannel mChannel;
+    private static Hashtable<String, MiniThumbFile> sThumbFiles =
+        new Hashtable<String, MiniThumbFile>();
+
+    /**
+     * We store different types of thumbnails in different files. To remain backward compatibility,
+     * we should hashcode of content://media/external/images/media remains the same.
+     */
+    public static synchronized void reset() {
+        sThumbFiles.clear();
+    }
+
+    public static synchronized MiniThumbFile instance(Uri uri) {
+        String type = uri.getPathSegments().get(1);
+        MiniThumbFile file = sThumbFiles.get(type);
+        // Log.v(TAG, "get minithumbfile for type: "+type);
+        if (file == null) {
+            file = new MiniThumbFile(
+                    Uri.parse("content://media/external/" + type + "/media"));
+            sThumbFiles.put(type, file);
+        }
+
+        return file;
+    }
+
+    private String randomAccessFilePath(int version) {
+        String directoryName =
+                Environment.getExternalStorageDirectory().toString()
+                + "/DCIM/.thumbnails";
+        return directoryName + "/.thumbdata" + version + "-" + mUri.hashCode();
+    }
+
+    private void removeOldFile() {
+        String oldPath = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION - 1);
+        File oldFile = new File(oldPath);
+        if (oldFile.exists()) {
+            try {
+                oldFile.delete();
+            } catch (SecurityException ex) {
+                // ignore
+            }
+        }
+    }
+
+    private RandomAccessFile miniThumbDataFile() {
+        if (mMiniThumbFile == null) {
+            removeOldFile();
+            String path = randomAccessFilePath(MINI_THUMB_DATA_FILE_VERSION);
+            File directory = new File(path).getParentFile();
+            if (!directory.isDirectory()) {
+                if (!directory.mkdirs()) {
+                    Log.e(TAG, "Unable to create .thumbnails directory "
+                            + directory.toString());
+                }
+            }
+            File f = new File(path);
+            try {
+                mMiniThumbFile = new RandomAccessFile(f, "rw");
+            } catch (IOException ex) {
+                // Open as read-only so we can at least read the existing
+                // thumbnails.
+                try {
+                    mMiniThumbFile = new RandomAccessFile(f, "r");
+                } catch (IOException ex2) {
+                    // ignore exception
+                }
+            }
+            mChannel = mMiniThumbFile.getChannel();
+        }
+        return mMiniThumbFile;
+    }
+
+    public MiniThumbFile(Uri uri) {
+        mUri = uri;
+    }
+
+    public synchronized void deactivate() {
+        if (mMiniThumbFile != null) {
+            try {
+                mMiniThumbFile.close();
+                mMiniThumbFile = null;
+            } catch (IOException ex) {
+                // ignore exception
+            }
+        }
+    }
+
+    // Get the magic number for the specified id in the mini-thumb file.
+    // Returns 0 if the magic is not available.
+    public long getMagic(long id) {
+        // check the mini thumb file for the right data.  Right is
+        // defined as having the right magic number at the offset
+        // reserved for this "id".
+        RandomAccessFile r = miniThumbDataFile();
+        if (r != null) {
+            long pos = id * BYTES_PER_MINTHUMB;
+            FileLock lock = null;
+            try {
+                lock = mChannel.lock();
+                // check that we can read the following 9 bytes
+                // (1 for the "status" and 8 for the long)
+                if (r.length() >= pos + 1 + 8) {
+                    r.seek(pos);
+                    if (r.readByte() == 1) {
+                        long fileMagic = r.readLong();
+                        return fileMagic;
+                    }
+                }
+            } catch (IOException ex) {
+                Log.v(TAG, "Got exception checking file magic: ", ex);
+            } catch (RuntimeException ex) {
+                // Other NIO related exception like disk full, read only channel..etc
+                Log.e(TAG, "Got exception when reading magic, id = " + id +
+                        ", disk full or mount read-only? " + ex.getClass());
+            } finally {
+                try {
+                    if (lock != null) lock.release();
+                }
+                catch (IOException ex) {
+                    // ignore it.
+                }
+            }
+        }
+        return 0;
+    }
+
+    public void saveMiniThumbToFile(Bitmap bitmap, long id, long magic)
+            throws IOException {
+        byte[] data = ThumbnailUtil.miniThumbData(bitmap);
+        saveMiniThumbToFile(data, id, magic);
+    }
+
+    public void saveMiniThumbToFile(byte[] data, long id, long magic)
+            throws IOException {
+        RandomAccessFile r = miniThumbDataFile();
+        if (r == null) return;
+
+        long pos = id * BYTES_PER_MINTHUMB;
+        FileLock lock = null;
+        try {
+            lock = mChannel.lock();
+            if (data != null) {
+                if (data.length > BYTES_PER_MINTHUMB - HEADER_SIZE) {
+                    // not enough space to store it.
+                    return;
+                }
+                r.seek(pos);
+                r.writeByte(0);     // we have no data in this slot
+
+                // if magic is 0 then leave it alone
+                if (magic == 0) {
+                    r.skipBytes(8);
+                } else {
+                    r.writeLong(magic);
+                }
+                r.writeInt(data.length);
+                r.write(data);
+                r.seek(pos);
+                r.writeByte(1);  // we have data in this slot
+                mChannel.force(true);
+            }
+        } catch (IOException ex) {
+            Log.e(TAG, "couldn't save mini thumbnail data for "
+                    + id + "; ", ex);
+            throw ex;
+        } catch (RuntimeException ex) {
+            // Other NIO related exception like disk full, read only channel..etc
+            Log.e(TAG, "couldn't save mini thumbnail data for "
+                    + id + "; disk full or mount read-only? " + ex.getClass());
+        } finally {
+            try {
+                if (lock != null) lock.release();
+            }
+            catch (IOException ex) {
+                // ignore it.
+            }
+        }
+    }
+
+    /**
+     * Gallery app can use this method to retrieve mini-thumbnail. Full size
+     * images share the same IDs with their corresponding thumbnails.
+     *
+     * @param id the ID of the image (same of full size image).
+     * @param data the buffer to store mini-thumbnail.
+     */
+    public byte [] getMiniThumbFromFile(long id, byte [] data) {
+        RandomAccessFile r = miniThumbDataFile();
+        if (r == null) return null;
+
+        long pos = id * BYTES_PER_MINTHUMB;
+        FileLock lock = null;
+        try {
+            lock = mChannel.lock();
+            r.seek(pos);
+            if (r.readByte() == 1) {
+                long magic = r.readLong();
+                int length = r.readInt();
+                int got = r.read(data, 0, length);
+                if (got != length) return null;
+                return data;
+            } else {
+                return null;
+            }
+        } catch (IOException ex) {
+            Log.w(TAG, "got exception when reading thumbnail: " + ex);
+            return null;
+        } catch (RuntimeException ex) {
+            // Other NIO related exception like disk full, read only channel..etc
+            Log.e(TAG, "Got exception when reading thumbnail, id = " + id +
+                    ", disk full or mount read-only? " + ex.getClass());
+        } finally {
+            try {
+                if (lock != null) lock.release();
+            }
+            catch (IOException ex) {
+                // ignore it.
+            }
+        }
+        return null;
+    }
+}
diff --git a/media/java/android/media/ThumbnailUtil.java b/media/java/android/media/ThumbnailUtil.java
new file mode 100644
index 0000000..3db10b8
--- /dev/null
+++ b/media/java/android/media/ThumbnailUtil.java
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media;
+
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+import android.content.ContentResolver;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.media.MediaMetadataRetriever;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Thumbnail generation routines for media provider. This class should only be used internaly.
+ * {@hide} THIS IS NOT FOR PUBLIC API.
+ */
+
+public class ThumbnailUtil {
+    private static final String TAG = "ThumbnailUtil";
+    //Whether we should recycle the input (unless the output is the input).
+    public static final boolean RECYCLE_INPUT = true;
+    public static final boolean NO_RECYCLE_INPUT = false;
+    public static final boolean ROTATE_AS_NEEDED = true;
+    public static final boolean NO_ROTATE = false;
+    public static final boolean USE_NATIVE = true;
+    public static final boolean NO_NATIVE = false;
+
+    public static final int THUMBNAIL_TARGET_SIZE = 320;
+    public static final int MINI_THUMB_TARGET_SIZE = 96;
+    public static final int THUMBNAIL_MAX_NUM_PIXELS = 512 * 384;
+    public static final int MINI_THUMB_MAX_NUM_PIXELS = 128 * 128;
+    public static final int UNCONSTRAINED = -1;
+
+    // Returns Options that set the native alloc flag for Bitmap decode.
+    public static BitmapFactory.Options createNativeAllocOptions() {
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inNativeAlloc = true;
+        return options;
+    }
+    /**
+     * Make a bitmap from a given Uri.
+     *
+     * @param uri
+     */
+    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+            Uri uri, ContentResolver cr) {
+        return makeBitmap(minSideLength, maxNumOfPixels, uri, cr,
+                NO_NATIVE);
+    }
+
+    /*
+     * Compute the sample size as a function of minSideLength
+     * and maxNumOfPixels.
+     * minSideLength is used to specify that minimal width or height of a
+     * bitmap.
+     * maxNumOfPixels is used to specify the maximal size in pixels that is
+     * tolerable in terms of memory usage.
+     *
+     * The function returns a sample size based on the constraints.
+     * Both size and minSideLength can be passed in as IImage.UNCONSTRAINED,
+     * which indicates no care of the corresponding constraint.
+     * The functions prefers returning a sample size that
+     * generates a smaller bitmap, unless minSideLength = IImage.UNCONSTRAINED.
+     *
+     * Also, the function rounds up the sample size to a power of 2 or multiple
+     * of 8 because BitmapFactory only honors sample size this way.
+     * For example, BitmapFactory downsamples an image by 2 even though the
+     * request is 3. So we round up the sample size to avoid OOM.
+     */
+    public static int computeSampleSize(BitmapFactory.Options options,
+            int minSideLength, int maxNumOfPixels) {
+        int initialSize = computeInitialSampleSize(options, minSideLength,
+                maxNumOfPixels);
+
+        int roundedSize;
+        if (initialSize <= 8 ) {
+            roundedSize = 1;
+            while (roundedSize < initialSize) {
+                roundedSize <<= 1;
+            }
+        } else {
+            roundedSize = (initialSize + 7) / 8 * 8;
+        }
+
+        return roundedSize;
+    }
+
+    private static int computeInitialSampleSize(BitmapFactory.Options options,
+            int minSideLength, int maxNumOfPixels) {
+        double w = options.outWidth;
+        double h = options.outHeight;
+
+        int lowerBound = (maxNumOfPixels == UNCONSTRAINED) ? 1 :
+                (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));
+        int upperBound = (minSideLength == UNCONSTRAINED) ? 128 :
+                (int) Math.min(Math.floor(w / minSideLength),
+                Math.floor(h / minSideLength));
+
+        if (upperBound < lowerBound) {
+            // return the larger one when there is no overlapping zone.
+            return lowerBound;
+        }
+
+        if ((maxNumOfPixels == UNCONSTRAINED) &&
+                (minSideLength == UNCONSTRAINED)) {
+            return 1;
+        } else if (minSideLength == UNCONSTRAINED) {
+            return lowerBound;
+        } else {
+            return upperBound;
+        }
+    }
+
+    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+            Uri uri, ContentResolver cr, boolean useNative) {
+        ParcelFileDescriptor input = null;
+        try {
+            input = cr.openFileDescriptor(uri, "r");
+            BitmapFactory.Options options = null;
+            if (useNative) {
+                options = createNativeAllocOptions();
+            }
+            return makeBitmap(minSideLength, maxNumOfPixels, uri, cr, input,
+                    options);
+        } catch (IOException ex) {
+            Log.e(TAG, "", ex);
+            return null;
+        } finally {
+            closeSilently(input);
+        }
+    }
+
+    // Rotates the bitmap by the specified degree.
+    // If a new bitmap is created, the original bitmap is recycled.
+    public static Bitmap rotate(Bitmap b, int degrees) {
+        if (degrees != 0 && b != null) {
+            Matrix m = new Matrix();
+            m.setRotate(degrees,
+                    (float) b.getWidth() / 2, (float) b.getHeight() / 2);
+            try {
+                Bitmap b2 = Bitmap.createBitmap(
+                        b, 0, 0, b.getWidth(), b.getHeight(), m, true);
+                if (b != b2) {
+                    b.recycle();
+                    b = b2;
+                }
+            } catch (OutOfMemoryError ex) {
+                // We have no memory to rotate. Return the original bitmap.
+            }
+        }
+        return b;
+    }
+
+    private static void closeSilently(ParcelFileDescriptor c) {
+      if (c == null) return;
+      try {
+          c.close();
+      } catch (Throwable t) {
+          // do nothing
+      }
+    }
+
+    private static ParcelFileDescriptor makeInputStream(
+            Uri uri, ContentResolver cr) {
+        try {
+            return cr.openFileDescriptor(uri, "r");
+        } catch (IOException ex) {
+            return null;
+        }
+    }
+
+    public static Bitmap makeBitmap(int minSideLength, int maxNumOfPixels,
+        Uri uri, ContentResolver cr, ParcelFileDescriptor pfd,
+        BitmapFactory.Options options) {
+        Bitmap b = null;
+        try {
+            if (pfd == null) pfd = makeInputStream(uri, cr);
+            if (pfd == null) return null;
+            if (options == null) options = new BitmapFactory.Options();
+
+            FileDescriptor fd = pfd.getFileDescriptor();
+            options.inSampleSize = 1;
+            options.inJustDecodeBounds = true;
+            BitmapFactory.decodeFileDescriptor(fd, null, options);
+            if (options.mCancel || options.outWidth == -1
+                    || options.outHeight == -1) {
+                return null;
+            }
+            options.inSampleSize = computeSampleSize(
+                    options, minSideLength, maxNumOfPixels);
+            options.inJustDecodeBounds = false;
+
+            options.inDither = false;
+            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            b = BitmapFactory.decodeFileDescriptor(fd, null, options);
+        } catch (OutOfMemoryError ex) {
+            Log.e(TAG, "Got oom exception ", ex);
+            return null;
+        } finally {
+            closeSilently(pfd);
+        }
+        return b;
+    }
+
+    /**
+     * Creates a centered bitmap of the desired size.
+     * @param source
+     * @param recycle whether we want to recycle the input
+     */
+    public static Bitmap extractMiniThumb(
+            Bitmap source, int width, int height, boolean recycle) {
+        if (source == null) {
+            return null;
+        }
+
+        float scale;
+        if (source.getWidth() < source.getHeight()) {
+            scale = width / (float) source.getWidth();
+        } else {
+            scale = height / (float) source.getHeight();
+        }
+        Matrix matrix = new Matrix();
+        matrix.setScale(scale, scale);
+        Bitmap miniThumbnail = transform(matrix, source, width, height, false, recycle);
+        return miniThumbnail;
+    }
+
+    /**
+     * Creates a byte[] for a given bitmap of the desired size. Recycles the
+     * input bitmap.
+     */
+    public static byte[] miniThumbData(Bitmap source) {
+        if (source == null) return null;
+
+        Bitmap miniThumbnail = extractMiniThumb(
+                source, MINI_THUMB_TARGET_SIZE,
+                MINI_THUMB_TARGET_SIZE,
+                RECYCLE_INPUT);
+
+        ByteArrayOutputStream miniOutStream = new ByteArrayOutputStream();
+        miniThumbnail.compress(Bitmap.CompressFormat.JPEG, 75, miniOutStream);
+        miniThumbnail.recycle();
+
+        try {
+            miniOutStream.close();
+            byte [] data = miniOutStream.toByteArray();
+            return data;
+        } catch (java.io.IOException ex) {
+            Log.e(TAG, "got exception ex " + ex);
+        }
+        return null;
+    }
+
+    /**
+     * Create a video thumbnail for a video. May return null if the video is
+     * corrupt.
+     *
+     * @param filePath
+     */
+    public static Bitmap createVideoThumbnail(String filePath) {
+        Bitmap bitmap = null;
+        MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+        try {
+            retriever.setMode(MediaMetadataRetriever.MODE_CAPTURE_FRAME_ONLY);
+            retriever.setDataSource(filePath);
+            bitmap = retriever.captureFrame();
+        } catch (IllegalArgumentException ex) {
+            // Assume this is a corrupt video file
+        } catch (RuntimeException ex) {
+            // Assume this is a corrupt video file.
+        } finally {
+            try {
+                retriever.release();
+            } catch (RuntimeException ex) {
+                // Ignore failures while cleaning up.
+            }
+        }
+        return bitmap;
+    }
+
+    public static Bitmap transform(Matrix scaler,
+            Bitmap source,
+            int targetWidth,
+            int targetHeight,
+            boolean scaleUp,
+            boolean recycle) {
+
+        int deltaX = source.getWidth() - targetWidth;
+        int deltaY = source.getHeight() - targetHeight;
+        if (!scaleUp && (deltaX < 0 || deltaY < 0)) {
+            /*
+            * In this case the bitmap is smaller, at least in one dimension,
+            * than the target.  Transform it by placing as much of the image
+            * as possible into the target and leaving the top/bottom or
+            * left/right (or both) black.
+            */
+            Bitmap b2 = Bitmap.createBitmap(targetWidth, targetHeight,
+            Bitmap.Config.ARGB_8888);
+            Canvas c = new Canvas(b2);
+
+            int deltaXHalf = Math.max(0, deltaX / 2);
+            int deltaYHalf = Math.max(0, deltaY / 2);
+            Rect src = new Rect(
+            deltaXHalf,
+            deltaYHalf,
+            deltaXHalf + Math.min(targetWidth, source.getWidth()),
+            deltaYHalf + Math.min(targetHeight, source.getHeight()));
+            int dstX = (targetWidth  - src.width())  / 2;
+            int dstY = (targetHeight - src.height()) / 2;
+            Rect dst = new Rect(
+                    dstX,
+                    dstY,
+                    targetWidth - dstX,
+                    targetHeight - dstY);
+            c.drawBitmap(source, src, dst, null);
+            if (recycle) {
+                source.recycle();
+            }
+            return b2;
+        }
+        float bitmapWidthF = source.getWidth();
+        float bitmapHeightF = source.getHeight();
+
+        float bitmapAspect = bitmapWidthF / bitmapHeightF;
+        float viewAspect   = (float) targetWidth / targetHeight;
+
+        if (bitmapAspect > viewAspect) {
+            float scale = targetHeight / bitmapHeightF;
+            if (scale < .9F || scale > 1F) {
+                scaler.setScale(scale, scale);
+            } else {
+                scaler = null;
+            }
+        } else {
+            float scale = targetWidth / bitmapWidthF;
+            if (scale < .9F || scale > 1F) {
+                scaler.setScale(scale, scale);
+            } else {
+                scaler = null;
+            }
+        }
+
+        Bitmap b1;
+        if (scaler != null) {
+            // this is used for minithumb and crop, so we want to filter here.
+            b1 = Bitmap.createBitmap(source, 0, 0,
+            source.getWidth(), source.getHeight(), scaler, true);
+        } else {
+            b1 = source;
+        }
+
+        if (recycle && b1 != source) {
+            source.recycle();
+        }
+
+        int dx1 = Math.max(0, b1.getWidth() - targetWidth);
+        int dy1 = Math.max(0, b1.getHeight() - targetHeight);
+
+        Bitmap b2 = Bitmap.createBitmap(
+                b1,
+                dx1 / 2,
+                dy1 / 2,
+                targetWidth,
+                targetHeight);
+
+        if (b2 != b1) {
+            if (recycle || b1 != source) {
+                b1.recycle();
+            }
+        }
+
+        return b2;
+    }
+
+
+
+}