summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/com/android/gallery3d/app/AbstractGalleryActivity.java8
-rw-r--r--src/com/android/gallery3d/app/AlbumPage.java8
-rw-r--r--src/com/android/gallery3d/app/AppBridge.java62
-rw-r--r--src/com/android/gallery3d/app/CameraScreenNail.java225
-rw-r--r--src/com/android/gallery3d/app/GalleryActivity.java1
-rw-r--r--src/com/android/gallery3d/app/OrientationManager.java193
-rw-r--r--src/com/android/gallery3d/app/PhotoDataAdapter.java26
-rw-r--r--src/com/android/gallery3d/app/PhotoPage.java156
-rw-r--r--src/com/android/gallery3d/app/SinglePhotoDataAdapter.java16
-rw-r--r--src/com/android/gallery3d/ui/GLRoot.java1
-rw-r--r--src/com/android/gallery3d/ui/GLRootView.java86
-rw-r--r--src/com/android/gallery3d/ui/GLView.java10
-rw-r--r--src/com/android/gallery3d/ui/PhotoView.java267
-rw-r--r--src/com/android/gallery3d/ui/PositionController.java285
-rw-r--r--src/com/android/gallery3d/ui/ScreenNailHolder.java31
-rw-r--r--src/com/android/gallery3d/ui/TileImageView.java4
-rw-r--r--tests/src/com/android/gallery3d/ui/GLRootMock.java1
-rw-r--r--tests/src/com/android/gallery3d/ui/GLRootStub.java1
18 files changed, 878 insertions, 503 deletions
diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
index 09e72c09b..2c63e3409 100644
--- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java
+++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java
@@ -44,6 +44,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
private GLRootView mGLRootView;
private StateManager mStateManager;
private GalleryActionBar mActionBar;
+ private OrientationManager mOrientationManager;
private boolean mDisableToggleStatusBar;
private AlertDialog mAlertDialog = null;
@@ -58,6 +59,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ mOrientationManager = new OrientationManager(this);
toggleStatusBarByOrientation();
}
@@ -103,6 +105,10 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
return mGLRootView;
}
+ public OrientationManager getOrientationManager() {
+ return mOrientationManager;
+ }
+
@Override
public void setContentView(int resId) {
super.setContentView(resId);
@@ -165,11 +171,13 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
mGLRootView.unlockRenderThread();
}
mGLRootView.onResume();
+ mOrientationManager.resume();
}
@Override
protected void onPause() {
super.onPause();
+ mOrientationManager.pause();
mGLRootView.onPause();
mGLRootView.lockRenderThread();
try {
diff --git a/src/com/android/gallery3d/app/AlbumPage.java b/src/com/android/gallery3d/app/AlbumPage.java
index c3b04d617..9dd05ac89 100644
--- a/src/com/android/gallery3d/app/AlbumPage.java
+++ b/src/com/android/gallery3d/app/AlbumPage.java
@@ -51,7 +51,6 @@ import com.android.gallery3d.ui.GLCanvas;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLView;
import com.android.gallery3d.ui.RelativePosition;
-import com.android.gallery3d.ui.ScreenNailHolder;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SlotView;
import com.android.gallery3d.ui.SynchronizedHandler;
@@ -79,7 +78,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster
private static final int BIT_LOADING_SYNC = 2;
private static final float USER_DISTANCE_METER = 0.3f;
- private static final boolean TEST_CAMERA_PREVIEW = false;
private boolean mIsActive = false;
private AlbumSlotRenderer mAlbumView;
@@ -210,10 +208,6 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster
mMediaSetPath.toString());
data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH,
item.getPath().toString());
- if (TEST_CAMERA_PREVIEW) {
- ScreenNailHolder holder = new CameraScreenNailHolder(mActivity);
- data.putParcelable(PhotoPage.KEY_SCREENNAIL_HOLDER, holder);
- }
mActivity.getStateManager().startStateForResult(
PhotoPage.class, REQUEST_PHOTO, data);
}
@@ -527,7 +521,7 @@ public class AlbumPage extends ActivityState implements GalleryActionBar.Cluster
}
case REQUEST_PHOTO: {
if (data == null) return;
- mFocusIndex = data.getIntExtra(PhotoPage.KEY_INDEX_HINT, 0);
+ mFocusIndex = data.getIntExtra(PhotoPage.KEY_RETURN_INDEX_HINT, 0);
mSlotView.setCenterIndex(mFocusIndex);
mSlotView.startRestoringAnimation(mFocusIndex);
break;
diff --git a/src/com/android/gallery3d/app/AppBridge.java b/src/com/android/gallery3d/app/AppBridge.java
new file mode 100644
index 000000000..d5376581e
--- /dev/null
+++ b/src/com/android/gallery3d/app/AppBridge.java
@@ -0,0 +1,62 @@
+/*
+ * 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.app;
+
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.gallery3d.ui.ScreenNail;
+
+// This is the bridge to connect a PhotoPage to the external environment.
+public abstract class AppBridge implements Parcelable {
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // These are requests sent from PhotoPage to the app
+ //////////////////////////////////////////////////////////////////////////
+
+ public abstract ScreenNail attachScreenNail();
+ public abstract void detachScreenNail();
+
+ // Return true if the tap is consumed.
+ public abstract boolean onSingleTapUp(int x, int y);
+
+ // This is used to notify that the screen nail will be drawn in full screen
+ // or not in next draw() call.
+ public abstract void onFullScreenChanged(boolean full);
+
+ //////////////////////////////////////////////////////////////////////////
+ // These are requests send from app to PhotoPage
+ //////////////////////////////////////////////////////////////////////////
+
+ public interface Server {
+ // Set the camera frame relative to GLRootView.
+ public void setCameraNaturalFrame(Rect frame);
+ // Switch to the previous or next picture using the capture animation.
+ // The offset is -1 to switch to the previous picture, 1 to switch to
+ // the next picture.
+ public boolean switchWithCaptureAnimation(int offset);
+ }
+
+ // If server is null, the services are not available.
+ public abstract void setServer(Server server);
+}
diff --git a/src/com/android/gallery3d/app/CameraScreenNail.java b/src/com/android/gallery3d/app/CameraScreenNail.java
deleted file mode 100644
index 24857a48f..000000000
--- a/src/com/android/gallery3d/app/CameraScreenNail.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * 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.app;
-
-import android.app.Activity;
-import android.graphics.SurfaceTexture;
-import android.hardware.Camera;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-import android.util.Log;
-import android.view.Surface;
-
-import com.android.gallery3d.ui.GLCanvas;
-import com.android.gallery3d.ui.ScreenNail;
-import com.android.gallery3d.ui.ScreenNailHolder;
-import com.android.gallery3d.ui.SurfaceTextureScreenNail;
-
-// This is a ScreenNail which displays camera preview. This demos the usage of
-// SurfaceTextureScreenNail. It is not intended for production use.
-class CameraScreenNail extends SurfaceTextureScreenNail {
- private static final String TAG = "CameraScreenNail";
- private static final int CAMERA_ID = 0;
- private static final int PREVIEW_WIDTH = 960;
- private static final int PREVIEW_HEIGHT = 720;
- private static final int MSG_START_CAMERA = 1;
- private static final int MSG_STOP_CAMERA = 2;
-
- public interface Listener {
- void requestRender();
- }
-
- private Activity mActivity;
- private Listener mListener;
- private int mOrientation;
- private Camera mCamera;
-
- private HandlerThread mHandlerThread;
- private Handler mHandler;
- private volatile boolean mVisible;
- private volatile boolean mHasFrame;
-
- public CameraScreenNail(Activity activity, Listener listener) {
- mActivity = activity;
- mListener = listener;
-
- mOrientation = getCameraDisplayOrientation(mActivity, CAMERA_ID);
- if (mOrientation % 180 == 0) {
- setSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);
- } else {
- setSize(PREVIEW_HEIGHT, PREVIEW_WIDTH);
- }
-
- mHandlerThread = new HandlerThread("Camera");
- mHandlerThread.start();
- mHandler = new Handler(mHandlerThread.getLooper()) {
- public void handleMessage(Message message) {
- if (message.what == MSG_START_CAMERA && mCamera == null) {
- startCamera();
- } else if (message.what == MSG_STOP_CAMERA && mCamera != null) {
- stopCamera();
- }
- }
- };
- mHandler.sendEmptyMessage(MSG_START_CAMERA);
- }
-
- private void startCamera() {
- try {
- acquireSurfaceTexture();
- Camera camera = Camera.open(CAMERA_ID);
- Camera.Parameters param = camera.getParameters();
- param.setPreviewSize(PREVIEW_WIDTH, PREVIEW_HEIGHT);
- camera.setParameters(param);
- camera.setDisplayOrientation(mOrientation);
- camera.setPreviewTexture(getSurfaceTexture());
- camera.startPreview();
- synchronized (this) {
- mCamera = camera;
- }
- } catch (Throwable th) {
- Log.e(TAG, "cannot open camera", th);
- }
- }
-
- private void stopCamera() {
- releaseSurfaceTexture();
- mCamera.stopPreview();
- mCamera.release();
- synchronized (this) {
- mCamera = null;
- notifyAll();
- }
- mHasFrame = false;
- }
-
- @Override
- public void draw(GLCanvas canvas, int x, int y, int width, int height) {
- if (!mVisible) {
- mVisible = true;
- // Only send one message when mVisible makes transition from
- // false to true.
- mHandler.sendEmptyMessage(MSG_START_CAMERA);
- }
-
- if (mVisible && mHasFrame) {
- super.draw(canvas, x, y, width, height);
- }
- }
-
- @Override
- public void noDraw() {
- mVisible = false;
- }
-
- @Override
- public void recycle() {
- mVisible = false;
- }
-
- @Override
- public void onFrameAvailable(SurfaceTexture surfaceTexture) {
- mHasFrame = true;
- if (mVisible) {
- // We need to ask for re-render if the SurfaceTexture receives a new
- // frame (and we are visible).
- mListener.requestRender();
- }
- }
-
- public void destroy() {
- synchronized (this) {
- mHandler.sendEmptyMessage(MSG_STOP_CAMERA);
-
- // Wait until camera is closed.
- while (mCamera != null) {
- try {
- wait();
- } catch (Exception ex) {
- // ignore.
- }
- }
- }
- mHandlerThread.quit();
- }
-
- // The three methods below are copied from Camera.java
- private static int getCameraDisplayOrientation(
- Activity activity, int cameraId) {
- int displayRotation = getDisplayRotation(activity);
- int displayOrientation = getDisplayOrientation(
- displayRotation, cameraId);
- return displayOrientation;
- }
-
- private static int getDisplayRotation(Activity activity) {
- int rotation = activity.getWindowManager().getDefaultDisplay()
- .getRotation();
- switch (rotation) {
- case Surface.ROTATION_0: return 0;
- case Surface.ROTATION_90: return 90;
- case Surface.ROTATION_180: return 180;
- case Surface.ROTATION_270: return 270;
- }
- return 0;
- }
-
- private static int getDisplayOrientation(int degrees, int cameraId) {
- // See android.hardware.Camera.setDisplayOrientation for
- // documentation.
- Camera.CameraInfo info = new Camera.CameraInfo();
- Camera.getCameraInfo(cameraId, info);
- int result;
- if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
- result = (info.orientation + degrees) % 360;
- result = (360 - result) % 360; // compensate the mirror
- } else { // back-facing
- result = (info.orientation - degrees + 360) % 360;
- }
- return result;
- }
-}
-
-// This holds a CameraScreenNail, so we can pass it to a PhotoPage.
-class CameraScreenNailHolder extends ScreenNailHolder
- implements CameraScreenNail.Listener {
- private static final String TAG = "CameraScreenNailHolder";
- private GalleryActivity mActivity;
- private CameraScreenNail mCameraScreenNail;
-
- public CameraScreenNailHolder(GalleryActivity activity) {
- mActivity = activity;
- }
-
- @Override
- public void requestRender() {
- mActivity.getGLRoot().requestRender();
- }
-
- @Override
- public ScreenNail attach() {
- mCameraScreenNail = new CameraScreenNail((Activity) mActivity, this);
- return mCameraScreenNail;
- }
-
- @Override
- public void detach() {
- mCameraScreenNail.destroy();
- mCameraScreenNail = null;
- }
-}
diff --git a/src/com/android/gallery3d/app/GalleryActivity.java b/src/com/android/gallery3d/app/GalleryActivity.java
index f41811b02..0c6375fd7 100644
--- a/src/com/android/gallery3d/app/GalleryActivity.java
+++ b/src/com/android/gallery3d/app/GalleryActivity.java
@@ -22,4 +22,5 @@ public interface GalleryActivity extends GalleryContext {
public StateManager getStateManager();
public GLRoot getGLRoot();
public GalleryActionBar getGalleryActionBar();
+ public OrientationManager getOrientationManager();
}
diff --git a/src/com/android/gallery3d/app/OrientationManager.java b/src/com/android/gallery3d/app/OrientationManager.java
new file mode 100644
index 000000000..93a1fc506
--- /dev/null
+++ b/src/com/android/gallery3d/app/OrientationManager.java
@@ -0,0 +1,193 @@
+/*
+ * 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.app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.ViewConfiguration;
+
+import java.util.ArrayList;
+
+public class OrientationManager {
+ private static final String TAG = "OrientationManager";
+
+ public interface Listener {
+ public void onOrientationCompensationChanged(int degrees);
+ }
+
+ // Orientation hysteresis amount used in rounding, in degrees
+ private static final int ORIENTATION_HYSTERESIS = 5;
+
+ private Activity mActivity;
+ private ArrayList<Listener> mListeners;
+ private MyOrientationEventListener mOrientationListener;
+ // The degrees of the device rotated clockwise from its natural orientation.
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ // If the framework orientation is locked.
+ private boolean mOrientationLocked = false;
+ // The orientation compensation: if the framwork orientation is locked, the
+ // device orientation and the framework orientation may be different, so we
+ // need to rotate the UI. For example, if this value is 90, the UI
+ // components should be rotated 90 degrees counter-clockwise.
+ private int mOrientationCompensation = 0;
+
+ public OrientationManager(Activity activity) {
+ mActivity = activity;
+ mListeners = new ArrayList<Listener>();
+ mOrientationListener = new MyOrientationEventListener(activity);
+ }
+
+ public void resume() {
+ mOrientationListener.enable();
+ }
+
+ public void pause() {
+ mOrientationListener.disable();
+ }
+
+ public void addListener(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.add(listener);
+ }
+ }
+
+ public void removeListener(Listener listener) {
+ synchronized (mListeners) {
+ mListeners.remove(listener);
+ }
+ }
+
+ ////////////////////////////////////////////////////////////////////////////
+ // Orientation handling
+ //
+ // We can choose to lock the framework orientation or not. If we lock the
+ // framework orientation, we calculate a a compensation value according to
+ // current device orientation and send it to listeners. If we don't lock
+ // the framework orientation, we always set the compensation value to 0.
+ ////////////////////////////////////////////////////////////////////////////
+
+ // Lock the framework orientation to the current device orientation
+ public void lockOrientation() {
+ if (mOrientationLocked) return;
+ mOrientationLocked = true;
+ if (mActivity.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE) {
+ Log.d(TAG, "lock orientation to landscape");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
+ } else {
+ Log.d(TAG, "lock orientation to portrait");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ }
+ updateCompensation();
+ }
+
+ // Unlock the framework orientation, so it can change when the device
+ // rotates.
+ public void unlockOrientation() {
+ if (!mOrientationLocked) return;
+ mOrientationLocked = false;
+ Log.d(TAG, "unlock orientation");
+ mActivity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ disableCompensation();
+ }
+
+ // Calculate the compensation value and send it to listeners.
+ private void updateCompensation() {
+ if (mOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+ return;
+ }
+
+ int orientationCompensation =
+ (mOrientation + getDisplayRotation(mActivity)) % 360;
+
+ if (mOrientationCompensation != orientationCompensation) {
+ mOrientationCompensation = orientationCompensation;
+ notifyListeners();
+ }
+ }
+
+ // Make the compensation value 0 and send it to listeners.
+ private void disableCompensation() {
+ if (mOrientationCompensation != 0) {
+ mOrientationCompensation = 0;
+ notifyListeners();
+ }
+ }
+
+ private void notifyListeners() {
+ synchronized (mListeners) {
+ for (int i = 0, n = mListeners.size(); i < n; i++) {
+ mListeners.get(i).onOrientationCompensationChanged(
+ mOrientationCompensation);
+ }
+ }
+ }
+
+ // This listens to the device orientation, so we can update the compensation.
+ private class MyOrientationEventListener extends OrientationEventListener {
+ public MyOrientationEventListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == ORIENTATION_UNKNOWN) return;
+ mOrientation = roundOrientation(orientation, mOrientation);
+ // If the framework orientation is locked, we update the
+ // compensation value and notify the listeners.
+ if (mOrientationLocked) updateCompensation();
+ }
+ }
+
+ public int getDisplayRotation() {
+ return getDisplayRotation(mActivity);
+ }
+
+ private static int roundOrientation(int orientation, int orientationHistory) {
+ boolean changeOrientation = false;
+ if (orientationHistory == OrientationEventListener.ORIENTATION_UNKNOWN) {
+ changeOrientation = true;
+ } else {
+ int dist = Math.abs(orientation - orientationHistory);
+ dist = Math.min(dist, 360 - dist);
+ changeOrientation = (dist >= 45 + ORIENTATION_HYSTERESIS);
+ }
+ if (changeOrientation) {
+ return ((orientation + 45) / 90 * 90) % 360;
+ }
+ return orientationHistory;
+ }
+
+ private static int getDisplayRotation(Activity activity) {
+ int rotation = activity.getWindowManager().getDefaultDisplay()
+ .getRotation();
+ switch (rotation) {
+ case Surface.ROTATION_0: return 0;
+ case Surface.ROTATION_90: return 90;
+ case Surface.ROTATION_180: return 180;
+ case Surface.ROTATION_270: return 270;
+ }
+ return 0;
+ }
+}
diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java
index d0fde246f..86ef04d17 100644
--- a/src/com/android/gallery3d/app/PhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java
@@ -143,6 +143,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
private long mSourceVersion = MediaObject.INVALID_DATA_VERSION;
private int mSize = 0;
private Path mItemPath;
+ private int mCameraIndex;
private boolean mIsActive;
private boolean mNeedFullImage;
@@ -158,12 +159,13 @@ public class PhotoDataAdapter implements PhotoPage.Model {
// If mItemPath is not null, mCurrentIndex is only a hint for where we
// can find the item. If mItemPath is null, then we use the mCurrentIndex to
// find the image being viewed.
- public PhotoDataAdapter(GalleryActivity activity,
- PhotoView view, MediaSet mediaSet, Path itemPath, int indexHint) {
+ public PhotoDataAdapter(GalleryActivity activity, PhotoView view,
+ MediaSet mediaSet, Path itemPath, int indexHint, int cameraIndex) {
mSource = Utils.checkNotNull(mediaSet);
mPhotoView = Utils.checkNotNull(view);
mItemPath = Utils.checkNotNull(itemPath);
mCurrentIndex = indexHint;
+ mCameraIndex = cameraIndex;
mThreadPool = activity.getThreadPool();
mNeedFullImage = true;
@@ -312,6 +314,7 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
private void updateCurrentIndex(int index) {
+ if (mCurrentIndex == index) return;
mCurrentIndex = index;
updateSlidingWindow();
@@ -330,18 +333,8 @@ public class PhotoDataAdapter implements PhotoPage.Model {
}
@Override
- public void next() {
- updateCurrentIndex(mCurrentIndex + 1);
- }
-
- @Override
- public void previous() {
- updateCurrentIndex(mCurrentIndex - 1);
- }
-
- @Override
- public void moveToFirst() {
- updateCurrentIndex(0);
+ public void moveTo(int index) {
+ updateCurrentIndex(index);
}
@Override
@@ -373,6 +366,11 @@ public class PhotoDataAdapter implements PhotoPage.Model {
mMainHandler.sendEmptyMessage(MSG_UPDATE_IMAGE_REQUESTS);
}
+ @Override
+ public boolean isCamera(int offset) {
+ return mCurrentIndex + offset == mCameraIndex;
+ }
+
public ScreenNail getScreenNail() {
return mTileProvider.getScreenNail();
}
diff --git a/src/com/android/gallery3d/app/PhotoPage.java b/src/com/android/gallery3d/app/PhotoPage.java
index 116972902..c9977b9e9 100644
--- a/src/com/android/gallery3d/app/PhotoPage.java
+++ b/src/com/android/gallery3d/app/PhotoPage.java
@@ -43,6 +43,7 @@ import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.MtpDevice;
import com.android.gallery3d.data.Path;
+import com.android.gallery3d.data.SnailItem;
import com.android.gallery3d.data.SnailSource;
import com.android.gallery3d.picasasource.PicasaSource;
import com.android.gallery3d.ui.DetailsHelper;
@@ -54,17 +55,20 @@ import com.android.gallery3d.ui.ImportCompleteListener;
import com.android.gallery3d.ui.MenuExecutor;
import com.android.gallery3d.ui.PhotoView;
import com.android.gallery3d.ui.ScreenNail;
-import com.android.gallery3d.ui.ScreenNailHolder;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.ui.UserInteractionListener;
import com.android.gallery3d.util.GalleryUtils;
-public class PhotoPage extends ActivityState
- implements PhotoView.PhotoTapListener, UserInteractionListener {
+public class PhotoPage extends ActivityState implements
+ PhotoView.Listener, UserInteractionListener,
+ OrientationManager.Listener, AppBridge.Server {
private static final String TAG = "PhotoPage";
private static final int MSG_HIDE_BARS = 1;
+ private static final int MSG_LOCK_ORIENTATION = 2;
+ private static final int MSG_UNLOCK_ORIENTATION = 3;
+ private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
private static final int HIDE_BARS_TIMEOUT = 3500;
@@ -76,7 +80,9 @@ public class PhotoPage extends ActivityState
public static final String KEY_MEDIA_ITEM_PATH = "media-item-path";
public static final String KEY_INDEX_HINT = "index-hint";
public static final String KEY_OPEN_ANIMATION_RECT = "open-animation-rect";
- public static final String KEY_SCREENNAIL_HOLDER = "screennail-holder";
+ public static final String KEY_APP_BRIDGE = "app-bridge";
+
+ public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
private GalleryApp mApplication;
private SelectionManager mSelectionManager;
@@ -86,20 +92,17 @@ public class PhotoPage extends ActivityState
private DetailsHelper mDetailsHelper;
private boolean mShowDetails;
private Path mPendingSharePath;
- private Path mScreenNailItemPath;
// mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
// E.g., viewing a photo in gmail attachment
private MediaSet mMediaSet;
private Menu mMenu;
- private final Intent mResultIntent = new Intent();
private int mCurrentIndex = 0;
private Handler mHandler;
private boolean mShowBars = true;
private GalleryActionBar mActionBar;
private MyMenuVisibilityListener mMenuVisibilityListener;
- private PageTapListener mPageTapListener;
private boolean mIsMenuVisible;
private boolean mIsInteracting;
private MediaItem mCurrentPhoto = null;
@@ -107,8 +110,10 @@ public class PhotoPage extends ActivityState
private boolean mIsActive;
private ShareActionProvider mShareActionProvider;
private String mSetPathString;
- private ScreenNailHolder mScreenNailHolder;
+ private AppBridge mAppBridge;
private ScreenNail mScreenNail;
+ private MediaItem mScreenNailItem;
+ private OrientationManager mOrientationManager;
private NfcAdapter mNfcAdapter;
@@ -117,7 +122,6 @@ public class PhotoPage extends ActivityState
public void pause();
public boolean isEmpty();
public MediaItem getCurrentMediaItem();
- public int getCurrentIndex();
public void setCurrentPhoto(Path path, int indexHint);
}
@@ -128,15 +132,6 @@ public class PhotoPage extends ActivityState
}
}
- public interface PageTapListener {
- // Return true if the tap is consumed.
- public boolean onSingleTapUp(int x, int y);
- }
-
- public void setPageTapListener(PageTapListener listener) {
- mPageTapListener = listener;
- }
-
private final GLView mRootPane = new GLView() {
@Override
@@ -152,6 +147,14 @@ public class PhotoPage extends ActivityState
mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
}
}
+
+ @Override
+ protected void orient(int displayRotation, int compensation) {
+ displayRotation = mOrientationManager.getDisplayRotation();
+ Log.d(TAG, "orient -- display rotation " + displayRotation
+ + ", compensation = " + compensation);
+ super.orient(displayRotation, compensation);
+ }
};
@Override
@@ -161,31 +164,35 @@ public class PhotoPage extends ActivityState
mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);
mPhotoView = new PhotoView(mActivity);
- mPhotoView.setPhotoTapListener(this);
+ mPhotoView.setListener(this);
mRootPane.addComponent(mPhotoView);
mApplication = (GalleryApp)((Activity) mActivity).getApplication();
+ mOrientationManager = mActivity.getOrientationManager();
+ mOrientationManager.addListener(this);
mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
mNfcAdapter = NfcAdapter.getDefaultAdapter(mActivity.getAndroidContext());
Path itemPath = Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH));
if (mSetPathString != null) {
- mScreenNailHolder =
- (ScreenNailHolder) data.getParcelable(KEY_SCREENNAIL_HOLDER);
- if (mScreenNailHolder != null) {
- mScreenNail = mScreenNailHolder.attach();
+ mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
+ if (mAppBridge != null) {
+ mOrientationManager.lockOrientation();
- // Get the ScreenNail from ScreenNailHolder and register it.
+ // Get the ScreenNail from AppBridge and register it.
+ mScreenNail = mAppBridge.attachScreenNail();
int id = SnailSource.registerScreenNail(mScreenNail);
Path screenNailSetPath = SnailSource.getSetPath(id);
- mScreenNailItemPath = SnailSource.getItemPath(id);
+ Path screenNailItemPath = SnailSource.getItemPath(id);
+ mScreenNailItem = (MediaItem) mActivity.getDataManager()
+ .getMediaObject(screenNailItemPath);
// Combine the original MediaSet with the one for CameraScreenNail.
mSetPathString = "/combo/item/{" + screenNailSetPath +
"," + mSetPathString + "}";
// Start from the screen nail.
- itemPath = mScreenNailItemPath;
+ itemPath = screenNailItemPath;
// Action bar should not be displayed when camera starts.
mFlags |= FLAG_HIDE_ACTION_BAR;
@@ -193,33 +200,24 @@ public class PhotoPage extends ActivityState
mMediaSet = mActivity.getDataManager().getMediaSet(mSetPathString);
mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
- mMediaSet = (MediaSet)
- mActivity.getDataManager().getMediaObject(mSetPathString);
if (mMediaSet == null) {
Log.w(TAG, "failed to restore " + mSetPathString);
}
PhotoDataAdapter pda = new PhotoDataAdapter(
- mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex);
+ mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
+ mAppBridge == null ? -1 : 0);
mModel = pda;
mPhotoView.setModel(mModel);
- mResultIntent.putExtra(KEY_INDEX_HINT, mCurrentIndex);
- setStateResult(Activity.RESULT_OK, mResultIntent);
-
pda.setDataListener(new PhotoDataAdapter.DataListener() {
@Override
public void onPhotoChanged(int index, Path item) {
mCurrentIndex = index;
- mResultIntent.putExtra(KEY_INDEX_HINT, index);
if (item != null) {
- mResultIntent.putExtra(KEY_MEDIA_ITEM_PATH, item.toString());
MediaItem photo = mModel.getCurrentMediaItem();
if (photo != null) updateCurrentPhoto(photo);
- } else {
- mResultIntent.removeExtra(KEY_MEDIA_ITEM_PATH);
}
- setStateResult(Activity.RESULT_OK, mResultIntent);
}
@Override
@@ -255,6 +253,18 @@ public class PhotoPage extends ActivityState
hideBars();
break;
}
+ case MSG_LOCK_ORIENTATION: {
+ mOrientationManager.lockOrientation();
+ break;
+ }
+ case MSG_UNLOCK_ORIENTATION: {
+ mOrientationManager.unlockOrientation();
+ break;
+ }
+ case MSG_ON_FULL_SCREEN_CHANGED: {
+ mAppBridge.onFullScreenChanged(message.arg1 == 1);
+ break;
+ }
default: throw new AssertionError(message.what);
}
}
@@ -291,7 +301,7 @@ public class PhotoPage extends ActivityState
if (mCurrentPhoto == null) return;
updateMenuOperations();
// Hide the action bar when going back to camera preview.
- if (photo.getPath() == mScreenNailItemPath) hideBars();
+ if (photo == mScreenNailItem) hideBars();
updateTitle();
if (mShowDetails) {
mDetailsHelper.reloadDetails(mModel.getCurrentIndex());
@@ -401,18 +411,41 @@ public class PhotoPage extends ActivityState
}
@Override
+ public void onOrientationCompensationChanged(int degrees) {
+ mActivity.getGLRoot().setOrientationCompensation(degrees);
+ }
+
+ @Override
protected void onBackPressed() {
if (mShowDetails) {
hideDetails();
} else if (mScreenNail == null
|| !switchWithCaptureAnimation(-1)) {
+ // We are leaving this page. Set the result now.
+ setResult();
super.onBackPressed();
}
}
- // Switch to the previous or next picture using the capture animation.
- // The offset is -1 to switch to the previous picture, 1 to switch to
- // the next picture.
+ private void setResult() {
+ Intent result = null;
+ if (!mPhotoView.getFilmMode()) {
+ result = new Intent();
+ result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
+ }
+ setStateResult(Activity.RESULT_OK, result);
+ }
+
+ //////////////////////////////////////////////////////////////////////////
+ // AppBridge.Server interface
+ //////////////////////////////////////////////////////////////////////////
+
+ @Override
+ public void setCameraNaturalFrame(Rect frame) {
+ mPhotoView.setCameraNaturalFrame(frame);
+ }
+
+ @Override
public boolean switchWithCaptureAnimation(int offset) {
return mPhotoView.switchWithCaptureAnimation(offset);
}
@@ -531,9 +564,13 @@ public class PhotoPage extends ActivityState
mDetailsHelper.show();
}
+ ////////////////////////////////////////////////////////////////////////////
+ // Callbacks from PhotoView
+ ////////////////////////////////////////////////////////////////////////////
+ @Override
public void onSingleTapUp(int x, int y) {
- if (mPageTapListener != null) {
- if (mPageTapListener.onSingleTapUp(x, y)) return;
+ if (mAppBridge != null) {
+ if (mAppBridge.onSingleTapUp(x, y)) return;
}
MediaItem item = mModel.getCurrentMediaItem();
@@ -561,6 +598,24 @@ public class PhotoPage extends ActivityState
}
}
+ @Override
+ public void lockOrientation() {
+ mHandler.sendEmptyMessage(MSG_LOCK_ORIENTATION);
+ }
+
+ @Override
+ public void unlockOrientation() {
+ // Temporarily disabled until Camera UI can switch orientation.
+ // mHandler.sendEmptyMessage(MSG_UNLOCK_ORIENTATION);
+ }
+
+ @Override
+ public void onFullScreenChanged(boolean full) {
+ Message m = mHandler.obtainMessage(
+ MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
+ m.sendToTarget();
+ }
+
public static void playVideo(Activity activity, Uri uri, String title) {
try {
Intent intent = new Intent(Intent.ACTION_VIEW)
@@ -610,6 +665,7 @@ public class PhotoPage extends ActivityState
public void onPause() {
super.onPause();
mIsActive = false;
+ if (mAppBridge != null) mAppBridge.setServer(null);
DetailsHelper.pause();
mPhotoView.pause();
mModel.pause();
@@ -634,17 +690,22 @@ public class PhotoPage extends ActivityState
mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
onUserInteraction();
+ if (mAppBridge != null) {
+ mAppBridge.setServer(this);
+ mModel.moveTo(0); // move to the camera preview after resume
+ }
}
@Override
protected void onDestroy() {
- if (mScreenNailHolder != null) {
- // Unregister the ScreenNail and notify mScreenNailHolder.
+ if (mAppBridge != null) {
+ // Unregister the ScreenNail and notify mAppBridge.
SnailSource.unregisterScreenNail(mScreenNail);
- mScreenNailHolder.detach();
- mScreenNailHolder = null;
+ mAppBridge.detachScreenNail();
+ mAppBridge = null;
mScreenNail = null;
}
+ mOrientationManager.removeListener(this);
super.onDestroy();
}
@@ -672,5 +733,4 @@ public class PhotoPage extends ActivityState
return mIndex;
}
}
-
}
diff --git a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
index d0e8161d7..5e2565790 100644
--- a/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
+++ b/src/com/android/gallery3d/app/SinglePhotoDataAdapter.java
@@ -154,17 +154,7 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter
}
@Override
- public void next() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void previous() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void moveToFirst() {
+ public void moveTo(int index) {
throw new UnsupportedOperationException();
}
@@ -194,6 +184,10 @@ public class SinglePhotoDataAdapter extends TileImageViewAdapter
// currently not necessary.
}
+ @Override
+ public boolean isCamera(int offset) {
+ return false;
+ }
public MediaItem getCurrentMediaItem() {
return mItem;
diff --git a/src/com/android/gallery3d/ui/GLRoot.java b/src/com/android/gallery3d/ui/GLRoot.java
index fe040ba34..753f73c4b 100644
--- a/src/com/android/gallery3d/ui/GLRoot.java
+++ b/src/com/android/gallery3d/ui/GLRoot.java
@@ -36,4 +36,5 @@ public interface GLRoot {
public void unlockRenderThread();
public void setContentPane(GLView content);
+ public void setOrientationCompensation(int degrees);
}
diff --git a/src/com/android/gallery3d/ui/GLRootView.java b/src/com/android/gallery3d/ui/GLRootView.java
index f98f9a829..f063b84dd 100644
--- a/src/com/android/gallery3d/ui/GLRootView.java
+++ b/src/com/android/gallery3d/ui/GLRootView.java
@@ -17,6 +17,7 @@
package com.android.gallery3d.ui;
import android.content.Context;
+import android.graphics.Matrix;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.opengl.GLSurfaceView;
@@ -67,9 +68,17 @@ public class GLRootView extends GLSurfaceView
private GL11 mGL;
private GLCanvas mCanvas;
-
private GLView mContentView;
+ // mCompensation is the difference between the UI orientation on GLCanvas
+ // and the framework orientation. See OrientationManager for details.
+ private int mCompensation;
+ // mCompensationMatrix maps the coordinates of touch events. It is kept sync
+ // with mCompensation.
+ private Matrix mCompensationMatrix = new Matrix();
+ // The value which will become mCompensation in next layout.
+ private int mPendingCompensation;
+
private int mFlags = FLAG_NEED_LAYOUT;
private volatile boolean mRenderRequested = false;
@@ -175,11 +184,43 @@ public class GLRootView extends GLSurfaceView
private void layoutContentPane() {
mFlags &= ~FLAG_NEED_LAYOUT;
- int width = getWidth();
- int height = getHeight();
- Log.i(TAG, "layout content pane " + width + "x" + height);
- if (mContentView != null && width != 0 && height != 0) {
- mContentView.layout(0, 0, width, height);
+
+ int w = getWidth();
+ int h = getHeight();
+
+ // Before doing layout, if there is a compensation change pending, update
+ // mCompensation and mCompensationMatrix.
+ if (mCompensation != mPendingCompensation) {
+ mCompensation = mPendingCompensation;
+ if (mCompensation % 180 != 0) {
+ mCompensationMatrix.setRotate(mCompensation);
+ // move center to origin before rotation
+ mCompensationMatrix.preTranslate(-w / 2, -h / 2);
+ // align with the new origin after rotation
+ mCompensationMatrix.postTranslate(h / 2, w / 2);
+ } else {
+ mCompensationMatrix.setRotate(mCompensation, w / 2, h / 2);
+ }
+ }
+
+ // Tell the views about current display rotation and compensation value.
+ if (mContentView != null) {
+ // This is a hack: note the 0 should be the display rotation, but we
+ // don't know the display rotation here. The PhotoPage will inject
+ // the correct value in its mRootPane.orient() method.
+ mContentView.orient(0, mCompensation);
+ }
+
+ // Do the actual layout.
+ if (mCompensation % 180 != 0) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+ Log.i(TAG, "layout content pane " + w + "x" + h
+ + " (compensation " + mCompensation + ")");
+ if (mContentView != null && w != 0 && h != 0) {
+ mContentView.layout(0, 0, w, h);
}
// Uncomment this to dump the view hierarchy.
//mContentView.dumpTree("");
@@ -290,9 +331,12 @@ public class GLRootView extends GLSurfaceView
if ((mFlags & FLAG_NEED_LAYOUT) != 0) layoutContentPane();
+ mCanvas.save(GLCanvas.SAVE_FLAG_ALL);
+ rotateCanvas(-mCompensation);
if (mContentView != null) {
mContentView.render(mCanvas);
}
+ mCanvas.restore();
if (!mAnimations.isEmpty()) {
long now = AnimationTime.get();
@@ -320,9 +364,25 @@ public class GLRootView extends GLSurfaceView
}
}
+ private void rotateCanvas(int degrees) {
+ if (degrees == 0) return;
+ int w = getWidth();
+ int h = getHeight();
+ int cx = w / 2;
+ int cy = h / 2;
+ mCanvas.translate(cx, cy);
+ mCanvas.rotate(degrees, 0, 0, 1);
+ if (degrees % 180 != 0) {
+ mCanvas.translate(-cy, -cx);
+ } else {
+ mCanvas.translate(-cx, -cy);
+ }
+ }
+
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
- AnimationTime.update();
+ if (!isEnabled()) return false;
+
int action = event.getAction();
if (action == MotionEvent.ACTION_CANCEL
|| action == MotionEvent.ACTION_UP) {
@@ -330,6 +390,11 @@ public class GLRootView extends GLSurfaceView
} else if (!mInDownState && action != MotionEvent.ACTION_DOWN) {
return false;
}
+
+ if (mCompensation != 0) {
+ event.transform(mCompensationMatrix);
+ }
+
mRenderLock.lock();
try {
// If this has been detached from root, we don't need to handle event
@@ -396,4 +461,11 @@ public class GLRootView extends GLSurfaceView
Profile.reset();
}
}
+
+ @Override
+ public void setOrientationCompensation(int degrees) {
+ if (mPendingCompensation == degrees) return;
+ mPendingCompensation = degrees;
+ requestLayoutContentPane();
+ }
}
diff --git a/src/com/android/gallery3d/ui/GLView.java b/src/com/android/gallery3d/ui/GLView.java
index 45471f910..ec29939e7 100644
--- a/src/com/android/gallery3d/ui/GLView.java
+++ b/src/com/android/gallery3d/ui/GLView.java
@@ -359,6 +359,16 @@ public class GLView {
boolean changeSize, int left, int top, int right, int bottom) {
}
+ protected void orient(int displayRotation, int compensation) {
+ onOrient(displayRotation, compensation);
+ for (int i = 0, n = getComponentCount(); i < n; ++i) {
+ getComponent(i).orient(displayRotation, compensation);
+ }
+ }
+
+ protected void onOrient(int displayRotation, int compensation) {
+ }
+
/**
* Gets the bounds of the given descendant that relative to this view.
*/
diff --git a/src/com/android/gallery3d/ui/PhotoView.java b/src/com/android/gallery3d/ui/PhotoView.java
index c76996189..7f9cd043d 100644
--- a/src/com/android/gallery3d/ui/PhotoView.java
+++ b/src/com/android/gallery3d/ui/PhotoView.java
@@ -47,9 +47,8 @@ public class PhotoView extends GLView {
}
public interface Model extends TileImageView.Model {
- public void next();
- public void previous();
- public void moveToFirst();
+ public int getCurrentIndex();
+ public void moveTo(int index);
// Returns the size for the specified picture. If the size information is
// not avaiable, width = height = 0.
@@ -65,12 +64,42 @@ public class PhotoView extends GLView {
// Set this to true if we need the model to provide full images.
public void setNeedFullImage(boolean enabled);
+
+ // Returns true if the item is the Camera preview.
+ public boolean isCamera(int offset);
}
- public interface PhotoTapListener {
+ public interface Listener {
public void onSingleTapUp(int x, int y);
+ public void lockOrientation();
+ public void unlockOrientation();
+ public void onFullScreenChanged(boolean full);
}
+ // Here is a graph showing the places we need to lock/unlock device
+ // orientation:
+ //
+ // +------------+ A +------------+
+ // Page mode | Camera |<---| Photo |
+ // | [locked] |--->| [unlocked] |
+ // +------------+ B +------------+
+ // ^ ^
+ // | C | D
+ // +------------+ +------------+
+ // | Camera | | Photo |
+ // Film mode | [*] | | [*] |
+ // +------------+ +------------+
+ //
+ // In Page mode, we want to lock in Camera because we don't want the system
+ // rotation animation. We also want to unlock in Photo because we want to
+ // show the system action bar in the right place.
+ //
+ // We don't show action bar in Film mode, so it's fine for it to be locked
+ // or unlocked in Film mode.
+ //
+ // There are four transitions we need to check if we need to
+ // lock/unlock. Marked as A to D above and in the code.
+
private static final int MSG_SHOW_LOADING = 1;
private static final int MSG_CANCEL_EXTRA_SCALING = 2;
private static final int MSG_SWITCH_FOCUS = 3;
@@ -111,11 +140,9 @@ public class PhotoView extends GLView {
private final int mFromIndex[] = new int[2 * SCREEN_NAIL_MAX + 1];
private final GestureRecognizer mGestureRecognizer;
-
- private PhotoTapListener mPhotoTapListener;
-
private final PositionController mPositionController;
+ private Listener mListener;
private Model mModel;
private StringTexture mLoadingText;
private StringTexture mNoThumbnailText;
@@ -133,6 +160,11 @@ public class PhotoView extends GLView {
private Point mImageCenter = new Point();
private boolean mCancelExtraScalingPending;
private boolean mFilmMode = false;
+ private int mDisplayRotation = 0;
+ private int mCompensation = 0;
+ private boolean mFullScreen = true;
+ private Rect mCameraNaturalFrame = new Rect();
+ private Rect mCameraRect = new Rect();
// [mPrevBound, mNextBound] is the range of index for all pictures in the
// model, if we assume the index of current focused picture is 0. So if
@@ -240,7 +272,9 @@ public class PhotoView extends GLView {
break;
}
case MSG_CAPTURE_ANIMATION_DONE: {
- captureAnimationDone();
+ // message.arg1 is the offset parameter passed to
+ // switchWithCaptureAnimation().
+ captureAnimationDone(message.arg1);
break;
}
default: throw new AssertionError(message.what);
@@ -314,7 +348,8 @@ public class PhotoView extends GLView {
}
// Move the boxes
- mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0);
+ mPositionController.moveBox(mFromIndex, mPrevBound < 0, mNextBound > 0,
+ mModel.isCamera(0));
// Update the ScreenNails.
for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
@@ -329,6 +364,69 @@ public class PhotoView extends GLView {
invalidate();
}
+ @Override
+ protected void onOrient(int displayRotation, int compensation) {
+ // onLayout will be called soon. We need to change the size and rotation
+ // of the Camera ScreenNail, but we don't want it start moving because
+ // the view size will be changed soon.
+ mDisplayRotation = displayRotation;
+ mCompensation = compensation;
+ for (int i = -SCREEN_NAIL_MAX; i <= SCREEN_NAIL_MAX; i++) {
+ Picture p = mPictures.get(i);
+ if (p.isCamera()) {
+ p.updateSize(true);
+ }
+ }
+ }
+
+ @Override
+ protected void onLayout(
+ boolean changeSize, int left, int top, int right, int bottom) {
+ mTileView.layout(left, top, right, bottom);
+ mEdgeView.layout(left, top, right, bottom);
+ updateConstrainedFrame();
+ if (changeSize) {
+ mPositionController.setViewSize(getWidth(), getHeight());
+ }
+ }
+
+ // Update the constrained frame due to layout change.
+ private void updateConstrainedFrame() {
+ int w = getWidth();
+ int h = getHeight();
+ int rotation = getCameraRotation();
+ if (rotation % 180 != 0) {
+ int tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ int l = mCameraNaturalFrame.left;
+ int t = mCameraNaturalFrame.top;
+ int r = mCameraNaturalFrame.right;
+ int b = mCameraNaturalFrame.bottom;
+
+ switch (rotation) {
+ case 0: mCameraRect.set(l, t, r, b); break;
+ case 90: mCameraRect.set(h - b, l, h - t, r); break;
+ case 180: mCameraRect.set(w - r, h - b, w - l, h - t); break;
+ case 270: mCameraRect.set(t, w - r, b, w - l); break;
+ }
+
+ mPositionController.setConstrainedFrame(mCameraRect);
+ }
+
+ public void setCameraNaturalFrame(Rect frame) {
+ mCameraNaturalFrame.set(frame);
+ }
+
+ // Returns the rotation we need to do to the camera texture before drawing
+ // it to the canvas, assuming the camera texture is correct when the device
+ // is in its natural orientation.
+ private int getCameraRotation() {
+ return (mCompensation - mDisplayRotation + 360) % 360;
+ }
+
////////////////////////////////////////////////////////////////////////////
// Pictures
////////////////////////////////////////////////////////////////////////////
@@ -338,16 +436,13 @@ public class PhotoView extends GLView {
void draw(GLCanvas canvas, Rect r);
void setScreenNail(ScreenNail s);
boolean isCamera(); // whether the picture is a camera preview
+ void updateSize(boolean force); // called when mCompensation changes
};
- private boolean isCameraScreenNail(ScreenNail s) {
- return s != null && !(s instanceof BitmapScreenNail);
- }
-
class FullPicture implements Picture {
private int mRotation;
private boolean mIsCamera;
- private boolean mWasCenter;
+ private boolean mWasCameraCenter;
public void FullPicture(TileImageView tileView) {
mTileView = tileView;
@@ -359,45 +454,73 @@ public class PhotoView extends GLView {
mTileView.notifyModelInvalidated();
mTileView.setAlpha(1.0f);
- mRotation = mModel.getImageRotation(0);
+ mIsCamera = mModel.isCamera(0);
+ setScreenNail(mModel.getScreenNail(0));
+ updateSize(false);
+ updateLoadingState();
+ }
+
+ @Override
+ public void updateSize(boolean force) {
+ if (mIsCamera) {
+ mRotation = getCameraRotation();
+ } else {
+ mRotation = mModel.getImageRotation(0);
+ }
+
int w = mTileView.mImageWidth;
int h = mTileView.mImageHeight;
mPositionController.setImageSize(0,
getRotated(mRotation, w, h),
- getRotated(mRotation, h, w));
-
- setScreenNail(mModel.getScreenNail(0));
- updateLoadingState();
+ getRotated(mRotation, h, w),
+ force);
}
@Override
public void draw(GLCanvas canvas, Rect r) {
+ boolean isCenter = mPositionController.isCenter();
+
if (mLoadingState == LOADING_COMPLETE) {
+ if (mIsCamera) {
+ boolean full = !mFilmMode && isCenter
+ && mPositionController.isAtMinimalScale();
+ if (full != mFullScreen) {
+ mFullScreen = full;
+ mListener.onFullScreenChanged(full);
+ }
+ }
setTileViewPosition(r);
PhotoView.super.render(canvas);
}
renderMessage(canvas, r.centerX(), r.centerY());
- boolean isCenter = r.centerX() == getWidth() / 2;
-
- // We want to have following transitions:
+ // We want to have the following transitions:
// (1) Move camera preview out of its place: switch to film mode
// (2) Move camera preview into its place: switch to page mode
// The extra mWasCenter check makes sure (1) does not apply if in
// page mode, we move _to_ the camera preview from another picture.
- if ((mHolding & ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA)) == 0) {
- if (mWasCenter && !isCenter && mIsCamera && !mFilmMode) {
- setFilmMode(true);
- } else if (mIsCamera && isCenter && mFilmMode) {
- setFilmMode(false);
- }
+
+ // Holdings except touch-down prevent the transitions.
+ if ((mHolding & ~(HOLD_TOUCH_DOWN | HOLD_TOUCH_DOWN_FROM_CAMERA)) != 0) {
+ return;
}
- mWasCenter = isCenter;
+
+ boolean isCameraCenter = mIsCamera && isCenter;
+
+ if (mWasCameraCenter && mIsCamera && !isCenter && !mFilmMode) {
+ setFilmMode(true);
+ } else if (isCameraCenter && mFilmMode) {
+ setFilmMode(false);
+ } else if (isCameraCenter && !mFilmMode) {
+ // move into camera, lock
+ mListener.lockOrientation(); // Transition A
+ }
+
+ mWasCameraCenter = isCameraCenter;
}
@Override
public void setScreenNail(ScreenNail s) {
- mIsCamera = isCameraScreenNail(s);
mTileView.setScreenNail(s);
}
@@ -421,7 +544,7 @@ public class PhotoView extends GLView {
(viewH / 2f - r.exactCenterY()) / scale + 0.5f);
boolean wantsCardEffect = CARD_EFFECT && !mFilmMode
- && !mPictures.get(-1).isCamera() && !mIsCamera;
+ && !mIsCamera && !mPictures.get(-1).isCamera();
if (wantsCardEffect) {
// Calculate the move-out progress value.
int left = r.left;
@@ -431,7 +554,7 @@ public class PhotoView extends GLView {
// We only want to apply the fading animation if the scrolling
// movement is to the right.
- if (progress <= 0) {
+ if (progress < 0) {
if (right - left < viewW) {
// If the picture is narrower than the view, keep it at
// the center of the view.
@@ -509,6 +632,7 @@ public class PhotoView extends GLView {
@Override
public void reload() {
+ mIsCamera = mModel.isCamera(mIndex);
setScreenNail(mModel.getScreenNail(mIndex));
}
@@ -529,6 +653,11 @@ public class PhotoView extends GLView {
return;
}
+ if (mIsCamera && mFullScreen != false) {
+ mFullScreen = false;
+ mListener.onFullScreenChanged(false);
+ }
+
boolean wantsCardEffect = CARD_EFFECT && !mFilmMode
&& (mIndex > 0) && !mPictures.get(0).isCamera();
@@ -561,13 +690,21 @@ public class PhotoView extends GLView {
public void setScreenNail(ScreenNail s) {
if (mScreenNail == s) return;
mScreenNail = s;
- mIsCamera = isCameraScreenNail(s);
- mRotation = mModel.getImageRotation(mIndex);
+ updateSize(false);
+ }
+
+ @Override
+ public void updateSize(boolean force) {
+ if (mIsCamera) {
+ mRotation = getCameraRotation();
+ } else {
+ mRotation = mModel.getImageRotation(mIndex);
+ }
int w = 0, h = 0;
if (mScreenNail != null) {
- w = s.getWidth();
- h = s.getHeight();
+ w = mScreenNail.getWidth();
+ h = mScreenNail.getHeight();
} else if (mModel != null) {
// If we don't have ScreenNail available, we can still try to
// get the size information of it.
@@ -579,7 +716,8 @@ public class PhotoView extends GLView {
if (w != 0 && h != 0) {
mPositionController.setImageSize(mIndex,
getRotated(mRotation, w, h),
- getRotated(mRotation, h, w));
+ getRotated(mRotation, h, w),
+ force);
}
}
@@ -617,8 +755,8 @@ public class PhotoView extends GLView {
return true;
}
- if (mPhotoTapListener != null) {
- mPhotoTapListener.onSingleTapUp((int) x, (int) y);
+ if (mListener != null) {
+ mListener.onSingleTapUp((int) x, (int) y);
}
return true;
}
@@ -737,22 +875,25 @@ public class PhotoView extends GLView {
mFilmMode = enabled;
mPositionController.setFilmMode(mFilmMode);
mModel.setNeedFullImage(!enabled);
+
+ // If we leave filmstrip mode, we should lock/unlock
+ if (!enabled) {
+ if (mPictures.get(0).isCamera()) {
+ mListener.lockOrientation(); // Transition C
+ } else {
+ mListener.unlockOrientation(); // Transition D
+ }
+ }
+ }
+
+ public boolean getFilmMode() {
+ return mFilmMode;
}
////////////////////////////////////////////////////////////////////////////
// Framework events
////////////////////////////////////////////////////////////////////////////
- @Override
- protected void onLayout(
- boolean changeSize, int left, int top, int right, int bottom) {
- mTileView.layout(left, top, right, bottom);
- mEdgeView.layout(left, top, right, bottom);
- if (changeSize) {
- mPositionController.setViewSize(getWidth(), getHeight());
- }
- }
-
public void pause() {
mPositionController.skipAnimation();
mTileView.freeTextures();
@@ -926,15 +1067,15 @@ public class PhotoView extends GLView {
////////////////////////////////////////////////////////////////////////////
private void switchToNextImage() {
- mModel.next();
+ mModel.moveTo(mModel.getCurrentIndex() + 1);
}
private void switchToPrevImage() {
- mModel.previous();
+ mModel.moveTo(mModel.getCurrentIndex() - 1);
}
private void switchToFirstImage() {
- mModel.moveToFirst();
+ mModel.moveTo(0);
}
////////////////////////////////////////////////////////////////////////////
@@ -973,12 +1114,17 @@ public class PhotoView extends GLView {
return false;
}
mHolding |= HOLD_CAPTURE_ANIMATION;
- mHandler.sendEmptyMessageDelayed(MSG_CAPTURE_ANIMATION_DONE, 800);
+ Message m = mHandler.obtainMessage(MSG_CAPTURE_ANIMATION_DONE, offset, 0);
+ mHandler.sendMessageDelayed(m, 800);
return true;
}
- private void captureAnimationDone() {
+ private void captureAnimationDone(int offset) {
mHolding &= ~HOLD_CAPTURE_ANIMATION;
+ if (offset == 1) {
+ // move out of camera, unlock
+ if (!mFilmMode) mListener.unlockOrientation(); // Transition B
+ }
snapback();
}
@@ -1004,10 +1150,15 @@ public class PhotoView extends GLView {
// If the object width is smaller than the view width,
// |....view....|
// |<-->| progress = -1 when left = viewWidth
+ // |<-->| progress = 0 when left = viewWidth / 2 - w / 2
// |<-->| progress = 1 when left = -w
- // So progress = 1 - 2 * (left + w) / (viewWidth + w)
if (w < viewWidth) {
- return 1f - 2f * (left + w) / (viewWidth + w);
+ int zx = viewWidth / 2 - w / 2;
+ if (left > zx) {
+ return -(left - zx) / (float) (viewWidth - zx); // progress = (0, -1]
+ } else {
+ return (left - zx) / (float) (-w - zx); // progress = [0, 1]
+ }
}
// If the object width is larger than the view width,
@@ -1066,8 +1217,8 @@ public class PhotoView extends GLView {
// Simple public utilities
////////////////////////////////////////////////////////////////////////////
- public void setPhotoTapListener(PhotoTapListener listener) {
- mPhotoTapListener = listener;
+ public void setListener(Listener listener) {
+ mListener = listener;
}
public void showVideoPlayIcon(boolean show) {
diff --git a/src/com/android/gallery3d/ui/PositionController.java b/src/com/android/gallery3d/ui/PositionController.java
index e6c132b85..ac0d191f4 100644
--- a/src/com/android/gallery3d/ui/PositionController.java
+++ b/src/com/android/gallery3d/ui/PositionController.java
@@ -112,6 +112,26 @@ class PositionController {
// comments above calculateStableBound() for details.
private int mBoundLeft, mBoundRight, mBoundTop, mBoundBottom;
+ // Constrained frame is a rectangle that the focused box should fit into if
+ // it is constrained. It has two effects:
+ //
+ // (1) In page mode, if the focused box is constrained, scaling for the
+ // focused box is adjusted to fit into the constrained frame, instead of the
+ // whole view.
+ //
+ // (2) In page mode, if the focused box is constrained, the mPlatform's
+ // default center (mDefaultX/Y) is moved to the center of the constrained
+ // frame, instead of the view center.
+ //
+ private Rect mConstrainedFrame = new Rect();
+
+ // Whether the focused box is constrained.
+ //
+ // Our current program's first call to moveBox() sets constrained = true, so
+ // we set the initial value of this variable to true, and we will not see
+ // see unwanted transition animation.
+ private boolean mConstrained = true;
+
//
// ___________________________________________________________
// | _____ _____ _____ _____ _____ |
@@ -123,7 +143,7 @@ class PositionController {
//
// <-- Platform -->
//
- // The focused box (Box*) centers at mPlatform.mCurrentX
+ // The focused box (Box*) centers at mPlatform's (mCurrentX, mCurrentY)
private Platform mPlatform = new Platform();
private RangeArray<Box> mBoxes = new RangeArray<Box>(-BOX_MAX, BOX_MAX);
@@ -152,7 +172,8 @@ class PositionController {
public PositionController(Context context, Listener listener) {
mListener = listener;
mPageScroller = new FlingScroller();
- mFilmScroller = new OverScroller(context);
+ mFilmScroller = new OverScroller(context,
+ null /* default interpolator */, false /* no flywheel */);
// Initialize the areas.
initPlatform();
@@ -186,7 +207,22 @@ class PositionController {
snapAndRedraw();
}
- public void setImageSize(int index, int width, int height) {
+ public void setConstrainedFrame(Rect f) {
+ if (mConstrainedFrame.equals(f)) return;
+ mConstrainedFrame.set(f);
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ snapAndRedraw();
+ }
+
+ public void setImageSize(int index, int width, int height, boolean force) {
+ if (force) {
+ Box b = mBoxes.get(index);
+ b.mImageW = width;
+ b.mImageH = height;
+ return;
+ }
+
if (width == 0 || height == 0) {
initBox(index);
} else if (!setBoxSize(index, width, height, false)) {
@@ -216,11 +252,14 @@ class PositionController {
float ratio = Math.min(
(float) b.mImageW / width, (float) b.mImageH / height);
+ b.mImageW = width;
+ b.mImageH = height;
+
// If this is the first time we receive an image size, we change the
// scale directly. Otherwise adjust the scales by a ratio, and snapback
// will animate the scale into the min/max bounds if necessary.
if (wasViewSize && !isViewSize) {
- b.mCurrentScale = getMinimalScale(width, height);
+ b.mCurrentScale = getMinimalScale(b);
b.mAnimationStartTime = NO_ANIMATION;
} else {
b.mCurrentScale *= ratio;
@@ -228,9 +267,6 @@ class PositionController {
b.mToScale *= ratio;
}
- b.mImageW = width;
- b.mImageH = height;
-
if (i == 0) {
mFocusX /= ratio;
mFocusY /= ratio;
@@ -247,17 +283,19 @@ class PositionController {
// Start animation from the saved rectangle if we have one.
Rect r = mOpenAnimationRect;
mOpenAnimationRect = null;
- mPlatform.mCurrentX = r.centerX();
- b.mCurrentY = r.centerY();
+ mPlatform.mCurrentX = r.centerX() - mViewW / 2;
+ b.mCurrentY = r.centerY() - mViewH / 2;
b.mCurrentScale = Math.max(r.width() / (float) b.mImageW,
r.height() / (float) b.mImageH);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_OPENING);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin,
+ ANIM_KIND_OPENING);
}
public void setFilmMode(boolean enabled) {
if (enabled == mFilmMode) return;
mFilmMode = enabled;
+ mPlatform.updateDefaultXY();
updateScaleAndGapLimit();
stopAnimation();
snapAndRedraw();
@@ -273,12 +311,12 @@ class PositionController {
// This should be called whenever the scale range of boxes or the default
// gap size may change. Currently this can happen due to change of view
- // size, image size, and mode.
+ // size, image size, mFilmMode, mConstrained, and mConstrainedFrame.
private void updateScaleAndGapLimit() {
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
Box b = mBoxes.get(i);
- b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
- b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
+ b.mScaleMin = getMinimalScale(b);
+ b.mScaleMax = getMaximalScale(b);
}
for (int i = -BOX_MAX; i < BOX_MAX; i++) {
@@ -326,6 +364,7 @@ class PositionController {
public void skipAnimation() {
if (mPlatform.mAnimationStartTime != NO_ANIMATION) {
mPlatform.mCurrentX = mPlatform.mToX;
+ mPlatform.mCurrentY = mPlatform.mToY;
mPlatform.mAnimationStartTime = NO_ANIMATION;
}
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
@@ -353,14 +392,16 @@ class PositionController {
////////////////////////////////////////////////////////////////////////////
public void zoomIn(float tapX, float tapY, float targetScale) {
+ tapX -= mViewW / 2;
+ tapY -= mViewH / 2;
Box b = mBoxes.get(0);
// Convert the tap position to distance to center in bitmap coordinates
float tempX = (tapX - mPlatform.mCurrentX) / b.mCurrentScale;
float tempY = (tapY - b.mCurrentY) / b.mCurrentScale;
- int x = (int) (mViewW / 2 - tempX * targetScale + 0.5f);
- int y = (int) (mViewH / 2 - tempY * targetScale + 0.5f);
+ int x = (int) (-tempX * targetScale + 0.5f);
+ int y = (int) (-tempY * targetScale + 0.5f);
calculateStableBound(targetScale);
int targetX = Utils.clamp(x, mBoundLeft, mBoundRight);
@@ -372,10 +413,12 @@ class PositionController {
public void resetToFullView() {
Box b = mBoxes.get(0);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_ZOOM);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_ZOOM);
}
public void beginScale(float focusX, float focusY) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
Box b = mBoxes.get(0);
Platform p = mPlatform;
mInScale = true;
@@ -389,6 +432,8 @@ class PositionController {
// 0 if the intended scale is in the stable range.
// -1 if the intended scale is too small for the stable range.
public int scaleBy(float s, float focusX, float focusY) {
+ focusX -= mViewW / 2;
+ focusY -= mViewH / 2;
Box b = mBoxes.get(0);
Platform p = mPlatform;
@@ -414,7 +459,7 @@ class PositionController {
// Slide the focused box to the center of the view.
public void startHorizontalSlide() {
Box b = mBoxes.get(0);
- startAnimation(mViewW / 2, mViewH / 2, b.mScaleMin, ANIM_KIND_SLIDE);
+ startAnimation(mPlatform.mDefaultX, 0, b.mScaleMin, ANIM_KIND_SLIDE);
}
// Slide the focused box to the center of the view with the capture
@@ -426,9 +471,10 @@ class PositionController {
Box n = mBoxes.get(offset); // the neighbor box
Gap g = mGaps.get(offset); // the gap between the two boxes
- mPlatform.doAnimation(mViewW / 2, ANIM_KIND_CAPTURE);
- b.doAnimation(mViewH / 2, b.mScaleMin, ANIM_KIND_CAPTURE);
- n.doAnimation(mViewH / 2, n.mScaleMin, ANIM_KIND_CAPTURE);
+ mPlatform.doAnimation(mPlatform.mDefaultX, mPlatform.mDefaultY,
+ ANIM_KIND_CAPTURE);
+ b.doAnimation(0, b.mScaleMin, ANIM_KIND_CAPTURE);
+ n.doAnimation(0, n.mScaleMin, ANIM_KIND_CAPTURE);
g.doAnimation(g.mDefaultSize, ANIM_KIND_CAPTURE);
redraw();
}
@@ -484,17 +530,15 @@ class PositionController {
// Horizontal direction: we show the edge effect when the scrolling
// tries to go left of the first image or go right of the last image.
- int cx = mViewW / 2;
- if (!mHasPrev && x > cx) {
- int pixels = x - cx;
- mListener.onPull(pixels, EdgeView.LEFT);
- x = cx;
- } else if (!mHasNext && x < cx) {
- int pixels = cx - x;
- mListener.onPull(pixels, EdgeView.RIGHT);
- x = cx;
- }
-
+ x -= mPlatform.mDefaultX;
+ if (!mHasPrev && x > 0) {
+ mListener.onPull(x, EdgeView.LEFT);
+ x = 0;
+ } else if (!mHasNext && x < 0) {
+ mListener.onPull(-x, EdgeView.RIGHT);
+ x = 0;
+ }
+ x += mPlatform.mDefaultX;
startAnimation(x, y, b.mCurrentScale, ANIM_KIND_SCROLL);
}
@@ -542,9 +586,9 @@ class PositionController {
Platform p = mPlatform;
// If we are already at the edge, don't start the fling.
- int cx = mViewW / 2;
- if ((!mHasPrev && p.mCurrentX >= cx)
- || (!mHasNext && p.mCurrentX <= cx)) {
+ int defaultX = p.mDefaultX;
+ if ((!mHasPrev && p.mCurrentX >= defaultX)
+ || (!mHasNext && p.mCurrentX <= defaultX)) {
return false;
}
@@ -595,7 +639,7 @@ class PositionController {
private void startAnimation(int targetX, int targetY, float targetScale,
int kind) {
boolean changed = false;
- changed |= mPlatform.doAnimation(targetX, kind);
+ changed |= mPlatform.doAnimation(targetX, mPlatform.mDefaultY, kind);
changed |= mBoxes.get(0).doAnimation(targetY, targetScale, kind);
if (changed) redraw();
}
@@ -691,11 +735,11 @@ class PositionController {
private void convertBoxToRect(int i) {
Box b = mBoxes.get(i);
Rect r = mRects.get(i);
- int y = b.mCurrentY;
+ int y = b.mCurrentY + mPlatform.mCurrentY + mViewH / 2;
int w = widthOf(b);
int h = heightOf(b);
if (i == 0) {
- int x = mPlatform.mCurrentX;
+ int x = mPlatform.mCurrentX + mViewW / 2;
r.left = x - w / 2;
r.right = r.left + w;
} else if (i > 0) {
@@ -724,7 +768,9 @@ class PositionController {
// Initialize the platform to be at the view center.
private void initPlatform() {
- mPlatform.mCurrentX = mViewW / 2;
+ mPlatform.updateDefaultXY();
+ mPlatform.mCurrentX = mPlatform.mDefaultX;
+ mPlatform.mCurrentY = mPlatform.mDefaultY;
mPlatform.mAnimationStartTime = NO_ANIMATION;
}
@@ -734,9 +780,9 @@ class PositionController {
b.mImageW = mViewW;
b.mImageH = mViewH;
b.mUseViewSize = true;
- b.mScaleMin = getMinimalScale(b.mImageW, b.mImageH);
- b.mScaleMax = getMaximalScale(b.mImageW, b.mImageH);
- b.mCurrentY = mViewH / 2;
+ b.mScaleMin = getMinimalScale(b);
+ b.mScaleMax = getMaximalScale(b);
+ b.mCurrentY = 0;
b.mCurrentScale = b.mScaleMin;
b.mAnimationStartTime = NO_ANIMATION;
}
@@ -784,10 +830,16 @@ class PositionController {
// -2 -1 0 1 2 3 N -- focus goes to the next box
// N -3 -2 -1 0 1 2 -- focuse goes to the previous box
// -3 -2 -1 1 2 3 N -- the focused box was deleted.
- public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext) {
+ //
+ // hasPrev/hasNext indicates if there are previous/next boxes for the
+ // focused box. constrained indicates whether the focused box should be put
+ // into the constrained frame.
+ public void moveBox(int fromIndex[], boolean hasPrev, boolean hasNext,
+ boolean constrained) {
//debugMoveBox(fromIndex);
mHasPrev = hasPrev;
mHasNext = hasNext;
+
RangeIntArray from = new RangeIntArray(fromIndex, -BOX_MAX, BOX_MAX);
// 1. Get the absolute X coordiates for the boxes.
@@ -795,7 +847,7 @@ class PositionController {
for (int i = -BOX_MAX; i <= BOX_MAX; i++) {
Box b = mBoxes.get(i);
Rect r = mRects.get(i);
- b.mAbsoluteX = r.centerX();
+ b.mAbsoluteX = r.centerX() - mViewW / 2;
}
// 2. copy boxes and gaps to temporary storage.
@@ -892,6 +944,12 @@ class PositionController {
mPlatform.mToX += dx;
mPlatform.mFlingOffset += dx;
+ if (mConstrained != constrained) {
+ mConstrained = constrained;
+ mPlatform.updateDefaultXY();
+ updateScaleAndGapLimit();
+ }
+
snapAndRedraw();
}
@@ -899,34 +957,17 @@ class PositionController {
// Public utilities
////////////////////////////////////////////////////////////////////////////
- public float getMinimalScale(int imageW, int imageH) {
- float wFactor = 1.0f;
- float hFactor = 1.0f;
-
- if (mFilmMode) {
- if (mViewH > mViewW) { // portrait
- wFactor = FILM_MODE_PORTRAIT_WIDTH;
- hFactor = FILM_MODE_PORTRAIT_HEIGHT;
- } else { // landscape
- wFactor = FILM_MODE_LANDSCAPE_WIDTH;
- hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
- }
- }
-
- float s = Math.min(wFactor * mViewW / imageW,
- hFactor * mViewH / imageH);
- return Math.min(SCALE_LIMIT, s);
- }
-
- public float getMaximalScale(int imageW, int imageH) {
- return mFilmMode ? getMinimalScale(imageW, imageH) : SCALE_LIMIT;
- }
-
public boolean isAtMinimalScale() {
Box b = mBoxes.get(0);
return isAlmostEqual(b.mCurrentScale, b.mScaleMin);
}
+ public boolean isCenter() {
+ Box b = mBoxes.get(0);
+ return mPlatform.mCurrentX == mPlatform.mDefaultX
+ && b.mCurrentY == 0;
+ }
+
public int getImageWidth() {
Box b = mBoxes.get(0);
return b.mImageW;
@@ -967,11 +1008,35 @@ class PositionController {
////////////////////////////////////////////////////////////////////////////
private float getMinimalScale(Box b) {
- return getMinimalScale(b.mImageW, b.mImageH);
+ float wFactor = 1.0f;
+ float hFactor = 1.0f;
+ int viewW, viewH;
+
+ if (!mFilmMode && mConstrained && b == mBoxes.get(0)) {
+ viewW = mConstrainedFrame.width();
+ viewH = mConstrainedFrame.height();
+ } else {
+ viewW = mViewW;
+ viewH = mViewH;
+ }
+
+ if (mFilmMode) {
+ if (mViewH > mViewW) { // portrait
+ wFactor = FILM_MODE_PORTRAIT_WIDTH;
+ hFactor = FILM_MODE_PORTRAIT_HEIGHT;
+ } else { // landscape
+ wFactor = FILM_MODE_LANDSCAPE_WIDTH;
+ hFactor = FILM_MODE_LANDSCAPE_HEIGHT;
+ }
+ }
+
+ float s = Math.min(wFactor * viewW / b.mImageW,
+ hFactor * viewH / b.mImageH);
+ return Math.min(SCALE_LIMIT, s);
}
- private float getMaxmimalScale(Box b) {
- return getMaximalScale(b.mImageW, b.mImageH);
+ private float getMaximalScale(Box b) {
+ return mFilmMode ? getMinimalScale(b) : SCALE_LIMIT;
}
private static boolean isAlmostEqual(float a, float b) {
@@ -979,7 +1044,8 @@ class PositionController {
return (diff < 0 ? -diff : diff) < 0.02f;
}
- // Calculates the stable region of mCurrent{X/Y}, where "stable" means
+ // Calculates the stable region of mPlatform.mCurrentX and
+ // mBoxes.get(0).mCurrentY, where "stable" means
//
// (1) If the dimension of scaled image >= view dimension, we will not
// see black region outside the image (at that dimension).
@@ -1002,20 +1068,20 @@ class PositionController {
int h = heightOf(b, scale);
// When the edge of the view is aligned with the edge of the box
- mBoundLeft = (mViewW - horizontalSlack) - w / 2;
- mBoundRight = mViewW - mBoundLeft;
- mBoundTop = mViewH - h / 2;
- mBoundBottom = mViewH - mBoundTop;
+ mBoundLeft = (mViewW + 1) / 2 - (w + 1) / 2 - horizontalSlack;
+ mBoundRight = w / 2 - mViewW / 2 + horizontalSlack;
+ mBoundTop = (mViewH + 1) / 2 - (h + 1) / 2;
+ mBoundBottom = h / 2 - mViewH / 2;
// If the scaled height is smaller than the view height,
// force it to be in the center.
if (viewTallerThanScaledImage(scale)) {
- mBoundTop = mBoundBottom = mViewH / 2;
+ mBoundTop = mBoundBottom = 0;
}
// Same for width
if (viewWiderThanScaledImage(scale)) {
- mBoundLeft = mBoundRight = mViewW / 2;
+ mBoundLeft = mBoundRight = mPlatform.mDefaultX;
}
}
@@ -1049,13 +1115,6 @@ class PositionController {
a.mAnimationKind == ANIM_KIND_FLING;
}
- // Returns the index of the anchor box.
- private int anchorIndex(int i) {
- if (i > 0) return i - 1;
- if (i < 0) return i + 1;
- throw new IllegalArgumentException();
- }
-
////////////////////////////////////////////////////////////////////////////
// Animatable: an thing which can do animation.
////////////////////////////////////////////////////////////////////////////
@@ -1127,10 +1186,11 @@ class PositionController {
}
////////////////////////////////////////////////////////////////////////////
- // Platform: captures the global X movement.
+ // Platform: captures the global X/Y movement.
////////////////////////////////////////////////////////////////////////////
private class Platform extends Animatable {
- public int mCurrentX, mFromX, mToX;
+ public int mCurrentX, mFromX, mToX, mDefaultX;
+ public int mCurrentY, mFromY, mToY, mDefaultY;
public int mFlingOffset;
@Override
@@ -1146,25 +1206,47 @@ class PositionController {
b.mScaleMax * SCALE_MAX_EXTRA : b.mScaleMax;
float scale = Utils.clamp(b.mCurrentScale, scaleMin, scaleMax);
int x = mCurrentX;
+ int y = mDefaultY;
if (mFilmMode) {
- if (!mHasNext) x = Math.max(x, mViewW / 2);
- if (!mHasPrev) x = Math.min(x, mViewW / 2);
+ int defaultX = mDefaultX;
+ if (!mHasNext) x = Math.max(x, defaultX);
+ if (!mHasPrev) x = Math.min(x, defaultX);
} else {
calculateStableBound(scale, HORIZONTAL_SLACK);
x = Utils.clamp(x, mBoundLeft, mBoundRight);
}
- if (mCurrentX != x) {
- return doAnimation(x, ANIM_KIND_SNAPBACK);
+ if (mCurrentX != x || mCurrentY != y) {
+ return doAnimation(x, y, ANIM_KIND_SNAPBACK);
}
return false;
}
+ // The updateDefaultXY() should be called whenever these variables
+ // changes: (1) mConstrained (2) mConstrainedFrame (3) mViewW/H (4)
+ // mFilmMode
+ public void updateDefaultXY() {
+ // We don't check mFilmMode and return 0 for mDefaultX. Because
+ // otherwise if we decide to leave film mode because we are
+ // centered, we will immediately back into film mode because we find
+ // we are not centered.
+ if (mConstrained && !mConstrainedFrame.isEmpty()) {
+ mDefaultX = mConstrainedFrame.centerX() - mViewW / 2;
+ mDefaultY = mFilmMode ? 0 :
+ mConstrainedFrame.centerY() - mViewH / 2;
+ } else {
+ mDefaultX = 0;
+ mDefaultY = 0;
+ }
+ }
+
// Starts an animation for the platform.
- public boolean doAnimation(int targetX, int kind) {
- if (mCurrentX == targetX) return false;
+ private boolean doAnimation(int targetX, int targetY, int kind) {
+ if (mCurrentX == targetX && mCurrentY == targetY) return false;
mAnimationKind = kind;
mFromX = mCurrentX;
+ mFromY = mCurrentY;
mToX = targetX;
+ mToY = targetY;
mAnimationStartTime = AnimationTime.startTime();
mAnimationDuration = ANIM_TIME[kind];
mFlingOffset = 0;
@@ -1188,11 +1270,11 @@ class PositionController {
mCurrentX = mFilmScroller.getCurrX() + mFlingOffset;
int dir = EdgeView.INVALID_DIRECTION;
- if (mCurrentX < mViewW / 2) {
+ if (mCurrentX < mDefaultX) {
if (!mHasNext) {
dir = EdgeView.RIGHT;
}
- } else if (mCurrentX > mViewW / 2) {
+ } else if (mCurrentX > mDefaultX) {
if (!mHasPrev) {
dir = EdgeView.LEFT;
}
@@ -1201,7 +1283,7 @@ class PositionController {
int v = (int) (mFilmScroller.getCurrVelocity() + 0.5f);
mListener.onAbsorb(v, dir);
mFilmScroller.forceFinished(true);
- mCurrentX = mViewW / 2;
+ mCurrentX = mDefaultX;
}
return mFilmScroller.isFinished();
}
@@ -1230,15 +1312,18 @@ class PositionController {
// Other animations
if (progress >= 1) {
mCurrentX = mToX;
+ mCurrentY = mToY;
return true;
} else {
if (mAnimationKind == ANIM_KIND_CAPTURE) {
progress = CaptureAnimation.calculateSlide(progress);
- mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ }
+ mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
+ mCurrentY = (int) (mFromY + progress * (mToY - mFromY));
+ if (mAnimationKind == ANIM_KIND_CAPTURE) {
return false;
} else {
- mCurrentX = (int) (mFromX + progress * (mToX - mFromX));
- return (mCurrentX == mToX);
+ return (mCurrentX == mToX && mCurrentY == mToY);
}
}
}
@@ -1287,13 +1372,13 @@ class PositionController {
mScaleMax * SCALE_MAX_EXTRA : mScaleMax;
scale = Utils.clamp(mCurrentScale, scaleMin, scaleMax);
if (mFilmMode) {
- y = mViewH / 2;
+ y = 0;
} else {
calculateStableBound(scale, HORIZONTAL_SLACK);
y = Utils.clamp(mCurrentY, mBoundTop, mBoundBottom);
}
} else {
- y = mViewH / 2;
+ y = 0;
scale = mScaleMin;
}
@@ -1312,7 +1397,7 @@ class PositionController {
// in the center. (We do this for height only, not width, because the
// user may want to scroll to the previous/next image.)
if (!mInScale && viewTallerThanScaledImage(targetScale)) {
- targetY = mViewH / 2;
+ targetY = 0;
}
if (mCurrentY == targetY && mCurrentScale == targetScale
diff --git a/src/com/android/gallery3d/ui/ScreenNailHolder.java b/src/com/android/gallery3d/ui/ScreenNailHolder.java
deleted file mode 100644
index a7d541767..000000000
--- a/src/com/android/gallery3d/ui/ScreenNailHolder.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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.ui;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-public abstract class ScreenNailHolder implements Parcelable {
- public int describeContents() {
- return 0;
- }
-
- public void writeToParcel(Parcel dest, int flags) {
- }
-
- public abstract ScreenNail attach();
- public abstract void detach();
-}
diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java
index b37cf9c4a..eb5da891a 100644
--- a/src/com/android/gallery3d/ui/TileImageView.java
+++ b/src/com/android/gallery3d/ui/TileImageView.java
@@ -337,8 +337,8 @@ public class TileImageView extends GLView {
}
public boolean setPosition(int centerX, int centerY, float scale, int rotation) {
- if (mCenterX == centerX
- && mCenterY == centerY && mScale == scale) return false;
+ if (mCenterX == centerX && mCenterY == centerY
+ && mScale == scale && mRotation == rotation) return false;
mCenterX = centerX;
mCenterY = centerY;
mScale = scale;
diff --git a/tests/src/com/android/gallery3d/ui/GLRootMock.java b/tests/src/com/android/gallery3d/ui/GLRootMock.java
index c83e94342..55f1c9a12 100644
--- a/tests/src/com/android/gallery3d/ui/GLRootMock.java
+++ b/tests/src/com/android/gallery3d/ui/GLRootMock.java
@@ -34,4 +34,5 @@ public class GLRootMock implements GLRoot {
public void lockRenderThread() {}
public void unlockRenderThread() {}
public void setContentPane(GLView content) {}
+ public void setOrientationCompensation(int degrees) {}
}
diff --git a/tests/src/com/android/gallery3d/ui/GLRootStub.java b/tests/src/com/android/gallery3d/ui/GLRootStub.java
index d6bc678d4..bf9b63f6b 100644
--- a/tests/src/com/android/gallery3d/ui/GLRootStub.java
+++ b/tests/src/com/android/gallery3d/ui/GLRootStub.java
@@ -27,4 +27,5 @@ public class GLRootStub implements GLRoot {
public void lockRenderThread() {}
public void unlockRenderThread() {}
public void setContentPane(GLView content) {}
+ public void setOrientationCompensation(int degrees) {}
}