private final Executor mExecutor;
public ThreadPool() {
+ this(CORE_POOL_SIZE, MAX_POOL_SIZE);
+ }
+
+ public ThreadPool(int initPoolSize, int maxPoolSize) {
mExecutor = new ThreadPoolExecutor(
- CORE_POOL_SIZE, MAX_POOL_SIZE, KEEP_ALIVE_TIME,
+ initPoolSize, maxPoolSize, KEEP_ALIVE_TIME,
TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(),
new PriorityThreadFactory("thread-pool",
android.os.Process.THREAD_PRIORITY_BACKGROUND));
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
+import com.android.gallery3d.data.MediaObject.SupportedOperationsListener;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpSource;
import com.android.gallery3d.data.Path;
private static final int MSG_ON_PICTURE_CENTER = 10;
private static final int MSG_REFRESH_IMAGE = 11;
private static final int MSG_UPDATE_DEFERRED = 12;
+ private static final int MSG_UPDATE_PROGRESS = 13;
private static final int HIDE_BARS_TIMEOUT = 3500;
private static final int UNFREEZE_GLROOT_TIMEOUT = 250;
new MyMenuVisibilityListener();
private UpdateProgressListener mProgressListener;
+ private SupportedOperationsListener mSupportedOperationsListener =
+ new SupportedOperationsListener() {
+ @Override
+ public void onChange(int operations) {
+ mHandler.sendEmptyMessage(MSG_REFRESH_IMAGE);
+ }
+ };
+
public static interface Model extends PhotoView.Model {
public void resume();
public void pause();
@Override
public void onStitchingResult(Uri uri) {
- sendUpdate(uri);
+ sendUpdate(uri, MSG_REFRESH_IMAGE);
}
@Override
public void onStitchingQueued(Uri uri) {
- sendUpdate(uri);
+ sendUpdate(uri, MSG_UPDATE_PROGRESS);
}
@Override
public void onStitchingProgress(Uri uri, final int progress) {
- sendUpdate(uri);
+ sendUpdate(uri, MSG_UPDATE_PROGRESS);
}
- private void sendUpdate(Uri uri) {
+ private void sendUpdate(Uri uri, int message) {
boolean isCurrentPhoto = mCurrentPhoto instanceof LocalImage
&& mCurrentPhoto.getContentUri().equals(uri);
if (isCurrentPhoto) {
- mHandler.sendEmptyMessage(MSG_REFRESH_IMAGE);
+ mHandler.sendEmptyMessage(message);
}
}
};
break;
}
case MSG_REFRESH_IMAGE: {
- MediaItem currentPhoto = mCurrentPhoto;
- mCurrentPhoto = null;
- updateCurrentPhoto(currentPhoto);
+ updateUIForCurrentPhoto();
+ break;
+ }
+ case MSG_UPDATE_PROGRESS: {
+ updateProgressBar();
break;
}
default: throw new AssertionError(message.what);
&& (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
updateShareURI(mCurrentPhoto.getPath());
}
+ updateProgressBar();
}
private void updateCurrentPhoto(MediaItem photo) {
if (mCurrentPhoto == photo) return;
+ if (mCurrentPhoto != null) {
+ mCurrentPhoto.setSupportedOperationsListener(null);
+ }
mCurrentPhoto = photo;
+ mCurrentPhoto.setSupportedOperationsListener(
+ mSupportedOperationsListener);
if (mPhotoView.getFilmMode()) {
requestDeferredUpdate();
} else {
updateUIForCurrentPhoto();
}
+ }
+
+ private void updateProgressBar() {
if (mProgressBar != null) {
mProgressBar.hideProgress();
StitchingProgressManager progressManager = mApplication.getStitchingProgressManager();
if (progressManager != null && mCurrentPhoto instanceof LocalImage) {
- Integer progress = progressManager.getProgress(photo.getContentUri());
+ Integer progress = progressManager.getProgress(mCurrentPhoto.getContentUri());
if (progress != null) {
mProgressBar.setProgress(progress);
}
return getMediaObject(path).getSupportedOperations();
}
+ // getAll will cause this call to wait if any of the operations
+ // are expensive to compute. Do not call in UI thread.
+ public int getSupportedOperations(Path path, boolean getAll) {
+ return getMediaObject(path).getSupportedOperations(getAll);
+ }
+
public void delete(Path path) {
getMediaObject(path).delete();
}
import com.android.gallery3d.app.StitchingProgressManager;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.LightCycleHelper;
+import com.android.gallery3d.util.LightCycleHelper.PanoramaMetadata;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
import com.android.gallery3d.util.UpdateHelper;
public int rotation;
- private boolean mUsePanoramaViewer;
- private boolean mUsePanoramaViewerInitialized;
-
- private boolean mIsPanorama360;
- private boolean mIsPanorama360Initialized;
+ private Object mLock = new Object();
+ private Future<PanoramaMetadata> mGetPanoMetadataTask;
+ private boolean mPanoramaMetadataInitialized;
+ private PanoramaMetadata mPanoramaMetadata;
+ private SupportedOperationsListener mListener;
public LocalImage(Path path, GalleryApp application, Cursor cursor) {
super(path, nextVersionNumber());
operation |= SUPPORT_SHOW_ON_MAP;
}
- if (usePanoramaViewer()) {
+ if (mPanoramaMetadata != null && mPanoramaMetadata.mUsePanoramaViewer) {
operation |= SUPPORT_PANORAMA;
- if (isPanorama360()) {
+ if (mPanoramaMetadata.mIsPanorama360) {
operation |= SUPPORT_PANORAMA360;
// disable destructive rotate and crop for 360 degree panorama
operation &= ~(SUPPORT_ROTATE | SUPPORT_CROP);
}
@Override
+ public int getSupportedOperations(boolean getAll) {
+ synchronized (mLock) {
+ if (getAll && !mPanoramaMetadataInitialized) {
+ if (mGetPanoMetadataTask == null) {
+ mGetPanoMetadataTask = getThreadPool().submit(
+ new PanoramaMetadataJob(mApplication.getAndroidContext(),
+ getContentUri()));
+ }
+ mPanoramaMetadata = mGetPanoMetadataTask.get();
+ mPanoramaMetadataInitialized = true;
+ }
+ }
+ return getSupportedOperations();
+ }
+
+ @Override
+ public void setSupportedOperationsListener(SupportedOperationsListener l) {
+ synchronized (mLock) {
+ if (mPanoramaMetadataInitialized) return; // no more updates
+
+ if (l == null) {
+ if (mGetPanoMetadataTask != null) {
+ mGetPanoMetadataTask.cancel();
+ mGetPanoMetadataTask = null;
+ }
+ } else {
+ if (mGetPanoMetadataTask == null) {
+ mGetPanoMetadataTask = getThreadPool().submit(
+ new PanoramaMetadataJob(mApplication.getAndroidContext(),
+ getContentUri()),
+ new FutureListener<PanoramaMetadata>() {
+ @Override
+ public void onFutureDone(Future<PanoramaMetadata> future) {
+ mGetPanoMetadataTask = null;
+ if (future.isCancelled()) return;
+ mPanoramaMetadata = future.get();
+ mPanoramaMetadataInitialized = true;
+ if (mListener != null) {
+ mListener.onChange(getSupportedOperations());
+ }
+ }
+ });
+ }
+ }
+ mListener = l;
+ }
+ }
+
+ @Override
public void delete() {
GalleryUtils.assertNotInRenderThread();
Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI;
public String getFilePath() {
return filePath;
}
-
- @Override
- public boolean usePanoramaViewer() {
- if (!mUsePanoramaViewerInitialized) {
- Context context = mApplication.getAndroidContext();
- mUsePanoramaViewer = LightCycleHelper.hasLightCycleView(context)
- && LightCycleHelper.isPanorama(mApplication.getContentResolver(),
- getContentUri());
- mUsePanoramaViewerInitialized = true;
- }
- return mUsePanoramaViewer;
- }
-
- @Override
- public boolean isPanorama360() {
- // cache flag for faster access
- if (!mIsPanorama360Initialized) {
- mIsPanorama360 = LightCycleHelper.isPanorama360(
- mApplication.getAndroidContext(), getContentUri());
- mIsPanorama360Initialized = true;
- }
- return mIsPanorama360;
- }
}
return "";
}
- public boolean usePanoramaViewer() {
- return false;
- }
-
- public boolean isPanorama360() {
- return false;
- }
-
// Returns width and height of the media item.
// Returns 0, 0 if the information is not available.
public abstract int getWidth();
import android.net.Uri;
+import com.android.gallery3d.util.ThreadPool;
+
public abstract class MediaObject {
@SuppressWarnings("unused")
private static final String TAG = "MediaObject";
public static final int SUPPORT_INFO = 1 << 10;
public static final int SUPPORT_IMPORT = 1 << 11;
public static final int SUPPORT_TRIM = 1 << 12;
- public static final int SUPPORT_PANORAMA = 1 << 13;
- public static final int SUPPORT_PANORAMA360 = 1 << 14;
- public static final int SUPPORT_UNLOCK = 1 << 15;
- public static final int SUPPORT_BACK = 1 << 16;
- public static final int SUPPORT_ACTION = 1 << 17;
- public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 18;
+ public static final int SUPPORT_UNLOCK = 1 << 13;
+ public static final int SUPPORT_BACK = 1 << 14;
+ public static final int SUPPORT_ACTION = 1 << 15;
+ public static final int SUPPORT_CAMERA_SHORTCUT = 1 << 16;
+ // The panorama specific bits are expensive to compute.
+ // Use SupportedOperationsListener to request them.
+ public static final int SUPPORT_PANORAMA = 1 << 30;
+ public static final int SUPPORT_PANORAMA360 = 1 << 31;
public static final int SUPPORT_ALL = 0xffffffff;
+ public static interface SupportedOperationsListener {
+ public void onChange(int operations);
+ }
+
// These are the bits returned from getMediaType():
public static final int MEDIA_TYPE_UNKNOWN = 1;
public static final int MEDIA_TYPE_IMAGE = 2;
protected final Path mPath;
+ private static ThreadPool sThreadPool = new ThreadPool(1, 1);
+ public static ThreadPool getThreadPool() {
+ return sThreadPool;
+ }
+
public MediaObject(Path path, long version) {
path.setObject(this);
mPath = path;
return 0;
}
+ public int getSupportedOperations(boolean getAll) {
+ return 0;
+ }
+
+ public void setSupportedOperationsListener(SupportedOperationsListener l) {
+ // nothing to do
+ }
+
public void delete() {
throw new UnsupportedOperationException();
}
--- /dev/null
+/*
+ * Copyright (C) 2012 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.gallery3d.data;
+
+import android.content.Context;
+import android.net.Uri;
+
+import com.android.gallery3d.util.LightCycleHelper;
+import com.android.gallery3d.util.LightCycleHelper.PanoramaMetadata;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+class PanoramaMetadataJob implements Job<PanoramaMetadata> {
+ Context mContext;
+ Uri mUri;
+
+ public PanoramaMetadataJob(Context context, Uri uri) {
+ mContext = context;
+ mUri = uri;
+ }
+
+ @Override
+ public PanoramaMetadata run(JobContext jc) {
+ return LightCycleHelper.getPanoramaMetadata(mContext, mUri);
+ }
+}
import com.android.gallery3d.app.GalleryApp;
import com.android.gallery3d.common.BitmapUtils;
import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.Future;
+import com.android.gallery3d.util.FutureListener;
import com.android.gallery3d.util.LightCycleHelper;
+import com.android.gallery3d.util.LightCycleHelper.PanoramaMetadata;
import com.android.gallery3d.util.ThreadPool.CancelListener;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;
private int mWidth;
private int mHeight;
private int mRotation;
- private boolean mUsePanoramaViewer;
- private boolean mUsePanoramaViewerInitialized;
- private boolean mIsPanorama360;
- private boolean mIsPanorama360Initialized;
+
+ private Object mLock = new Object();
+ private Future<PanoramaMetadata> mGetPanoMetadataTask;
+ private boolean mPanoramaMetadataInitialized;
+ private PanoramaMetadata mPanoramaMetadata;
+ private SupportedOperationsListener mListener;
private GalleryApp mApplication;
if (BitmapUtils.isSupportedByRegionDecoder(mContentType)) {
supported |= SUPPORT_FULL_IMAGE;
}
- if (usePanoramaViewer()) {
+ if (mPanoramaMetadata != null && mPanoramaMetadata.mUsePanoramaViewer) {
supported |= SUPPORT_PANORAMA;
- if (isPanorama360()) {
+ if (mPanoramaMetadata.mIsPanorama360) {
supported |= SUPPORT_PANORAMA360;
// disable destructive crop for 360 degree panorama
supported &= ~SUPPORT_CROP;
return supported;
}
+ @Override
+ public int getSupportedOperations(boolean getAll) {
+ synchronized (mLock) {
+ if (getAll && !mPanoramaMetadataInitialized) {
+ if (mGetPanoMetadataTask == null) {
+ mGetPanoMetadataTask = getThreadPool().submit(
+ new PanoramaMetadataJob(mApplication.getAndroidContext(),
+ getContentUri()));
+ }
+ mPanoramaMetadata = mGetPanoMetadataTask.get();
+ mPanoramaMetadataInitialized = true;
+ }
+ }
+ return getSupportedOperations();
+ }
+
+ @Override
+ public void setSupportedOperationsListener(SupportedOperationsListener l) {
+ synchronized (mLock) {
+ if (mPanoramaMetadataInitialized) return; // no more updates
+
+ if (l != null) {
+ if (mGetPanoMetadataTask != null) {
+ mGetPanoMetadataTask.cancel();
+ mGetPanoMetadataTask = null;
+ }
+ } else {
+ if (mGetPanoMetadataTask == null) {
+ mGetPanoMetadataTask = getThreadPool().submit(
+ new PanoramaMetadataJob(mApplication.getAndroidContext(),
+ getContentUri()),
+ new FutureListener<PanoramaMetadata>() {
+ @Override
+ public void onFutureDone(Future<PanoramaMetadata> future) {
+ mGetPanoMetadataTask = null;
+ if (future.isCancelled()) return;
+ mPanoramaMetadata = future.get();
+ mPanoramaMetadataInitialized = true;
+ if (mListener != null) {
+ mListener.onChange(getSupportedOperations());
+ }
+ }
+ });
+ }
+ }
+ mListener = l;
+ }
+ }
+
private boolean isSharable() {
// We cannot grant read permission to the receiver since we put
// the data URI in EXTRA_STREAM instead of the data part of an intent
public int getRotation() {
return mRotation;
}
-
- @Override
- public boolean usePanoramaViewer() {
- if (!mUsePanoramaViewerInitialized) {
- Context context = mApplication.getAndroidContext();
- mUsePanoramaViewer = LightCycleHelper.hasLightCycleView(context)
- && LightCycleHelper.isPanorama(mApplication.getContentResolver(),
- getContentUri());
- mUsePanoramaViewerInitialized = true;
- }
- return mUsePanoramaViewer;
- }
-
- @Override
- public boolean isPanorama360() {
- // cache flag for faster access
- if (!mIsPanorama360Initialized) {
- mIsPanorama360 = LightCycleHelper.isPanorama360(
- mApplication.getAndroidContext(), getContentUri());
- mIsPanorama360Initialized = true;
- }
- return mIsPanorama360;
- }
}
int type = 0;
for (Path path : unexpandedPaths) {
if (jc.isCancelled()) return 0;
- int support = manager.getSupportedOperations(path);
+ int support = manager.getSupportedOperations(path, true);
type |= manager.getMediaType(path);
operation &= support;
}
final Intent intent = new Intent();
for (Path path : expandedPaths) {
if (jc.isCancelled()) return null;
- int support = manager.getSupportedOperations(path);
+ int support = manager.getSupportedOperations(path, true);
type |= manager.getMediaType(path);
if ((support & MediaObject.SUPPORT_SHARE) != 0) {
import com.android.gallery3d.app.StitchingProgressManager;
public class LightCycleHelper {
+ public static class PanoramaMetadata {
+ // Whether a panorama viewer should be used
+ public final boolean mUsePanoramaViewer;
+ // Whether a panorama is 360 degrees
+ public final boolean mIsPanorama360;
- public static void setupCaptureIntent(Context context, Intent it, String outputDir) {
- /* Do nothing */
+ public PanoramaMetadata(boolean usePanoramaViewer, boolean isPanorama360) {
+ mUsePanoramaViewer = usePanoramaViewer;
+ mIsPanorama360 = isPanorama360;
+ }
}
- public static boolean hasLightCycleView(Context context) {
- return false;
+ public static void setupCaptureIntent(Context context, Intent it, String outputDir) {
+ /* Do nothing */
}
public static boolean hasLightCycleCapture(Context context) {
/* Do nothing */
}
- public static boolean isPanorama(ContentResolver contentResolver, Uri uri) {
- return false;
- }
-
- public static boolean isPanorama360(Context context, Uri uri) {
- return false;
+ public static PanoramaMetadata getPanoramaMetadata(Context context, Uri uri) {
+ return null;
}
public static CameraModule createPanoramaModule() {