auto import from //depot/cupcake/@132589
diff --git a/src/com/android/browser/BrowserActivity.java b/src/com/android/browser/BrowserActivity.java
index 6179da2..0ca4248 100644
--- a/src/com/android/browser/BrowserActivity.java
+++ b/src/com/android/browser/BrowserActivity.java
@@ -1950,17 +1950,15 @@
                             public void run() {
                                 // Remove the AnimatingView.
                                 mContentView.removeView(view);
-                                if (mTabOverview != null) {
-                                    // Make newIndex visible.
-                                    mTabOverview.setCurrentIndex(newIndex);
-                                    // Restore the listener.
-                                    mTabOverview.setListener(mTabListener);
-                                    // Change the menu to TAB_MENU if the
-                                    // ImageGrid is interactive.
-                                    if (mTabOverview.isLive()) {
-                                        mMenuState = R.id.TAB_MENU;
-                                        mTabOverview.requestFocus();
-                                    }
+                                // Make newIndex visible.
+                                mTabOverview.setCurrentIndex(newIndex);
+                                // Restore the listener.
+                                mTabOverview.setListener(mTabListener);
+                                // Change the menu to TAB_MENU if the
+                                // ImageGrid is interactive.
+                                if (mTabOverview.isLive()) {
+                                    mMenuState = R.id.TAB_MENU;
+                                    mTabOverview.requestFocus();
                                 }
                                 // If a remove was requested, remove the tab.
                                 if (remove) {
@@ -1978,12 +1976,10 @@
                                     if (currentTab != tab) {
                                         mTabControl.setCurrentTab(currentTab);
                                     }
-                                    if (mTabOverview != null) {
-                                        mTabOverview.remove(newIndex);
-                                        // Make the current tab visible.
-                                        mTabOverview.setCurrentIndex(
-                                                mTabControl.getCurrentIndex());
-                                    }
+                                    mTabOverview.remove(newIndex);
+                                    // Make the current tab visible.
+                                    mTabOverview.setCurrentIndex(
+                                            mTabControl.getCurrentIndex());
                                 }
                             }
                         });
diff --git a/src/com/android/browser/BrowserHistoryPage.java b/src/com/android/browser/BrowserHistoryPage.java
index 42ca848..c529fe8 100644
--- a/src/com/android/browser/BrowserHistoryPage.java
+++ b/src/com/android/browser/BrowserHistoryPage.java
@@ -115,18 +115,11 @@
         addContentView(v, new LayoutParams(LayoutParams.FILL_PARENT,
                 LayoutParams.FILL_PARENT));
         list.setEmptyView(v);
-        // Do not post the runnable if there is nothing in the list.
-        if (list.getExpandableListAdapter().getGroupCount() > 0) {
-            list.post(new Runnable() {
-                public void run() {
-                    // In case the history gets cleared before this event
-                    // happens.
-                    if (list.getExpandableListAdapter().getGroupCount() > 0) {
-                        list.expandGroup(0);
-                    }
-                }
-            });
-        }
+        list.post(new Runnable() {
+            public void run() {
+                list.expandGroup(0);
+            }
+        });
         mMaxTabsOpen = getIntent().getBooleanExtra("maxTabsOpen", false);
         CombinedBookmarkHistoryActivity.getIconListenerSet(getContentResolver())
                 .addListener(mIconReceiver);
@@ -265,12 +258,9 @@
     
     private class HistoryAdapter implements ExpandableListAdapter {
         
-        // Array for each of our bins.  Each entry represents how many items are
-        // in that bin.
+        // Map of items. Negative values are labels, positive values
+        // and zero are cursor offsets.
         int mItemMap[];
-        // This is our GroupCount.  We will have at most DateSorter.DAY_COUNT
-        // bins, less if the user has no items in one or more bins.
-        int mNumberOfBins;
         Vector<DataSetObserver> mObservers;
         Cursor mCursor;
         
@@ -305,14 +295,12 @@
             for (int j = 0; j < DateSorter.DAY_COUNT; j++) {
                 array[j] = 0;
             }
-            mNumberOfBins = 0;
             int dateIndex = -1;
             if (mCursor.moveToFirst() && mCursor.getCount() > 0) {
                 while (!mCursor.isAfterLast()) {
                     long date = mCursor.getLong(Browser.HISTORY_PROJECTION_DATE_INDEX);
                     int index = mDateSorter.getIndex(date);
                     if (index > dateIndex) {
-                        mNumberOfBins++;
                         if (index == DateSorter.DAY_COUNT - 1) {
                             // We are already in the last bin, so it will
                             // include all the remaining items
@@ -328,37 +316,9 @@
             }
             mItemMap = array;
         }
-
-        // This translates from a group position in the Adapter to a position in
-        // our array.  This is necessary because some positions in the array
-        // have no history items, so we simply do not present those positions
-        // to the Adapter.
-        private int groupPositionToArrayPosition(int groupPosition) {
-            if (groupPosition < 0 || groupPosition >= DateSorter.DAY_COUNT) {
-                throw new AssertionError("group position out of range");
-            }
-            if (DateSorter.DAY_COUNT == mNumberOfBins || 0 == mNumberOfBins) {
-                // In the first case, we have exactly the same number of bins
-                // as our maximum possible, so there is no need to do a
-                // conversion
-                // The second statement is in case this method gets called when
-                // the array is empty, in which case the provided groupPosition
-                // will do fine.
-                return groupPosition;
-            }
-            int arrayPosition = -1;
-            while (groupPosition > -1) {
-                arrayPosition++;
-                if (mItemMap[arrayPosition] != 0) {
-                    groupPosition--;
-                }
-            }
-            return arrayPosition;
-        }
-
+        
         public View getChildView(int groupPosition, int childPosition, boolean isLastChild,
                 View convertView, ViewGroup parent) {
-            groupPosition = groupPositionToArrayPosition(groupPosition);
             HistoryItem item;
             if (null == convertView || !(convertView instanceof HistoryItem)) {
                 item = new HistoryItem(BrowserHistoryPage.this);
@@ -387,7 +347,6 @@
         }
         
         public View getGroupView(int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
-            groupPosition = groupPositionToArrayPosition(groupPosition);
             TextView item;
             if (null == convertView || !(convertView instanceof TextView)) {
                 LayoutInflater factory = 
@@ -410,11 +369,11 @@
         }
 
         public int getGroupCount() {
-            return mNumberOfBins;
+            return DateSorter.DAY_COUNT;
         }
 
         public int getChildrenCount(int groupPosition) {
-            return mItemMap[groupPositionToArrayPosition(groupPosition)];
+            return mItemMap[groupPosition];
         }
 
         public Object getGroup(int groupPosition) {
diff --git a/src/com/android/browser/BrowserHomepagePreference.java b/src/com/android/browser/BrowserHomepagePreference.java
index d4708c3..bc21143 100644
--- a/src/com/android/browser/BrowserHomepagePreference.java
+++ b/src/com/android/browser/BrowserHomepagePreference.java
@@ -48,10 +48,8 @@
         AlertDialog dialog = (AlertDialog) getDialog();
         // This callback is called before the dialog has been fully constructed
         if (dialog != null) {
-            String url = s.toString();
             dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(
-                    url.length() == 0 || url.equals("about:blank") ||
-                    Regex.WEB_URL_PATTERN.matcher(url).matches());
+                    Regex.WEB_URL_PATTERN.matcher(s.toString()).matches());
         }
     }
 
diff --git a/src/com/android/browser/BrowserPreferencesPage.java b/src/com/android/browser/BrowserPreferencesPage.java
index 5d6795b..b23f750 100644
--- a/src/com/android/browser/BrowserPreferencesPage.java
+++ b/src/com/android/browser/BrowserPreferencesPage.java
@@ -83,7 +83,8 @@
             if (needUpdate) {
                 value = value.trim().replace(" ", "%20");
             }
-            if (value.length() != 0 && Uri.parse(value).getScheme() == null) {
+            Uri path = Uri.parse(value);
+            if (path.getScheme() == null) {
                 value = "http://" + value;
                 needUpdate = true;
             }
diff --git a/src/com/android/browser/FindDialog.java b/src/com/android/browser/FindDialog.java
index 43cd1c4..44109ff 100644
--- a/src/com/android/browser/FindDialog.java
+++ b/src/com/android/browser/FindDialog.java
@@ -17,7 +17,6 @@
 package com.android.browser;
 
 import android.app.Dialog;
-import android.content.Context;
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
@@ -31,7 +30,6 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
 import android.webkit.WebView;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -68,19 +66,9 @@
                 throw new AssertionError("No WebView for FindDialog::onClick");
             }
             mWebView.findNext(false);
-            hideSoftInput();
         }
     };
-
-    /*
-     * Remove the soft keyboard from the screen.
-     */
-    private void hideSoftInput() {
-        InputMethodManager imm = (InputMethodManager)
-                mBrowserActivity.getSystemService(Context.INPUT_METHOD_SERVICE);
-        imm.hideSoftInputFromWindow(mEditText.getWindowToken(), 0);
-    }
-
+    
     private void disableButtons() {
         mPrevButton.setEnabled(false);
         mNextButton.setEnabled(false);
@@ -169,9 +157,8 @@
             throw new AssertionError("No WebView for FindDialog::findNext");
         }
         mWebView.findNext(true);
-        hideSoftInput();
     }
-
+    
     public void show() {
         super.show();
         mEditText.requestFocus();
diff --git a/src/com/android/browser/GearsFilePickerDialog.java b/src/com/android/browser/GearsFilePickerDialog.java
new file mode 100644
index 0000000..10cc03f
--- /dev/null
+++ b/src/com/android/browser/GearsFilePickerDialog.java
@@ -0,0 +1,930 @@
+/*
+ * Copyright (C) 2008 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 com.android.browser;
+
+import android.app.Activity;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images.Media;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.GridView;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Gears FilePicker dialog
+ */
+class GearsFilePickerDialog extends GearsBaseDialog
+  implements View.OnTouchListener {
+
+  private static final String TAG = "Gears FilePicker";
+  private static Bitmap mDirectoryIcon;
+  private static Bitmap mDefaultIcon;
+  private static Bitmap mImageIcon;
+  private static Bitmap mBackIcon;
+
+  private static String MULTIPLE_FILES = "MULTIPLE_FILES";
+  private static String SINGLE_FILE = "SINGLE_FILE";
+
+  private static ImagesLoad mImagesLoader;
+  private static SystemThumbnails mSystemThumbnails;
+  private FilePickerAdapter mAdapter;
+  private String mSelectionMode;
+  private boolean mMultipleSelection;
+  private String mCurrentPath;
+
+  // Disable saving thumbnails until this is refactored to fit into
+  // existing schemes.
+  private static final boolean enableSavedThumbnails = false;
+
+  public GearsFilePickerDialog(Activity activity,
+                               Handler handler,
+                               String arguments) {
+    super (activity, handler, arguments);
+    mAdapter = new FilePickerAdapter(activity);
+    parseArguments();
+  }
+
+  public void parseArguments() {
+    mSelectionMode = MULTIPLE_FILES;
+    try {
+      JSONObject json = new JSONObject(mDialogArguments);
+
+      if (json.has("mode")) {
+        mSelectionMode = json.getString("mode");
+      }
+    } catch (JSONException e) {
+      Log.e(TAG, "exc: " + e);
+    }
+    if (mSelectionMode.equalsIgnoreCase(SINGLE_FILE)) {
+      mMultipleSelection = false;
+    } else {
+      mMultipleSelection = true;
+    }
+  }
+
+  public void setup() {
+    inflate(R.layout.gears_dialog_filepicker, R.id.panel_content);
+    setupButtons(0,
+                 R.string.filepicker_button_allow,
+                 R.string.filepicker_button_deny);
+    setupDialog();
+
+    TextView textViewPath = (TextView) findViewById(R.id.path_name);
+    if (textViewPath != null) {
+      textViewPath.setText(R.string.filepicker_path);
+    }
+
+    GridView view = (GridView) findViewById(R.id.files_list);
+    view.setAdapter(mAdapter);
+    view.setOnTouchListener(this);
+
+    showView(null, R.id.selection);
+    setSelectionText();
+
+    mImagesLoader = new ImagesLoad(mAdapter);
+    mImagesLoader.setAdapterView(view);
+    Thread imagesLoaderThread = new Thread(mImagesLoader);
+    imagesLoaderThread.setPriority(Thread.MIN_PRIORITY);
+    imagesLoaderThread.start();
+
+    mSystemThumbnails = new SystemThumbnails();
+    Thread systemThumbnailsThread = new Thread(mSystemThumbnails);
+    systemThumbnailsThread.setPriority(Thread.MIN_PRIORITY);
+    systemThumbnailsThread.start();
+  }
+
+  public void setSelectionText() {
+    Vector elements = mAdapter.selectedElements();
+    if (elements == null)
+      return;
+    TextView info = (TextView) findViewById(R.id.selection);
+    int nbElements = elements.size();
+    if (nbElements == 0) {
+      info.setText(R.string.filepicker_no_files_selected);
+    } else if (nbElements == 1) {
+      info.setText(R.string.filepicker_one_file_selected);
+    } else {
+      info.setText(nbElements + " " +
+                   mActivity.getString(
+                       R.string.filepicker_some_files_selected));
+    }
+  }
+
+  public void setCurrentPath(String path) {
+    if (path != null) {
+      mCurrentPath = path;
+      TextView textViewPath = (TextView) findViewById(R.id.current_path);
+      if (textViewPath != null) {
+        textViewPath.setText(path);
+      }
+    }
+  }
+
+  public void setupDialog(TextView message, ImageView icon) {
+    message.setText(R.string.filepicker_message);
+    message.setTextSize(24);
+    icon.setImageResource(R.drawable.ic_dialog_menu_generic);
+  }
+
+  public boolean onTouch(View v, MotionEvent event) {
+    mImagesLoader.pauseIconRequest();
+    return false;
+  }
+
+  /**
+   * Utility class encapsulating thumbnails information
+   * for a file (file image id and magic number)
+   */
+  class SystemThumbnailInfo {
+    private long mID;
+    private long mMagicNumber;
+    SystemThumbnailInfo(long anID, long magicNumber) {
+      mID = anID;
+      mMagicNumber = magicNumber;
+    }
+    public long getID() {
+      return mID;
+    }
+    public long getMagicNumber() {
+      return mMagicNumber;
+    }
+  }
+
+  /**
+   * Utility class to pre-fetch the thumbnails information
+   */
+  class SystemThumbnails implements Runnable {
+    private Map<String, SystemThumbnailInfo> mThumbnails;
+
+    SystemThumbnails() {
+      mThumbnails = Collections.synchronizedMap(new HashMap());
+    }
+
+    public void run() {
+      Uri query = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+      Cursor cursor = mActivity.managedQuery(query,
+          new String[] { "_id", "mini_thumb_magic", "_data" },
+          null, null, null);
+
+      if (cursor != null) {
+        int count = cursor.getCount();
+        for (int i = 0; i < count; i++) {
+          cursor.moveToPosition(i);
+          SystemThumbnailInfo info = new SystemThumbnailInfo(cursor.getLong(0),
+                                                             cursor.getLong(1));
+          mThumbnails.put(cursor.getString(2), info);
+        }
+      }
+    }
+
+    public SystemThumbnailInfo getThumb(String path) {
+      SystemThumbnailInfo ret = mThumbnails.get(path);
+      if (ret == null) {
+        Uri query = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+        Cursor cursor = mActivity.managedQuery(query,
+          new String[] { "_id", "mini_thumb_magic", "_data" },
+              "_data = ?", new String[] { path }, null);
+        if (cursor != null && cursor.moveToFirst()) {
+          long longid = cursor.getLong(0);
+          long miniThumbMagic = cursor.getLong(1);
+          ret = new SystemThumbnailInfo(longid, miniThumbMagic);
+          mThumbnails.put(path, ret);
+        }
+      }
+      return ret;
+    }
+  }
+
+  /**
+   * Utility class to load and generate thumbnails
+   * for image files
+   */
+  class ImagesLoad implements Runnable {
+    private Vector mImagesPath;
+    private BaseAdapter mAdapter;
+    private AdapterView mAdapterView;
+    private Vector<FilePickerElement> mElements;
+    private Handler mLoaderHandler;
+    // We use the same value as in Camera.app's ImageManager.java
+    private static final int BYTES_PER_MINI_THUMB = 10000;
+    private final byte[] mMiniThumbData = new byte[BYTES_PER_MINI_THUMB];
+    private final int MINI_THUMB_DATA_FILE_VERSION = 3;
+    private final int THUMBNAIL_SIZE = 128;
+    private Map<Uri, RandomAccessFile> mThumbFiles;
+
+    ImagesLoad(BaseAdapter adapter) {
+      mAdapter = adapter;
+      mThumbFiles = Collections.synchronizedMap(new HashMap());
+    }
+
+    public void signalChanges() {
+      Message message = mHandler.obtainMessage(GearsBaseDialog.NEW_ICON,
+                                               mAdapter);
+      mHandler.sendMessage(message);
+    }
+
+    private String getMiniThumbFileFromUri(Uri uri) {
+      if (uri == null) {
+        return null;
+      }
+      String directoryName =
+          Environment.getExternalStorageDirectory().toString() +
+          "/dcim/.thumbnails";
+      String path = directoryName + "/.thumbdata" +
+          MINI_THUMB_DATA_FILE_VERSION + "-" + uri.hashCode();
+      return path;
+    }
+
+    private Bitmap getMiniThumbFor(Uri uri, long longid, long magic) {
+      RandomAccessFile thumbFile = mThumbFiles.get(uri);
+      try {
+        if (thumbFile == null) {
+          String path = getMiniThumbFileFromUri(uri);
+          File f = new File(path);
+          if (f.exists()) {
+            thumbFile = new RandomAccessFile(f, "rw");
+            mThumbFiles.put(uri, thumbFile);
+          }
+        }
+      } catch (IOException ex) {
+      }
+      if (thumbFile == null) {
+        return null;
+      }
+      byte[] data = getMiniThumbFromFile(thumbFile, longid,
+                                         mMiniThumbData, magic);
+      if (data != null) {
+        return BitmapFactory.decodeByteArray(data, 0, data.length);
+      }
+      return null;
+    }
+
+    private byte [] getMiniThumbFromFile(RandomAccessFile r,
+                                         long id,
+                                         byte [] data,
+                                         long magicCheck) {
+      if (r == null)
+        return null;
+      long pos = id * BYTES_PER_MINI_THUMB;
+      RandomAccessFile f = r;
+      synchronized (f) {
+        try {
+          f.seek(pos);
+          if (f.readByte() == 1) {
+            long magic = f.readLong();
+            if (magic != magicCheck) {
+              return null;
+            }
+            int length = f.readInt();
+            f.read(data, 0, length);
+            return data;
+          } else {
+            return null;
+          }
+        } catch (IOException ex) {
+          long fileLength;
+          try {
+            fileLength = f.length();
+          } catch (IOException ex1) {
+            fileLength = -1;
+          }
+          return null;
+        }
+      }
+    }
+
+    /*
+     * Returns a thumbnail saved by the Camera application
+     * We pre-cached the information (image id and magic number)
+     * when starting the filepicker.
+     */
+    public Bitmap getSystemThumbnail(FilePickerElement elem) {
+      if (elem.askedForSystemThumbnail() == false) {
+        elem.setAskedForSystemThumbnail(true);
+        String path = elem.getPath();
+        SystemThumbnailInfo thumbInfo = mSystemThumbnails.getThumb(path);
+        if (thumbInfo != null) {
+          Uri query = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+          Bitmap bmp = getMiniThumbFor(query, thumbInfo.getID(),
+                                       thumbInfo.getMagicNumber());
+          if (bmp != null) {
+            return bmp;
+          }
+        }
+      }
+      return null;
+    }
+
+    /*
+     * Generate a thumbnail for a given element
+     */
+    public Bitmap generateImage(FilePickerElement elem) {
+      String path = elem.getPath();
+      Bitmap finalImage = null;
+      try {
+
+        // First we try to get the thumbnail from the system
+        // (created by the Camera application)
+
+        finalImage = getSystemThumbnail(elem);
+        if (finalImage != null) {
+          return finalImage;
+        }
+
+        // No thumbnail was found, so we have to create one
+        //
+        // First we get the image information and
+        // determine the sampleSize
+
+        BitmapFactory.Options options = new BitmapFactory.Options();
+        options.inJustDecodeBounds = true;
+        BitmapFactory.decodeFile(path, options);
+
+        int width = options.outWidth;
+        int height = options.outHeight;
+        int sampleSize = 1;
+        if (width > THUMBNAIL_SIZE || height > THUMBNAIL_SIZE) {
+          sampleSize = 2;
+          while ((width / sampleSize > 2*THUMBNAIL_SIZE)
+                 || (height / sampleSize > 2*THUMBNAIL_SIZE)) {
+            sampleSize += 2;
+          }
+        }
+        options.inJustDecodeBounds = false;
+        options.inSampleSize = sampleSize;
+        Bitmap originalImage = BitmapFactory.decodeFile(path, options);
+        if (originalImage == null) {
+          return null;
+        }
+
+        // Let's rescale the image to a THUMBNAIL_SIZE
+
+        width = originalImage.getWidth();
+        height = originalImage.getHeight();
+
+        if (width > height) {
+          width = (int) (width * (THUMBNAIL_SIZE / (double) height));
+          height = THUMBNAIL_SIZE;
+        } else {
+          height = (int) (height * (THUMBNAIL_SIZE / (double) width));
+          width = THUMBNAIL_SIZE;
+        }
+        originalImage = Bitmap.createScaledBitmap(originalImage,
+                                                  width, height, true);
+
+        // We can now crop the image to a THUMBNAIL_SIZE rectangle
+
+        width = originalImage.getWidth();
+        height = originalImage.getHeight();
+        int d = 0;
+        if (width > height) {
+          d = (width - height) / 2;
+          finalImage = Bitmap.createBitmap(originalImage, d, 0,
+                                           THUMBNAIL_SIZE, THUMBNAIL_SIZE);
+        } else {
+          d = (height - width) / 2;
+          finalImage = Bitmap.createBitmap(originalImage, 0, d,
+                                           THUMBNAIL_SIZE, THUMBNAIL_SIZE);
+        }
+
+        originalImage.recycle();
+      } catch (java.lang.OutOfMemoryError e) {
+        Log.e(TAG, "Intercepted OOM ", e);
+      }
+      return finalImage;
+    }
+
+    public void pauseIconRequest() {
+      Message message = Message.obtain(mLoaderHandler,
+                                       GearsBaseDialog.PAUSE_REQUEST_ICON);
+      mLoaderHandler.sendMessageAtFrontOfQueue(message);
+    }
+
+    public void clearIconRequests() {
+      Message message = Message.obtain(mLoaderHandler,
+                                       GearsBaseDialog.CLEAR_REQUEST_ICON);
+      mLoaderHandler.sendMessageAtFrontOfQueue(message);
+    }
+
+    public void postIconRequest(FilePickerElement item,
+                                int position,
+                                boolean front) {
+      if (item == null) {
+        return;
+      }
+      if (item.isImage() && (item.getThumbnail() == null))  {
+        Message message = mLoaderHandler.obtainMessage(
+            GearsBaseDialog.REQUEST_ICON, position, 0, item);
+        if (front) {
+          mLoaderHandler.sendMessageAtFrontOfQueue(message);
+        } else {
+          mLoaderHandler.sendMessage(message);
+        }
+      }
+    }
+
+    public boolean generateIcon(FilePickerElement elem) {
+      if (elem.isImage()) {
+        if (elem.getThumbnail() == null) {
+          Bitmap image = generateImage(elem);
+          if (image != null) {
+            elem.setThumbnail(image);
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    public void setAdapterView(AdapterView view) {
+      mAdapterView = view;
+    }
+
+    public void run() {
+      Looper.prepare();
+      mLoaderHandler = new Handler() {
+        public void handleMessage(Message msg) {
+          if (msg.what == GearsBaseDialog.CLEAR_REQUEST_ICON) {
+            mLoaderHandler.removeMessages(
+                GearsBaseDialog.PAUSE_REQUEST_ICON);
+            mLoaderHandler.removeMessages(
+                GearsBaseDialog.REQUEST_ICON);
+          } else if (msg.what == GearsBaseDialog.PAUSE_REQUEST_ICON) {
+            try {
+              // We are busy (likely) scrolling the view,
+              // so we just pause the loading.
+              Thread.sleep(1000);
+              mLoaderHandler.removeMessages(
+                  GearsBaseDialog.PAUSE_REQUEST_ICON);
+            } catch (InterruptedException e) {
+              Log.e(TAG, "InterruptedException ", e);
+            }
+          } else if (msg.what == GearsBaseDialog.REQUEST_ICON) {
+            FilePickerElement elem = (FilePickerElement) msg.obj;
+            if (generateIcon(elem)) {
+              signalChanges();
+            }
+            try {
+              Thread.sleep(50);
+            } catch (InterruptedException e) {
+              Log.e(TAG, "InterruptedException ", e);
+            }
+          }
+        }
+      };
+      Looper.loop();
+    }
+  }
+
+  /**
+   * Utility class representing an element displayed in the
+   * file picker, associated with an icon and/or thumbnail
+   */
+  class FilePickerElement {
+    private File mPath;
+    private String mName;
+    private Bitmap mIcon;
+    private boolean mIsSelected;
+    private Vector mChildren;
+    private FilePickerElement mParent;
+    private boolean mIsParent;
+    private BaseAdapter mAdapter;
+    private String mExtension;
+    private Bitmap mThumbnail;
+    private boolean mIsImage;
+    private boolean mAskedForSystemThumbnail;
+
+    public FilePickerElement(String name, BaseAdapter adapter) {
+      this(name, adapter, null);
+    }
+
+    public FilePickerElement(String path, String name, BaseAdapter adapter) {
+      this(path, name, adapter, null);
+    }
+
+    public FilePickerElement(String name,
+                             BaseAdapter adapter,
+                             FilePickerElement parent) {
+      mName = name;
+      mAdapter = adapter;
+      mParent = parent;
+      mIsSelected = false;
+      mChildren = null;
+      mAskedForSystemThumbnail = false;
+    }
+
+    public FilePickerElement(String path,
+                             String name,
+                             BaseAdapter adapter,
+                             FilePickerElement parent) {
+      mPath = new File(path);
+      mName = name;
+      mIsSelected = false;
+      mChildren = null;
+      mParent = parent;
+      mAdapter = adapter;
+      mExtension = null;
+      mAskedForSystemThumbnail = false;
+
+      setIcons();
+    }
+
+    public void setAskedForSystemThumbnail(boolean value) {
+      mAskedForSystemThumbnail = value;
+    }
+
+    public boolean askedForSystemThumbnail() {
+      return mAskedForSystemThumbnail;
+    }
+
+    public void setIcons() {
+      if (mPath.isDirectory()) {
+        if (mDirectoryIcon == null) {
+          mDirectoryIcon = BitmapFactory.decodeResource(
+              getResources(), R.drawable.gears_folder);
+        }
+        mIcon = mDirectoryIcon;
+
+      } else {
+        if (isImage()) {
+          if (mImageIcon == null) {
+            mImageIcon = BitmapFactory.decodeResource(
+                getResources(), R.drawable.gears_file_image);
+          }
+          mIcon = mImageIcon;
+        } else if (isAudio()) {
+          mIcon = BitmapFactory.decodeResource(
+              getResources(), R.drawable.gears_file_audio);
+        } else if (isVideo()) {
+          mIcon = BitmapFactory.decodeResource(
+              getResources(), R.drawable.gears_file_video);
+        } else {
+          if (mDefaultIcon == null) {
+            mDefaultIcon = BitmapFactory.decodeResource(
+                getResources(), R.drawable.gears_file_unknown);
+          }
+          mIcon = mDefaultIcon;
+        }
+      }
+      if (mBackIcon == null) {
+        mBackIcon = BitmapFactory.decodeResource(getResources(),
+                com.android.internal.R.drawable.ic_menu_back);
+      }
+    }
+
+    public boolean isImage() {
+      if (mIsImage) return mIsImage;
+      String extension = getExtension();
+      if (extension != null) {
+        if (extension.equalsIgnoreCase("jpg") ||
+            extension.equalsIgnoreCase("jpeg") ||
+            extension.equalsIgnoreCase("png") ||
+            extension.equalsIgnoreCase("gif")) {
+          mIsImage = true;
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public boolean isAudio() {
+      String extension = getExtension();
+      if (extension != null) {
+        if (extension.equalsIgnoreCase("mp3") ||
+            extension.equalsIgnoreCase("wav") ||
+            extension.equalsIgnoreCase("aac")) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public boolean isVideo() {
+      String extension = getExtension();
+      if (extension != null) {
+        if (extension.equalsIgnoreCase("mpg") ||
+            extension.equalsIgnoreCase("mpeg") ||
+            extension.equalsIgnoreCase("mpe") ||
+            extension.equalsIgnoreCase("divx") ||
+            extension.equalsIgnoreCase("3gpp") ||
+            extension.equalsIgnoreCase("avi")) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public void setParent(boolean isParent) {
+      mIsParent = isParent;
+    }
+
+    public boolean isDirectory() {
+      return mPath.isDirectory();
+    }
+
+    public String getExtension() {
+      if (isDirectory()) {
+        return null;
+      }
+      if (mExtension == null) {
+        String path = getPath();
+        int index = path.lastIndexOf(".");
+        if ((index != -1) && (index != path.length() - 1)){
+          // if we find a dot that is not the last character
+          mExtension = path.substring(index+1);
+          return mExtension;
+        }
+      }
+      return mExtension;
+    }
+
+    public void refresh() {
+      mChildren = null;
+      Vector children = getChildren();
+      mImagesLoader.clearIconRequests();
+    }
+
+    public Vector getChildren() {
+      if (isDirectory()) {
+        if (mChildren == null) {
+          mChildren = new Vector();
+          File[] files = mPath.listFiles();
+          if (mParent != null) {
+            mChildren.add(mParent);
+            mParent.setParent(true);
+          }
+          for (int i = 0; i < files.length; i++) {
+            String name = files[i].getName();
+            String fpath = files[i].getPath();
+            if (!name.startsWith(".")) { // hide dotfiles
+              FilePickerElement elem = new FilePickerElement(fpath, name,
+                                                             mAdapter, this);
+              elem.setParent(false);
+              mChildren.add(elem);
+            }
+          }
+        }
+      }
+      return mChildren;
+    }
+
+    public FilePickerElement getChild(int position) {
+      Vector children = getChildren();
+      if (children != null) {
+        FilePickerElement elem = (FilePickerElement) children.get(position);
+        return elem;
+      }
+      return null;
+    }
+
+    /*
+     * Depending on the type, we return either
+     * the icon (mIcon) or the back icon (mBackIcon).
+     * If we can load a system thumbnail we do this
+     * synchronously and return it, else we ask the
+     * mImagesLoader to generate a thumbnail for us.
+     */
+    public Bitmap getIcon(int position) {
+      if (mIsParent) {
+        return mBackIcon;
+      }
+      if (isImage()) {
+        if (mThumbnail != null) {
+          return mThumbnail;
+        } else {
+          Bitmap image = mImagesLoader.getSystemThumbnail(this);
+          if (image != null) {
+            mThumbnail = image;
+            return mThumbnail;
+          }
+          mImagesLoader.postIconRequest(this, position, true);
+        }
+      }
+      return mIcon;
+    }
+
+    public Bitmap getThumbnail() {
+      return mThumbnail;
+    }
+
+    public void setThumbnail(Bitmap icon) {
+      mThumbnail = icon;
+    }
+
+    public String getName() {
+      return mName;
+    }
+
+    public String getPath() {
+      return mPath.getPath();
+    }
+
+    public void toggleSelection() {
+      mIsSelected = !mIsSelected;
+    }
+
+    public boolean isSelected() {
+      return mIsSelected;
+    }
+
+  }
+
+  /**
+   * Adapter for the GridView
+   */
+  class FilePickerAdapter extends BaseAdapter {
+    private Context mContext;
+    private Map mImagesMap;
+    private Map mImagesSelected;
+
+    private Vector mImages;
+    private Vector<FilePickerElement> mFiles;
+
+    private FilePickerElement mRootElement;
+    private FilePickerElement mCurrentElement;
+
+    public FilePickerAdapter(Context context) {
+      mContext = context;
+      mImages = new Vector();
+      mFiles = new Vector();
+
+      mImagesMap = Collections.synchronizedMap(new HashMap());
+      mImagesSelected = new HashMap();
+
+      String startingPath = Environment.getExternalStorageDirectory().getPath();
+      mRootElement = new FilePickerElement(startingPath, "SD Card", this);
+      mCurrentElement = mRootElement;
+    }
+
+    public void addImage(String path) {
+      mImages.add(path);
+      Bitmap image = BitmapFactory.decodeResource(
+          getResources(), R.drawable.gears_file_unknown);
+      mImagesMap.put(path, image);
+      mImagesSelected.put(path, Boolean.FALSE);
+    }
+
+    public int getCount() {
+      Vector elems = mCurrentElement.getChildren();
+      setCurrentPath(mCurrentElement.getPath());
+      return elems.size();
+    }
+
+    public Object getItem(int position) {
+      return position;
+    }
+
+    public long getItemId(int position) {
+      return position;
+    }
+
+    public Vector selectedElements() {
+      if (mCurrentElement == null) {
+        return null;
+      }
+      Vector children = mCurrentElement.getChildren();
+      Vector ret = new Vector();
+      for (int i = 0; i < children.size(); i++) {
+        FilePickerElement elem = (FilePickerElement) children.get(i);
+        if (elem.isSelected()) {
+          ret.add(elem);
+        }
+      }
+      return ret;
+    }
+
+    public View getView(int position, View convertView, ViewGroup parent) {
+      View cell = convertView;
+      if (cell == null) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(
+            Context.LAYOUT_INFLATER_SERVICE);
+        cell = inflater.inflate(R.layout.gears_dialog_filepicker_cell, null);
+      }
+      ImageView imageView = (ImageView) cell.findViewById(R.id.icon);
+      TextView textView = (TextView) cell.findViewById(R.id.name);
+      FilePickerElement elem = mCurrentElement.getChild(position);
+      if (elem == null) {
+        String message = "Could not get elem " + position;
+        message += " for " + mCurrentElement.getPath();
+        Log.e(TAG, message);
+        return null;
+      }
+      String path = elem.getPath();
+      textView.setText(elem.getName());
+
+      View.OnClickListener listener = new View.OnClickListener() {
+        public void onClick(View view) {
+          int pos = (Integer) view.getTag();
+          FilePickerElement elem = mCurrentElement.getChild(pos);
+          if (elem.isDirectory()) {
+            mCurrentElement = elem;
+            mCurrentElement.refresh();
+          } else {
+            if (mMultipleSelection) {
+              elem.toggleSelection();
+            } else {
+              Vector elems = selectedElements();
+              if (elems != null) {
+                if (elems.size() == 0) {
+                  elem.toggleSelection();
+                } else if ((elems.size() == 1)
+                           && elem.isSelected()) {
+                  elem.toggleSelection();
+                }
+              }
+            }
+          }
+          setSelectionText();
+          notifyDataSetChanged();
+        }
+      };
+      cell.setLayoutParams(new GridView.LayoutParams(96, 96));
+      cell.setOnClickListener(listener);
+      cell.setOnTouchListener(new View.OnTouchListener() {
+        public boolean onTouch(View v, MotionEvent event) {
+          if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            int color = getResources().getColor(R.color.icon_selection);
+            v.setBackgroundColor(color);
+          } else {
+            v.setBackgroundColor(android.R.color.background_dark);
+          }
+          return false;
+        }
+      });
+
+      cell.setTag(position);
+
+      if (elem.isSelected()) {
+        int color = getResources().getColor(R.color.icon_selection);
+        cell.setBackgroundColor(color);
+      } else {
+        cell.setBackgroundColor(android.R.color.background_dark);
+      }
+      Bitmap bmp = elem.getIcon(position);
+      if (bmp != null) {
+        imageView.setImageBitmap(bmp);
+      }
+
+      return cell;
+    }
+  }
+
+  private String selectedFiles() {
+    Vector selection = mAdapter.selectedElements();
+    JSONArray jsonSelection = new JSONArray();
+    if (selection != null) {
+      for (int i = 0; i < selection.size(); i++) {
+        FilePickerElement elem = (FilePickerElement) selection.get(i);
+        jsonSelection.put(elem.getPath());
+      }
+    }
+    return jsonSelection.toString();
+  }
+
+  public String closeDialog(int closingType) {
+    return selectedFiles();
+  }
+}
diff --git a/src/com/android/browser/GearsNativeDialog.java b/src/com/android/browser/GearsNativeDialog.java
index ecf166d..c72ad8e 100644
--- a/src/com/android/browser/GearsNativeDialog.java
+++ b/src/com/android/browser/GearsNativeDialog.java
@@ -36,6 +36,8 @@
 import com.android.browser.GearsBaseDialog;
 import com.android.browser.GearsPermissionsDialog;
 import com.android.browser.GearsSettingsDialog;
+import com.android.browser.GearsShortcutDialog;
+import com.android.browser.GearsFilePickerDialog;
 
 /**
  * Native dialog Activity used by gears
@@ -55,12 +57,16 @@
   private int mDialogType;
   private final int SETTINGS_DIALOG = 1;
   private final int PERMISSION_DIALOG = 2;
-  private final int LOCATION_DIALOG = 3;
+  private final int SHORTCUT_DIALOG = 3;
+  private final int LOCATION_DIALOG = 4;
+  private final int FILEPICKER_DIALOG = 5;
 
   private final String VERSION_STRING = "version";
   private final String SETTINGS_DIALOG_STRING = "settings_dialog";
   private final String PERMISSION_DIALOG_STRING = "permissions_dialog";
+  private final String SHORTCUT_DIALOG_STRING = "shortcuts_dialog";
   private final String LOCATION_DIALOG_STRING = "locations_dialog";
+  private final String FILEPICKER_DIALOG_STRING = "filepicker_dialog";
 
   private boolean mDialogDismissed = false;
 
@@ -105,9 +111,15 @@
       case PERMISSION_DIALOG:
         dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
         break;
+      case SHORTCUT_DIALOG:
+        dialog = new GearsShortcutDialog(this, mHandler, mDialogArguments);
+        break;
       case LOCATION_DIALOG:
         dialog = new GearsPermissionsDialog(this, mHandler, mDialogArguments);
         break;
+      case FILEPICKER_DIALOG:
+        dialog = new GearsFilePickerDialog(this, mHandler, mDialogArguments);
+        break;
       default:
         dialog = new GearsBaseDialog(this, mHandler, mDialogArguments);
     }
@@ -124,7 +136,7 @@
    */
   private void getArguments() {
     if (mDebug) {
-      mDialogType = LOCATION_DIALOG +1;
+      mDialogType = FILEPICKER_DIALOG +1;
       mockArguments();
 
       return;
@@ -146,8 +158,12 @@
       mGearsVersion = intent.getStringExtra(VERSION_STRING);
     } else if (dialogTypeString.equalsIgnoreCase(PERMISSION_DIALOG_STRING)) {
       mDialogType = PERMISSION_DIALOG;
+    } else if (dialogTypeString.equalsIgnoreCase(SHORTCUT_DIALOG_STRING)) {
+      mDialogType = SHORTCUT_DIALOG;
     } else if (dialogTypeString.equalsIgnoreCase(LOCATION_DIALOG_STRING)) {
       mDialogType = LOCATION_DIALOG;
+    } else if (dialogTypeString.equalsIgnoreCase(FILEPICKER_DIALOG_STRING)) {
+      mDialogType = FILEPICKER_DIALOG;
     }
   }
 
@@ -157,6 +173,17 @@
    * Set mock arguments.
    */
   private void mockArguments() {
+    String argumentsShortcuts = "{ locale: \"en-US\","
+        + "name: \"My Application\", link: \"http://www.google.com/\","
+        + "description: \"This application does things does things!\","
+        + "icon16x16: \"http://google-gears.googlecode.com/"
+        + "svn/trunk/gears/test/manual/shortcuts/16.png\","
+        + "icon32x32: \"http://google-gears.googlecode.com/"
+        + "svn/trunk/gears/test/manual/shortcuts/32.png\","
+        + "icon48x48: \"http://google-gears.googlecode.com/"
+        + "svn/trunk/gears/test/manual/shortcuts/48.png\","
+        + "icon128x128: \"http://google-gears.googlecode.com/"
+        + "svn/trunk/gears/test/manual/shortcuts/128.png\"}";
 
     String argumentsPermissions = "{ locale: \"en-US\", "
         + "origin: \"http://www.google.com\", dialogType: \"localData\","
@@ -188,7 +215,16 @@
         + "localStorage: { permissionState: 2 }, "
         + "locationData: { permissionState: 2 } } ] }";
 
+    String argumentsFilePicker = "{ \"cameraMode\" : \"OFF\", \"filters\""
+        + ": [ \"text/html\", \".txt\" ], \"mode\" : \"MULTIPLE_FILES\" }\"";
+
+    String argumentsFilePicker2 = "{ \"cameraMode\" : \"OFF\", \"filters\""
+        + ": [ \"text/html\", \".txt\" ], \"mode\" : \"SINGLE_FILE\" }\"";
+
     switch (mDialogType) {
+      case SHORTCUT_DIALOG:
+        mDialogArguments = argumentsShortcuts;
+        break;
       case PERMISSION_DIALOG:
         mDialogArguments = argumentsPermissions;
         break;
@@ -198,6 +234,8 @@
       case SETTINGS_DIALOG:
         mDialogArguments = argumentsSettings;
         break;
+      case FILEPICKER_DIALOG:
+        mDialogArguments = argumentsFilePicker2;
     }
   }
 
diff --git a/src/com/android/browser/GearsShortcutDialog.java b/src/com/android/browser/GearsShortcutDialog.java
new file mode 100644
index 0000000..11d936d
--- /dev/null
+++ b/src/com/android/browser/GearsShortcutDialog.java
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2008 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 com.android.browser;
+
+import android.app.Activity;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Gears Shortcut dialog
+ */
+class GearsShortcutDialog extends GearsBaseDialog {
+
+  private static final String TAG = "GearsPermissionsDialog";
+
+  private final String ICON_16 = "icon16x16";
+  private final String ICON_32 = "icon32x32";
+  private final String ICON_48 = "icon48x48";
+  private final String ICON_128 = "icon128x128";
+  private int mNotification = 0;
+
+  public GearsShortcutDialog(Activity activity,
+                             Handler handler,
+                             String arguments) {
+    super (activity, handler, arguments);
+  }
+
+  public void setup() {
+    inflate(R.layout.gears_dialog_shortcut, R.id.panel_content);
+    setupButtons(R.string.shortcut_button_alwaysdeny,
+                 R.string.shortcut_button_allow,
+                 R.string.shortcut_button_deny);
+
+    try {
+      JSONObject json = new JSONObject(mDialogArguments);
+
+      String iconUrl = pickIconToRender(json);
+      if (iconUrl != null) {
+        downloadIcon(iconUrl);
+      }
+
+      setupDialog();
+
+      setLabel(json, "name", R.id.shortcut_name);
+      setLabel(json, "link", R.id.origin_subtitle);
+      setLabel(json, "description", R.id.origin_message);
+    } catch (JSONException e) {
+      Log.e(TAG, "JSON exception", e);
+    }
+
+    TextView msg = (TextView) findViewById(R.id.permission_dialog_message);
+    msg.setText(R.string.shortcut_message);
+
+    View shortcutIcon = findViewById(R.id.shortcut_panel);
+    if (shortcutIcon != null) {
+      shortcutIcon.setVisibility(View.VISIBLE);
+    }
+  }
+
+  public void setupDialog(TextView message, ImageView icon) {
+    message.setText(R.string.shortcut_prompt);
+    icon.setImageResource(R.drawable.ic_dialog_menu_generic);
+  }
+
+  /**
+   * Utility method to validate an icon url. Used in the
+   * shortcut dialog.
+   */
+  boolean validIcon(JSONObject json, String name) {
+    try {
+      if (json.has(name)) {
+        String str = json.getString(name);
+        if (str.length() > 0) {
+          return true;
+        }
+      }
+    } catch (JSONException e) {
+      Log.e(TAG, "JSON exception", e);
+    }
+    return false;
+  }
+
+
+  /**
+   * Utility method to pick the best indicated icon
+   * from the dialogs' arguments. Used in the
+   * shortcut dialog.
+   */
+  String pickIconToRender(JSONObject json) {
+    try {
+      if (validIcon(json, ICON_48)) { // ideal size
+        mChoosenIconSize = 48;
+        return json.getString(ICON_48);
+      } else if (validIcon(json, ICON_32)) {
+        mChoosenIconSize = 32;
+        return json.getString(ICON_32);
+      } else if (validIcon(json, ICON_128)) {
+        mChoosenIconSize = 128;
+        return json.getString(ICON_128);
+      } else if (validIcon(json, ICON_16)) {
+        mChoosenIconSize = 16;
+        return json.getString(ICON_16);
+      }
+    } catch (JSONException e) {
+      Log.e(TAG, "JSON exception", e);
+    }
+    mChoosenIconSize = 0;
+    return null;
+  }
+
+  public String closeDialog(int closingType) {
+    String ret = null;
+    switch (closingType) {
+      case ALWAYS_DENY:
+        ret = "{\"allow\": false, \"permanently\": true }";
+        break;
+      case ALLOW:
+        ret = "{\"allow\": true, \"locations\": 0 }";
+        mNotification = R.string.shortcut_notification;
+        break;
+      case DENY:
+        ret = null;
+        break;
+    }
+    return ret;
+  }
+
+  public int notification() {
+    return mNotification;
+  }
+}