blob: 4a3c0edb6e9fb0b8304999c53e8a8f278b1897d4 [file] [log] [blame]
The Android Open Source Projected217d92008-12-17 18:05:52 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.browser;
18
19import android.app.Activity;
20import android.content.Context;
21import android.graphics.Bitmap;
22import android.graphics.BitmapFactory;
23import android.graphics.Color;
24import android.net.Uri;
25import android.os.Environment;
26import android.os.Handler;
27import android.os.Looper;
28import android.os.Message;
29import android.provider.MediaStore;
30import android.util.Log;
31import android.view.LayoutInflater;
32import android.view.MotionEvent;
33import android.view.View;
34import android.view.ViewGroup;
35import android.widget.AdapterView;
36import android.widget.BaseAdapter;
37import android.widget.GridView;
38import android.widget.ImageView;
39import android.widget.TextView;
40
41import java.io.File;
42import java.io.FileOutputStream;
43import java.io.IOException;
44import java.util.Collections;
45import java.util.HashMap;
46import java.util.Map;
47import java.util.Vector;
48
49import org.json.JSONArray;
50import org.json.JSONException;
51
52/**
53 * Gears FilePicker dialog
54 */
55class GearsFilePickerDialog extends GearsBaseDialog
56 implements View.OnTouchListener {
57
58 private static final String TAG = "Gears FilePicker";
59 private static Bitmap mDirectoryIcon;
60 private static Bitmap mDefaultIcon;
61 private static Bitmap mImageIcon;
62 private static Bitmap mBackIcon;
63 private static ImagesLoad mImagesLoader;
64 private FilePickerAdapter mAdapter;
65
66 public GearsFilePickerDialog(Activity activity,
67 Handler handler,
68 String arguments) {
69 super (activity, handler, arguments);
70 mAdapter = new FilePickerAdapter(activity);
71 }
72
73 public void setup() {
74 inflate(R.layout.gears_dialog_filepicker, R.id.panel_content);
75 setupButtons(0,
76 R.string.filepicker_button_allow,
77 R.string.filepicker_button_deny);
78 setupDialog();
79 GridView view = (GridView) findViewById(R.id.files_list);
80 view.setAdapter(mAdapter);
81 view.setOnTouchListener(this);
82
83 mImagesLoader = new ImagesLoad(mAdapter);
84 mImagesLoader.setAdapterView(view);
85 Thread thread = new Thread(mImagesLoader);
86 thread.start();
87 }
88
89 public void setupDialog(TextView message, ImageView icon) {
90 message.setText(R.string.filepicker_message);
91 message.setTextSize(24);
92 icon.setImageResource(R.drawable.gears_icon_48x48);
93 }
94
95 public boolean onTouch(View v, MotionEvent event) {
96 mImagesLoader.pauseIconRequest();
97 return false;
98 }
99
100 /**
101 * Utility class to load and generate thumbnails
102 * for image files
103 */
104 class ImagesLoad implements Runnable {
105 private Map mImagesMap;
106 private Vector mImagesPath;
107 private BaseAdapter mAdapter;
108 private AdapterView mAdapterView;
109 private Vector<FilePickerElement> mElements;
110 private Handler mLoaderHandler;
111
112 ImagesLoad(BaseAdapter adapter) {
113 mAdapter = adapter;
114 }
115
116 public void signalChanges() {
117 Message message = mHandler.obtainMessage(GearsBaseDialog.NEW_ICON,
118 mAdapter);
119 mHandler.sendMessage(message);
120 }
121
122 /**
123 * TODO: use the same thumbnails as the photo app
124 * (bug: http://b/issue?id=1497927)
125 */
126 public String getThumbnailPath(String path) {
127 File f = new File(path);
128 String myPath = f.getParent() + "/.thumbnails";
129 File d = new File(myPath);
130 if (!d.exists()) {
131 d.mkdirs();
132 }
133 return myPath + "/" + f.getName();
134 }
135
136 public boolean saveImage(String path, Bitmap image) {
137 boolean ret = false;
138 try {
139 FileOutputStream outStream = new FileOutputStream(path);
140 ret = image.compress(Bitmap.CompressFormat.JPEG, 100, outStream);
141 } catch (IOException e) {
142 Log.e(TAG, "IOException ", e);
143 }
144 return ret;
145 }
146
147 public Bitmap generateImage(FilePickerElement elem) {
148 String path = elem.getPath();
149 Bitmap finalImage = null;
150 try {
151 String thumbnailPath = getThumbnailPath(path);
152 File thumbnail = new File(thumbnailPath);
153 if (thumbnail.exists()) {
154 finalImage = BitmapFactory.decodeFile(thumbnailPath);
155 if (finalImage != null) {
156 return finalImage;
157 }
158 }
159 BitmapFactory.Options options = new BitmapFactory.Options();
160 options.inJustDecodeBounds = true;
161 BitmapFactory.decodeFile(path, options);
162
163 int width = options.outWidth;
164 int height = options.outHeight;
165 int size = 128;
166 int sampleSize = 1;
167 if (width > size || height > size) {
168 sampleSize = 2;
169 while ((width / sampleSize > size)
170 || (height / sampleSize > size)) {
171 sampleSize += 2;
172 }
173 }
174 options.inJustDecodeBounds = false;
175 options.inSampleSize = sampleSize;
176 Bitmap originalImage = BitmapFactory.decodeFile(path, options);
177 if (originalImage == null) {
178 return null;
179 }
180 finalImage = Bitmap.createScaledBitmap(originalImage, size, size, true);
181 if (saveImage(thumbnailPath, finalImage)) {
182 if (mDebug) {
183 Log.v(TAG, "Saved thumbnail for file " + path);
184 }
185 } else {
186 Log.e(TAG, "Could NOT Save thumbnail for file " + path);
187 }
188 originalImage.recycle();
189 } catch (java.lang.OutOfMemoryError e) {
190 Log.e(TAG, "Intercepted OOM ", e);
191 }
192 return finalImage;
193 }
194
195 public void pauseIconRequest() {
196 Message message = Message.obtain(mLoaderHandler,
197 GearsBaseDialog.PAUSE_REQUEST_ICON);
198 mLoaderHandler.sendMessageAtFrontOfQueue(message);
199 }
200 public void postIconRequest(FilePickerElement item, int position) {
201 if (item == null) {
202 return;
203 }
204 Message message = mLoaderHandler.obtainMessage(
205 GearsBaseDialog.REQUEST_ICON, position, 0, item);
206 mLoaderHandler.sendMessage(message);
207 }
208
209 public void generateIcon(FilePickerElement elem) {
210 if (elem.isImage()) {
211 if (elem.getThumbnail() == null) {
212 Bitmap image = generateImage(elem);
213 if (image != null) {
214 elem.setThumbnail(image);
215 }
216 }
217 }
218 }
219
220 public void setAdapterView(AdapterView view) {
221 mAdapterView = view;
222 }
223
224 public void run() {
225 Looper.prepare();
226 mLoaderHandler = new Handler() {
227 public void handleMessage(Message msg) {
228 int visibleElements = 10;
229 if (msg.what == GearsBaseDialog.PAUSE_REQUEST_ICON) {
230 try {
231 // We are busy (likely) scrolling the view,
232 // so we just pause the loading.
233 Thread.sleep(1000);
234 mLoaderHandler.removeMessages(
235 GearsBaseDialog.PAUSE_REQUEST_ICON);
236 } catch (InterruptedException e) {
237 Log.e(TAG, "InterruptedException ", e);
238 }
239 } else if (msg.what == GearsBaseDialog.REQUEST_ICON) {
240 try {
241 Thread.sleep(10);
242 } catch (InterruptedException e) {
243 Log.e(TAG, "InterruptedException ", e);
244 }
245 FilePickerElement elem = (FilePickerElement) msg.obj;
246 int firstVisiblePosition = mAdapterView.getFirstVisiblePosition();
247 // If the elements are not visible, we slow down the update
248 // TODO: replace this by a low-priority thread
249 if ((msg.arg1 < firstVisiblePosition - visibleElements)
250 && msg.arg1 > firstVisiblePosition + visibleElements) {
251 try {
252 Thread.sleep(100);
253 } catch (InterruptedException e) {
254 }
255 }
256 generateIcon(elem);
257 signalChanges();
258 }
259 }
260 };
261 Looper.loop();
262 }
263 }
264
265 /**
266 * Utility class representing an element displayed in the
267 * file picker, associated with an icon and/or thumbnail
268 */
269 class FilePickerElement {
270 private File mPath;
271 private String mName;
272 private Bitmap mIcon;
273 private boolean mIsSelected;
274 private Vector mChildren;
275 private FilePickerElement mParent;
276 private boolean mIsParent;
277 private BaseAdapter mAdapter;
278 private String mExtension;
279 private Bitmap mThumbnail;
280 private boolean mIsImage;
281
282 public FilePickerElement(String name, BaseAdapter adapter) {
283 this(name, adapter, null);
284 }
285
286 public FilePickerElement(String path, String name, BaseAdapter adapter) {
287 this(path, name, adapter, null);
288 }
289
290 public FilePickerElement(String name,
291 BaseAdapter adapter,
292 FilePickerElement parent) {
293 mName = name;
294 mAdapter = adapter;
295 mParent = parent;
296 mIsSelected = false;
297 mChildren = null;
298 }
299
300 public FilePickerElement(String path,
301 String name,
302 BaseAdapter adapter,
303 FilePickerElement parent) {
304 mPath = new File(path);
305 mName = name;
306 mIsSelected = false;
307 mChildren = null;
308 mParent = parent;
309 mAdapter = adapter;
310 mExtension = null;
311
312 setIcons();
313 }
314
315 public void setIcons() {
316 if (mPath.isDirectory()) {
317 if (mDirectoryIcon == null) {
318 mDirectoryIcon = BitmapFactory.decodeResource(
319 getResources(), R.drawable.gears_folder);
320 }
321 mIcon = mDirectoryIcon;
322
323 } else {
324 if (isImage()) {
325 if (mImageIcon == null) {
326 mImageIcon = BitmapFactory.decodeResource(
327 getResources(), R.drawable.gears_file_image);
328 }
329 mIcon = mImageIcon;
330 } else if (isAudio()) {
331 mIcon = BitmapFactory.decodeResource(
332 getResources(), R.drawable.gears_file_audio);
333 } else if (isVideo()) {
334 mIcon = BitmapFactory.decodeResource(
335 getResources(), R.drawable.gears_file_video);
336 } else {
337 if (mDefaultIcon == null) {
338 mDefaultIcon = BitmapFactory.decodeResource(
339 getResources(), R.drawable.gears_file_unknown);
340 }
341 mIcon = mDefaultIcon;
342 }
343 }
344 if (mBackIcon == null) {
345 mBackIcon = BitmapFactory.decodeResource(getResources(),
346 R.drawable.gears_back);
347 }
348 }
349
350 public boolean isImage() {
351 if (mIsImage) return mIsImage;
352 String extension = getExtension();
353 if (extension != null) {
354 if (extension.equalsIgnoreCase("jpg") ||
355 extension.equalsIgnoreCase("jpeg") ||
356 extension.equalsIgnoreCase("png") ||
357 extension.equalsIgnoreCase("gif")) {
358 mIsImage = true;
359 return true;
360 }
361 }
362 return false;
363 }
364
365 public boolean isAudio() {
366 String extension = getExtension();
367 if (extension != null) {
368 if (extension.equalsIgnoreCase("mp3") ||
369 extension.equalsIgnoreCase("wav") ||
370 extension.equalsIgnoreCase("aac")) {
371 return true;
372 }
373 }
374 return false;
375 }
376
377 public boolean isVideo() {
378 String extension = getExtension();
379 if (extension != null) {
380 if (extension.equalsIgnoreCase("mpg") ||
381 extension.equalsIgnoreCase("mpeg") ||
382 extension.equalsIgnoreCase("mpe") ||
383 extension.equalsIgnoreCase("divx") ||
384 extension.equalsIgnoreCase("3gpp") ||
385 extension.equalsIgnoreCase("avi")) {
386 return true;
387 }
388 }
389 return false;
390 }
391
392 public void setParent(boolean isParent) {
393 mIsParent = isParent;
394 }
395
396 public boolean isDirectory() {
397 return mPath.isDirectory();
398 }
399
400 public String getExtension() {
401 if (isDirectory()) {
402 return null;
403 }
404 if (mExtension == null) {
405 String path = getPath();
406 int index = path.lastIndexOf(".");
407 if ((index != -1) && (index != path.length() - 1)){
408 // if we find a dot that is not the last character
409 mExtension = path.substring(index+1);
410 return mExtension;
411 }
412 }
413 return mExtension;
414 }
415
416 public void refresh() {
417 mChildren = null;
418 Vector children = getChildren();
419 for (int i = 0; i < children.size(); i++) {
420 FilePickerElement elem = (FilePickerElement) children.get(i);
421 mImagesLoader.postIconRequest(elem, i);
422 }
423 }
424
425 public Vector getChildren() {
426 if (isDirectory()) {
427 if (mChildren == null) {
428 mChildren = new Vector();
429 File[] files = mPath.listFiles();
430 if (mParent != null) {
431 mChildren.add(mParent);
432 mParent.setParent(true);
433 }
434 for (int i = 0; i < files.length; i++) {
435 String name = files[i].getName();
436 String fpath = files[i].getPath();
437 if (!name.startsWith(".")) { // hide dotfiles
438 FilePickerElement elem = new FilePickerElement(fpath, name,
439 mAdapter, this);
440 elem.setParent(false);
441 mChildren.add(elem);
442 }
443 }
444 }
445 }
446 return mChildren;
447 }
448
449 public FilePickerElement getChild(int position) {
450 Vector children = getChildren();
451 if (children != null) {
452 return (FilePickerElement) children.get(position);
453 }
454 return null;
455 }
456
457 public Bitmap getIcon(int position) {
458 if (mIsParent) {
459 return mBackIcon;
460 }
461 if (isImage()) {
462 if (mThumbnail != null) {
463 return mThumbnail;
464 } else {
465 mImagesLoader.postIconRequest(this, position);
466 }
467 }
468 return mIcon;
469 }
470
471 public Bitmap getThumbnail() {
472 return mThumbnail;
473 }
474
475 public void setThumbnail(Bitmap icon) {
476 mThumbnail = icon;
477 }
478
479 public String getName() {
480 return mName;
481 }
482
483 public String getPath() {
484 return mPath.getPath();
485 }
486
487 public void toggleSelection() {
488 mIsSelected = !mIsSelected;
489 }
490
491 public boolean isSelected() {
492 return mIsSelected;
493 }
494
495 }
496
497 /**
498 * Adapter for the GridView
499 */
500 class FilePickerAdapter extends BaseAdapter {
501 private Context mContext;
502 private Map mImagesMap;
503 private Map mImagesSelected;
504
505 private Vector mImages;
506 private Vector<FilePickerElement> mFiles;
507
508 private FilePickerElement mRootElement;
509 private FilePickerElement mCurrentElement;
510
511 public FilePickerAdapter(Context context) {
512 mContext = context;
513 mImages = new Vector();
514 mFiles = new Vector();
515
516 mImagesMap = Collections.synchronizedMap(new HashMap());
517 mImagesSelected = new HashMap();
518
519 Uri requests[] = { MediaStore.Images.Media.INTERNAL_CONTENT_URI,
520 MediaStore.Images.Media.EXTERNAL_CONTENT_URI };
521
522 String sdCardPath = Environment.getExternalStorageDirectory().getPath();
523 mRootElement = new FilePickerElement(sdCardPath, "SD Card", this);
524 mCurrentElement = mRootElement;
525 }
526
527 public void addImage(String path) {
528 mImages.add(path);
529 Bitmap image = BitmapFactory.decodeResource(
530 getResources(), R.drawable.gears_file_unknown);
531 mImagesMap.put(path, image);
532 mImagesSelected.put(path, Boolean.FALSE);
533 }
534
535 public int getCount() {
536 Vector elems = mCurrentElement.getChildren();
537 return elems.size();
538 }
539
540 public Object getItem(int position) {
541 return position;
542 }
543
544 public long getItemId(int position) {
545 return position;
546 }
547
548 public Vector selectedElements() {
549 if (mCurrentElement == null) {
550 return null;
551 }
552 Vector children = mCurrentElement.getChildren();
553 Vector ret = new Vector();
554 for (int i = 0; i < children.size(); i++) {
555 FilePickerElement elem = (FilePickerElement) children.get(i);
556 if (elem.isSelected()) {
557 ret.add(elem);
558 }
559 }
560 return ret;
561 }
562
563 public View getView(int position, View convertView, ViewGroup parent) {
564 View cell = convertView;
565 if (cell == null) {
566 LayoutInflater inflater = (LayoutInflater) getSystemService(
567 Context.LAYOUT_INFLATER_SERVICE);
568 cell = inflater.inflate(R.layout.gears_dialog_filepicker_cell, null);
569 }
570 ImageView imageView = (ImageView) cell.findViewById(R.id.icon);
571 TextView textView = (TextView) cell.findViewById(R.id.name);
572 FilePickerElement elem = mCurrentElement.getChild(position);
573 if (elem == null) {
574 String message = "Could not get elem " + position;
575 message += " for " + mCurrentElement.getPath();
576 Log.e(TAG, message);
577 return null;
578 }
579 String path = elem.getPath();
580 textView.setText(elem.getName());
581
582 View.OnClickListener listener = new View.OnClickListener() {
583 public void onClick(View view) {
584 int pos = (Integer) view.getTag();
585 FilePickerElement elem = mCurrentElement.getChild(pos);
586 if (elem.isDirectory()) {
587 mCurrentElement = elem;
588 mCurrentElement.refresh();
589 } else {
590 elem.toggleSelection();
591 }
592 notifyDataSetChanged();
593 }
594 };
595 imageView.setOnClickListener(listener);
596 cell.setLayoutParams(new GridView.LayoutParams(96, 96));
597
598 imageView.setTag(position);
599
600 if (elem.isSelected()) {
601 cell.setBackgroundColor(Color.LTGRAY);
602 } else {
603 cell.setBackgroundColor(Color.WHITE);
604 }
605 Bitmap bmp = elem.getIcon(position);
606 if (bmp != null) {
607 imageView.setImageBitmap(bmp);
608 }
609
610 return cell;
611 }
612 }
613
614 private String selectedFiles() {
615 Vector selection = mAdapter.selectedElements();
616 JSONArray jsonSelection = new JSONArray();
617 if (selection != null) {
618 for (int i = 0; i < selection.size(); i++) {
619 FilePickerElement elem = (FilePickerElement) selection.get(i);
620 jsonSelection.put(elem.getPath());
621 }
622 }
623 return jsonSelection.toString();
624 }
625
626 public String closeDialog(int closingType) {
627 return selectedFiles();
628 }
629}