blob: 29a06a66e4681e2cf603ea53139d0d44dddaaad9 [file] [log] [blame]
The Android Open Source Projectb64d3452009-03-03 19:32:20 -08001/*
2 * Copyright (C) 2007 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.camera;
18
Owen Lin101d5282009-04-03 16:20:08 -070019import com.android.camera.gallery.IImage;
20import com.android.camera.gallery.IImageList;
21
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080022import android.app.Activity;
23import android.app.Dialog;
24import android.app.ProgressDialog;
25import android.content.BroadcastReceiver;
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +080026import android.content.ContentResolver;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080027import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080030import android.content.res.Resources;
repo sync62ca1912009-07-01 18:37:14 +080031import android.database.ContentObserver;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080032import android.graphics.Bitmap;
33import android.graphics.Canvas;
34import android.graphics.Matrix;
35import android.graphics.Paint;
Chih-Chung Chang885e5582009-04-08 03:24:13 -070036import android.graphics.PorterDuff;
37import android.graphics.PorterDuffXfermode;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080038import android.graphics.Rect;
39import android.graphics.drawable.Drawable;
40import android.net.Uri;
41import android.os.Bundle;
42import android.os.Environment;
43import android.os.Handler;
44import android.os.StatFs;
Owen Lin588e29b2009-07-07 11:37:12 -070045import android.provider.MediaStore;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080046import android.provider.MediaStore.Images;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080047import android.util.Log;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080048import android.view.ContextMenu;
49import android.view.LayoutInflater;
50import android.view.Menu;
51import android.view.MenuItem;
52import android.view.View;
53import android.view.ViewGroup;
Owen Lin59b09db2009-05-28 20:21:39 -070054import android.view.ContextMenu.ContextMenuInfo;
55import android.view.MenuItem.OnMenuItemClickListener;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080056import android.widget.AdapterView;
57import android.widget.BaseAdapter;
58import android.widget.GridView;
59import android.widget.TextView;
60import android.widget.Toast;
Owen Lin59b09db2009-05-28 20:21:39 -070061import android.widget.AdapterView.AdapterContextMenuInfo;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080062
63import java.util.ArrayList;
64import java.util.HashMap;
65import java.util.Map;
66
Ray Chen993105a2009-04-10 02:11:35 -070067/**
68 * The GalleryPicker activity.
69 */
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080070public class GalleryPicker extends Activity {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -070071 private static final String TAG = "GalleryPicker";
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080072
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +080073 Handler mHandler = new Handler(); // handler for the main thread
74 Thread mWorkerThread;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080075 BroadcastReceiver mReceiver;
repo sync62ca1912009-07-01 18:37:14 +080076 ContentObserver mDbObserver;
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +080077 GridView mGridView;
78 GalleryPickerAdapter mAdapter; // mAdapter is only accessed in main thread.
79 boolean mScanning;
80 boolean mUnmounted;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080081
82 @Override
83 public void onCreate(Bundle icicle) {
84 super.onCreate(icicle);
85
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080086 setContentView(R.layout.gallerypicker);
87
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080088 mGridView = (GridView) findViewById(R.id.albums);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080089
90 mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
Chih-Chung Changd8209aa2009-04-07 11:45:12 -070091 public void onItemClick(AdapterView<?> parent, View view,
92 int position, long id) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -080093 launchFolderGallery(position);
94 }
95 });
Owen Lin937fc482009-04-14 02:02:51 -070096
Chih-Chung Changd8209aa2009-04-07 11:45:12 -070097 mGridView.setOnCreateContextMenuListener(
Chih-Chung Chang885e5582009-04-08 03:24:13 -070098 new View.OnCreateContextMenuListener() {
Chih-Chung Chang885e5582009-04-08 03:24:13 -070099 public void onCreateContextMenu(ContextMenu menu, View v,
100 final ContextMenuInfo menuInfo) {
101 onCreateGalleryPickerContextMenu(menu, menuInfo);
102 }
103 });
Owen Lin937fc482009-04-14 02:02:51 -0700104
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800105 mReceiver = new BroadcastReceiver() {
106 @Override
107 public void onReceive(Context context, Intent intent) {
108 onReceiveMediaBroadcast(intent);
109 }
110 };
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800111
repo sync62ca1912009-07-01 18:37:14 +0800112 mDbObserver = new ContentObserver(mHandler) {
113 @Override
114 public void onChange(boolean selfChange) {
115 rebake(false, ImageManager.isMediaScannerScanning(
116 getContentResolver()));
117 }
118 };
119
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800120 ImageManager.ensureOSXCompatibleFolder();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800121 }
122
123 Dialog mMediaScanningDialog;
124
125 // Display a dialog if the storage is being scanned now.
126 public void updateScanningDialog(boolean scanning) {
127 boolean prevScanning = (mMediaScanningDialog != null);
repo sync62ca1912009-07-01 18:37:14 +0800128 if (prevScanning == scanning && mAdapter.mItems.size() == 0) return;
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800129 // Now we are certain the state is changed.
130 if (prevScanning) {
131 mMediaScanningDialog.cancel();
132 mMediaScanningDialog = null;
repo sync62ca1912009-07-01 18:37:14 +0800133 } else if (scanning && mAdapter.mItems.size() == 0) {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800134 mMediaScanningDialog = ProgressDialog.show(
135 this,
136 null,
137 getResources().getString(R.string.wait),
138 true,
139 true);
140 }
141 }
142
143 private View mNoImagesView;
144
145 // Show/Hide the "no images" icon and text. Load resources on demand.
146 private void showNoImagesView() {
147 if (mNoImagesView == null) {
148 ViewGroup root = (ViewGroup) findViewById(R.id.root);
149 getLayoutInflater().inflate(R.layout.gallerypicker_no_images, root);
150 mNoImagesView = findViewById(R.id.no_images);
151 }
152 mNoImagesView.setVisibility(View.VISIBLE);
153 }
154
155 private void hideNoImagesView() {
156 if (mNoImagesView != null) {
157 mNoImagesView.setVisibility(View.GONE);
158 }
159 }
160
161 // The storage status is changed, restart the worker or show "no images".
162 private void rebake(boolean unmounted, boolean scanning) {
163 if (unmounted == mUnmounted && scanning == mScanning) return;
164 abortWorker();
165 mUnmounted = unmounted;
166 mScanning = scanning;
167 updateScanningDialog(mScanning);
168 if (mUnmounted) {
169 showNoImagesView();
170 } else {
171 hideNoImagesView();
172 startWorker();
173 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800174 }
Owen Lin937fc482009-04-14 02:02:51 -0700175
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700176 // This is called when we receive media-related broadcast.
177 private void onReceiveMediaBroadcast(Intent intent) {
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700178 String action = intent.getAction();
179 if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) {
180 // SD card available
181 // TODO put up a "please wait" message
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700182 } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) {
183 // SD card unavailable
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700184 rebake(true, false);
185 } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700186 rebake(false, true);
187 } else if (action.equals(
188 Intent.ACTION_MEDIA_SCANNER_FINISHED)) {
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700189 rebake(false, false);
190 } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700191 rebake(true, false);
192 }
193 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800194
195 private void launchFolderGallery(int position) {
196 mAdapter.mItems.get(position).launch(this);
197 }
198
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700199 private void onCreateGalleryPickerContextMenu(ContextMenu menu,
200 final ContextMenuInfo menuInfo) {
201 int position = ((AdapterContextMenuInfo) menuInfo).position;
202 menu.setHeaderTitle(mAdapter.baseTitleForPosition(position));
203 // "Slide Show"
204 if ((mAdapter.getIncludeMediaTypes(position)
205 & ImageManager.INCLUDE_IMAGES) != 0) {
Chih-Chung Changd1890832009-09-08 13:32:52 +0800206 menu.add(R.string.slide_show)
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700207 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
208 public boolean onMenuItemClick(MenuItem item) {
209 return onSlideShowClicked(menuInfo);
210 }
211 });
212 }
213 // "View"
Chih-Chung Changd1890832009-09-08 13:32:52 +0800214 menu.add(R.string.view)
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700215 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
216 public boolean onMenuItemClick(MenuItem item) {
217 return onViewClicked(menuInfo);
218 }
219 });
220 }
221
222 // This is called when the user clicks "Slideshow" from the context menu.
223 private boolean onSlideShowClicked(ContextMenuInfo menuInfo) {
224 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
225 int position = info.position;
226
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800227 if (position < 0 || position >= mAdapter.mItems.size()) {
228 return true;
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700229 }
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800230 // Slide show starts from the first image on the list.
231 Item item = mAdapter.mItems.get(position);
232 Uri targetUri = item.mFirstImageUri;
233
234 if (targetUri != null && item.mBucketId != null) {
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700235 targetUri = targetUri.buildUpon()
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800236 .appendQueryParameter("bucketId", item.mBucketId)
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700237 .build();
238 }
239 Intent intent = new Intent(Intent.ACTION_VIEW, targetUri);
240 intent.putExtra("slideshow", true);
241 startActivity(intent);
242 return true;
243 }
244
245 // This is called when the user clicks "View" from the context menu.
246 private boolean onViewClicked(ContextMenuInfo menuInfo) {
247 AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
248 launchFolderGallery(info.position);
249 return true;
250 }
251
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800252 @Override
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800253 public void onStop() {
254 super.onStop();
Owen Lin937fc482009-04-14 02:02:51 -0700255
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800256 abortWorker();
257
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800258 unregisterReceiver(mReceiver);
Owen Lin588e29b2009-07-07 11:37:12 -0700259 getContentResolver().unregisterContentObserver(mDbObserver);
repo sync62ca1912009-07-01 18:37:14 +0800260
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800261 // free up some ram
262 mAdapter = null;
263 mGridView.setAdapter(null);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800264 unloadDrawable();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800265 }
266
267 @Override
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800268 public void onStart() {
269 super.onStart();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800270
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800271 mAdapter = new GalleryPickerAdapter(getLayoutInflater());
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800272 mGridView.setAdapter(mAdapter);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800273
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800274 // install an intent filter to receive SD card related events.
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700275 IntentFilter intentFilter = new IntentFilter();
276 intentFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800277 intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
278 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
279 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
280 intentFilter.addAction(Intent.ACTION_MEDIA_EJECT);
281 intentFilter.addDataScheme("file");
282
283 registerReceiver(mReceiver, intentFilter);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800284
repo sync62ca1912009-07-01 18:37:14 +0800285 getContentResolver().registerContentObserver(
286 MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
287 true, mDbObserver);
288
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800289 // Assume the storage is mounted and not scanning.
290 mUnmounted = false;
291 mScanning = false;
292 startWorker();
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800293 }
294
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800295 // This is used to stop the worker thread.
296 volatile boolean mAbort = false;
297
298 // Create the worker thread.
299 private void startWorker() {
300 mAbort = false;
301 mWorkerThread = new Thread("GalleryPicker Worker") {
Owen Lindeb57252009-05-27 14:55:45 -0700302 @Override
303 public void run() {
304 workerRun();
305 }
306 };
Owen Lin33f86b82009-07-13 16:45:23 -0700307 BitmapManager.instance().allowThreadDecoding(mWorkerThread);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800308 mWorkerThread.start();
309 }
310
311 private void abortWorker() {
312 if (mWorkerThread != null) {
Ray Chen28f35952009-10-05 14:34:24 -0700313 BitmapManager.instance().cancelThreadDecoding(mWorkerThread, getContentResolver());
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800314 mAbort = true;
315 try {
316 mWorkerThread.join();
317 } catch (InterruptedException ex) {
318 Log.e(TAG, "join interrupted");
319 }
320 mWorkerThread = null;
Chih-Chung Changa61075a2009-05-07 11:07:06 +0800321 // Remove all runnables in mHandler.
322 // (We assume that the "what" field in the messages are 0
323 // for runnables).
324 mHandler.removeMessages(0);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800325 mAdapter.clear();
326 mAdapter.updateDisplay();
327 clearImageLists();
328 }
329 }
330
331 // This is run in the worker thread.
332 private void workerRun() {
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800333 // We collect items from checkImageList() and checkBucketIds() and
334 // put them in allItems. Later we give allItems to checkThumbBitmap()
335 // and generated thumbnail bitmaps for each item. We do this instead of
336 // generating thumbnail bitmaps in checkImageList() and checkBucketIds()
337 // because we want to show all the folders first, then update them with
338 // the thumb bitmaps. (Generating thumbnail bitmaps takes some time.)
339 ArrayList<Item> allItems = new ArrayList<Item>();
340
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800341 checkScanning();
342 if (mAbort) return;
343
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800344 checkImageList(allItems);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800345 if (mAbort) return;
346
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800347 checkBucketIds(allItems);
348 if (mAbort) return;
349
350 checkThumbBitmap(allItems);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800351 if (mAbort) return;
352
353 checkLowStorage();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800354 }
355
356 // This is run in the worker thread.
357 private void checkScanning() {
358 ContentResolver cr = getContentResolver();
359 final boolean scanning =
360 ImageManager.isMediaScannerScanning(cr);
361 mHandler.post(new Runnable() {
362 public void run() {
363 checkScanningFinished(scanning);
364 }
365 });
366 }
367
368 // This is run in the main thread.
369 private void checkScanningFinished(boolean scanning) {
370 updateScanningDialog(scanning);
371 }
372
373 // This is run in the worker thread.
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800374 private void checkImageList(ArrayList<Item> allItems) {
Owen Lin59b09db2009-05-28 20:21:39 -0700375 int length = IMAGE_LIST_DATA.length;
376 IImageList[] lists = new IImageList[length];
377 for (int i = 0; i < length; i++) {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800378 ImageListData data = IMAGE_LIST_DATA[i];
379 lists[i] = createImageList(data.mInclude, data.mBucketId,
380 getContentResolver());
381 if (mAbort) return;
382 Item item = null;
383
384 if (lists[i].isEmpty()) continue;
385
Owen Lin59b09db2009-05-28 20:21:39 -0700386 // i >= 3 means we are looking at All Images/All Videos.
387 // lists[i-3] is the corresponding Camera Images/Camera Videos.
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800388 // We want to add the "All" list only if it's different from
389 // the "Camera" list.
Owen Lin59b09db2009-05-28 20:21:39 -0700390 if (i >= 3 && lists[i].getCount() == lists[i - 3].getCount()) {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800391 continue;
392 }
393
394 item = new Item(data.mType,
395 data.mBucketId,
396 getResources().getString(data.mStringId),
397 lists[i]);
398
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800399 allItems.add(item);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800400
401 final Item finalItem = item;
402 mHandler.post(new Runnable() {
403 public void run() {
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800404 updateItem(finalItem);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800405 }
406 });
407 }
408 }
409
410 // This is run in the main thread.
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800411 private void updateItem(Item item) {
Owen Linf68887f2009-07-16 17:09:24 -0700412 // Hide NoImageView if we are going to add the first item
413 if (mAdapter.getCount() == 0) {
414 hideNoImagesView();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800415 }
Owen Linf68887f2009-07-16 17:09:24 -0700416 mAdapter.addItem(item);
417 mAdapter.updateDisplay();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800418 }
419
420 private static final String CAMERA_BUCKET =
421 ImageManager.CAMERA_IMAGE_BUCKET_ID;
422
423 // This is run in the worker thread.
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800424 private void checkBucketIds(ArrayList<Item> allItems) {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800425 final IImageList allImages;
426 if (!mScanning && !mUnmounted) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800427 allImages = ImageManager.makeImageList(
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800428 getContentResolver(),
429 ImageManager.DataLocation.ALL,
Owen Linf2718932009-06-03 17:07:33 -0700430 ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS,
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800431 ImageManager.SORT_DESCENDING,
432 null);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800433 } else {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800434 allImages = ImageManager.makeEmptyImageList();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800435 }
436
437 if (mAbort) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800438 allImages.close();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800439 return;
440 }
441
442 HashMap<String, String> hashMap = allImages.getBucketIds();
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800443 allImages.close();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800444 if (mAbort) return;
445
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800446 for (Map.Entry<String, String> entry : hashMap.entrySet()) {
447 String key = entry.getKey();
448 if (key == null) {
449 continue;
450 }
451 if (!key.equals(CAMERA_BUCKET)) {
452 IImageList list = createImageList(
453 ImageManager.INCLUDE_IMAGES
454 | ImageManager.INCLUDE_VIDEOS, key,
455 getContentResolver());
456 if (mAbort) return;
457
458 Item item = new Item(Item.TYPE_NORMAL_FOLDERS, key,
459 entry.getValue(), list);
460
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800461 allItems.add(item);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800462
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800463 final Item finalItem = item;
464 mHandler.post(new Runnable() {
465 public void run() {
466 updateItem(finalItem);
467 }
468 });
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800469 }
470 }
471
472 mHandler.post(new Runnable() {
473 public void run() {
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800474 checkBucketIdsFinished();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800475 }
476 });
477 }
478
479 // This is run in the main thread.
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800480 private void checkBucketIdsFinished() {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800481
482 // If we just have one folder, open it.
483 // If we have zero folder, show the "no images" icon.
484 if (!mScanning) {
485 int numItems = mAdapter.mItems.size();
486 if (numItems == 0) {
487 showNoImagesView();
488 } else if (numItems == 1) {
489 mAdapter.mItems.get(0).launch(this);
490 finish();
491 return;
492 }
493 }
494 }
495
Chih-Chung Changdfedc5b2009-05-07 11:49:24 +0800496 private static final int THUMB_SIZE = 142;
497 // This is run in the worker thread.
498 private void checkThumbBitmap(ArrayList<Item> allItems) {
499 for (Item item : allItems) {
500 final Bitmap b = makeMiniThumbBitmap(THUMB_SIZE, THUMB_SIZE,
501 item.mImageList);
502 if (mAbort) {
503 if (b != null) b.recycle();
504 return;
505 }
506
507 final Item finalItem = item;
508 mHandler.post(new Runnable() {
509 public void run() {
510 updateThumbBitmap(finalItem, b);
511 }
512 });
513 }
514 }
515
516 // This is run in the main thread.
517 private void updateThumbBitmap(Item item, Bitmap b) {
518 item.setThumbBitmap(b);
519 mAdapter.updateDisplay();
520 }
521
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800522 private static final long LOW_STORAGE_THRESHOLD = 1024 * 1024 * 2;
523
524 // This is run in the worker thread.
525 private void checkLowStorage() {
526 // Check available space only if we are writable
527 if (ImageManager.hasStorage()) {
528 String storageDirectory = Environment
529 .getExternalStorageDirectory().toString();
530 StatFs stat = new StatFs(storageDirectory);
531 long remaining = (long) stat.getAvailableBlocks()
532 * (long) stat.getBlockSize();
533 if (remaining < LOW_STORAGE_THRESHOLD) {
534 mHandler.post(new Runnable() {
535 public void run() {
536 checkLowStorageFinished();
537 }
538 });
539 }
540 }
541 }
542
543 // This is run in the main thread.
544 // This is called only if the storage is low.
545 private void checkLowStorageFinished() {
546 Toast.makeText(GalleryPicker.this, R.string.not_enough_space, 5000)
547 .show();
548 }
549
550 // IMAGE_LIST_DATA stores the parameters for the four image lists
551 // we are interested in. The order of the IMAGE_LIST_DATA array is
552 // significant (See the implementation of GalleryPickerAdapter.init).
553 private static final class ImageListData {
554 ImageListData(int type, int include, String bucketId, int stringId) {
555 mType = type;
556 mInclude = include;
557 mBucketId = bucketId;
558 mStringId = stringId;
559 }
560 int mType;
561 int mInclude;
562 String mBucketId;
563 int mStringId;
564 }
565
566 private static final ImageListData[] IMAGE_LIST_DATA = {
567 // Camera Images
568 new ImageListData(Item.TYPE_CAMERA_IMAGES,
569 ImageManager.INCLUDE_IMAGES,
570 ImageManager.CAMERA_IMAGE_BUCKET_ID,
571 R.string.gallery_camera_bucket_name),
572 // Camera Videos
573 new ImageListData(Item.TYPE_CAMERA_VIDEOS,
574 ImageManager.INCLUDE_VIDEOS,
575 ImageManager.CAMERA_IMAGE_BUCKET_ID,
576 R.string.gallery_camera_videos_bucket_name),
Owen Lin59b09db2009-05-28 20:21:39 -0700577
578 // Camera Medias
579 new ImageListData(Item.TYPE_CAMERA_MEDIAS,
580 ImageManager.INCLUDE_VIDEOS | ImageManager.INCLUDE_IMAGES,
581 ImageManager.CAMERA_IMAGE_BUCKET_ID,
582 R.string.gallery_camera_media_bucket_name),
583
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800584 // All Images
585 new ImageListData(Item.TYPE_ALL_IMAGES,
586 ImageManager.INCLUDE_IMAGES,
587 null,
588 R.string.all_images),
589
590 // All Videos
591 new ImageListData(Item.TYPE_ALL_VIDEOS,
592 ImageManager.INCLUDE_VIDEOS,
593 null,
594 R.string.all_videos),
595 };
596
597
598 // These drawables are loaded on-demand.
599 Drawable mFrameGalleryMask;
600 Drawable mCellOutline;
601 Drawable mVideoOverlay;
602
603 private void loadDrawableIfNeeded() {
604 if (mFrameGalleryMask != null) return; // already loaded
605 Resources r = getResources();
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700606 mFrameGalleryMask = r.getDrawable(
607 R.drawable.frame_gallery_preview_album_mask);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800608 mCellOutline = r.getDrawable(android.R.drawable.gallery_thumb);
609 mVideoOverlay = r.getDrawable(R.drawable.ic_gallery_video_overlay);
610 }
611
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800612 private void unloadDrawable() {
613 mFrameGalleryMask = null;
614 mCellOutline = null;
615 mVideoOverlay = null;
616 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800617
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800618 private static void placeImage(Bitmap image, Canvas c, Paint paint,
619 int imageWidth, int widthPadding, int imageHeight,
620 int heightPadding, int offsetX, int offsetY,
621 int pos) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800622 int row = pos / 2;
623 int col = pos - (row * 2);
624
625 int xPos = (col * (imageWidth + widthPadding)) - offsetX;
626 int yPos = (row * (imageHeight + heightPadding)) - offsetY;
627
628 c.drawBitmap(image, xPos, yPos, paint);
629 }
630
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800631 // This is run in worker thread.
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700632 private Bitmap makeMiniThumbBitmap(int width, int height,
633 IImageList images) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800634 int count = images.getCount();
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700635 // We draw three different version of the folder image depending on the
636 // number of images in the folder.
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800637 // For a single image, that image draws over the whole folder.
638 // For two or three images, we draw the two most recent photos.
639 // For four or more images, we draw four photos.
640 final int padding = 4;
641 int imageWidth = width;
642 int imageHeight = height;
643 int offsetWidth = 0;
644 int offsetHeight = 0;
645
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700646 imageWidth = (imageWidth - padding) / 2; // 2 here because we show two
647 // images
648 imageHeight = (imageHeight - padding) / 2; // per row and column
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800649
Chih-Chung Chang6b270502009-04-29 11:57:06 +0800650 final Paint p = new Paint();
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700651 final Bitmap b = Bitmap.createBitmap(width, height,
652 Bitmap.Config.ARGB_8888);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800653 final Canvas c = new Canvas(b);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800654 final Matrix m = new Matrix();
655
656 // draw the whole canvas as transparent
657 p.setColor(0x00000000);
658 c.drawPaint(p);
659
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800660 // load the drawables
661 loadDrawableIfNeeded();
662
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800663 // draw the mask normally
664 p.setColor(0xFFFFFFFF);
665 mFrameGalleryMask.setBounds(0, 0, width, height);
666 mFrameGalleryMask.draw(c);
667
668 Paint pdpaint = new Paint();
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700669 pdpaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800670
671 pdpaint.setStyle(Paint.Style.FILL);
672 c.drawRect(0, 0, width, height, pdpaint);
673
674 for (int i = 0; i < 4; i++) {
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800675 if (mAbort) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800676 return null;
677 }
678
679 Bitmap temp = null;
Owen Lin101d5282009-04-03 16:20:08 -0700680 IImage image = i < count ? images.getImageAt(i) : null;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800681
682 if (image != null) {
683 temp = image.miniThumbBitmap();
684 }
685
686 if (temp != null) {
687 if (ImageManager.isVideo(image)) {
688 Bitmap newMap = temp.copy(temp.getConfig(), true);
689 Canvas overlayCanvas = new Canvas(newMap);
690 int overlayWidth = mVideoOverlay.getIntrinsicWidth();
691 int overlayHeight = mVideoOverlay.getIntrinsicHeight();
692 int left = (newMap.getWidth() - overlayWidth) / 2;
693 int top = (newMap.getHeight() - overlayHeight) / 2;
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700694 Rect newBounds = new Rect(left, top, left + overlayWidth,
695 top + overlayHeight);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800696 mVideoOverlay.setBounds(newBounds);
697 mVideoOverlay.draw(overlayCanvas);
698 temp.recycle();
699 temp = newMap;
700 }
701
Chih-Chung Chang34fe2a92009-08-19 15:52:32 +0800702 temp = Util.transform(m, temp, imageWidth,
703 imageHeight, true, Util.RECYCLE_INPUT);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800704 }
705
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700706 Bitmap thumb = Bitmap.createBitmap(imageWidth, imageHeight,
707 Bitmap.Config.ARGB_8888);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800708 Canvas tempCanvas = new Canvas(thumb);
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700709 if (temp != null) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800710 tempCanvas.drawBitmap(temp, new Matrix(), new Paint());
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700711 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800712 mCellOutline.setBounds(0, 0, imageWidth, imageHeight);
713 mCellOutline.draw(tempCanvas);
714
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700715 placeImage(thumb, c, pdpaint, imageWidth, padding, imageHeight,
716 padding, offsetWidth, offsetHeight, i);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800717
718 thumb.recycle();
719
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700720 if (temp != null) {
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800721 temp.recycle();
Chih-Chung Changd8209aa2009-04-07 11:45:12 -0700722 }
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800723 }
724
725 return b;
726 }
727
728 @Override
729 public boolean onCreateOptionsMenu(Menu menu) {
730 super.onCreateOptionsMenu(menu);
731
732 MenuHelper.addCaptureMenuItems(menu, this);
733
Chih-Chung Changd1890832009-09-08 13:32:52 +0800734 menu.add(Menu.NONE, Menu.NONE, MenuHelper.POSITION_GALLERY_SETTING,
735 R.string.camerasettings)
Chih-Chung Chang885e5582009-04-08 03:24:13 -0700736 .setOnMenuItemClickListener(new OnMenuItemClickListener() {
737 public boolean onMenuItemClick(MenuItem item) {
738 Intent preferences = new Intent();
739 preferences.setClass(GalleryPicker.this,
740 GallerySettings.class);
741 startActivity(preferences);
742 return true;
743 }
744 })
745 .setAlphabeticShortcut('p')
746 .setIcon(android.R.drawable.ic_menu_preferences);
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800747
748 return true;
749 }
750
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800751 // image lists created by createImageList() are collected in mAllLists.
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800752 // They will be closed in clearImageList, so they don't hold open files
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800753 // on SD card. We will be killed if we don't close files when the SD card
754 // is unmounted.
755 ArrayList<IImageList> mAllLists = new ArrayList<IImageList>();
756
757 private IImageList createImageList(int mediaTypes, String bucketId,
758 ContentResolver cr) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800759 IImageList list = ImageManager.makeImageList(
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800760 cr,
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800761 ImageManager.DataLocation.ALL,
762 mediaTypes,
763 ImageManager.SORT_DESCENDING,
764 bucketId);
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800765 mAllLists.add(list);
766 return list;
767 }
768
769 private void clearImageLists() {
770 for (IImageList list : mAllLists) {
Chih-Chung Changf5bf8ca2009-08-25 18:28:29 +0800771 list.close();
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800772 }
773 mAllLists.clear();
774 }
775}
776
777// Item is the underlying data for GalleryPickerAdapter.
778// It is passed from the activity to the adapter.
779class Item {
780 public static final int TYPE_NONE = -1;
781 public static final int TYPE_ALL_IMAGES = 0;
782 public static final int TYPE_ALL_VIDEOS = 1;
783 public static final int TYPE_CAMERA_IMAGES = 2;
784 public static final int TYPE_CAMERA_VIDEOS = 3;
Owen Lin59b09db2009-05-28 20:21:39 -0700785 public static final int TYPE_CAMERA_MEDIAS = 4;
786 public static final int TYPE_NORMAL_FOLDERS = 5;
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800787
788 public final int mType;
789 public final String mBucketId;
790 public final String mName;
791 public final IImageList mImageList;
792 public final int mCount;
793 public final Uri mFirstImageUri; // could be null if the list is empty
794
795 // The thumbnail bitmap is set by setThumbBitmap() later because we want
796 // to let the user sees the folder icon as soon as possible (and possibly
797 // select them), then present more detailed information when we have it.
798 public Bitmap mThumbBitmap; // the thumbnail bitmap for the image list
799
800 public Item(int type, String bucketId, String name, IImageList list) {
801 mType = type;
802 mBucketId = bucketId;
803 mName = name;
804 mImageList = list;
805 mCount = list.getCount();
806 if (mCount > 0) {
807 mFirstImageUri = list.getImageAt(0).fullSizeImageUri();
808 } else {
809 mFirstImageUri = null;
810 }
811 }
812
813 public void setThumbBitmap(Bitmap thumbBitmap) {
814 mThumbBitmap = thumbBitmap;
815 }
816
817 public boolean needsBucketId() {
818 return mType >= TYPE_CAMERA_IMAGES;
819 }
820
821 public void launch(Activity activity) {
822 Uri uri = Images.Media.INTERNAL_CONTENT_URI;
823 if (needsBucketId()) {
824 uri = uri.buildUpon()
825 .appendQueryParameter("bucketId", mBucketId).build();
826 }
827 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
828 intent.putExtra("windowTitle", mName);
829 intent.putExtra("mediaTypes", getIncludeMediaTypes());
830 activity.startActivity(intent);
831 }
832
833 public int getIncludeMediaTypes() {
834 return convertItemTypeToIncludedMediaType(mType);
835 }
836
837 public static int convertItemTypeToIncludedMediaType(int itemType) {
838 switch (itemType) {
839 case TYPE_ALL_IMAGES:
840 case TYPE_CAMERA_IMAGES:
841 return ImageManager.INCLUDE_IMAGES;
842 case TYPE_ALL_VIDEOS:
843 case TYPE_CAMERA_VIDEOS:
844 return ImageManager.INCLUDE_VIDEOS;
845 case TYPE_NORMAL_FOLDERS:
Owen Lin59b09db2009-05-28 20:21:39 -0700846 case TYPE_CAMERA_MEDIAS:
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800847 default:
848 return ImageManager.INCLUDE_IMAGES
849 | ImageManager.INCLUDE_VIDEOS;
850 }
851 }
852
853 public int getOverlay() {
854 switch (mType) {
855 case TYPE_ALL_IMAGES:
856 case TYPE_CAMERA_IMAGES:
857 return R.drawable.frame_overlay_gallery_camera;
858 case TYPE_ALL_VIDEOS:
859 case TYPE_CAMERA_VIDEOS:
Owen Lin59b09db2009-05-28 20:21:39 -0700860 case TYPE_CAMERA_MEDIAS:
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800861 return R.drawable.frame_overlay_gallery_video;
862 case TYPE_NORMAL_FOLDERS:
863 default:
864 return R.drawable.frame_overlay_gallery_folder;
865 }
866 }
867}
868
869class GalleryPickerAdapter extends BaseAdapter {
870 ArrayList<Item> mItems = new ArrayList<Item>();
871 LayoutInflater mInflater;
872
873 GalleryPickerAdapter(LayoutInflater inflater) {
874 mInflater = inflater;
875 }
876
877 public void addItem(Item item) {
878 mItems.add(item);
879 }
880
881 public void updateDisplay() {
882 notifyDataSetChanged();
883 }
884
885 public void clear() {
886 mItems.clear();
887 }
888
889 public int getCount() {
890 return mItems.size();
891 }
892
893 public Object getItem(int position) {
894 return null;
895 }
896
897 public long getItemId(int position) {
898 return position;
899 }
900
901 public String baseTitleForPosition(int position) {
902 return mItems.get(position).mName;
903 }
904
905 public int getIncludeMediaTypes(int position) {
906 return mItems.get(position).getIncludeMediaTypes();
907 }
908
909 public View getView(final int position, View convertView,
910 ViewGroup parent) {
911 View v;
912
913 if (convertView == null) {
914 v = mInflater.inflate(R.layout.gallery_picker_item, null);
915 } else {
916 v = convertView;
917 }
918
919 TextView titleView = (TextView) v.findViewById(R.id.title);
920
921 GalleryPickerItem iv =
922 (GalleryPickerItem) v.findViewById(R.id.thumbnail);
923 Item item = mItems.get(position);
924 iv.setOverlay(item.getOverlay());
925 if (item.mThumbBitmap != null) {
926 iv.setImageBitmap(item.mThumbBitmap);
927 String title = item.mName + " (" + item.mCount + ")";
928 titleView.setText(title);
929 } else {
930 iv.setImageResource(android.R.color.transparent);
931 titleView.setText(item.mName);
932 }
933
Owen Lind10dda22009-10-13 12:15:29 +0800934 // An workaround due to a bug in TextView. If the length of text is
935 // different from the previous in convertView, the layout would be
936 // wrong.
937 titleView.requestLayout();
938
Chih-Chung Changeb99d4e2009-05-06 15:21:04 +0800939 return v;
The Android Open Source Projectb64d3452009-03-03 19:32:20 -0800940 }
941}