diff options
20 files changed, 881 insertions, 506 deletions
diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c907da177..86d2c9ba9 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -36,6 +36,7 @@ <application android:icon="@mipmap/ic_launcher_gallery" android:label="@string/app_name" android:name="com.android.gallery3d.app.GalleryAppImpl" android:theme="@style/Theme.Gallery" + android:logo="@mipmap/ic_launcher_gallery" android:hardwareAccelerated="true"> <uses-library android:name="com.google.android.media.effects" android:required="false" /> <activity android:name="com.android.gallery3d.app.MovieActivity" 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/ManageCachePage.java b/src/com/android/gallery3d/app/ManageCachePage.java index f97958e0e..2e52b8cbb 100644 --- a/src/com/android/gallery3d/app/ManageCachePage.java +++ b/src/com/android/gallery3d/app/ManageCachePage.java @@ -310,7 +310,6 @@ public class ManageCachePage extends ActivityState implements private void initializeFooterViews() { Activity activity = (Activity) mActivity; - FrameLayout footer = (FrameLayout) activity.findViewById(R.id.footer); LayoutInflater inflater = activity.getLayoutInflater(); mFooterContent = inflater.inflate(R.layout.manage_offline_bar, null); 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..afa56c905 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,14 +564,18 @@ 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(); - if (item == null) { - // item is not ready, ignore + if (item == null || item == mScreenNailItem) { + // item is not ready or it is camera preview, ignore return; } @@ -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) {} } |