| /* |
| * Copyright (C) 2007 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.camera; |
| |
| import android.content.BroadcastReceiver; |
| import android.app.Activity; |
| import android.app.Dialog; |
| import android.app.ProgressDialog; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.SharedPreferences; |
| import android.content.pm.ActivityInfo; |
| import android.graphics.Bitmap; |
| import android.graphics.BitmapFactory; |
| import android.graphics.Canvas; |
| import android.graphics.Matrix; |
| import android.graphics.Paint; |
| import android.graphics.Rect; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Bundle; |
| import android.os.Handler; |
| import android.os.PowerManager; |
| import android.util.AttributeSet; |
| import android.util.Config; |
| import android.util.Log; |
| import android.view.ContextMenu; |
| import android.view.GestureDetector; |
| import android.view.GestureDetector.SimpleOnGestureListener; |
| import android.view.KeyEvent; |
| import android.view.Menu; |
| import android.view.MenuItem; |
| import android.view.MotionEvent; |
| import android.view.View; |
| import android.view.ViewConfiguration; |
| import android.view.Window; |
| import android.widget.TextView; |
| import android.widget.Toast; |
| import android.preference.PreferenceManager; |
| import android.provider.MediaStore; |
| import android.widget.Scroller; |
| |
| import java.util.Calendar; |
| import java.util.GregorianCalendar; |
| |
| import com.android.camera.ImageManager.IImage; |
| |
| public class ImageGallery2 extends Activity { |
| private static final String TAG = "ImageGallery2"; |
| private ImageManager.IImageList mAllImages; |
| private int mInclusion; |
| private boolean mSortAscending = false; |
| private View mNoImagesView; |
| public final static int CROP_MSG = 2; |
| public final static int VIEW_MSG = 3; |
| |
| private static final String INSTANCE_STATE_TAG = "scrollY"; |
| |
| private Dialog mMediaScanningDialog; |
| |
| private MenuItem mFlipItem; |
| private MenuItem mSlideShowItem; |
| private SharedPreferences mPrefs; |
| |
| public ImageGallery2() { |
| } |
| |
| BroadcastReceiver mReceiver = null; |
| |
| Handler mHandler = new Handler(); |
| boolean mLayoutComplete; |
| boolean mPausing = false; |
| boolean mStopThumbnailChecking = false; |
| |
| CameraThread mThumbnailCheckThread; |
| GridViewSpecial mGvs; |
| |
| @Override |
| public void onCreate(Bundle icicle) { |
| if (Config.LOGV) Log.v(TAG, "onCreate"); |
| super.onCreate(icicle); |
| |
| mPrefs = PreferenceManager.getDefaultSharedPreferences(this); |
| |
| requestWindowFeature(Window.FEATURE_CUSTOM_TITLE); // must be called before setContentView() |
| setContentView(R.layout.image_gallery_2); |
| |
| getWindow().setFeatureInt(Window.FEATURE_CUSTOM_TITLE, R.layout.custom_gallery_title); |
| if (Config.LOGV) |
| Log.v(TAG, "findView... " + findViewById(R.id.loading_indicator)); |
| |
| mGvs = (GridViewSpecial) findViewById(R.id.grid); |
| mGvs.requestFocus(); |
| |
| if (!isPickIntent()) { |
| mGvs.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() { |
| public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { |
| if (mSelectedImageGetter.getCurrentImage() == null) |
| return; |
| |
| boolean isImage = ImageManager.isImage(mSelectedImageGetter.getCurrentImage()); |
| if (isImage) { |
| menu.add(0, 0, 0, R.string.view).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { |
| public boolean onMenuItemClick(MenuItem item) { |
| mGvs.onSelect(mGvs.mCurrentSelection); |
| return true; |
| } |
| }); |
| } |
| |
| menu.setHeaderTitle(isImage ? R.string.context_menu_header |
| : R.string.video_context_menu_header); |
| if ((mInclusion & (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS)) != 0) { |
| MenuHelper.MenuItemsResult r = MenuHelper.addImageMenuItems( |
| menu, |
| MenuHelper.INCLUDE_ALL, |
| isImage, |
| ImageGallery2.this, |
| mHandler, |
| mDeletePhotoRunnable, |
| new MenuHelper.MenuInvoker() { |
| public void run(MenuHelper.MenuCallback cb) { |
| cb.run(mSelectedImageGetter.getCurrentImageUri(), mSelectedImageGetter.getCurrentImage()); |
| |
| mGvs.clearCache(); |
| mGvs.invalidate(); |
| mGvs.requestLayout(); |
| mGvs.start(); |
| mNoImagesView.setVisibility(mAllImages.getCount() > 0 ? View.GONE : View.VISIBLE); |
| } |
| }); |
| if (r != null) |
| r.gettingReadyToOpen(menu, mSelectedImageGetter.getCurrentImage()); |
| |
| if (isImage) { |
| addSlideShowMenu(menu, 1000); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| private MenuItem addSlideShowMenu(Menu menu, int position) { |
| return menu.add(0, 207, position, R.string.slide_show) |
| .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { |
| public boolean onMenuItemClick(MenuItem item) { |
| ImageManager.IImage img = mSelectedImageGetter.getCurrentImage(); |
| if (img == null) { |
| img = mAllImages.getImageAt(0); |
| if (img == null) { |
| return true; |
| } |
| } |
| Uri targetUri = img.fullSizeImageUri(); |
| Uri thisUri = getIntent().getData(); |
| if (thisUri != null) { |
| String bucket = thisUri.getQueryParameter("bucketId"); |
| if (bucket != null) { |
| targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build(); |
| } |
| } |
| Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); |
| intent.putExtra("slideshow", true); |
| startActivity(intent); |
| return true; |
| } |
| }) |
| .setIcon(android.R.drawable.ic_menu_slideshow); |
| } |
| |
| private Runnable mDeletePhotoRunnable = new Runnable() { |
| public void run() { |
| mGvs.clearCache(); |
| mAllImages.removeImage(mSelectedImageGetter.getCurrentImage()); |
| mGvs.invalidate(); |
| mGvs.requestLayout(); |
| mGvs.start(); |
| mNoImagesView.setVisibility(mAllImages.isEmpty() ? View.VISIBLE : View.GONE); |
| } |
| }; |
| |
| private SelectedImageGetter mSelectedImageGetter = new SelectedImageGetter() { |
| public Uri getCurrentImageUri() { |
| ImageManager.IImage image = getCurrentImage(); |
| if (image != null) |
| return image.fullSizeImageUri(); |
| else |
| return null; |
| } |
| public ImageManager.IImage getCurrentImage() { |
| int currentSelection = mGvs.mCurrentSelection; |
| if (currentSelection < 0 || currentSelection >= mAllImages.getCount()) |
| return null; |
| else |
| return mAllImages.getImageAt(currentSelection); |
| } |
| }; |
| |
| @Override |
| public void onConfigurationChanged(android.content.res.Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| mTargetScroll = mGvs.getScrollY(); |
| } |
| |
| private Runnable mLongPressCallback = new Runnable() { |
| public void run() { |
| mGvs.select(-2, false); |
| mGvs.showContextMenu(); |
| } |
| }; |
| |
| @Override |
| public boolean onKeyUp(int keyCode, KeyEvent event) { |
| if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) { |
| mGvs.select(-2, false); |
| // The keyUp doesn't get called when the longpress menu comes up. We only get here when the user |
| // lets go of the center key before the longpress menu comes up. |
| mHandler.removeCallbacks(mLongPressCallback); |
| |
| // open the photo |
| if (mSelectedImageGetter.getCurrentImage() != null) { |
| mGvs.onSelect(mGvs.mCurrentSelection); |
| } |
| return true; |
| } |
| return super.onKeyUp(keyCode, event); |
| } |
| |
| @Override |
| public boolean onKeyDown(int keyCode, KeyEvent event) { |
| boolean handled = true; |
| int sel = mGvs.mCurrentSelection; |
| int columns = mGvs.mCurrentSpec.mColumns; |
| int count = mAllImages.getCount(); |
| boolean pressed = false; |
| if (mGvs.mShowSelection) { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DPAD_RIGHT: |
| if (sel != count && (sel % columns < columns - 1)) { |
| sel += 1; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_LEFT: |
| if (sel > 0 && (sel % columns != 0)) { |
| sel -= 1; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_UP: |
| if ((sel / columns) != 0) { |
| sel -= columns; |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| if ((sel / columns) != (sel+columns / columns)) { |
| sel = Math.min(count-1, sel + columns); |
| } |
| break; |
| case KeyEvent.KEYCODE_DPAD_CENTER: |
| pressed = true; |
| mHandler.postDelayed(mLongPressCallback, ViewConfiguration.getLongPressTimeout()); |
| break; |
| case KeyEvent.KEYCODE_DEL: |
| MenuHelper.deleteImage(this, mDeletePhotoRunnable, |
| mSelectedImageGetter.getCurrentImage()); |
| break; |
| default: |
| handled = false; |
| break; |
| } |
| } else { |
| switch (keyCode) { |
| case KeyEvent.KEYCODE_DPAD_RIGHT: |
| case KeyEvent.KEYCODE_DPAD_LEFT: |
| case KeyEvent.KEYCODE_DPAD_UP: |
| case KeyEvent.KEYCODE_DPAD_DOWN: |
| int [] range = new int[2]; |
| GridViewSpecial.ImageBlockManager ibm = mGvs.mImageBlockManager; |
| if (ibm != null) { |
| mGvs.mImageBlockManager.getVisibleRange(range); |
| int topPos = range[0]; |
| android.graphics.Rect r = mGvs.getRectForPosition(topPos); |
| if (r.top < mGvs.getScrollY()) |
| topPos += columns; |
| topPos = Math.min(count - 1, topPos); |
| sel = topPos; |
| } |
| break; |
| default: |
| handled = false; |
| break; |
| } |
| } |
| if (handled) { |
| mGvs.select(sel, pressed); |
| return true; |
| } |
| else |
| return super.onKeyDown(keyCode, event); |
| } |
| |
| private boolean isPickIntent() { |
| String action = getIntent().getAction(); |
| return (Intent.ACTION_PICK.equals(action) || Intent.ACTION_GET_CONTENT.equals(action)); |
| } |
| |
| private void launchCropperOrFinish(ImageManager.IImage img) { |
| Bundle myExtras = getIntent().getExtras(); |
| |
| String cropValue = myExtras != null ? myExtras.getString("crop") : null; |
| if (cropValue != null) { |
| Bundle newExtras = new Bundle(); |
| if (cropValue.equals("circle")) |
| newExtras.putString("circleCrop", "true"); |
| |
| Intent cropIntent = new Intent(); |
| cropIntent.setData(img.fullSizeImageUri()); |
| cropIntent.setClass(this, CropImage.class); |
| cropIntent.putExtras(newExtras); |
| |
| /* pass through any extras that were passed in */ |
| cropIntent.putExtras(myExtras); |
| if (Config.LOGV) Log.v(TAG, "startSubActivity " + cropIntent); |
| startActivityForResult(cropIntent, CROP_MSG); |
| } else { |
| Intent result = new Intent(null, img.fullSizeImageUri()); |
| if (myExtras != null && myExtras.getString("return-data") != null) { |
| Bitmap bitmap = img.fullSizeBitmap(1000); |
| if (bitmap != null) |
| result.putExtra("data", bitmap); |
| } |
| setResult(RESULT_OK, result); |
| finish(); |
| } |
| } |
| |
| @Override |
| protected void onActivityResult(int requestCode, int resultCode, Intent data) { |
| if (Config.LOGV) |
| Log.v(TAG, "onActivityResult: " + requestCode + "; resultCode is " + resultCode + "; data is " + data); |
| switch (requestCode) { |
| case CROP_MSG: { |
| if (Config.LOGV) Log.v(TAG, "onActivityResult " + data); |
| setResult(resultCode, data); |
| finish(); |
| break; |
| } |
| case VIEW_MSG: { |
| if (Config.LOGV) |
| Log.v(TAG, "got VIEW_MSG with " + data); |
| ImageManager.IImage img = mAllImages.getImageForUri(data.getData()); |
| launchCropperOrFinish(img); |
| break; |
| } |
| } |
| } |
| |
| @Override |
| public void onPause() { |
| super.onPause(); |
| mPausing = true; |
| stopCheckingThumbnails(); |
| mGvs.onPause(); |
| |
| if (mReceiver != null) { |
| unregisterReceiver(mReceiver); |
| mReceiver = null; |
| } |
| // Now that we've paused the threads that are using the cursor it is safe |
| // to deactivate it. |
| mAllImages.deactivate(); |
| } |
| |
| private void rebake(boolean unmounted, boolean scanning) { |
| stopCheckingThumbnails(); |
| mGvs.clearCache(); |
| if (mAllImages != null) { |
| mAllImages.deactivate(); |
| mAllImages = null; |
| } |
| if (mMediaScanningDialog != null) { |
| mMediaScanningDialog.cancel(); |
| mMediaScanningDialog = null; |
| } |
| if (scanning) { |
| mMediaScanningDialog = ProgressDialog.show( |
| this, |
| null, |
| getResources().getString(R.string.wait), |
| true, |
| true); |
| mAllImages = ImageManager.instance().emptyImageList(); |
| } else { |
| mAllImages = allImages(!unmounted); |
| if (Config.LOGV) |
| Log.v(TAG, "mAllImages is now " + mAllImages); |
| mGvs.init(mHandler); |
| mGvs.start(); |
| mGvs.requestLayout(); |
| checkThumbnails(); |
| } |
| } |
| |
| @Override |
| protected void onSaveInstanceState(Bundle state) { |
| super.onSaveInstanceState(state); |
| mTargetScroll = mGvs.getScrollY(); |
| state.putInt(INSTANCE_STATE_TAG, mTargetScroll); |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Bundle state) { |
| super.onRestoreInstanceState(state); |
| mTargetScroll = state.getInt(INSTANCE_STATE_TAG, 0); |
| } |
| |
| int mTargetScroll; |
| |
| @Override |
| public void onResume() { |
| super.onResume(); |
| |
| try { |
| mGvs.setSizeChoice(Integer.parseInt(mPrefs.getString("pref_gallery_size_key", "1")), mTargetScroll); |
| |
| String sortOrder = mPrefs.getString("pref_gallery_sort_key", null); |
| if (sortOrder != null) { |
| mSortAscending = sortOrder.equals("ascending"); |
| } |
| } catch (Exception ex) { |
| |
| } |
| mPausing = false; |
| |
| // install an intent filter to receive SD card related events. |
| IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_MOUNTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED); |
| intentFilter.addAction(Intent.ACTION_MEDIA_EJECT); |
| intentFilter.addDataScheme("file"); |
| |
| mReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| if (Config.LOGV) Log.v(TAG, "onReceiveIntent " + intent.getAction()); |
| String action = intent.getAction(); |
| if (action.equals(Intent.ACTION_MEDIA_MOUNTED)) { |
| // SD card available |
| // TODO put up a "please wait" message |
| // TODO also listen for the media scanner finished message |
| } else if (action.equals(Intent.ACTION_MEDIA_UNMOUNTED)) { |
| // SD card unavailable |
| if (Config.LOGV) Log.v(TAG, "sd card no longer available"); |
| Toast.makeText(ImageGallery2.this, getResources().getString(R.string.wait), 5000); |
| rebake(true, false); |
| } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) { |
| Toast.makeText(ImageGallery2.this, getResources().getString(R.string.wait), 5000); |
| rebake(false, true); |
| } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_FINISHED)) { |
| if (Config.LOGV) |
| Log.v(TAG, "rebake because of ACTION_MEDIA_SCANNER_FINISHED"); |
| rebake(false, false); |
| } else if (action.equals(Intent.ACTION_MEDIA_EJECT)) { |
| if (Config.LOGV) |
| Log.v(TAG, "rebake because of ACTION_MEDIA_EJECT"); |
| rebake(true, false); |
| } |
| } |
| }; |
| registerReceiver(mReceiver, intentFilter); |
| |
| MenuHelper.requestOrientation(this, mPrefs); |
| |
| rebake(false, ImageManager.isMediaScannerScanning(this)); |
| } |
| |
| private void stopCheckingThumbnails() { |
| mStopThumbnailChecking = true; |
| if (mThumbnailCheckThread != null) { |
| mThumbnailCheckThread.join(); |
| } |
| mStopThumbnailChecking = false; |
| } |
| |
| private void checkThumbnails() { |
| final long startTime = System.currentTimeMillis(); |
| final long t1 = System.currentTimeMillis(); |
| mThumbnailCheckThread = new CameraThread(new Runnable() { |
| public void run() { |
| android.content.res.Resources resources = getResources(); |
| boolean loadingVideos = mInclusion == ImageManager.INCLUDE_VIDEOS; |
| final TextView progressTextView = (TextView) findViewById(R.id.loading_text); |
| final String progressTextFormatString = resources.getString(R.string.loading_progress_format_string); |
| |
| PowerManager pm = (PowerManager)getSystemService(Context.POWER_SERVICE); |
| PowerManager.WakeLock mWakeLock = |
| pm.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, |
| "ImageGallery2.checkThumbnails"); |
| mWakeLock.acquire(); |
| ImageManager.IImageList.ThumbCheckCallback r = new ImageManager.IImageList.ThumbCheckCallback() { |
| boolean mDidSetProgress = false; |
| |
| public boolean checking(final int count, final int maxCount) { |
| if (mStopThumbnailChecking) { |
| return false; |
| } |
| |
| if (!mLayoutComplete) { |
| return true; |
| } |
| |
| if (!mDidSetProgress) { |
| mHandler.post(new Runnable() { |
| public void run() { |
| findViewById(R.id.loading_indicator).setVisibility(View.VISIBLE); |
| } |
| }); |
| mDidSetProgress = true; |
| } |
| mGvs.postInvalidate(); |
| |
| if (System.currentTimeMillis() - startTime > 1000) { |
| mHandler.post(new Runnable() { |
| public void run() { |
| String s = String.format(progressTextFormatString, maxCount - count); |
| progressTextView.setText(s); |
| } |
| }); |
| } |
| |
| return !mPausing; |
| } |
| }; |
| allImages(true).checkThumbnails(r); |
| mWakeLock.release(); |
| mThumbnailCheckThread = null; |
| mHandler.post(new Runnable() { |
| public void run() { |
| findViewById(R.id.loading_indicator).setVisibility(View.GONE); |
| } |
| }); |
| long t2 = System.currentTimeMillis(); |
| if (Config.LOGV) |
| Log.v(TAG, "check thumbnails thread finishing; took " + (t2-t1)); |
| } |
| }); |
| |
| mThumbnailCheckThread.setName("check_thumbnails"); |
| mThumbnailCheckThread.start(); |
| mThumbnailCheckThread.toBackground(); |
| |
| ImageManager.IImageList list = allImages(true); |
| mNoImagesView.setVisibility(list.getCount() > 0 ? View.GONE : View.VISIBLE); |
| } |
| |
| @Override |
| public boolean onCreateOptionsMenu(android.view.Menu menu) { |
| MenuItem item; |
| MenuHelper.addCaptureMenuItems(menu, this); |
| if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { |
| mSlideShowItem = addSlideShowMenu(menu, 5); |
| |
| } |
| mFlipItem = MenuHelper.addFlipOrientation(menu, this, mPrefs); |
| |
| item = menu.add(0, 0, 1000, R.string.camerasettings); |
| item.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { |
| public boolean onMenuItemClick(MenuItem item) { |
| Intent preferences = new Intent(); |
| preferences.setClass(ImageGallery2.this, GallerySettings.class); |
| startActivity(preferences); |
| return true; |
| } |
| }); |
| item.setAlphabeticShortcut('p'); |
| item.setIcon(android.R.drawable.ic_menu_preferences); |
| |
| return true; |
| } |
| |
| @Override |
| public boolean onPrepareOptionsMenu(android.view.Menu menu) { |
| if ((mInclusion & ImageManager.INCLUDE_IMAGES) != 0) { |
| boolean videoSelected = isVideoSelected(); |
| // TODO: Only enable slide show if there is at least one image in the folder. |
| mSlideShowItem.setEnabled(!videoSelected); |
| } |
| MenuHelper.setFlipOrientationEnabled(this, mFlipItem); |
| |
| return true; |
| } |
| |
| private boolean isImageSelected() { |
| IImage image = mSelectedImageGetter.getCurrentImage(); |
| return (image != null) && ImageManager.isImage(image); |
| } |
| |
| private boolean isVideoSelected() { |
| IImage image = mSelectedImageGetter.getCurrentImage(); |
| return (image != null) && ImageManager.isVideo(image); |
| } |
| |
| private synchronized ImageManager.IImageList allImages(boolean assumeMounted) { |
| if (mAllImages == null) { |
| mNoImagesView = findViewById(R.id.no_images); |
| |
| mInclusion = ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS; |
| ImageManager.DataLocation location = ImageManager.DataLocation.ALL; |
| |
| Intent intent = getIntent(); |
| if (intent != null) { |
| String type = intent.resolveType(this); |
| if (Config.LOGV) |
| Log.v(TAG, "allImages... type is " + type); |
| TextView leftText = (TextView) findViewById(R.id.left_text); |
| if (type != null) { |
| if (type.equals("vnd.android.cursor.dir/image") || type.equals("image/*")) { |
| mInclusion = ImageManager.INCLUDE_IMAGES; |
| if (isPickIntent()) |
| leftText.setText(R.string.pick_photos_gallery_title); |
| else |
| leftText.setText(R.string.photos_gallery_title); |
| } |
| if (type.equals("vnd.android.cursor.dir/video") || type.equals("video/*")) { |
| mInclusion = ImageManager.INCLUDE_VIDEOS; |
| if (isPickIntent()) |
| leftText.setText(R.string.pick_videos_gallery_title); |
| else |
| leftText.setText(R.string.videos_gallery_title); |
| } |
| } |
| Bundle extras = intent.getExtras(); |
| String title = extras!= null ? extras.getString("windowTitle") : null; |
| if (title != null && title.length() > 0) { |
| leftText.setText(title); |
| } |
| |
| if (extras != null) { |
| mInclusion = (ImageManager.INCLUDE_IMAGES | ImageManager.INCLUDE_VIDEOS) |
| & extras.getInt("mediaTypes", mInclusion); |
| } |
| |
| if (extras != null && extras.getBoolean("pick-drm")) { |
| Log.d(TAG, "pick-drm is true"); |
| mInclusion = ImageManager.INCLUDE_DRM_IMAGES; |
| location = ImageManager.DataLocation.INTERNAL; |
| } |
| } |
| if (Config.LOGV) |
| Log.v(TAG, "computing images... mSortAscending is " + mSortAscending |
| + "; assumeMounted is " + assumeMounted); |
| Uri uri = getIntent().getData(); |
| if (!assumeMounted) { |
| mAllImages = ImageManager.instance().emptyImageList(); |
| } else { |
| mAllImages = ImageManager.instance().allImages( |
| ImageGallery2.this, |
| getContentResolver(), |
| ImageManager.DataLocation.NONE, |
| mInclusion, |
| mSortAscending ? ImageManager.SORT_ASCENDING : ImageManager.SORT_DESCENDING, |
| uri != null ? uri.getQueryParameter("bucketId") : null); |
| } |
| } |
| return mAllImages; |
| } |
| |
| public static class GridViewSpecial extends View { |
| private ImageGallery2 mGallery; |
| private Paint mGridViewPaint = new Paint(); |
| |
| private ImageBlockManager mImageBlockManager; |
| private Handler mHandler; |
| |
| private LayoutSpec mCurrentSpec; |
| private boolean mShowSelection = false; |
| private int mCurrentSelection = -1; |
| private boolean mCurrentSelectionPressed; |
| |
| private boolean mDirectionBiasDown = true; |
| private final static boolean sDump = false; |
| |
| class LayoutSpec { |
| LayoutSpec(int cols, int w, int h, int leftEdgePadding, int rightEdgePadding, int intercellSpacing) { |
| mColumns = cols; |
| mCellWidth = w; |
| mCellHeight = h; |
| mLeftEdgePadding = leftEdgePadding; |
| mRightEdgePadding = rightEdgePadding; |
| mCellSpacing = intercellSpacing; |
| } |
| int mColumns; |
| int mCellWidth, mCellHeight; |
| int mLeftEdgePadding, mRightEdgePadding; |
| int mCellSpacing; |
| }; |
| |
| private LayoutSpec [] mCellSizeChoices = new LayoutSpec[] { |
| new LayoutSpec(0, 67, 67, 14, 14, 8), |
| new LayoutSpec(0, 92, 92, 14, 14, 8), |
| }; |
| private int mSizeChoice = 1; |
| |
| // Use a number like 100 or 200 here to allow the user to |
| // overshoot the start (top) or end (bottom) of the gallery. |
| // After overshooting the gallery will animate back to the |
| // appropriate location. |
| private int mMaxOvershoot = 0; // 100; |
| private int mMaxScrollY; |
| private int mMinScrollY; |
| |
| private boolean mFling = true; |
| private Scroller mScroller = null; |
| |
| private GestureDetector mGestureDetector; |
| |
| public void dump() { |
| if (Config.LOGV){ |
| Log.v(TAG, "mSizeChoice is " + mCellSizeChoices[mSizeChoice]); |
| Log.v(TAG, "mCurrentSpec.width / mCellHeight are " + mCurrentSpec.mCellWidth + " / " + mCurrentSpec.mCellHeight); |
| } |
| mImageBlockManager.dump(); |
| } |
| |
| private void init(Context context) { |
| mGridViewPaint.setColor(0xFF000000); |
| mGallery = (ImageGallery2) context; |
| |
| setVerticalScrollBarEnabled(true); |
| initializeScrollbars(context.obtainStyledAttributes(android.R.styleable.View)); |
| |
| mGestureDetector = new GestureDetector(new SimpleOnGestureListener() { |
| @Override |
| public boolean onDown(MotionEvent e) { |
| if (mScroller != null && !mScroller.isFinished()) { |
| mScroller.forceFinished(true); |
| return false; |
| } |
| |
| int pos = computeSelectedIndex(e); |
| if (pos >= 0 && pos < mGallery.mAllImages.getCount()) { |
| select(pos, true); |
| } else { |
| select(-1, false); |
| } |
| if (mImageBlockManager != null) |
| mImageBlockManager.repaintSelection(mCurrentSelection); |
| invalidate(); |
| return true; |
| } |
| |
| @Override |
| public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { |
| final float maxVelocity = 2500; |
| if (velocityY > maxVelocity) |
| velocityY = maxVelocity; |
| else if (velocityY < -maxVelocity) |
| velocityY = -maxVelocity; |
| |
| select(-1, false); |
| if (mFling) { |
| mScroller = new Scroller(getContext()); |
| mScroller.fling(0, mScrollY, 0, -(int)velocityY, 0, 0, 0, mMaxScrollY); |
| computeScroll(); |
| } |
| return true; |
| } |
| |
| @Override |
| public void onLongPress(MotionEvent e) { |
| performLongClick(); |
| } |
| |
| @Override |
| public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { |
| select(-1, false); |
| scrollBy(0, (int)distanceY); |
| invalidate(); |
| return true; |
| } |
| |
| @Override |
| public void onShowPress(MotionEvent e) { |
| super.onShowPress(e); |
| } |
| |
| @Override |
| public boolean onSingleTapUp(MotionEvent e) { |
| select(mCurrentSelection, false); |
| int index = computeSelectedIndex(e); |
| if (index >= 0 && index < mGallery.mAllImages.getCount()) { |
| onSelect(index); |
| return true; |
| } |
| return false; |
| } |
| }); |
| // mGestureDetector.setIsLongpressEnabled(false); |
| } |
| |
| public GridViewSpecial(Context context, AttributeSet attrs, int defStyle) { |
| super(context, attrs, defStyle); |
| init(context); |
| } |
| |
| public GridViewSpecial(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| init(context); |
| } |
| |
| public GridViewSpecial(Context context) { |
| super(context); |
| init(context); |
| } |
| |
| @Override |
| protected int computeVerticalScrollRange() { |
| return mMaxScrollY + getHeight(); |
| } |
| |
| public void setSizeChoice(int choice, int scrollY) { |
| mSizeChoice = choice; |
| clearCache(); |
| scrollTo(0, scrollY); |
| requestLayout(); |
| invalidate(); |
| } |
| |
| /** |
| * |
| * @param newSel -2 means use old selection, -1 means remove selection |
| * @param newPressed |
| */ |
| public void select(int newSel, boolean newPressed) { |
| if (newSel == -2) { |
| newSel = mCurrentSelection; |
| } |
| int oldSel = mCurrentSelection; |
| if ((oldSel == newSel) && (mCurrentSelectionPressed == newPressed)) |
| return; |
| |
| mShowSelection = (newSel != -1); |
| mCurrentSelection = newSel; |
| mCurrentSelectionPressed = newPressed; |
| if (mImageBlockManager != null) { |
| mImageBlockManager.repaintSelection(oldSel); |
| mImageBlockManager.repaintSelection(newSel); |
| } |
| |
| if (newSel != -1) |
| ensureVisible(newSel); |
| } |
| |
| private void ensureVisible(int pos) { |
| android.graphics.Rect r = getRectForPosition(pos); |
| int top = getScrollY(); |
| int bot = top + getHeight(); |
| |
| if (r.bottom > bot) { |
| mScroller = new Scroller(getContext()); |
| mScroller.startScroll(mScrollX, mScrollY, 0, r.bottom - getHeight() - mScrollY, 200); |
| computeScroll(); |
| } else if (r.top < top) { |
| mScroller = new Scroller(getContext()); |
| mScroller.startScroll(mScrollX, mScrollY, 0, r.top - mScrollY, 200); |
| computeScroll(); |
| } |
| invalidate(); |
| } |
| |
| public void start() { |
| if (mGallery.mLayoutComplete) { |
| if (mImageBlockManager == null) { |
| mImageBlockManager = new ImageBlockManager(); |
| mImageBlockManager.moveDataWindow(true, true); |
| } |
| } |
| } |
| |
| public void onPause() { |
| mScroller = null; |
| if (mImageBlockManager != null) { |
| mImageBlockManager.onPause(); |
| mImageBlockManager = null; |
| } |
| } |
| |
| public void clearCache() { |
| if (mImageBlockManager != null) { |
| mImageBlockManager.onPause(); |
| mImageBlockManager = null; |
| } |
| } |
| |
| |
| @Override |
| public void onLayout(boolean changed, int left, int top, int right, int bottom) { |
| super.onLayout(changed, left, top, right, bottom); |
| |
| if (mGallery.isFinishing() || mGallery.mPausing) { |
| return; |
| } |
| |
| clearCache(); |
| |
| mCurrentSpec = mCellSizeChoices[mSizeChoice]; |
| int oldColumnCount = mCurrentSpec.mColumns; |
| |
| int width = right - left; |
| mCurrentSpec.mColumns = 1; |
| width -= mCurrentSpec.mCellWidth; |
| mCurrentSpec.mColumns += width / (mCurrentSpec.mCellWidth + mCurrentSpec.mCellSpacing); |
| |
| mCurrentSpec.mLeftEdgePadding = ((right - left) - ((mCurrentSpec.mColumns - 1) * mCurrentSpec.mCellSpacing) - (mCurrentSpec.mColumns * mCurrentSpec.mCellWidth)) / 2; |
| mCurrentSpec.mRightEdgePadding = mCurrentSpec.mLeftEdgePadding; |
| |
| int rows = (mGallery.mAllImages.getCount() + mCurrentSpec.mColumns - 1) / mCurrentSpec.mColumns; |
| mMaxScrollY = mCurrentSpec.mCellSpacing + (rows * (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight)) - (bottom - top) + mMaxOvershoot; |
| mMinScrollY = 0 - mMaxOvershoot; |
| |
| mGallery.mLayoutComplete = true; |
| |
| start(); |
| |
| if (mGallery.mSortAscending && mGallery.mTargetScroll == 0) { |
| scrollTo(0, mMaxScrollY - mMaxOvershoot); |
| } else { |
| if (oldColumnCount != 0) { |
| int y = mGallery.mTargetScroll * oldColumnCount / mCurrentSpec.mColumns; |
| Log.v(TAG, "target was " + mGallery.mTargetScroll + " now " + y); |
| scrollTo(0, y); |
| } |
| } |
| } |
| |
| Bitmap scaleTo(int width, int height, Bitmap b) { |
| Matrix m = new Matrix(); |
| m.setScale((float)width/64F, (float)height/64F); |
| Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false); |
| if (b2 != b) |
| b.recycle(); |
| return b2; |
| } |
| |
| private class ImageBlockManager { |
| private ImageLoader mLoader; |
| private int mBlockCacheFirstBlockNumber = 0; |
| |
| // mBlockCache is an array with a starting point which is not necessaryily |
| // zero. The first element of the array is indicated by mBlockCacheStartOffset. |
| private int mBlockCacheStartOffset = 0; |
| private ImageBlock [] mBlockCache; |
| |
| private static final int sRowsPerPage = 6; // should compute this |
| |
| private static final int sPagesPreCache = 2; |
| private static final int sPagesPostCache = 2; |
| |
| private int mWorkCounter = 0; |
| private boolean mDone = false; |
| |
| private Thread mWorkerThread; |
| private Bitmap mMissingImageThumbnailBitmap; |
| private Bitmap mMissingVideoThumbnailBitmap; |
| |
| public void dump() { |
| synchronized (ImageBlockManager.this) { |
| StringBuilder line1 = new StringBuilder(); |
| StringBuilder line2 = new StringBuilder(); |
| if (Config.LOGV) |
| Log.v(TAG, ">>> mBlockCacheFirstBlockNumber: " + mBlockCacheFirstBlockNumber + " " + mBlockCacheStartOffset); |
| for (int i = 0; i < mBlockCache.length; i++) { |
| int index = (mBlockCacheStartOffset + i) % mBlockCache.length; |
| ImageBlock block = mBlockCache[index]; |
| block.dump(line1, line2); |
| } |
| if (Config.LOGV){ |
| Log.v(TAG, line1.toString()); |
| Log.v(TAG, line2.toString()); |
| } |
| } |
| } |
| |
| ImageBlockManager() { |
| mLoader = new ImageLoader(mHandler, 1); |
| |
| mBlockCache = new ImageBlock[sRowsPerPage * (sPagesPreCache + sPagesPostCache + 1)]; |
| for (int i = 0; i < mBlockCache.length; i++) { |
| mBlockCache[i] = new ImageBlock(); |
| } |
| |
| mWorkerThread = new Thread(new Runnable() { |
| public void run() { |
| while (true) { |
| int workCounter; |
| synchronized (ImageBlockManager.this) { |
| workCounter = mWorkCounter; |
| } |
| if (mDone) { |
| if (Config.LOGV) |
| Log.v(TAG, "stopping the loader here " + Thread.currentThread().getName()); |
| if (mLoader != null) { |
| mLoader.stop(); |
| } |
| if (mBlockCache != null) { |
| for (int i = 0; i < mBlockCache.length; i++) { |
| ImageBlock block = mBlockCache[i]; |
| if (block != null) { |
| block.recycleBitmaps(); |
| mBlockCache[i] = null; |
| } |
| } |
| } |
| mBlockCache = null; |
| mBlockCacheStartOffset = 0; |
| mBlockCacheFirstBlockNumber = 0; |
| |
| break; |
| } |
| |
| loadNext(); |
| |
| synchronized (ImageBlockManager.this) { |
| if (workCounter == mWorkCounter) { |
| try { |
| ImageBlockManager.this.wait(); |
| } catch (InterruptedException ex) { |
| } |
| } |
| } |
| } |
| } |
| }); |
| mWorkerThread.setName("image-block-manager"); |
| mWorkerThread.start(); |
| } |
| |
| // Create this bitmap lazily, and only once for all the ImageBlocks to use |
| public Bitmap getErrorBitmap(ImageManager.IImage image) { |
| if (ImageManager.isImage(image)) { |
| if (mMissingImageThumbnailBitmap == null) { |
| mMissingImageThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(), |
| R.drawable.ic_missing_thumbnail_picture); |
| } |
| return mMissingImageThumbnailBitmap; |
| } else { |
| if (mMissingVideoThumbnailBitmap == null) { |
| mMissingVideoThumbnailBitmap = BitmapFactory.decodeResource(GridViewSpecial.this.getResources(), |
| R.drawable.ic_missing_thumbnail_video); |
| } |
| return mMissingVideoThumbnailBitmap; |
| } |
| } |
| |
| private ImageBlock getBlockForPos(int pos) { |
| synchronized (ImageBlockManager.this) { |
| int blockNumber = pos / mCurrentSpec.mColumns; |
| int delta = blockNumber - mBlockCacheFirstBlockNumber; |
| if (delta >= 0 && delta < mBlockCache.length) { |
| int index = (mBlockCacheStartOffset + delta) % mBlockCache.length; |
| ImageBlock b = mBlockCache[index]; |
| return b; |
| } |
| } |
| return null; |
| } |
| |
| private void repaintSelection(int pos) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlock b = getBlockForPos(pos); |
| if (b != null) { |
| b.repaintSelection(); |
| } |
| } |
| } |
| |
| private void onPause() { |
| synchronized (ImageBlockManager.this) { |
| mDone = true; |
| ImageBlockManager.this.notify(); |
| } |
| if (mWorkerThread != null) { |
| try { |
| mWorkerThread.join(); |
| mWorkerThread = null; |
| } catch (InterruptedException ex) { |
| // |
| } |
| } |
| Log.v(TAG, "/ImageBlockManager.onPause"); |
| } |
| |
| private void getVisibleRange(int [] range) { |
| // try to work around a possible bug in the VM wherein this appears to be null |
| try { |
| synchronized (ImageBlockManager.this) { |
| int blockLength = mBlockCache.length; |
| boolean lookingForStart = true; |
| ImageBlock prevBlock = null; |
| for (int i = 0; i < blockLength; i++) { |
| int index = (mBlockCacheStartOffset + i) % blockLength; |
| ImageBlock block = mBlockCache[index]; |
| if (lookingForStart) { |
| if (block.mIsVisible) { |
| range[0] = block.mBlockNumber * mCurrentSpec.mColumns; |
| lookingForStart = false; |
| } |
| } else { |
| if (!block.mIsVisible || i == blockLength - 1) { |
| range[1] = (prevBlock.mBlockNumber * mCurrentSpec.mColumns) + mCurrentSpec.mColumns - 1; |
| break; |
| } |
| } |
| prevBlock = block; |
| } |
| } |
| } catch (NullPointerException ex) { |
| Log.e(TAG, "this is somewhat null, what up?"); |
| range[0] = range[1] = 0; |
| } |
| } |
| |
| private void loadNext() { |
| final int blockHeight = (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight); |
| |
| final int firstVisBlock = Math.max(0, (mScrollY - mCurrentSpec.mCellSpacing) / blockHeight); |
| final int lastVisBlock = (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) / blockHeight; |
| |
| // Log.v(TAG, "firstVisBlock == " + firstVisBlock + "; lastVisBlock == " + lastVisBlock); |
| |
| synchronized (ImageBlockManager.this) { |
| ImageBlock [] blocks = mBlockCache; |
| int numBlocks = blocks.length; |
| if (mDirectionBiasDown) { |
| int first = (mBlockCacheStartOffset + (firstVisBlock - mBlockCacheFirstBlockNumber)) % blocks.length; |
| for (int i = 0; i < numBlocks; i++) { |
| int j = first + i; |
| if (j >= numBlocks) |
| j -= numBlocks; |
| ImageBlock b = blocks[j]; |
| if (b.startLoading() > 0) |
| break; |
| } |
| } else { |
| int first = (mBlockCacheStartOffset + (lastVisBlock - mBlockCacheFirstBlockNumber)) % blocks.length; |
| for (int i = 0; i < numBlocks; i++) { |
| int j = first - i; |
| if (j < 0) |
| j += numBlocks; |
| ImageBlock b = blocks[j]; |
| if (b.startLoading() > 0) |
| break; |
| } |
| } |
| if (sDump) |
| this.dump(); |
| } |
| } |
| |
| private void moveDataWindow(boolean directionBiasDown, boolean forceRefresh) { |
| final int blockHeight = (mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight); |
| |
| final int firstVisBlock = (mScrollY - mCurrentSpec.mCellSpacing) / blockHeight; |
| final int lastVisBlock = (mScrollY - mCurrentSpec.mCellSpacing + getHeight()) / blockHeight; |
| |
| final int preCache = sPagesPreCache; |
| final int startBlock = Math.max(0, firstVisBlock - (preCache * sRowsPerPage)); |
| |
| // Log.v(TAG, "moveDataWindow directionBiasDown == " + directionBiasDown + "; preCache is " + preCache); |
| synchronized (ImageBlockManager.this) { |
| boolean any = false; |
| ImageBlock [] blocks = mBlockCache; |
| int numBlocks = blocks.length; |
| |
| int delta = startBlock - mBlockCacheFirstBlockNumber; |
| |
| mBlockCacheFirstBlockNumber = startBlock; |
| if (Math.abs(delta) > numBlocks || forceRefresh) { |
| for (int i = 0; i < numBlocks; i++) { |
| int blockNum = startBlock + i; |
| blocks[i].setStart(blockNum); |
| any = true; |
| } |
| mBlockCacheStartOffset = 0; |
| } else if (delta > 0) { |
| mBlockCacheStartOffset += delta; |
| if (mBlockCacheStartOffset >= numBlocks) |
| mBlockCacheStartOffset -= numBlocks; |
| |
| for (int i = delta; i > 0; i--) { |
| int index = (mBlockCacheStartOffset + numBlocks - i) % numBlocks; |
| int blockNum = mBlockCacheFirstBlockNumber + numBlocks - i; |
| blocks[index].setStart(blockNum); |
| any = true; |
| } |
| } else if (delta < 0) { |
| mBlockCacheStartOffset += delta; |
| if (mBlockCacheStartOffset < 0) |
| mBlockCacheStartOffset += numBlocks; |
| |
| for (int i = 0; i < -delta; i++) { |
| int index = (mBlockCacheStartOffset + i) % numBlocks; |
| int blockNum = mBlockCacheFirstBlockNumber + i; |
| blocks[index].setStart(blockNum); |
| any = true; |
| } |
| } |
| |
| for (int i = 0; i < numBlocks; i++) { |
| int index = (mBlockCacheStartOffset + i) % numBlocks; |
| ImageBlock block = blocks[index]; |
| int blockNum = block.mBlockNumber; // mBlockCacheFirstBlockNumber + i; |
| boolean isVis = blockNum >= firstVisBlock && blockNum <= lastVisBlock; |
| // Log.v(TAG, "blockNum " + blockNum + " setting vis to " + isVis); |
| block.setVisibility(isVis); |
| } |
| |
| if (sDump) |
| mImageBlockManager.dump(); |
| |
| if (any) { |
| ImageBlockManager.this.notify(); |
| mWorkCounter += 1; |
| } |
| } |
| if (sDump) |
| dump(); |
| } |
| |
| private void check() { |
| ImageBlock [] blocks = mBlockCache; |
| int blockLength = blocks.length; |
| |
| // check the results |
| for (int i = 0; i < blockLength; i++) { |
| int index = (mBlockCacheStartOffset + i) % blockLength; |
| if (blocks[index].mBlockNumber != mBlockCacheFirstBlockNumber + i) { |
| if (blocks[index].mBlockNumber != -1) |
| Log.e(TAG, "at " + i + " block cache corrupted; found " + blocks[index].mBlockNumber + " but wanted " + (mBlockCacheFirstBlockNumber + i) + "; offset is " + mBlockCacheStartOffset); |
| } |
| } |
| if (true) { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < blockLength; i++) { |
| int index = (mBlockCacheStartOffset + i) % blockLength; |
| ImageBlock b = blocks[index]; |
| if (b.mRequestedMask != 0) |
| sb.append("X"); |
| else |
| sb.append(String.valueOf(b.mBlockNumber) + " "); |
| } |
| if (Config.LOGV) |
| Log.v(TAG, "moveDataWindow " + sb.toString()); |
| } |
| } |
| |
| void doDraw(Canvas canvas) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlockManager.ImageBlock [] blocks = mBlockCache; |
| int blockCount = 0; |
| |
| if (blocks[0] == null) { |
| return; |
| } |
| |
| final int thisHeight = getHeight(); |
| final int thisWidth = getWidth(); |
| final int height = blocks[0].mBitmap.getHeight(); |
| final int scrollPos = mScrollY; |
| |
| int currentBlock = (scrollPos < 0) ? ((scrollPos-height+1) / height) : (scrollPos / height); |
| |
| while (true) { |
| final int yPos = currentBlock * height; |
| if (yPos >= scrollPos + thisHeight) |
| break; |
| |
| if (currentBlock < 0) { |
| canvas.drawRect(0, yPos, thisWidth, 0, mGridViewPaint); |
| currentBlock += 1; |
| continue; |
| } |
| int effectiveOffset = (mBlockCacheStartOffset + (currentBlock++ - mBlockCacheFirstBlockNumber)) % blocks.length; |
| if (effectiveOffset < 0 || effectiveOffset >= blocks.length) { |
| break; |
| } |
| |
| ImageBlock block = blocks[effectiveOffset]; |
| if (block == null) { |
| break; |
| } |
| synchronized (block) { |
| Bitmap b = block.mBitmap; |
| if (b == null) { |
| break; |
| } |
| canvas.drawBitmap(b, 0, yPos, mGridViewPaint); |
| blockCount += 1; |
| } |
| } |
| } |
| } |
| |
| int blockHeight() { |
| return mCurrentSpec.mCellSpacing + mCurrentSpec.mCellHeight; |
| } |
| |
| private class ImageBlock { |
| Drawable mCellOutline; |
| Bitmap mBitmap = Bitmap.createBitmap(getWidth(), blockHeight(), |
| Bitmap.Config.RGB_565);; |
| Canvas mCanvas = new Canvas(mBitmap); |
| Paint mPaint = new Paint(); |
| |
| int mBlockNumber; |
| int mRequestedMask; // columns which have been requested to the loader |
| int mCompletedMask; // columns which have been completed from the loader |
| boolean mIsVisible; |
| Drawable mVideoOverlay; |
| |
| public void dump(StringBuilder line1, StringBuilder line2) { |
| synchronized (ImageBlock.this) { |
| // Log.v(TAG, "block " + mBlockNumber + " isVis == " + mIsVisible); |
| line2.append(mCompletedMask != 0xF ? 'L' : '_'); |
| line1.append(mIsVisible ? 'V' : ' '); |
| } |
| } |
| |
| ImageBlock() { |
| mPaint.setTextSize(14F); |
| mPaint.setStyle(Paint.Style.FILL); |
| |
| mBlockNumber = -1; |
| mCellOutline = GridViewSpecial.this.getResources().getDrawable(android.R.drawable.gallery_thumb); |
| } |
| |
| private void recycleBitmaps() { |
| synchronized (ImageBlock.this) { |
| mBitmap.recycle(); |
| mBitmap = null; |
| } |
| } |
| |
| private void cancelExistingRequests() { |
| synchronized (ImageBlock.this) { |
| for (int i = 0; i < mCurrentSpec.mColumns; i++) { |
| int mask = (1 << i); |
| if ((mRequestedMask & mask) != 0) { |
| int pos = (mBlockNumber * mCurrentSpec.mColumns) + i; |
| if (mLoader.cancel(mGallery.mAllImages.getImageAt(pos))) { |
| mRequestedMask &= ~mask; |
| } |
| } |
| } |
| } |
| } |
| |
| private void setStart(final int blockNumber) { |
| synchronized (ImageBlock.this) { |
| if (blockNumber == mBlockNumber) |
| return; |
| |
| cancelExistingRequests(); |
| |
| mBlockNumber = blockNumber; |
| mRequestedMask = 0; |
| mCompletedMask = 0; |
| mCanvas.drawColor(0xFF000000); |
| mPaint.setColor(0xFFDDDDDD); |
| int imageNumber = blockNumber * mCurrentSpec.mColumns; |
| int lastImageNumber = mGallery.mAllImages.getCount() - 1; |
| |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| |
| final int yPos = spacing; |
| |
| for (int col = 0; col < mCurrentSpec.mColumns; col++) { |
| if (imageNumber++ >= lastImageNumber) |
| break; |
| final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing)); |
| mCanvas.drawRect(xPos, yPos, xPos+mCurrentSpec.mCellWidth, yPos+mCurrentSpec.mCellHeight, mPaint); |
| paintSel(0, xPos, yPos); |
| } |
| } |
| } |
| |
| private boolean setVisibility(boolean isVis) { |
| synchronized (ImageBlock.this) { |
| boolean retval = mIsVisible != isVis; |
| mIsVisible = isVis; |
| return retval; |
| } |
| } |
| |
| private int startLoading() { |
| synchronized (ImageBlock.this) { |
| final int startRow = mBlockNumber; |
| int count = mGallery.mAllImages.getCount(); |
| |
| if (startRow == -1) |
| return 0; |
| |
| if ((startRow * mCurrentSpec.mColumns) >= count) { |
| return 0; |
| } |
| |
| int retVal = 0; |
| int base = (mBlockNumber * mCurrentSpec.mColumns); |
| for (int col = 0; col < mCurrentSpec.mColumns; col++) { |
| if ((mCompletedMask & (1 << col)) != 0) { |
| continue; |
| } |
| |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| final int yPos = spacing; |
| final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing)); |
| |
| int pos = base + col; |
| if (pos >= count) |
| break; |
| |
| ImageManager.IImage image = mGallery.mAllImages.getImageAt(pos); |
| if (image != null) { |
| // Log.v(TAG, "calling loadImage " + (base + col)); |
| loadImage(base, col, image, xPos, yPos); |
| retVal += 1; |
| } |
| } |
| return retVal; |
| |
| }} |
| |
| Bitmap resizeBitmap(Bitmap b) { |
| // assume they're both square for now |
| if (b == null || (b.getWidth() == mCurrentSpec.mCellWidth && b.getHeight() == mCurrentSpec.mCellHeight)) { |
| return b; |
| } |
| float scale = (float) mCurrentSpec.mCellWidth / (float)b.getWidth(); |
| Matrix m = new Matrix(); |
| m.setScale(scale, scale, b.getWidth(), b.getHeight()); |
| Bitmap b2 = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false); |
| return b2; |
| } |
| |
| private void drawBitmap(ImageManager.IImage image, int base, int baseOffset, Bitmap b, int xPos, int yPos) { |
| mCanvas.setBitmap(mBitmap); |
| if (b != null) { |
| // if the image is close to the target size then crop, otherwise scale |
| // both the bitmap and the view should be square but I suppose that could |
| // change in the future. |
| int w = mCurrentSpec.mCellWidth; |
| int h = mCurrentSpec.mCellHeight; |
| |
| int bw = b.getWidth(); |
| int bh = b.getHeight(); |
| |
| int deltaW = bw - w; |
| int deltaH = bh - h; |
| |
| if (deltaW < 10 && deltaH < 10) { |
| int halfDeltaW = deltaW / 2; |
| int halfDeltaH = deltaH / 2; |
| android.graphics.Rect src = new android.graphics.Rect(0+halfDeltaW, 0+halfDeltaH, bw-halfDeltaW, bh-halfDeltaH); |
| android.graphics.Rect dst = new android.graphics.Rect(xPos, yPos, xPos+w, yPos+h); |
| if (src.width() != dst.width() || src.height() != dst.height()) { |
| if (Config.LOGV){ |
| Log.v(TAG, "nope... width doesn't match " + src.width() + " " + dst.width()); |
| Log.v(TAG, "nope... height doesn't match " + src.height() + " " + dst.height()); |
| } |
| } |
| mCanvas.drawBitmap(b, src, dst, mPaint); |
| } else { |
| android.graphics.Rect src = new android.graphics.Rect(0, 0, bw, bh); |
| android.graphics.Rect dst = new android.graphics.Rect(xPos, yPos, xPos+w, yPos+h); |
| mCanvas.drawBitmap(b, src, dst, mPaint); |
| } |
| } else { |
| // If the thumbnail cannot be drawn, put up an error icon instead |
| Bitmap error = mImageBlockManager.getErrorBitmap(image); |
| int width = error.getWidth(); |
| int height = error.getHeight(); |
| Rect source = new Rect(0, 0, width, height); |
| int left = (mCurrentSpec.mCellWidth - width) / 2 + xPos; |
| int top = (mCurrentSpec.mCellHeight - height) / 2 + yPos; |
| Rect dest = new Rect(left, top, left + width, top + height); |
| mCanvas.drawBitmap(error, source, dest, mPaint); |
| } |
| if (ImageManager.isVideo(image)) { |
| if (mVideoOverlay == null) { |
| mVideoOverlay = getResources().getDrawable( |
| R.drawable.ic_gallery_video_overlay); |
| } |
| int width = mVideoOverlay.getIntrinsicWidth(); |
| int height = mVideoOverlay.getIntrinsicHeight(); |
| int left = (mCurrentSpec.mCellWidth - width) / 2 + xPos; |
| int top = (mCurrentSpec.mCellHeight - height) / 2 + yPos; |
| Rect newBounds = new Rect(left, top, left + width, top + height); |
| mVideoOverlay.setBounds(newBounds); |
| mVideoOverlay.draw(mCanvas); |
| } |
| paintSel(base + baseOffset, xPos, yPos); |
| } |
| |
| private void repaintSelection() { |
| int count = mGallery.mAllImages.getCount(); |
| int startPos = mBlockNumber * mCurrentSpec.mColumns; |
| synchronized (ImageBlock.this) { |
| for (int i = 0; i < mCurrentSpec.mColumns; i++) { |
| int pos = startPos + i; |
| |
| if (pos >= count) |
| break; |
| |
| int row = 0; // i / mCurrentSpec.mColumns; |
| int col = i - (row * mCurrentSpec.mColumns); |
| |
| // this is duplicated from getOrKick (TODO: don't duplicate this code) |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| final int yPos = spacing + (row * (mCurrentSpec.mCellHeight + spacing)); |
| final int xPos = leftSpacing + (col * (mCurrentSpec.mCellWidth + spacing)); |
| |
| paintSel(pos, xPos, yPos); |
| } |
| } |
| } |
| |
| private void paintSel(int pos, int xPos, int yPos) { |
| int[] stateSet = EMPTY_STATE_SET; |
| if (pos == mCurrentSelection && mShowSelection) { |
| if (mCurrentSelectionPressed) { |
| stateSet = PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; |
| } else { |
| stateSet = ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET; |
| } |
| } |
| |
| mCellOutline.setState(stateSet); |
| mCanvas.setBitmap(mBitmap); |
| mCellOutline.setBounds(xPos, yPos, xPos+mCurrentSpec.mCellWidth, yPos+mCurrentSpec.mCellHeight); |
| mCellOutline.draw(mCanvas); |
| } |
| |
| private void loadImage( |
| final int base, |
| final int baseOffset, |
| final ImageManager.IImage image, |
| final int xPos, |
| final int yPos) { |
| synchronized (ImageBlock.this) { |
| final int startBlock = mBlockNumber; |
| final int pos = base + baseOffset; |
| final ImageLoader.LoadedCallback r = new ImageLoader.LoadedCallback() { |
| public void run(Bitmap b) { |
| boolean more = false; |
| synchronized (ImageBlock.this) { |
| if (startBlock != mBlockNumber) { |
| // Log.v(TAG, "wanted block " + mBlockNumber + " but got " + startBlock); |
| return; |
| } |
| |
| if (mBitmap == null) { |
| return; |
| } |
| |
| drawBitmap(image, base, baseOffset, b, xPos, yPos); |
| |
| int mask = (1 << baseOffset); |
| mRequestedMask &= ~mask; |
| mCompletedMask |= mask; |
| |
| // Log.v(TAG, "for " + mBlockNumber + " mRequestedMask is " + String.format("%x", mRequestedMask) + " and mCompletedMask is " + String.format("%x", mCompletedMask)); |
| |
| if (mRequestedMask == 0) { |
| if (mIsVisible) { |
| postInvalidate(); |
| } |
| more = true; |
| } |
| } |
| if (b != null) |
| b.recycle(); |
| |
| if (more) { |
| synchronized (ImageBlockManager.this) { |
| ImageBlockManager.this.notify(); |
| mWorkCounter += 1; |
| } |
| } |
| if (sDump) |
| ImageBlockManager.this.dump(); |
| } |
| }; |
| mRequestedMask |= (1 << baseOffset); |
| mLoader.getBitmap(image, pos, r, mIsVisible, false); |
| } |
| } |
| } |
| } |
| |
| public void init(Handler handler) { |
| mHandler = handler; |
| } |
| |
| public void onDraw(Canvas canvas) { |
| super.onDraw(canvas); |
| if (false) { |
| canvas.drawRect(0, 0, getWidth(), getHeight(), mGridViewPaint); |
| if (Config.LOGV) |
| Log.v(TAG, "painting background w/h " + getWidth() + " / " + getHeight()); |
| return; |
| } |
| |
| if (mImageBlockManager != null) { |
| mImageBlockManager.doDraw(canvas); |
| mImageBlockManager.moveDataWindow(mDirectionBiasDown, false); |
| } |
| } |
| |
| @Override |
| public void computeScroll() { |
| if (mScroller != null) { |
| boolean more = mScroller.computeScrollOffset(); |
| scrollTo(0, (int)mScroller.getCurrY()); |
| if (more) { |
| postInvalidate(); // So we draw again |
| } else { |
| mScroller = null; |
| } |
| } else { |
| super.computeScroll(); |
| } |
| } |
| |
| private android.graphics.Rect getRectForPosition(int pos) { |
| int row = pos / mCurrentSpec.mColumns; |
| int col = pos - (row * mCurrentSpec.mColumns); |
| |
| int left = mCurrentSpec.mLeftEdgePadding + (col * mCurrentSpec.mCellWidth) + (Math.max(0, col-1) * mCurrentSpec.mCellSpacing); |
| int top = (row * mCurrentSpec.mCellHeight) + (row * mCurrentSpec.mCellSpacing); |
| |
| return new android.graphics.Rect(left, top, left + mCurrentSpec.mCellWidth + mCurrentSpec.mCellWidth, top + mCurrentSpec.mCellHeight + mCurrentSpec.mCellSpacing); |
| } |
| |
| int computeSelectedIndex(android.view.MotionEvent ev) { |
| int spacing = mCurrentSpec.mCellSpacing; |
| int leftSpacing = mCurrentSpec.mLeftEdgePadding; |
| |
| int x = (int) ev.getX(); |
| int y = (int) ev.getY(); |
| int row = (mScrollY + y - spacing) / (mCurrentSpec.mCellHeight + spacing); |
| int col = Math.min(mCurrentSpec.mColumns - 1, (x - leftSpacing) / (mCurrentSpec.mCellWidth + spacing)); |
| return (row * mCurrentSpec.mColumns) + col; |
| } |
| |
| @Override |
| public boolean onTouchEvent(android.view.MotionEvent ev) { |
| mGestureDetector.onTouchEvent(ev); |
| return true; |
| } |
| |
| private void onSelect(int index) { |
| if (index >= 0 && index < mGallery.mAllImages.getCount()) { |
| ImageManager.IImage img = mGallery.mAllImages.getImageAt(index); |
| if (img == null) |
| return; |
| |
| if (mGallery.isPickIntent()) { |
| mGallery.launchCropperOrFinish(img); |
| } else { |
| Uri targetUri = img.fullSizeImageUri(); |
| Uri thisUri = mGallery.getIntent().getData(); |
| if (thisUri != null) { |
| String bucket = thisUri.getQueryParameter("bucketId"); |
| if (bucket != null) { |
| targetUri = targetUri.buildUpon().appendQueryParameter("bucketId", bucket).build(); |
| } |
| } |
| Intent intent = new Intent(Intent.ACTION_VIEW, targetUri); |
| |
| if (img instanceof ImageManager.VideoObject) { |
| intent.putExtra(MediaStore.EXTRA_SCREEN_ORIENTATION, |
| ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); |
| } |
| |
| try { |
| mContext.startActivity(intent); |
| } catch (Exception ex) { |
| // sdcard removal?? |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void scrollBy(int x, int y) { |
| scrollTo(x, mScrollY + y); |
| } |
| |
| Toast mDateLocationToast; |
| int [] mDateRange = new int[2]; |
| |
| private String month(int month) { |
| String text = ""; |
| switch (month) { |
| case 0: text = "January"; break; |
| case 1: text = "February"; break; |
| case 2: text = "March"; break; |
| case 3: text = "April"; break; |
| case 4: text = "May"; break; |
| case 5: text = "June"; break; |
| case 6: text = "July"; break; |
| case 7: text = "August"; break; |
| case 8: text = "September"; break; |
| case 9: text = "October"; break; |
| case 10: text = "November"; break; |
| case 11: text = "December"; break; |
| } |
| return text; |
| } |
| |
| Runnable mToastRunnable = new Runnable() { |
| public void run() { |
| if (mDateLocationToast != null) { |
| mDateLocationToast.cancel(); |
| mDateLocationToast = null; |
| } |
| |
| int count = mGallery.mAllImages.getCount(); |
| if (count == 0) |
| return; |
| |
| GridViewSpecial.this.mImageBlockManager.getVisibleRange(mDateRange); |
| |
| ImageManager.IImage firstImage = mGallery.mAllImages.getImageAt(mDateRange[0]); |
| int lastOffset = Math.min(count-1, mDateRange[1]); |
| ImageManager.IImage lastImage = mGallery.mAllImages.getImageAt(lastOffset); |
| |
| GregorianCalendar dateStart = new GregorianCalendar(); |
| GregorianCalendar dateEnd = new GregorianCalendar(); |
| |
| dateStart.setTimeInMillis(firstImage.getDateTaken()); |
| dateEnd.setTimeInMillis(lastImage.getDateTaken()); |
| |
| String text1 = month(dateStart.get(Calendar.MONTH)) + " " + dateStart.get(Calendar.YEAR); |
| String text2 = month(dateEnd .get(Calendar.MONTH)) + " " + dateEnd .get(Calendar.YEAR); |
| |
| String text = text1; |
| if (!text2.equals(text1)) |
| text = text + " : " + text2; |
| |
| mDateLocationToast = Toast.makeText(mContext, text, Toast.LENGTH_LONG); |
| mDateLocationToast.show(); |
| } |
| }; |
| |
| @Override |
| public void scrollTo(int x, int y) { |
| y = Math.min(mMaxScrollY, y); |
| y = Math.max(mMinScrollY, y); |
| if (y > mScrollY) |
| mDirectionBiasDown = true; |
| else if (y < mScrollY) |
| mDirectionBiasDown = false; |
| super.scrollTo(x, y); |
| } |
| } |
| } |