| /* |
| * 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; |
| 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 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); |
| |
| setSelectionText(); |
| |
| mImagesLoader = new ImagesLoad(mAdapter); |
| mImagesLoader.setAdapterView(view); |
| Thread thread = new Thread(mImagesLoader); |
| thread.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.gears_icon_32x32); |
| } |
| |
| 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); |
| if (enableSavedThumbnails) { |
| 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 (enableSavedThumbnails) { |
| 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(), |
| 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(); |
| 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 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(Color.WHITE); |
| } |
| return false; |
| } |
| }); |
| |
| cell.setTag(position); |
| |
| if (elem.isSelected()) { |
| int color = getResources().getColor(R.color.icon_selection); |
| cell.setBackgroundColor(color); |
| } 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(); |
| } |
| } |