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;
+ }
+}