diff options
Diffstat (limited to 'src')
28 files changed, 793 insertions, 320 deletions
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 31d31e128..64f3681de 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -235,8 +235,8 @@ public class CameraSettings { private void buildExposureCompensation( PreferenceGroup group, IconListPreference exposure) { - int max = mParameters.getMaxExposureCompensation(); - int min = mParameters.getMinExposureCompensation(); + int max = Math.min(3, mParameters.getMaxExposureCompensation()); + int min = Math.max(-3, mParameters.getMinExposureCompensation()); if (max == 0 && min == 0) { removePreference(group, exposure.getKey()); return; diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java index 2703c3596..623d96dc6 100644 --- a/src/com/android/camera/PanoramaModule.java +++ b/src/com/android/camera/PanoramaModule.java @@ -66,6 +66,7 @@ import com.android.gallery3d.exif.ExifOutputStream; import com.android.gallery3d.exif.ExifReader; import com.android.gallery3d.exif.ExifTag; import com.android.gallery3d.ui.GLRootView; +import com.android.gallery3d.util.UsageStatistics; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -1101,6 +1102,8 @@ public class PanoramaModule implements CameraModule, // Dismiss open menu if exists. PopupManager.getInstance(mActivity).notifyShowPopup(null); mRootView.requestLayout(); + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_CAMERA, "PanoramaModule"); } /** diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 4c627c92c..037e0fdb3 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -58,6 +58,7 @@ import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.filtershow.CropExtras; import com.android.gallery3d.filtershow.FilterShowActivity; +import com.android.gallery3d.util.UsageStatistics; import java.io.File; import java.io.FileNotFoundException; @@ -1255,6 +1256,8 @@ public class PhotoModule // Dismiss open menu if exists. PopupManager.getInstance(mActivity).notifyShowPopup(null); + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_CAMERA, "PhotoModule"); } void waitCameraStartUpThread() { diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index 995996480..18d0d3e86 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -676,7 +676,8 @@ public class PhotoUI implements PieListener, @Override public void clearFocus() { - getFocusIndicator().clear(); + FocusIndicator indicator = getFocusIndicator(); + if (indicator != null) indicator.clear(); } @Override diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index a168372d1..4ae46a897 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -77,6 +77,7 @@ import com.android.camera.ui.ZoomRenderer; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.util.AccessibilityUtils; +import com.android.gallery3d.util.UsageStatistics; import java.io.File; import java.io.IOException; @@ -866,6 +867,8 @@ public class VideoModule implements CameraModule, PopupManager.getInstance(mActivity).notifyShowPopup(null); mVideoNamer = new VideoNamer(); + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_CAMERA, "VideoModule"); } private void setDisplayOrientation() { diff --git a/src/com/android/camera/ui/CameraSwitcher.java b/src/com/android/camera/ui/CameraSwitcher.java index ce4f85003..e9551ada2 100644 --- a/src/com/android/camera/ui/CameraSwitcher.java +++ b/src/com/android/camera/ui/CameraSwitcher.java @@ -38,6 +38,7 @@ import android.widget.LinearLayout; import com.android.camera.Util; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; +import com.android.gallery3d.util.UsageStatistics; public class CameraSwitcher extends RotateImageView implements OnClickListener, OnTouchListener { @@ -106,6 +107,9 @@ public class CameraSwitcher extends RotateImageView private void onCameraSelected(int ix) { hidePopup(); if ((ix != mCurrentIndex) && (mListener != null)) { + UsageStatistics.onEvent("CameraModeSwitch", null, null); + UsageStatistics.setPendingTransitionCause( + UsageStatistics.TRANSITION_MENU_TAP); setCurrentIndex(ix); mListener.onCameraSelected(mModuleIds[ix]); } diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java index 44ff62feb..fe6840ecb 100644 --- a/src/com/android/gallery3d/app/PhotoPage.java +++ b/src/com/android/gallery3d/app/PhotoPage.java @@ -69,6 +69,7 @@ import com.android.gallery3d.ui.PhotoView; import com.android.gallery3d.ui.SelectionManager; import com.android.gallery3d.ui.SynchronizedHandler; import com.android.gallery3d.util.GalleryUtils; +import com.android.gallery3d.util.UsageStatistics; public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server, @@ -513,6 +514,10 @@ public abstract class PhotoPage extends ActivityState implements if (oldIndex == 0 && mCurrentIndex > 0 && !mPhotoView.getFilmMode()) { mPhotoView.setFilmMode(true); + if (mAppBridge != null) { + UsageStatistics.onEvent("CameraToFilmstrip", + UsageStatistics.TRANSITION_SWIPE, null); + } } else if (oldIndex == 2 && mCurrentIndex == 1) { mCameraSwitchCutoff = SystemClock.uptimeMillis() + CAMERA_SWITCH_CUTOFF_THRESHOLD_MS; @@ -1338,8 +1343,17 @@ public abstract class PhotoPage extends ActivityState implements } if (enabled) { mHandler.removeMessages(MSG_HIDE_BARS); + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_GALLERY, "FilmstripPage"); } else { refreshHidingMessage(); + if (mAppBridge == null || mCurrentIndex > 0) { + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_GALLERY, "SinglePhotoPage"); + } else { + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO + } } } diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java index b4b5d4b05..c0c84c950 100644 --- a/src/com/android/gallery3d/app/StateManager.java +++ b/src/com/android/gallery3d/app/StateManager.java @@ -24,6 +24,7 @@ import android.os.Parcelable; import android.view.Menu; import android.view.MenuItem; +import com.android.camera.CameraActivity; import com.android.gallery3d.anim.StateTransitionAnimation; import com.android.gallery3d.common.Utils; import com.android.gallery3d.util.UsageStatistics; @@ -63,9 +64,14 @@ public class StateManager { StateTransitionAnimation.Transition.Incoming); if (mIsResumed) top.onPause(); } - UsageStatistics.onContentViewChanged( - UsageStatistics.COMPONENT_GALLERY, - klass.getSimpleName()); + // Ignore the filmstrip used for the root of the camera app + boolean ignoreHit = (mActivity instanceof CameraActivity) + && mStack.isEmpty(); + if (!ignoreHit) { + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_GALLERY, + klass.getSimpleName()); + } state.initialize(mActivity, data); mStack.push(new StateEntry(data, state)); diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 93bb02483..37b2cd9da 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -574,6 +574,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, // TODO: Using singletons is a bad design choice for many of these // due static reference leaks and in general. Please refactor. MasterImage.reset(); + ImageFilterRS.destroyRenderScriptContext(); FilteringPipeline.reset(); ImageFilter.resetStatics(); FiltersManager.reset(); diff --git a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java b/src/com/android/gallery3d/filtershow/ImageStateAdapter.java index 62633e26e..1cd5f86c8 100644 --- a/src/com/android/gallery3d/filtershow/ImageStateAdapter.java +++ b/src/com/android/gallery3d/filtershow/ImageStateAdapter.java @@ -45,6 +45,7 @@ public class ImageStateAdapter extends ArrayAdapter<FilterRepresentation> { view = (MovableLinearLayout) inflater.inflate(R.layout.filtershow_imagestate_row, null); } FilterRepresentation filter = getItem(position); + filter.synchronizeRepresentation(); view.setFilterRepresentation(filter); ImageView markView = (ImageView) view.findViewById(R.id.selectedMark); if (filter == MasterImage.getImage().getCurrentFilterRepresentation()) { diff --git a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java index 39b39695c..d7e9a62a7 100644 --- a/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java +++ b/src/com/android/gallery3d/filtershow/cache/FilteringPipeline.java @@ -30,7 +30,7 @@ import com.android.gallery3d.filtershow.presets.ImagePreset; public class FilteringPipeline implements Handler.Callback { - private static FilteringPipeline sPipeline; + private static volatile FilteringPipeline sPipeline = null; private static final String LOGTAG = "FilteringPipeline"; private ImagePreset mPreviousGeometryPreset = null; private ImagePreset mPreviousFiltersPreset = null; @@ -49,6 +49,8 @@ public class FilteringPipeline implements Handler.Callback { private final static int COMPUTE_RENDERING_REQUEST = 3; private final static int COMPUTE_PARTIAL_RENDERING_REQUEST = 4; + private boolean mHasUnhandledPreviewRequest = false; + private Handler mProcessingHandler = null; private final Handler mUIHandler = new Handler() { @Override @@ -58,6 +60,9 @@ public class FilteringPipeline implements Handler.Callback { TripleBufferBitmap buffer = MasterImage.getImage().getDoubleBuffer(); buffer.swapConsumer(); MasterImage.getImage().notifyObservers(); + if (mHasUnhandledPreviewRequest) { + updatePreviewBuffer(); + } break; } case NEW_RENDERING_REQUEST: { @@ -115,7 +120,7 @@ public class FilteringPipeline implements Handler.Callback { mProcessingHandler = new Handler(mHandlerThread.getLooper(), this); } - public static FilteringPipeline getPipeline() { + public synchronized static FilteringPipeline getPipeline() { if (sPipeline == null) { sPipeline = new FilteringPipeline(); } @@ -168,8 +173,6 @@ public class FilteringPipeline implements Handler.Callback { Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); mPreviousGeometry = new GeometryMetadata(geometry); - - FiltersManager.getManager().resetBitmapsRS(); return true; } @@ -194,6 +197,7 @@ public class FilteringPipeline implements Handler.Callback { if (mOriginalAllocation == null) { return; } + mHasUnhandledPreviewRequest = true; if (mProcessingHandler.hasMessages(COMPUTE_PRESET)) { return; } @@ -205,6 +209,7 @@ public class FilteringPipeline implements Handler.Callback { } Message msg = mProcessingHandler.obtainMessage(COMPUTE_PRESET); msg.obj = MasterImage.getImage().getPreset(); + mHasUnhandledPreviewRequest = false; mProcessingHandler.sendMessageAtFrontOfQueue(msg); } @@ -252,17 +257,19 @@ public class FilteringPipeline implements Handler.Callback { preset.setupEnvironment(); if (request.getType() == RenderingRequest.PARTIAL_RENDERING) { - bitmap = MasterImage.getImage().getImageLoader().getScaleOneImageForPreset(null, preset, + ImageLoader loader = MasterImage.getImage().getImageLoader(); + if (loader == null) { + Log.w(LOGTAG, "loader not yet setup, cannot handle: " + getType(request)); + return; + } + bitmap = loader.getScaleOneImageForPreset(null, preset, request.getBounds(), request.getDestination(), false); if (bitmap == null) { + Log.w(LOGTAG, "could not get bitmap for: " + getType(request)); return; } } - if (request.getType() == RenderingRequest.FILTERS_RENDERING) { - FiltersManager.getManager().resetBitmapsRS(); - } - if (request.getType() != RenderingRequest.ICON_RENDERING && request.getType() != RenderingRequest.PARTIAL_RENDERING) { updateOriginalAllocation(preset); @@ -289,9 +296,6 @@ public class FilteringPipeline implements Handler.Callback { FiltersManager.getManager().freeFilterResources(preset); } - if (request.getType() == RenderingRequest.FILTERS_RENDERING) { - FiltersManager.getManager().resetBitmapsRS(); - } } private void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) { @@ -349,7 +353,8 @@ public class FilteringPipeline implements Handler.Callback { return mPreviewScaleFactor; } - public static void reset() { + public static synchronized void reset() { + sPipeline.mHandlerThread.quit(); sPipeline = null; } } diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index 215d5d438..f6c3bdd89 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -61,16 +61,6 @@ public abstract class BaseFiltersManager { return null; } - public void resetBitmapsRS() { - for (Class c : mFilters.keySet()) { - ImageFilter filter = mFilters.get(c); - if (filter instanceof ImageFilterRS) { - ImageFilterRS filterRS = (ImageFilterRS) filter; - filterRS.resetBitmap(); - } - } - } - public void freeFilterResources(ImagePreset preset) { if (preset == null) { return; diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java index a06c2e022..595aa9b30 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java @@ -18,74 +18,79 @@ package com.android.gallery3d.filtershow.filters; import android.app.Activity; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.support.v8.renderscript.*; import android.util.Log; +import android.content.res.Resources; import com.android.gallery3d.R; public abstract class ImageFilterRS extends ImageFilter { - private final String LOGTAG = "ImageFilterRS"; + private static final String LOGTAG = "ImageFilterRS"; - private static RenderScript mRS = null; - protected static Allocation mInPixelsAllocation; - protected static Allocation mOutPixelsAllocation; - private static android.content.res.Resources mResources = null; - private static Bitmap sOldBitmap = null; - private Bitmap mOldBitmap = null; + protected static volatile Allocation mInPixelsAllocation; + protected static volatile Allocation mOutPixelsAllocation; - private boolean mResourcesLoaded = false; + private static volatile RenderScript sRS = null; + private static volatile int sWidth = 0; + private static volatile int sHeight = 0; - private final Bitmap.Config mBitmapConfig = Bitmap.Config.ARGB_8888; + private static volatile Resources sResources = null; + private boolean mResourcesLoaded = false; - public void resetBitmap() { - mOldBitmap = null; - } + private static final Bitmap.Config BITMAP_CONFIG = Bitmap.Config.ARGB_8888; + // This must be used inside block synchronized on ImageFilterRS class object public void prepare(Bitmap bitmap, float scaleFactor, int quality) { - if (sOldBitmap == null - || (bitmap.getWidth() != sOldBitmap.getWidth()) - || (bitmap.getHeight() != sOldBitmap.getHeight())) { - if (mInPixelsAllocation != null) { - mInPixelsAllocation.destroy(); - mInPixelsAllocation = null; - } - if (mOutPixelsAllocation != null) { - mOutPixelsAllocation.destroy(); - mOutPixelsAllocation = null; + if (mOutPixelsAllocation == null || mInPixelsAllocation == null || + bitmap.getWidth() != sWidth || bitmap.getHeight() != sHeight) { + destroyPixelAllocations(); + Bitmap bitmapBuffer = bitmap; + if (bitmap.getConfig() == null || bitmap.getConfig() != BITMAP_CONFIG) { + bitmapBuffer = bitmap.copy(BITMAP_CONFIG, true); } - Bitmap bitmapBuffer = bitmap.copy(mBitmapConfig, true); - mOutPixelsAllocation = Allocation.createFromBitmap(mRS, bitmapBuffer, + mOutPixelsAllocation = Allocation.createFromBitmap(sRS, bitmapBuffer, Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); - mInPixelsAllocation = Allocation.createTyped(mRS, + mInPixelsAllocation = Allocation.createTyped(sRS, mOutPixelsAllocation.getType()); - sOldBitmap = bitmap; } mInPixelsAllocation.copyFrom(bitmap); - if (mOldBitmap != sOldBitmap || !isResourcesLoaded()) { + if (bitmap.getWidth() != sWidth + || bitmap.getHeight() != sHeight || !isResourcesLoaded()) { freeResources(); - createFilter(mResources, scaleFactor, quality); - mOldBitmap = sOldBitmap; + createFilter(sResources, scaleFactor, quality); + sWidth = bitmap.getWidth(); + sHeight = bitmap.getHeight(); setResourcesLoaded(true); } } + // This must be used inside block synchronized on ImageFilterRS class object abstract public void createFilter(android.content.res.Resources res, float scaleFactor, int quality); + // This must be used inside block synchronized on ImageFilterRS class object abstract public void runFilter(); + // This must be used inside block synchronized on ImageFilterRS class object public void update(Bitmap bitmap) { mOutPixelsAllocation.copyTo(bitmap); } @Override public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) { - if (bitmap == null) { + if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0) { return bitmap; } try { - prepare(bitmap, scaleFactor, quality); - runFilter(); - update(bitmap); + synchronized(ImageFilterRS.class) { + if (sRS == null) { + Log.w(LOGTAG, "Cannot apply before calling setRenderScriptContext"); + return bitmap; + } + prepare(bitmap, scaleFactor, quality); + runFilter(); + update(bitmap); + } } catch (android.renderscript.RSIllegalArgumentException e) { Log.e(LOGTAG, "Illegal argument? " + e); } catch (android.renderscript.RSRuntimeException e) { @@ -99,15 +104,21 @@ public abstract class ImageFilterRS extends ImageFilter { return bitmap; } - public static RenderScript getRenderScriptContext() { - return mRS; + public static synchronized RenderScript getRenderScriptContext() { + return sRS; } - public static void setRenderScriptContext(Activity context) { - if (mRS == null) { - mRS = RenderScript.create(context); + public static synchronized void setRenderScriptContext(Activity context) { + if( sRS != null) { + Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext"); + destroyRenderScriptContext(); } - mResources = context.getResources(); + sRS = RenderScript.create(context); + sResources = context.getResources(); + destroyPixelAllocations(); + } + + private static synchronized void destroyPixelAllocations() { if (mInPixelsAllocation != null) { mInPixelsAllocation.destroy(); mInPixelsAllocation = null; @@ -116,32 +127,75 @@ public abstract class ImageFilterRS extends ImageFilter { mOutPixelsAllocation.destroy(); mOutPixelsAllocation = null; } - sOldBitmap = null; + sWidth = 0; + sHeight = 0; + } + + public static synchronized void destroyRenderScriptContext() { + destroyPixelAllocations(); + sRS.destroy(); + sRS = null; + sResources = null; } - public Allocation convertRGBAtoA(Bitmap bitmap) { - Type.Builder tb_a8 = new Type.Builder(mRS, Element.U8(mRS)); - ScriptC_grey greyConvert = new ScriptC_grey(mRS, mResources, R.raw.grey); + private static synchronized Allocation convertBitmap(Bitmap bitmap) { + return Allocation.createFromBitmap(sRS, bitmap, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + } - Allocation bitmapTemp = Allocation.createFromBitmap(mRS, bitmap); + private static synchronized Allocation convertRGBAtoA(Bitmap bitmap) { + Type.Builder tb_a8 = new Type.Builder(sRS, Element.U8(sRS)); + ScriptC_grey greyConvert = new ScriptC_grey(sRS, + sRS.getApplicationContext().getResources(), R.raw.grey); - if (bitmapTemp.getType().getElement().isCompatible(Element.U8(mRS))) { + Allocation bitmapTemp = convertBitmap(bitmap); + if (bitmapTemp.getType().getElement().isCompatible(Element.U8(sRS))) { return bitmapTemp; } tb_a8.setX(bitmapTemp.getType().getX()); tb_a8.setY(bitmapTemp.getType().getY()); - Allocation bitmapAlloc = Allocation.createTyped(mRS, tb_a8.create()); + Allocation bitmapAlloc = Allocation.createTyped(sRS, tb_a8.create()); greyConvert.forEach_RGBAtoA(bitmapTemp, bitmapAlloc); return bitmapAlloc; } - public boolean isResourcesLoaded() { + public Allocation loadResourceAlpha(int resource) { + Resources res = null; + synchronized(ImageFilterRS.class) { + res = sRS.getApplicationContext().getResources(); + } + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ALPHA_8; + Bitmap bitmap = BitmapFactory.decodeResource( + res, + resource, options); + Allocation ret = convertRGBAtoA(bitmap); + bitmap.recycle(); + return ret; + } + + public Allocation loadResource(int resource) { + Resources res = null; + synchronized(ImageFilterRS.class) { + res = sRS.getApplicationContext().getResources(); + } + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bitmap = BitmapFactory.decodeResource( + res, + resource, options); + Allocation ret = convertBitmap(bitmap); + bitmap.recycle(); + return ret; + } + + private boolean isResourcesLoaded() { return mResourcesLoaded; } - public void setResourcesLoaded(boolean resourcesLoaded) { + private void setResourcesLoaded(boolean resourcesLoaded) { mResourcesLoaded = resourcesLoaded; } diff --git a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java index 38d415633..0b2f96abb 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java +++ b/src/com/android/gallery3d/filtershow/imageshow/ImageShow.java @@ -156,9 +156,6 @@ public class ImageShow extends View implements OnGestureListener, } public void onNewValue(int parameter) { - if (getImagePreset() != null) { - getImagePreset().fillImageStateAdapter(MasterImage.getImage().getState()); - } if (getPanelController() != null) { getPanelController().onNewValue(parameter); } diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index e27afe580..b75ac64fb 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -147,6 +147,7 @@ public class ImagePreset { } } MasterImage.getImage().invalidatePreview(); + fillImageStateAdapter(MasterImage.getImage().getState()); } public void setDoApplyGeometry(boolean value) { diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java index 40e01ab07..5c8ac1c3e 100644 --- a/src/com/android/gallery3d/ui/PhotoView.java +++ b/src/com/android/gallery3d/ui/PhotoView.java @@ -42,6 +42,7 @@ import com.android.gallery3d.glrenderer.StringTexture; import com.android.gallery3d.glrenderer.Texture; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.RangeArray; +import com.android.gallery3d.util.UsageStatistics; public class PhotoView extends GLView { @SuppressWarnings("unused") @@ -1180,8 +1181,16 @@ public class PhotoView extends GLView { // Removing the touch down flag allows snapback to happen // for film mode change. mHolding &= ~HOLD_TOUCH_DOWN; + if (mFilmMode) { + UsageStatistics.setPendingTransitionCause( + UsageStatistics.TRANSITION_PINCH_OUT); + } else { + UsageStatistics.setPendingTransitionCause( + UsageStatistics.TRANSITION_PINCH_IN); + } setFilmMode(!mFilmMode); + // We need to call onScaleEnd() before setting mModeChanged // to true. onScaleEnd(); diff --git a/src/com/android/photos/AlbumSetFragment.java b/src/com/android/photos/AlbumSetFragment.java index 0d4fcc023..d0bc81fd6 100644 --- a/src/com/android/photos/AlbumSetFragment.java +++ b/src/com/android/photos/AlbumSetFragment.java @@ -21,42 +21,54 @@ import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.Loader; import android.database.Cursor; -import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Bundle; -import android.text.format.DateFormat; +import android.provider.MediaStore.Files.FileColumns; +import android.util.SparseBooleanArray; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; -import android.widget.CursorAdapter; import android.widget.GridView; -import android.widget.ImageView; -import android.widget.ProgressBar; -import android.widget.TextView; import android.widget.Toast; import com.android.gallery3d.R; +import com.android.photos.adapters.AlbumSetCursorAdapter; import com.android.photos.data.AlbumSetLoader; import com.android.photos.shims.LoaderCompatShim; import com.android.photos.shims.MediaSetLoader; -import java.util.Date; +import java.util.ArrayList; public class AlbumSetFragment extends Fragment implements OnItemClickListener, - LoaderCallbacks<Cursor> { + LoaderCallbacks<Cursor>, MultiChoiceManager.Delegate, SelectionManager.Client { private GridView mAlbumSetView; private View mEmptyView; private AlbumSetCursorAdapter mAdapter; + private LoaderCompatShim<Cursor> mLoaderCompatShim; + private MultiChoiceManager mMultiChoiceManager; + private SelectionManager mSelectionManager; private static final int LOADER_ALBUMSET = 1; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mAdapter = new AlbumSetCursorAdapter(getActivity()); + Context context = getActivity(); + mAdapter = new AlbumSetCursorAdapter(context); + mMultiChoiceManager = new MultiChoiceManager(context, this); + mMultiChoiceManager.setSelectionManager(mSelectionManager); + } + + @Override + public void setSelectionManager(SelectionManager manager) { + mSelectionManager = manager; + if (mMultiChoiceManager != null) { + mMultiChoiceManager.setSelectionManager(manager); + } } @Override @@ -67,6 +79,8 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener, mEmptyView = root.findViewById(android.R.id.empty); mEmptyView.setVisibility(View.GONE); mAlbumSetView.setAdapter(mAdapter); + mAlbumSetView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL); + mAlbumSetView.setMultiChoiceModeListener(mMultiChoiceManager); mAlbumSetView.setOnItemClickListener(this); getLoaderManager().initLoader(LOADER_ALBUMSET, null, this); updateEmptyStatus(); @@ -78,6 +92,7 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener, // TODO: Switch to AlbumSetLoader MediaSetLoader loader = new MediaSetLoader(getActivity()); mAdapter.setDrawableFactory(loader); + mLoaderCompatShim = loader; return loader; } @@ -100,63 +115,54 @@ public class AlbumSetFragment extends Fragment implements OnItemClickListener, @Override public void onItemClick(AdapterView<?> av, View v, int pos, long id) { - Cursor c = (Cursor) av.getItemAtPosition(pos); - int albumId = c.getInt(AlbumSetLoader.INDEX_ID); - // TODO launch an activity showing the photos in the album - Toast.makeText(v.getContext(), "Clicked " + albumId, Toast.LENGTH_SHORT).show(); + if (mLoaderCompatShim == null) { + // Not fully initialized yet, discard + return; + } + Cursor item = (Cursor) mAdapter.getItem(pos); + Toast.makeText(v.getContext(), + "Tapped " + item.getInt(AlbumSetLoader.INDEX_ID), + Toast.LENGTH_SHORT).show(); } - private static class AlbumSetCursorAdapter extends CursorAdapter { + @Override + public int getItemMediaType(Object item) { + return FileColumns.MEDIA_TYPE_NONE; + } - private LoaderCompatShim<Cursor> mDrawableFactory; + @Override + public int getItemSupportedOperations(Object item) { + return ((Cursor) item).getInt(AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS); + } - public void setDrawableFactory(LoaderCompatShim<Cursor> factory) { - mDrawableFactory = factory; - } - private Date mDate = new Date(); // Used for converting timestamps for display + @Override + public Object getItemAtPosition(int position) { + return mAdapter.getItem(position); + } - public AlbumSetCursorAdapter(Context context) { - super(context, null, false); - } + @Override + public ArrayList<Uri> getSubItemUrisForItem(Object item) { + return mLoaderCompatShim.urisForSubItems((Cursor) item); + } - @Override - public void bindView(View v, Context context, Cursor cursor) { - TextView titleTextView = (TextView) v.findViewById( - R.id.album_set_item_title); - titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE)); - - TextView dateTextView = (TextView) v.findViewById( - R.id.album_set_item_date); - long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP); - if (timestamp > 0) { - mDate.setTime(timestamp); - dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate)); - } else { - dateTextView.setText(null); - } - - ProgressBar uploadProgressBar = (ProgressBar) v.findViewById( - R.id.album_set_item_upload_progress); - if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) { - uploadProgressBar.setVisibility(View.VISIBLE); - uploadProgressBar.setProgress(50); - } else { - uploadProgressBar.setVisibility(View.INVISIBLE); - } - - ImageView thumbImageView = (ImageView) v.findViewById( - R.id.album_set_item_image); - Drawable recycle = thumbImageView.getDrawable(); - Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle); - if (recycle != drawable) { - thumbImageView.setImageDrawable(drawable); - } - } + @Override + public Object getPathForItemAtPosition(int position) { + return mLoaderCompatShim.getPathForItem((Cursor) mAdapter.getItem(position)); + } - @Override - public View newView(Context context, Cursor cursor, ViewGroup parent) { - return LayoutInflater.from(context).inflate( - R.layout.album_set_item, parent, false); - } + @Override + public void deleteItemWithPath(Object itemPath) { + mLoaderCompatShim.deleteItemWithPath(itemPath); } + + @Override + public SparseBooleanArray getSelectedItemPositions() { + return mAlbumSetView.getCheckedItemPositions(); + } + + @Override + public int getSelectedItemCount() { + return mAlbumSetView.getCheckedItemCount(); + } + } diff --git a/src/com/android/photos/GalleryActivity.java b/src/com/android/photos/GalleryActivity.java index 2335658d1..ddf04e365 100644 --- a/src/com/android/photos/GalleryActivity.java +++ b/src/com/android/photos/GalleryActivity.java @@ -21,49 +21,53 @@ import android.app.ActionBar.Tab; import android.app.Activity; import android.app.Fragment; import android.app.FragmentTransaction; +import android.content.Context; import android.content.Intent; import android.os.Bundle; +import android.support.v13.app.FragmentPagerAdapter; +import android.support.v4.view.ViewPager; import android.view.Menu; import android.view.MenuItem; import com.android.camera.CameraActivity; import com.android.gallery3d.R; +import java.util.ArrayList; + public class GalleryActivity extends Activity { - private final String FTAG_PHOTOSET = "PhotoSet"; - private final String FTAG_ALBUMSET = "AlbumSet"; private SelectionManager mSelectionManager; + private ViewPager mViewPager; + private TabsAdapter mTabsAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + mSelectionManager = new SelectionManager(this); + mViewPager = new ViewPager(this); + mViewPager.setId(R.id.viewpager); + setContentView(mViewPager); - setupActionBar(); - } - - protected SelectionManager getSelectionManager() { - if (mSelectionManager == null) { - mSelectionManager = new SelectionManager(this); - } - return mSelectionManager; - } - - private void setupActionBar() { ActionBar ab = getActionBar(); ab.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); ab.setDisplayShowHomeEnabled(false); ab.setDisplayShowTitleEnabled(false); - Tab tab = ab.newTab(); - tab.setText(R.string.tab_photos); - tab.setTabListener(new TabListener<PhotoSetFragment>(this, - FTAG_PHOTOSET, PhotoSetFragment.class)); - ab.addTab(tab, true); - tab = ab.newTab(); - tab.setText(R.string.tab_albums); - tab.setTabListener(new TabListener<AlbumSetFragment>(this, - FTAG_ALBUMSET, AlbumSetFragment.class)); - ab.addTab(tab); + + mTabsAdapter = new TabsAdapter(this, mViewPager); + mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_photos), + PhotoSetFragment.class, null); + mTabsAdapter.addTab(ab.newTab().setText(R.string.tab_albums), + AlbumSetFragment.class, null); + + if (savedInstanceState != null) { + ab.setSelectedNavigationItem(savedInstanceState.getInt("tab", 0)); + } + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + outState.putInt("tab", getActionBar().getSelectedNavigationIndex()); } @Override @@ -85,49 +89,88 @@ public class GalleryActivity extends Activity { } } - private static class TabListener<T extends Fragment> implements ActionBar.TabListener { - private Fragment mFragment; - private final Activity mActivity; - private final String mTag; - private final Class<T> mClass; - - /** Constructor used each time a new tab is created. - * @param activity The host Activity, used to instantiate the fragment - * @param tag The identifier tag for the fragment - * @param clz The fragment's Class, used to instantiate the fragment - */ - public TabListener(Activity activity, String tag, Class<T> clz) { + public static class TabsAdapter extends FragmentPagerAdapter implements + ActionBar.TabListener, ViewPager.OnPageChangeListener { + + private final GalleryActivity mActivity; + private final ActionBar mActionBar; + private final ViewPager mViewPager; + private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>(); + + static final class TabInfo { + + private final Class<?> clss; + private final Bundle args; + + TabInfo(Class<?> _class, Bundle _args) { + clss = _class; + args = _args; + } + } + + public TabsAdapter(GalleryActivity activity, ViewPager pager) { + super(activity.getFragmentManager()); mActivity = activity; - mTag = tag; - mClass = clz; + mActionBar = activity.getActionBar(); + mViewPager = pager; + mViewPager.setAdapter(this); + mViewPager.setOnPageChangeListener(this); + } + + public void addTab(ActionBar.Tab tab, Class<?> clss, Bundle args) { + TabInfo info = new TabInfo(clss, args); + tab.setTag(info); + tab.setTabListener(this); + mTabs.add(info); + mActionBar.addTab(tab); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + return mTabs.size(); + } + + @Override + public Fragment getItem(int position) { + TabInfo info = mTabs.get(position); + Fragment item = Fragment.instantiate(mActivity, info.clss.getName(), + info.args); + ((SelectionManager.Client) item).setSelectionManager( + mActivity.mSelectionManager); + return item; } - /* The following are each of the ActionBar.TabListener callbacks */ + @Override + public void onPageScrolled(int position, float positionOffset, + int positionOffsetPixels) { + } + + @Override + public void onPageSelected(int position) { + mActionBar.setSelectedNavigationItem(position); + } + + @Override + public void onPageScrollStateChanged(int state) { + } @Override public void onTabSelected(Tab tab, FragmentTransaction ft) { - // Check if the fragment is already initialized - if (mFragment == null) { - // If not, instantiate and add it to the activity - mFragment = Fragment.instantiate(mActivity, mClass.getName()); - ft.add(android.R.id.content, mFragment, mTag); - } else { - // If it exists, simply attach it in order to show it - ft.attach(mFragment); + Object tag = tab.getTag(); + for (int i = 0; i < mTabs.size(); i++) { + if (mTabs.get(i) == tag) { + mViewPager.setCurrentItem(i); + } } } @Override public void onTabUnselected(Tab tab, FragmentTransaction ft) { - if (mFragment != null) { - // Detach the fragment, because another one is being attached - ft.detach(mFragment); - } } @Override public void onTabReselected(Tab tab, FragmentTransaction ft) { - // User selected the already selected tab. Usually do nothing. } } } diff --git a/src/com/android/photos/MultiChoiceManager.java b/src/com/android/photos/MultiChoiceManager.java new file mode 100644 index 000000000..e00c842fe --- /dev/null +++ b/src/com/android/photos/MultiChoiceManager.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2013 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.photos; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.SparseBooleanArray; +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.widget.AbsListView.MultiChoiceModeListener; +import android.widget.ShareActionProvider; +import android.widget.ShareActionProvider.OnShareTargetSelectedListener; + +import com.android.gallery3d.R; +import com.android.gallery3d.data.MediaObject; + +import java.util.ArrayList; +import java.util.List; + +public class MultiChoiceManager implements MultiChoiceModeListener, + OnShareTargetSelectedListener, SelectionManager.SelectedUriSource { + + public interface Delegate { + public SparseBooleanArray getSelectedItemPositions(); + public int getSelectedItemCount(); + public int getItemMediaType(Object item); + public int getItemSupportedOperations(Object item); + public ArrayList<Uri> getSubItemUrisForItem(Object item); + public Object getItemAtPosition(int position); + public Object getPathForItemAtPosition(int position); + public void deleteItemWithPath(Object itemPath); + } + + private SelectionManager mSelectionManager; + private ShareActionProvider mShareActionProvider; + private ActionMode mActionMode; + private int numSubItemsCollected = 0; + private Context mContext; + private Delegate mDelegate; + + private ArrayList<Uri> mSelectedUrisArray = new ArrayList<Uri>(); + + public MultiChoiceManager(Context context, Delegate delegate) { + mContext = context; + mDelegate = delegate; + } + + public void setSelectionManager(SelectionManager selectionManager) { + mSelectionManager = selectionManager; + } + + @Override + public ArrayList<Uri> getSelectedShareableUris() { + return mSelectedUrisArray; + } + + private void updateSelectedTitle(ActionMode mode) { + int count = mDelegate.getSelectedItemCount(); + mode.setTitle(mContext.getResources().getQuantityString( + R.plurals.number_of_items_selected, count, count)); + } + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, long id, + boolean checked) { + updateSelectedTitle(mode); + Object item = mDelegate.getItemAtPosition(position); + + ArrayList<Uri> subItems = mDelegate.getSubItemUrisForItem(item); + if (checked) { + mSelectedUrisArray.addAll(subItems); + numSubItemsCollected += subItems.size(); + } else { + mSelectedUrisArray.removeAll(subItems); + numSubItemsCollected -= subItems.size(); + } + + mSelectionManager.onItemSelectedStateChanged(mShareActionProvider, + mDelegate.getItemMediaType(item), + mDelegate.getItemSupportedOperations(item), + checked); + updateActionItemVisibilities(mode.getMenu(), + mSelectionManager.getSupportedOperations()); + } + + private void updateActionItemVisibilities(Menu menu, int supportedOperations) { + MenuItem shareItem = menu.findItem(R.id.menu_share); + MenuItem deleteItem = menu.findItem(R.id.menu_delete); + shareItem.setVisible((supportedOperations & MediaObject.SUPPORT_SHARE) > 0); + deleteItem.setVisible((supportedOperations & MediaObject.SUPPORT_DELETE) > 0); + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + mSelectionManager.setSelectedUriSource(this); + mActionMode = mode; + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.gallery_multiselect, menu); + MenuItem menuItem = menu.findItem(R.id.menu_share); + mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); + mShareActionProvider.setOnShareTargetSelectedListener(this); + updateSelectedTitle(mode); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // onDestroyActionMode gets called when the share target was selected, + // but apparently before the ArrayList is serialized in the intent + // so we can't clear the old one here. + mSelectedUrisArray = new ArrayList<Uri>(); + mSelectionManager.onClearSelection(); + mSelectionManager.setSelectedUriSource(null); + mShareActionProvider = null; + mActionMode = null; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + updateSelectedTitle(mode); + return false; + } + + @Override + public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) { + mActionMode.finish(); + return false; + } + + private static class BulkDeleteTask extends AsyncTask<Void, Void, Void> { + private Delegate mDelegate; + private List<Object> mPaths; + + public BulkDeleteTask(Delegate delegate, List<Object> paths) { + mDelegate = delegate; + mPaths = paths; + } + + @Override + protected Void doInBackground(Void... ignored) { + for (Object path : mPaths) { + mDelegate.deleteItemWithPath(path); + } + return null; + } + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_delete: + BulkDeleteTask deleteTask = new BulkDeleteTask(mDelegate, + getPathsForSelectedItems()); + deleteTask.execute(); + mode.finish(); + return true; + default: + return false; + } + } + + private List<Object> getPathsForSelectedItems() { + List<Object> paths = new ArrayList<Object>(); + SparseBooleanArray selected = mDelegate.getSelectedItemPositions(); + for (int i = 0; i < selected.size(); i++) { + if (selected.valueAt(i)) { + paths.add(mDelegate.getPathForItemAtPosition(i)); + } + } + return paths; + } +} diff --git a/src/com/android/photos/PhotoSetFragment.java b/src/com/android/photos/PhotoSetFragment.java index 25d80360d..b485cd051 100644 --- a/src/com/android/photos/PhotoSetFragment.java +++ b/src/com/android/photos/PhotoSetFragment.java @@ -18,41 +18,31 @@ package com.android.photos; import android.app.Fragment; import android.app.LoaderManager.LoaderCallbacks; +import android.content.Context; import android.content.Intent; import android.content.Loader; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; import android.util.SparseBooleanArray; -import android.view.ActionMode; import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuInflater; -import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView.MultiChoiceModeListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.GridView; -import android.widget.ShareActionProvider; -import android.widget.ShareActionProvider.OnShareTargetSelectedListener; import com.android.gallery3d.R; import com.android.gallery3d.app.Gallery; -import com.android.gallery3d.data.MediaItem; import com.android.photos.adapters.PhotoThumbnailAdapter; import com.android.photos.data.PhotoSetLoader; import com.android.photos.shims.LoaderCompatShim; import com.android.photos.shims.MediaItemsLoader; import java.util.ArrayList; -import java.util.HashSet; -import java.util.Set; -public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor>, - OnItemClickListener, SelectionManager.SelectedUriSource, MultiChoiceModeListener, - OnShareTargetSelectedListener { +public class PhotoSetFragment extends Fragment implements OnItemClickListener, + LoaderCallbacks<Cursor>, MultiChoiceManager.Delegate, SelectionManager.Client { private static final int LOADER_PHOTOSET = 1; @@ -62,14 +52,24 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor private boolean mInitialLoadComplete = false; private LoaderCompatShim<Cursor> mLoaderCompatShim; private PhotoThumbnailAdapter mAdapter; + private MultiChoiceManager mMultiChoiceManager; private SelectionManager mSelectionManager; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - GalleryActivity activity = (GalleryActivity) getActivity(); - mSelectionManager = activity.getSelectionManager(); - mAdapter = new PhotoThumbnailAdapter(activity); + Context context = getActivity(); + mAdapter = new PhotoThumbnailAdapter(context); + mMultiChoiceManager = new MultiChoiceManager(context, this); + mMultiChoiceManager.setSelectionManager(mSelectionManager); + } + + @Override + public void setSelectionManager(SelectionManager manager) { + mSelectionManager = manager; + if (mMultiChoiceManager != null) { + mMultiChoiceManager.setSelectionManager(manager); + } } @Override @@ -84,7 +84,7 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor mEmptyView.setVisibility(View.GONE); mPhotoSetView.setAdapter(mAdapter); mPhotoSetView.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL); - mPhotoSetView.setMultiChoiceModeListener(this); + mPhotoSetView.setMultiChoiceModeListener(mMultiChoiceManager); getLoaderManager().initLoader(LOADER_PHOTOSET, null, this); updateEmptyStatus(); return root; @@ -129,112 +129,51 @@ public class PhotoSetFragment extends Fragment implements LoaderCallbacks<Cursor updateEmptyStatus(); } - private Set<Uri> mSelectedUris = new HashSet<Uri>(); - private ArrayList<Uri> mSelectedUrisArray = new ArrayList<Uri>(); - @Override - public ArrayList<Uri> getSelectedShareableUris() { - mSelectedUrisArray.clear(); - mSelectedUrisArray.addAll(mSelectedUris); - return mSelectedUrisArray; - } - - public ArrayList<Uri> getSelectedShareableUrisUncached() { - mSelectedUrisArray.clear(); - SparseBooleanArray selected = mPhotoSetView.getCheckedItemPositions(); - - for (int i = 0; i < selected.size(); i++) { - if (selected.valueAt(i)) { - Cursor item = mAdapter.getItem(selected.keyAt(i)); - int supported = item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS); - if ((supported & MediaItem.SUPPORT_SHARE) > 0) { - mSelectedUrisArray.add(mLoaderCompatShim.uriForItem(item)); - } - } - } - - return mSelectedUrisArray; + public void onLoaderReset(Loader<Cursor> loader) { } @Override - public void onLoaderReset(Loader<Cursor> loader) { + public int getItemMediaType(Object item) { + return ((Cursor) item).getInt(PhotoSetLoader.INDEX_MEDIA_TYPE); } - - private ShareActionProvider mShareActionProvider; - private ActionMode mActionMode; - private boolean mSharePending = false; - - private void updateSelectedTitle(ActionMode mode) { - int count = mPhotoSetView.getCheckedItemCount(); - mode.setTitle(getResources().getQuantityString( - R.plurals.number_of_items_selected, count, count)); + @Override + public int getItemSupportedOperations(Object item) { + return ((Cursor) item).getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS); } @Override - public void onItemCheckedStateChanged(ActionMode mode, int position, long id, - boolean checked) { - updateSelectedTitle(mode); - Cursor item = mAdapter.getItem(position); - - if (checked) { - mSelectedUris.add(mLoaderCompatShim.uriForItem(item)); - } else { - mSelectedUris.remove(mLoaderCompatShim.uriForItem(item)); - } - - mSelectionManager.onItemSelectedStateChanged(mShareActionProvider, - item.getInt(PhotoSetLoader.INDEX_MEDIA_TYPE), - item.getInt(PhotoSetLoader.INDEX_SUPPORTED_OPERATIONS), - checked); + public Object getItemAtPosition(int position) { + return mAdapter.getItem(position); } + private ArrayList<Uri> mSubItemUriTemp = new ArrayList<Uri>(1); @Override - public boolean onCreateActionMode(ActionMode mode, Menu menu) { - mSelectionManager.setSelectedUriSource(PhotoSetFragment.this); - mActionMode = mode; - MenuInflater inflater = mode.getMenuInflater(); - inflater.inflate(R.menu.gallery_multiselect, menu); - MenuItem menuItem = menu.findItem(R.id.menu_share); - mShareActionProvider = (ShareActionProvider) menuItem.getActionProvider(); - mShareActionProvider.setOnShareTargetSelectedListener(this); - updateSelectedTitle(mode); - return true; + public ArrayList<Uri> getSubItemUrisForItem(Object item) { + mSubItemUriTemp.clear(); + mSubItemUriTemp.add(mLoaderCompatShim.uriForItem((Cursor) item)); + return mSubItemUriTemp; } + @Override - public void onDestroyActionMode(ActionMode mode) { - mSelectedUris.clear(); - if (mSharePending) { - // onDestroyActionMode gets called when the share target was selected, - // but apparently before the ArrayList is serialized in the intent - // so we can't clear the old one here. - mSelectedUrisArray = new ArrayList<Uri>(); - mSharePending = false; - } else { - mSelectedUrisArray.clear(); - } - mSelectionManager.onClearSelection(); - mSelectionManager.setSelectedUriSource(null); - mShareActionProvider = null; - mActionMode = null; + public Object getPathForItemAtPosition(int position) { + return mLoaderCompatShim.getPathForItem(mAdapter.getItem(position)); } @Override - public boolean onPrepareActionMode(ActionMode mode, Menu menu) { - updateSelectedTitle(mode); - return false; + public void deleteItemWithPath(Object itemPath) { + mLoaderCompatShim.deleteItemWithPath(itemPath); } @Override - public boolean onShareTargetSelected(ShareActionProvider provider, Intent intent) { - mSharePending = true; - mActionMode.finish(); - return false; + public SparseBooleanArray getSelectedItemPositions() { + return mPhotoSetView.getCheckedItemPositions(); } @Override - public boolean onActionItemClicked(ActionMode mode, MenuItem item) { - return false; + public int getSelectedItemCount() { + return mPhotoSetView.getCheckedItemCount(); } } diff --git a/src/com/android/photos/SelectionManager.java b/src/com/android/photos/SelectionManager.java index 979dcc7da..ce340c731 100644 --- a/src/com/android/photos/SelectionManager.java +++ b/src/com/android/photos/SelectionManager.java @@ -26,7 +26,7 @@ import android.provider.MediaStore.Files.FileColumns; import android.widget.ShareActionProvider; import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.util.GalleryUtils; import java.util.ArrayList; @@ -41,6 +41,10 @@ public class SelectionManager { public ArrayList<Uri> getSelectedShareableUris(); } + public interface Client { + public void setSelectionManager(SelectionManager manager); + } + public SelectionManager(Activity activity) { mActivity = activity; if (ApiHelper.AT_LEAST_16) { @@ -76,10 +80,10 @@ public class SelectionManager { mSelectedTotalCount += increment; mCachedShareableUris = null; - if ((itemSupportedOperations & MediaItem.SUPPORT_DELETE) > 0) { + if ((itemSupportedOperations & MediaObject.SUPPORT_DELETE) > 0) { mSelectedDeletableCount += increment; } - if ((itemSupportedOperations & MediaItem.SUPPORT_SHARE) > 0) { + if ((itemSupportedOperations & MediaObject.SUPPORT_SHARE) > 0) { mSelectedShareableCount += increment; if (itemType == FileColumns.MEDIA_TYPE_IMAGE) { mSelectedShareableImageCount += increment; @@ -93,24 +97,42 @@ public class SelectionManager { mShareIntent.setAction(null).setType(null); } else if (mSelectedShareableCount >= 1) { mCachedShareableUris = mUriSource.getSelectedShareableUris(); - if (mSelectedShareableImageCount == mSelectedShareableCount) { - mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE); - } else if (mSelectedShareableVideoCount == mSelectedShareableCount) { - mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO); - } else { - mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL); - } - if (mSelectedShareableCount == 1) { - mShareIntent.setAction(Intent.ACTION_SEND); - mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0)); + if (mCachedShareableUris.size() == 0) { + mShareIntent.setAction(null).setType(null); } else { - mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); - mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris); + if (mSelectedShareableImageCount == mSelectedShareableCount) { + mShareIntent.setType(GalleryUtils.MIME_TYPE_IMAGE); + } else if (mSelectedShareableVideoCount == mSelectedShareableCount) { + mShareIntent.setType(GalleryUtils.MIME_TYPE_VIDEO); + } else { + mShareIntent.setType(GalleryUtils.MIME_TYPE_ALL); + } + if (mCachedShareableUris.size() == 1) { + mShareIntent.setAction(Intent.ACTION_SEND); + mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris.get(0)); + } else { + mShareIntent.setAction(Intent.ACTION_SEND_MULTIPLE); + mShareIntent.putExtra(Intent.EXTRA_STREAM, mCachedShareableUris); + } } } share.setShareIntent(mShareIntent); - // TODO update deletability, editability, etc. + // TODO update editability, etc. + } + + public int getSupportedOperations() { + if (mSelectedTotalCount == 0) { + return 0; + } + int supported = 0; + if (mSelectedDeletableCount == mSelectedTotalCount) { + supported |= MediaObject.SUPPORT_DELETE; + } + if (mSelectedShareableCount > 0) { + supported |= MediaObject.SUPPORT_SHARE; + } + return supported; } public void onClearSelection() { diff --git a/src/com/android/photos/adapters/AlbumSetCursorAdapter.java b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java new file mode 100644 index 000000000..c387f8f47 --- /dev/null +++ b/src/com/android/photos/adapters/AlbumSetCursorAdapter.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013 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.photos.adapters; + +import android.content.Context; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.text.format.DateFormat; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.CursorAdapter; +import android.widget.ImageView; +import android.widget.ProgressBar; +import android.widget.TextView; + +import com.android.gallery3d.R; +import com.android.photos.data.AlbumSetLoader; +import com.android.photos.shims.LoaderCompatShim; + +import java.util.Date; + +public class AlbumSetCursorAdapter extends CursorAdapter { + + private LoaderCompatShim<Cursor> mDrawableFactory; + + public void setDrawableFactory(LoaderCompatShim<Cursor> factory) { + mDrawableFactory = factory; + } + private Date mDate = new Date(); // Used for converting timestamps for display + + public AlbumSetCursorAdapter(Context context) { + super(context, null, false); + } + + @Override + public void bindView(View v, Context context, Cursor cursor) { + TextView titleTextView = (TextView) v.findViewById( + R.id.album_set_item_title); + titleTextView.setText(cursor.getString(AlbumSetLoader.INDEX_TITLE)); + + TextView dateTextView = (TextView) v.findViewById( + R.id.album_set_item_date); + long timestamp = cursor.getLong(AlbumSetLoader.INDEX_TIMESTAMP); + if (timestamp > 0) { + mDate.setTime(timestamp); + dateTextView.setText(DateFormat.getMediumDateFormat(context).format(mDate)); + } else { + dateTextView.setText(null); + } + + ProgressBar uploadProgressBar = (ProgressBar) v.findViewById( + R.id.album_set_item_upload_progress); + if (cursor.getInt(AlbumSetLoader.INDEX_COUNT_PENDING_UPLOAD) > 0) { + uploadProgressBar.setVisibility(View.VISIBLE); + uploadProgressBar.setProgress(50); + } else { + uploadProgressBar.setVisibility(View.INVISIBLE); + } + + ImageView thumbImageView = (ImageView) v.findViewById( + R.id.album_set_item_image); + Drawable recycle = thumbImageView.getDrawable(); + Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle); + if (recycle != drawable) { + thumbImageView.setImageDrawable(drawable); + } + } + + @Override + public View newView(Context context, Cursor cursor, ViewGroup parent) { + return LayoutInflater.from(context).inflate( + R.layout.album_set_item, parent, false); + } +} diff --git a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java index 3776ca5a2..1190b8c85 100644 --- a/src/com/android/photos/adapters/PhotoThumbnailAdapter.java +++ b/src/com/android/photos/adapters/PhotoThumbnailAdapter.java @@ -22,9 +22,7 @@ import android.graphics.drawable.Drawable; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.view.ViewGroup.LayoutParams; import android.widget.CursorAdapter; -import android.widget.GridView; import android.widget.ImageView; import com.android.gallery3d.R; @@ -48,7 +46,7 @@ public class PhotoThumbnailAdapter extends CursorAdapter implements GalleryThumb @Override public void bindView(View view, Context context, Cursor cursor) { - ImageView iv = (ImageView) view; + ImageView iv = (ImageView) view.findViewById(R.id.thumbnail); Drawable recycle = iv.getDrawable(); Drawable drawable = mDrawableFactory.drawableForItem(cursor, recycle); if (recycle != drawable) { diff --git a/src/com/android/photos/data/AlbumSetLoader.java b/src/com/android/photos/data/AlbumSetLoader.java index b2b5204e6..940473255 100644 --- a/src/com/android/photos/data/AlbumSetLoader.java +++ b/src/com/android/photos/data/AlbumSetLoader.java @@ -12,6 +12,7 @@ public class AlbumSetLoader { public static final int INDEX_THUMBNAIL_HEIGHT = 5; public static final int INDEX_COUNT_PENDING_UPLOAD = 6; public static final int INDEX_COUNT = 7; + public static final int INDEX_SUPPORTED_OPERATIONS = 8; public static final String[] PROJECTION = { "_id", @@ -21,7 +22,8 @@ public class AlbumSetLoader { "thumb_width", "thumb_height", "count_pending_upload", - "_count" + "_count", + "supported_operations" }; public static final MatrixCursor MOCK = createRandomCursor(30); @@ -44,7 +46,8 @@ public class AlbumSetLoader { 0, 0, (random < .3 ? 1 : 0), - 1 + 1, + 0 }; return row; } diff --git a/src/com/android/photos/data/PhotoSetLoader.java b/src/com/android/photos/data/PhotoSetLoader.java index 72c8e93cc..56c82c4a9 100644 --- a/src/com/android/photos/data/PhotoSetLoader.java +++ b/src/com/android/photos/data/PhotoSetLoader.java @@ -29,6 +29,8 @@ import android.provider.MediaStore.Files.FileColumns; import com.android.photos.drawables.DataUriThumbnailDrawable; import com.android.photos.shims.LoaderCompatShim; +import java.util.ArrayList; + public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cursor> { public static final String SUPPORTED_OPERATIONS = "supported_operations"; @@ -95,4 +97,19 @@ public class PhotoSetLoader extends CursorLoader implements LoaderCompatShim<Cur public Uri uriForItem(Cursor item) { return null; } + + @Override + public ArrayList<Uri> urisForSubItems(Cursor item) { + return null; + } + + @Override + public void deleteItemWithPath(Object path) { + + } + + @Override + public Object getPathForItem(Cursor item) { + return null; + } } diff --git a/src/com/android/photos/shims/LoaderCompatShim.java b/src/com/android/photos/shims/LoaderCompatShim.java index 9da4436aa..d5bf710de 100644 --- a/src/com/android/photos/shims/LoaderCompatShim.java +++ b/src/com/android/photos/shims/LoaderCompatShim.java @@ -19,8 +19,13 @@ package com.android.photos.shims; import android.graphics.drawable.Drawable; import android.net.Uri; +import java.util.ArrayList; + public interface LoaderCompatShim<T> { Drawable drawableForItem(T item, Drawable recycle); Uri uriForItem(T item); + ArrayList<Uri> urisForSubItems(T item); + void deleteItemWithPath(Object path); + Object getPathForItem(T item); } diff --git a/src/com/android/photos/shims/MediaItemsLoader.java b/src/com/android/photos/shims/MediaItemsLoader.java index fa41c8ec8..d75823404 100644 --- a/src/com/android/photos/shims/MediaItemsLoader.java +++ b/src/com/android/photos/shims/MediaItemsLoader.java @@ -28,12 +28,16 @@ import android.util.SparseArray; import com.android.gallery3d.data.ContentListener; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; import com.android.gallery3d.data.MediaSet.ItemConsumer; import com.android.gallery3d.data.MediaSet.SyncListener; +import com.android.gallery3d.data.Path; import com.android.gallery3d.util.Future; import com.android.photos.data.PhotoSetLoader; +import java.util.ArrayList; + /** * Returns all MediaItems in a MediaSet, wrapping them in a cursor to appear * like a PhotoSetLoader @@ -47,6 +51,7 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC }; private final MediaSet mMediaSet; + private final DataManager mDataManager; private Future<Integer> mSyncTask = null; private ContentListener mObserver = new ContentListener() { @Override @@ -58,14 +63,15 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC public MediaItemsLoader(Context context) { super(context); - DataManager dm = DataManager.from(context); - String path = dm.getTopSetPath(DataManager.INCLUDE_ALL); - mMediaSet = dm.getMediaSet(path); + mDataManager = DataManager.from(context); + String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL); + mMediaSet = mDataManager.getMediaSet(path); } public MediaItemsLoader(Context context, String parentPath) { super(context); - mMediaSet = DataManager.from(getContext()).getMediaSet(parentPath); + mDataManager = DataManager.from(getContext()); + mMediaSet = mDataManager.getMediaSet(parentPath); } @Override @@ -157,4 +163,27 @@ public class MediaItemsLoader extends AsyncTaskLoader<Cursor> implements LoaderC return mi == null ? null : mi.getContentUri(); } + @Override + public ArrayList<Uri> urisForSubItems(Cursor item) { + return null; + } + + @Override + public void deleteItemWithPath(Object path) { + MediaObject o = mDataManager.getMediaObject((Path) path); + if (o != null) { + o.delete(); + } + } + + @Override + public Object getPathForItem(Cursor item) { + int index = item.getInt(PhotoSetLoader.INDEX_ID); + MediaItem mi = mMediaItems.get(index); + if (mi != null) { + return mi.getPath(); + } + return null; + } + } diff --git a/src/com/android/photos/shims/MediaSetLoader.java b/src/com/android/photos/shims/MediaSetLoader.java index 96c7485bb..d200807f9 100644 --- a/src/com/android/photos/shims/MediaSetLoader.java +++ b/src/com/android/photos/shims/MediaSetLoader.java @@ -26,7 +26,9 @@ import android.net.Uri; import com.android.gallery3d.data.ContentListener; import com.android.gallery3d.data.DataManager; import com.android.gallery3d.data.MediaItem; +import com.android.gallery3d.data.MediaObject; import com.android.gallery3d.data.MediaSet; +import com.android.gallery3d.data.Path; import com.android.gallery3d.data.MediaSet.SyncListener; import com.android.gallery3d.util.Future; import com.android.photos.data.AlbumSetLoader; @@ -46,6 +48,7 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom }; private final MediaSet mMediaSet; + private final DataManager mDataManager; private Future<Integer> mSyncTask = null; private ContentListener mObserver = new ContentListener() { @Override @@ -58,14 +61,15 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom public MediaSetLoader(Context context) { super(context); - DataManager dm = DataManager.from(context); - String path = dm.getTopSetPath(DataManager.INCLUDE_ALL); - mMediaSet = dm.getMediaSet(path); + mDataManager = DataManager.from(context); + String path = mDataManager.getTopSetPath(DataManager.INCLUDE_ALL); + mMediaSet = mDataManager.getMediaSet(path); } public MediaSetLoader(Context context, String path) { super(context); - mMediaSet = DataManager.from(getContext()).getMediaSet(path); + mDataManager = DataManager.from(getContext()); + mMediaSet = mDataManager.getMediaSet(path); } @Override @@ -111,6 +115,7 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom row[AlbumSetLoader.INDEX_ID] = i; row[AlbumSetLoader.INDEX_TITLE] = m.getName(); row[AlbumSetLoader.INDEX_COUNT] = m.getMediaItemCount(); + row[AlbumSetLoader.INDEX_SUPPORTED_OPERATIONS] = m.getSupportedOperations(); MediaItem coverItem = m.getCoverMediaItem(); if (coverItem != null) { row[AlbumSetLoader.INDEX_TIMESTAMP] = coverItem.getDateInMs(); @@ -147,4 +152,39 @@ public class MediaSetLoader extends AsyncTaskLoader<Cursor> implements LoaderCom MediaSet ms = mMediaSet.getSubMediaSet(index); return ms == null ? null : ms.getContentUri(); } + + @Override + public ArrayList<Uri> urisForSubItems(Cursor item) { + int index = item.getInt(AlbumSetLoader.INDEX_ID); + MediaSet ms = mMediaSet.getSubMediaSet(index); + if (ms == null) return null; + final ArrayList<Uri> result = new ArrayList<Uri>(); + ms.enumerateMediaItems(new MediaSet.ItemConsumer() { + @Override + public void consume(int index, MediaItem item) { + if (item != null) { + result.add(item.getContentUri()); + } + } + }); + return result; + } + + @Override + public void deleteItemWithPath(Object path) { + MediaObject o = mDataManager.getMediaObject((Path) path); + if (o != null) { + o.delete(); + } + } + + @Override + public Object getPathForItem(Cursor item) { + int index = item.getInt(AlbumSetLoader.INDEX_ID); + MediaSet ms = mMediaSet.getSubMediaSet(index); + if (ms != null) { + return ms.getPath(); + } + return null; + } } |