Code drop from //branches/cupcake/...@124589
diff --git a/src/com/android/browser/GearsFilePickerDialog.java b/src/com/android/browser/GearsFilePickerDialog.java
new file mode 100644
index 0000000..4a3c0ed
--- /dev/null
+++ b/src/com/android/browser/GearsFilePickerDialog.java
@@ -0,0 +1,629 @@
+/*
+ * 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.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.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.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Vector;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+/**
+ * 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 ImagesLoad mImagesLoader;
+ private FilePickerAdapter mAdapter;
+
+ public GearsFilePickerDialog(Activity activity,
+ Handler handler,
+ String arguments) {
+ super (activity, handler, arguments);
+ mAdapter = new FilePickerAdapter(activity);
+ }
+
+ 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();
+ GridView view = (GridView) findViewById(R.id.files_list);
+ view.setAdapter(mAdapter);
+ view.setOnTouchListener(this);
+
+ mImagesLoader = new ImagesLoad(mAdapter);
+ mImagesLoader.setAdapterView(view);
+ Thread thread = new Thread(mImagesLoader);
+ thread.start();
+ }
+
+ public void setupDialog(TextView message, ImageView icon) {
+ message.setText(R.string.filepicker_message);
+ message.setTextSize(24);
+ icon.setImageResource(R.drawable.gears_icon_48x48);
+ }
+
+ public boolean onTouch(View v, MotionEvent event) {
+ mImagesLoader.pauseIconRequest();
+ return false;
+ }
+
+ /**
+ * Utility class to load and generate thumbnails
+ * for image files
+ */
+ class ImagesLoad implements Runnable {
+ private Map mImagesMap;
+ private Vector mImagesPath;
+ private BaseAdapter mAdapter;
+ private AdapterView mAdapterView;
+ private Vector<FilePickerElement> mElements;
+ private Handler mLoaderHandler;
+
+ ImagesLoad(BaseAdapter adapter) {
+ mAdapter = adapter;
+ }
+
+ public void signalChanges() {
+ Message message = mHandler.obtainMessage(GearsBaseDialog.NEW_ICON,
+ mAdapter);
+ mHandler.sendMessage(message);
+ }
+
+ /**
+ * TODO: use the same thumbnails as the photo app
+ * (bug: http://b/issue?id=1497927)
+ */
+ public String getThumbnailPath(String path) {
+ File f = new File(path);
+ String myPath = f.getParent() + "/.thumbnails";
+ File d = new File(myPath);
+ if (!d.exists()) {
+ d.mkdirs();
+ }
+ return myPath + "/" + f.getName();
+ }
+
+ public boolean saveImage(String path, Bitmap image) {
+ boolean ret = false;
+ try {
+ FileOutputStream outStream = new FileOutputStream(path);
+ ret = image.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
+ } catch (IOException e) {
+ Log.e(TAG, "IOException ", e);
+ }
+ return ret;
+ }
+
+ public Bitmap generateImage(FilePickerElement elem) {
+ String path = elem.getPath();
+ Bitmap finalImage = null;
+ try {
+ String thumbnailPath = getThumbnailPath(path);
+ File thumbnail = new File(thumbnailPath);
+ if (thumbnail.exists()) {
+ finalImage = BitmapFactory.decodeFile(thumbnailPath);
+ if (finalImage != null) {
+ return finalImage;
+ }
+ }
+ BitmapFactory.Options options = new BitmapFactory.Options();
+ options.inJustDecodeBounds = true;
+ BitmapFactory.decodeFile(path, options);
+
+ int width = options.outWidth;
+ int height = options.outHeight;
+ int size = 128;
+ int sampleSize = 1;
+ if (width > size || height > size) {
+ sampleSize = 2;
+ while ((width / sampleSize > size)
+ || (height / sampleSize > size)) {
+ sampleSize += 2;
+ }
+ }
+ options.inJustDecodeBounds = false;
+ options.inSampleSize = sampleSize;
+ Bitmap originalImage = BitmapFactory.decodeFile(path, options);
+ if (originalImage == null) {
+ return null;
+ }
+ finalImage = Bitmap.createScaledBitmap(originalImage, size, size, true);
+ if (saveImage(thumbnailPath, finalImage)) {
+ if (mDebug) {
+ Log.v(TAG, "Saved thumbnail for file " + path);
+ }
+ } else {
+ Log.e(TAG, "Could NOT Save thumbnail for file " + path);
+ }
+ 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 postIconRequest(FilePickerElement item, int position) {
+ if (item == null) {
+ return;
+ }
+ Message message = mLoaderHandler.obtainMessage(
+ GearsBaseDialog.REQUEST_ICON, position, 0, item);
+ mLoaderHandler.sendMessage(message);
+ }
+
+ public void generateIcon(FilePickerElement elem) {
+ if (elem.isImage()) {
+ if (elem.getThumbnail() == null) {
+ Bitmap image = generateImage(elem);
+ if (image != null) {
+ elem.setThumbnail(image);
+ }
+ }
+ }
+ }
+
+ public void setAdapterView(AdapterView view) {
+ mAdapterView = view;
+ }
+
+ public void run() {
+ Looper.prepare();
+ mLoaderHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ int visibleElements = 10;
+ 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) {
+ try {
+ Thread.sleep(10);
+ } catch (InterruptedException e) {
+ Log.e(TAG, "InterruptedException ", e);
+ }
+ FilePickerElement elem = (FilePickerElement) msg.obj;
+ int firstVisiblePosition = mAdapterView.getFirstVisiblePosition();
+ // If the elements are not visible, we slow down the update
+ // TODO: replace this by a low-priority thread
+ if ((msg.arg1 < firstVisiblePosition - visibleElements)
+ && msg.arg1 > firstVisiblePosition + visibleElements) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ }
+ }
+ generateIcon(elem);
+ signalChanges();
+ }
+ }
+ };
+ 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;
+
+ 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;
+ }
+
+ 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;
+
+ setIcons();
+ }
+
+ 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(),
+ R.drawable.gears_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();
+ for (int i = 0; i < children.size(); i++) {
+ FilePickerElement elem = (FilePickerElement) children.get(i);
+ mImagesLoader.postIconRequest(elem, i);
+ }
+ }
+
+ 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) {
+ return (FilePickerElement) children.get(position);
+ }
+ return null;
+ }
+
+ public Bitmap getIcon(int position) {
+ if (mIsParent) {
+ return mBackIcon;
+ }
+ if (isImage()) {
+ if (mThumbnail != null) {
+ return mThumbnail;
+ } else {
+ mImagesLoader.postIconRequest(this, position);
+ }
+ }
+ 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();
+
+ Uri requests[] = { MediaStore.Images.Media.INTERNAL_CONTENT_URI,
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
+
+ String sdCardPath = Environment.getExternalStorageDirectory().getPath();
+ mRootElement = new FilePickerElement(sdCardPath, "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();
+ 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 {
+ elem.toggleSelection();
+ }
+ notifyDataSetChanged();
+ }
+ };
+ imageView.setOnClickListener(listener);
+ cell.setLayoutParams(new GridView.LayoutParams(96, 96));
+
+ imageView.setTag(position);
+
+ if (elem.isSelected()) {
+ cell.setBackgroundColor(Color.LTGRAY);
+ } else {
+ cell.setBackgroundColor(Color.WHITE);
+ }
+ 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();
+ }
+}