com.android.gallery3d.app.PhotoPage.java Source code

Java tutorial

Introduction

Here is the source code for com.android.gallery3d.app.PhotoPage.java

Source

/*
* Copyright (C) 2014 MediaTek Inc.
* Modification based on code covered by the mentioned copyright
* and/or permission notice(s).
*/
/*
 * Copyright (C) 2010 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 java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.PatternSyntaxException;

import android.annotation.TargetApi;
import android.app.ActionBar.OnMenuVisibilityListener;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.net.Uri;
import android.nfc.NfcAdapter;
import android.nfc.NfcAdapter.CreateBeamUrisCallback;
import android.nfc.NfcEvent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.StatFs;
import android.os.SystemClock;
import android.support.v4.print.PrintHelper;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ActivityChooserModel;
import android.widget.RelativeLayout;
import android.widget.ShareActionProvider;
import android.widget.Toast;

import com.android.gallery3d.R;
import com.android.gallery3d.app.MuteVideo.MuteDoneListener;
import com.android.gallery3d.common.ApiHelper;
import com.android.gallery3d.data.ClusterAlbum;
import com.android.gallery3d.data.ComboAlbum;
import com.android.gallery3d.data.DataManager;
import com.android.gallery3d.data.EmptyAlbumImage;
import com.android.gallery3d.data.FilterDeleteSet;
import com.android.gallery3d.data.FilterSource;
import com.android.gallery3d.data.ImageCacheService;
import com.android.gallery3d.data.LocalAlbum;
import com.android.gallery3d.data.LocalImage;
import com.android.gallery3d.data.LocalMediaItem;
import com.android.gallery3d.data.MediaDetails;
import com.android.gallery3d.data.MediaItem;
import com.android.gallery3d.data.MediaObject;
import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
import com.android.gallery3d.data.MediaSet;
import com.android.gallery3d.data.Path;
import com.android.gallery3d.data.SecureAlbum;
import com.android.gallery3d.data.SecureSource;
import com.android.gallery3d.data.SnailAlbum;
import com.android.gallery3d.data.SnailItem;
import com.android.gallery3d.data.SnailSource;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.crop.CropActivity;
import com.android.gallery3d.picasasource.PicasaSource;
import com.android.gallery3d.ui.DetailsHelper;
import com.android.gallery3d.ui.DetailsHelper.CloseListener;
import com.android.gallery3d.ui.DetailsHelper.DetailsSource;
import com.android.gallery3d.ui.GLRoot;
import com.android.gallery3d.ui.GLRootView;
import com.android.gallery3d.ui.GestureRecognizer.Listener;
import com.android.gallery3d.ui.GLView;
import com.android.gallery3d.ui.MenuExecutor;
import com.android.gallery3d.ui.PhotoView;
import com.android.gallery3d.ui.SelectionManager;
import com.android.gallery3d.ui.SynchronizedHandler;
import com.android.gallery3d.util.GalleryUtils;
import com.android.gallery3d.util.Log;
import com.android.gallery3d.util.UsageStatistics;
import com.android.gallery3d.util.ThreadPool.Job;
import com.android.gallery3d.util.ThreadPool.JobContext;

import com.mediatek.gallery3d.adapter.ContainerPage;
import com.mediatek.gallery3d.adapter.FeatureHelper;
import com.mediatek.gallery3d.adapter.PhotoPlayFacade;
import com.mediatek.gallery3d.adapter.StereoPickingAlbumPage;
import com.mediatek.gallery3d.video.SlowMotionSharer;
import com.mediatek.galleryfeature.animshare.AnimatedContentSharer;
import com.mediatek.galleryfeature.config.FeatureConfig;
import com.mediatek.galleryfeature.drm.DrmHelper;
import com.mediatek.galleryfeature.platform.PlatformHelper;
import com.mediatek.galleryfeature.pq.ImageDC;
import com.mediatek.galleryfeature.pq.PQBroadcastReceiver;
import com.mediatek.galleryfeature.pq.PictureQualityActivity;
import com.mediatek.galleryframework.base.ExtItem;
import com.mediatek.galleryframework.base.BottomControlLayer;
import com.mediatek.galleryframework.base.PhotoPageBottomViewControls;
import com.mediatek.galleryframework.base.LayerManager;
import com.mediatek.galleryframework.base.MediaData;

public abstract class PhotoPage extends ActivityState implements PhotoView.Listener, AppBridge.Server,
        /// M: [FEATURE.MODIFY] @{
        /*ShareActionProvider.OnShareTargetSelectedListener,*/
        ActivityChooserModel.OnChooseActivityListener,
        /// @}
        PhotoPageBottomControls.Delegate, GalleryActionBar.OnAlbumModeSelectedListener {
    private static final String TAG = "Gallery2/PhotoPage";

    private static final int MSG_HIDE_BARS = 1;
    private static final int MSG_ON_FULL_SCREEN_CHANGED = 4;
    private static final int MSG_UPDATE_ACTION_BAR = 5;
    private static final int MSG_UNFREEZE_GLROOT = 6;
    private static final int MSG_WANT_BARS = 7;
    private static final int MSG_REFRESH_BOTTOM_CONTROLS = 8;
    private static final int MSG_ON_CAMERA_CENTER = 9;
    private static final int MSG_ON_PICTURE_CENTER = 10;
    private static final int MSG_REFRESH_IMAGE = 11;
    private static final int MSG_UPDATE_PHOTO_UI = 12;
    private static final int MSG_UPDATE_DEFERRED = 14;
    private static final int MSG_UPDATE_SHARE_URI = 15;
    private static final int MSG_UPDATE_PANORAMA_UI = 16;

    private static final int HIDE_BARS_TIMEOUT = 3500;
    private static final int UNFREEZE_GLROOT_TIMEOUT = 250;

    private static final int REQUEST_SLIDESHOW = 1;
    private static final int REQUEST_CROP = 2;
    private static final int REQUEST_CROP_PICASA = 3;
    private static final int REQUEST_EDIT = 4;
    /// M:[FEATURE.MODIFY] {
    // private static final int REQUEST_PLAY_VIDEO = 5;
    public static final int REQUEST_PLAY_VIDEO = 5;
    /// @}
    private static final int REQUEST_TRIM = 6;
    /// M: [FEATURE.ADD] @{
    private static final int REQUEST_PQ = 7;
    /// @}
    public static final String KEY_MEDIA_SET_PATH = "media-set-path";
    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_APP_BRIDGE = "app-bridge";
    public static final String KEY_TREAT_BACK_AS_UP = "treat-back-as-up";
    public static final String KEY_START_IN_FILMSTRIP = "start-in-filmstrip";
    public static final String KEY_RETURN_INDEX_HINT = "return-index-hint";
    public static final String KEY_SHOW_WHEN_LOCKED = "show_when_locked";
    public static final String KEY_IN_CAMERA_ROLL = "in_camera_roll";
    public static final String KEY_READONLY = "read-only";

    public static final String KEY_ALBUMPAGE_TRANSITION = "albumpage-transition";
    public static final int MSG_ALBUMPAGE_NONE = 0;
    public static final int MSG_ALBUMPAGE_STARTED = 1;
    public static final int MSG_ALBUMPAGE_RESUMED = 2;
    public static final int MSG_ALBUMPAGE_PICKED = 4;

    public static final String ACTION_NEXTGEN_EDIT = "action_nextgen_edit";
    public static final String ACTION_SIMPLE_EDIT = "action_simple_edit";
    /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
    // Add for secure camera
    public static final String IS_SECURE_CAMERA = "isSecureCamera";
    public static final String SECURE_ALBUM = "secureAlbum";
    public static final String SECURE_PATH = "securePath";
    /// @}

    private GalleryApp mApplication;
    private SelectionManager mSelectionManager;

    private PhotoView mPhotoView;
    private PhotoPage.Model mModel;
    private DetailsHelper mDetailsHelper;
    private boolean mShowDetails;

    // mMediaSet could be null if there is no KEY_MEDIA_SET_PATH supplied.
    // E.g., viewing a photo in gmail attachment
    private FilterDeleteSet mMediaSet;

    // The mediaset used by camera launched from secure lock screen.
    private SecureAlbum mSecureAlbum;

    private int mCurrentIndex = 0;
    private Handler mHandler;
    private boolean mShowBars = true;
    private volatile boolean mActionBarAllowed = true;
    private GalleryActionBar mActionBar;
    private boolean mIsMenuVisible;
    private boolean mHaveImageEditor;
    private PhotoPageBottomControls mBottomControls;
    private MediaItem mCurrentPhoto = null;
    private MenuExecutor mMenuExecutor;
    private boolean mIsActive;
    private boolean mShowSpinner;
    private String mSetPathString;
    // This is the original mSetPathString before adding the camera preview item.
    private boolean mReadOnlyView = false;
    private String mOriginalSetPathString;
    private AppBridge mAppBridge;
    private SnailItem mScreenNailItem;
    private SnailAlbum mScreenNailSet;
    private OrientationManager mOrientationManager;
    private boolean mTreatBackAsUp;
    private boolean mStartInFilmstrip;
    private boolean mHasCameraScreennailOrPlaceholder = false;
    private boolean mRecenterCameraOnResume = true;

    // These are only valid after the panorama callback
    private boolean mIsPanorama;
    private boolean mIsPanorama360;

    private long mCameraSwitchCutoff = 0;
    private boolean mSkipUpdateCurrentPhoto = false;
    private static final long CAMERA_SWITCH_CUTOFF_THRESHOLD_MS = 300;

    private static final long DEFERRED_UPDATE_MS = 250;
    private boolean mDeferredUpdateWaiting = false;
    private long mDeferUpdateUntil = Long.MAX_VALUE;

    // The item that is deleted (but it can still be undeleted before commiting)
    private Path mDeletePath;
    private boolean mDeleteIsFocus; // whether the deleted item was in focus

    private Uri[] mNfcPushUris = new Uri[1];

    private final MyMenuVisibilityListener mMenuVisibilityListener = new MyMenuVisibilityListener();

    private int mLastSystemUiVis = 0;

    /// M: [PERF.ADD] @{
    private boolean mDisableBarChanges = false;
    /// @}
    /// M: [BUG.ADD] for NFC @{
    private Uri mShareUriFromChooserView = null;
    /// @}
    // / M: [BUG.ADD] @{
    // Google bug fix,mute dialog should be dismiss before gallery activity
    // destroyed.
    private MuteVideo mMuteVideo;
    // / @}

    private final PanoramaSupportCallback mUpdatePanoramaMenuItemsCallback = new PanoramaSupportCallback() {
        @Override
        public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360) {
            if (mediaObject == mCurrentPhoto) {
                mHandler.obtainMessage(MSG_UPDATE_PANORAMA_UI, isPanorama360 ? 1 : 0, 0, mediaObject)
                        .sendToTarget();
            }
        }
    };

    private final PanoramaSupportCallback mRefreshBottomControlsCallback = new PanoramaSupportCallback() {
        @Override
        public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360) {
            if (mediaObject == mCurrentPhoto) {
                mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, isPanorama ? 1 : 0, isPanorama360 ? 1 : 0,
                        mediaObject).sendToTarget();
            }
        }
    };

    private final PanoramaSupportCallback mUpdateShareURICallback = new PanoramaSupportCallback() {
        @Override
        public void panoramaInfoAvailable(MediaObject mediaObject, boolean isPanorama, boolean isPanorama360) {
            if (mediaObject == mCurrentPhoto) {
                mHandler.obtainMessage(MSG_UPDATE_SHARE_URI, isPanorama360 ? 1 : 0, 0, mediaObject).sendToTarget();
            }
        }
    };

    public static interface Model extends PhotoView.Model {
        public void resume();

        public void pause();

        public boolean isEmpty();

        public void setCurrentPhoto(Path path, int indexHint);
    }

    private class MyMenuVisibilityListener implements OnMenuVisibilityListener {
        @Override
        public void onMenuVisibilityChanged(boolean isVisible) {
            mIsMenuVisible = isVisible;
            refreshHidingMessage();
        }
    }

    @Override
    protected int getBackgroundColorId() {
        return R.color.photo_background;
    }

    private final GLView mRootPane = new GLView() {
        @Override
        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
            mPhotoView.layout(0, 0, right - left, bottom - top);
            if (mShowDetails) {
                mDetailsHelper.layout(left, mActionBar.getHeight(), right, bottom);
            }
        }
    };

    @Override
    public void onCreate(Bundle data, Bundle restoreState) {
        super.onCreate(data, restoreState);
        mActionBar = mActivity.getGalleryActionBar();
        mSelectionManager = new SelectionManager(mActivity, false);
        mMenuExecutor = new MenuExecutor(mActivity, mSelectionManager);

        mPhotoView = new PhotoView(mActivity);
        mPhotoView.setListener(this);
        mRootPane.addComponent(mPhotoView);
        mApplication = (GalleryApp) ((Activity) mActivity).getApplication();
        mOrientationManager = mActivity.getOrientationManager();
        mActivity.getGLRoot().setOrientationSource(mOrientationManager);

        mHandler = new SynchronizedHandler(mActivity.getGLRoot()) {
            @Override
            public void handleMessage(Message message) {
                switch (message.what) {
                case MSG_HIDE_BARS: {
                    /// M: [BUG.MODIFY] @{
                    /* hideBars(); */
                    if (mIsActive) {
                        hideBars();
                    } else {
                        Log.i(TAG, "<mHandler.MSG_HIDE_BARS> mIsActive = " + mIsActive + ", not hideBars");
                    }
                    /// @}
                    break;
                }
                case MSG_REFRESH_BOTTOM_CONTROLS: {
                    if (mCurrentPhoto == message.obj && mBottomControls != null) {
                        mIsPanorama = message.arg1 == 1;
                        mIsPanorama360 = message.arg2 == 1;
                        mBottomControls.refresh();
                        fresh(mBottomControls.getContainerVisibility());
                    }
                    break;
                }
                case MSG_ON_FULL_SCREEN_CHANGED: {
                    if (mAppBridge != null) {
                        mAppBridge.onFullScreenChanged(message.arg1 == 1);
                    }
                    break;
                }
                case MSG_UPDATE_ACTION_BAR: {
                    updateBars();
                    break;
                }
                case MSG_WANT_BARS: {
                    wantBars();
                    break;
                }
                case MSG_UNFREEZE_GLROOT: {
                    mActivity.getGLRoot().unfreeze();
                    break;
                }
                case MSG_UPDATE_DEFERRED: {
                    long nextUpdate = mDeferUpdateUntil - SystemClock.uptimeMillis();
                    if (nextUpdate <= 0) {
                        mDeferredUpdateWaiting = false;
                        updateUIForCurrentPhoto();
                    } else {
                        mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, nextUpdate);
                    }
                    break;
                }
                case MSG_ON_CAMERA_CENTER: {
                    mSkipUpdateCurrentPhoto = false;
                    boolean stayedOnCamera = false;
                    if (!mPhotoView.getFilmMode()) {
                        stayedOnCamera = true;
                    } else if (SystemClock.uptimeMillis() < mCameraSwitchCutoff
                            && mMediaSet.getMediaItemCount() > 1) {
                        mPhotoView.switchToImage(1);
                    } else {
                        if (mAppBridge != null)
                            mPhotoView.setFilmMode(false);
                        stayedOnCamera = true;
                    }

                    if (stayedOnCamera) {
                        if (mAppBridge == null && mMediaSet.getTotalMediaItemCount() > 1) {
                            launchCamera();
                            /// M: [FEATURE.ADD] @{
                            mPhotoView.stopUpdateEngineData();
                            /// @}
                            /* We got here by swiping from photo 1 to the
                               placeholder, so make it be the thing that
                               is in focus when the user presses back from
                               the camera app */
                            mPhotoView.switchToImage(1);
                        } else {
                            updateBars();
                            /// M: [BUG.MODIFY] getMediaItem(0) may be null, fix JE @{
                            /*updateCurrentPhoto(mModel.getMediaItem(0));*/
                            MediaItem photo = mModel.getMediaItem(0);
                            if (photo != null) {
                                updateCurrentPhoto(photo);
                            }
                            /// @}
                        }
                    }
                    break;
                }
                case MSG_ON_PICTURE_CENTER: {
                    if (!mPhotoView.getFilmMode() && mCurrentPhoto != null
                            && (mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0) {
                        /// M: [BUG.MODIFY] @{
                        /*mPhotoView.setFilmMode(true);*/
                        showEmptyAlbumToast(Toast.LENGTH_SHORT);
                        /// @}
                    }
                    break;
                }
                case MSG_REFRESH_IMAGE: {
                    final MediaItem photo = mCurrentPhoto;
                    mCurrentPhoto = null;
                    updateCurrentPhoto(photo);
                    break;
                }
                case MSG_UPDATE_PHOTO_UI: {
                    updateUIForCurrentPhoto();
                    break;
                }
                case MSG_UPDATE_SHARE_URI: {
                    /// M: [BUG.ADD] @{
                    // never update share uri when PhotoPage is not active
                    if (!mIsActive) {
                        break;
                    }
                    /// @}
                    /// M: [BUG.MARK] @{
                    // No matter what message.obj is, we update share intent for current photo
                    /* if (mCurrentPhoto == message.obj) {*/
                    /// @}
                    boolean isPanorama360 = message.arg1 != 0;
                    Uri contentUri = mCurrentPhoto.getContentUri();
                    Intent panoramaIntent = null;
                    if (isPanorama360) {
                        panoramaIntent = createSharePanoramaIntent(contentUri);
                    }
                    Intent shareIntent = createShareIntent(mCurrentPhoto);

                    mActionBar.setShareIntents(panoramaIntent, shareIntent, PhotoPage.this);
                    setNfcBeamPushUri(contentUri);
                    /// M: [BUG.MARK] @{
                    // }
                    /// @}
                    break;
                }
                case MSG_UPDATE_PANORAMA_UI: {
                    if (mCurrentPhoto == message.obj) {
                        boolean isPanorama360 = message.arg1 != 0;
                        updatePanoramaUI(isPanorama360);
                    }
                    break;
                }
                default:
                    throw new AssertionError(message.what);
                }
            }
        };

        mSetPathString = data.getString(KEY_MEDIA_SET_PATH);
        /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
        mLaunchFromCamera = data.getBoolean(KEY_LAUNCH_FROM_CAMERA, false);
        /// @}
        /// M: [BUG.MODIFY] @{
        // if there is mSetPathString, view is not read only, enable edit
        /*mReadOnlyView = data.getBoolean(KEY_READONLY);*/
        mReadOnlyView = data.getBoolean(KEY_READONLY) && (mSetPathString == null || mSetPathString.equals(""));
        Log.i(TAG, "<onCreate> mSetPathString = " + mSetPathString + ", mReadOnlyView = " + mReadOnlyView);
        /// @}
        mOriginalSetPathString = mSetPathString;
        setupNfcBeamPush();
        String itemPathString = data.getString(KEY_MEDIA_ITEM_PATH);
        Path itemPath = itemPathString != null ? Path.fromString(data.getString(KEY_MEDIA_ITEM_PATH)) : null;
        mTreatBackAsUp = data.getBoolean(KEY_TREAT_BACK_AS_UP, false);
        mStartInFilmstrip = data.getBoolean(KEY_START_IN_FILMSTRIP, false);
        boolean inCameraRoll = data.getBoolean(KEY_IN_CAMERA_ROLL, false);
        mCurrentIndex = data.getInt(KEY_INDEX_HINT, 0);
        if (mSetPathString != null) {
            mShowSpinner = true;
            /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
            // Launch from secure camera
            if (!mSetPathString.equals("/local/all/0") && SecureSource.isSecurePath(mSetPathString)) {
                Log.d(TAG, "<onCreate> secure album");
                mFlags |= FLAG_SHOW_WHEN_LOCKED;
                mSecureAlbum = (SecureAlbum) mActivity.getDataManager().getMediaSet(mSetPathString);
                mSecureAlbum.clearAll();
                ArrayList<String> secureAlbum = (ArrayList<String>) data.getSerializable(SECURE_ALBUM);
                if (secureAlbum != null) {
                    int albumCount = secureAlbum.size();
                    Log.d(TAG, "<onCreate> albumCount " + albumCount);
                    for (int i = 0; i < albumCount; i++) {
                        try {
                            String[] albumItem = secureAlbum.get(i).split("\\+");
                            int albumItemSize = albumItem.length;
                            Log.d(TAG, "<onCreate> albumItemSize " + albumItemSize);
                            if (albumItemSize == 2) {
                                int id = Integer.parseInt(albumItem[0].trim());
                                boolean isVideo = Boolean.parseBoolean(albumItem[1].trim());
                                Log.d(TAG, "<onCreate> secure item : id " + id + ", isVideo " + isVideo);
                                mSecureAlbum.addMediaItem(isVideo, id);
                            }
                        } catch (NullPointerException ex) {
                            Log.e(TAG, "<onCreate> exception " + ex);
                        } catch (PatternSyntaxException ex) {
                            Log.e(TAG, "<onCreate> exception " + ex);
                        } catch (NumberFormatException ex) {
                            Log.e(TAG, "<onCreate> exception " + ex);
                        }
                    }
                }
                mShowSpinner = false;
                mSetPathString = "/filter/empty/{" + mSetPathString + "}";
                mSetPathString = "/combo/item/{" + mSetPathString + "}";
            }
            /// @}
            mAppBridge = (AppBridge) data.getParcelable(KEY_APP_BRIDGE);
            if (mAppBridge != null) {
                mShowBars = false;
                mHasCameraScreennailOrPlaceholder = true;
                mAppBridge.setServer(this);

                // Get the ScreenNail from AppBridge and register it.
                int id = SnailSource.newId();
                Path screenNailSetPath = SnailSource.getSetPath(id);
                Path screenNailItemPath = SnailSource.getItemPath(id);
                mScreenNailSet = (SnailAlbum) mActivity.getDataManager().getMediaObject(screenNailSetPath);
                mScreenNailItem = (SnailItem) mActivity.getDataManager().getMediaObject(screenNailItemPath);
                mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());

                if (data.getBoolean(KEY_SHOW_WHEN_LOCKED, false)) {
                    // Set the flag to be on top of the lock screen.
                    mFlags |= FLAG_SHOW_WHEN_LOCKED;
                }
                // Don't display "empty album" action item for capture intents.
                if (!mSetPathString.equals("/local/all/0")) {
                    // Check if the path is a secure album.
                    if (SecureSource.isSecurePath(mSetPathString)) {
                        mSecureAlbum = (SecureAlbum) mActivity.getDataManager().getMediaSet(mSetPathString);
                        mShowSpinner = false;
                    }
                    mSetPathString = "/filter/empty/{" + mSetPathString + "}";
                }

                // Combine the original MediaSet with the one for ScreenNail
                // from AppBridge.
                mSetPathString = "/combo/item/{" + screenNailSetPath + "," + mSetPathString + "}";

                // Start from the screen nail.
                itemPath = screenNailItemPath;
                /// M: [FEATURE.MARK] [Camera independent from Gallery] @{
                // After camera is removed from gallery, modify the behavior as below:
                // When view the first image in camera folder, slide to left,
                // there is no place holder of camera, and it can not launch camera too.
                /*} else if (inCameraRoll && GalleryUtils.isCameraAvailable(mActivity)) {
                      mSetPathString = "/combo/item/{" + FilterSource.FILTER_CAMERA_SHORTCUT +
                  "," + mSetPathString + "}";
                      mCurrentIndex++;
                      mHasCameraScreennailOrPlaceholder = true;*/
                /// @}
                /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
                // When launch from camera, and not from secure camera, we show empty item
                // after delete all images.
            } else if (mLaunchFromCamera && mSecureAlbum == null) {
                mSetPathString = "/filter/empty/{" + mSetPathString + "}";
                Log.i(TAG, "<onCreate> launch from camera, not secure, mSetPathString = " + mSetPathString);
                /// @}
            }

            MediaSet originalSet = mActivity.getDataManager().getMediaSet(mSetPathString);
            if (mHasCameraScreennailOrPlaceholder && originalSet instanceof ComboAlbum) {
                // Use the name of the camera album rather than the default
                // ComboAlbum behavior
                ((ComboAlbum) originalSet).useNameOfChild(1);
            }
            /// M: [BUG.ADD] @{
            // tell PhotoView whether this album is cluster
            if (originalSet != null && originalSet instanceof ClusterAlbum) {
                mPhotoView.setIsCluster(true);
            } else {
                mPhotoView.setIsCluster(false);
            }
            /// @}
            mSelectionManager.setSourceMediaSet(originalSet);
            mSetPathString = "/filter/delete/{" + mSetPathString + "}";
            mMediaSet = (FilterDeleteSet) mActivity.getDataManager().getMediaSet(mSetPathString);
            if (mMediaSet == null) {
                Log.w(TAG, "failed to restore " + mSetPathString);
            }
            if (itemPath == null) {
                int mediaItemCount = mMediaSet.getMediaItemCount();
                if (mediaItemCount > 0) {
                    if (mCurrentIndex >= mediaItemCount)
                        mCurrentIndex = 0;
                    itemPath = mMediaSet.getMediaItem(mCurrentIndex, 1).get(0).getPath();
                } else {
                    // Bail out, PhotoPage can't load on an empty album
                    return;
                }
            }
            PhotoDataAdapter pda = new PhotoDataAdapter(mActivity, mPhotoView, mMediaSet, itemPath, mCurrentIndex,
                    mAppBridge == null ? -1 : 0, mAppBridge == null ? false : mAppBridge.isPanorama(),
                    mAppBridge == null ? false : mAppBridge.isStaticCamera());
            mModel = pda;
            mPhotoView.setModel(mModel);

            pda.setDataListener(new PhotoDataAdapter.DataListener() {

                @Override
                public void onPhotoChanged(int index, Path item) {
                    int oldIndex = mCurrentIndex;
                    mCurrentIndex = index;
                    if (mHasCameraScreennailOrPlaceholder) {
                        if (mCurrentIndex > 0) {
                            mSkipUpdateCurrentPhoto = false;
                        }

                        /// M: [FEATURE.MODIFY] @{
                        /*if (oldIndex == 0 && mCurrentIndex > 0
                         && !mPhotoView.getFilmMode()) {
                         mPhotoView.setFilmMode(true);*/
                        if (oldIndex == 0 && mCurrentIndex > 0) {
                            onActionBarAllowed(true);
                            mPhotoView.setFilmMode(false);
                            /// @}
                            if (mAppBridge != null) {
                                UsageStatistics.onEvent("CameraToFilmstrip", UsageStatistics.TRANSITION_SWIPE,
                                        null);
                            }
                        } else if (oldIndex == 2 && mCurrentIndex == 1) {
                            mCameraSwitchCutoff = SystemClock.uptimeMillis() + CAMERA_SWITCH_CUTOFF_THRESHOLD_MS;
                            mPhotoView.stopScrolling();
                        } else if (oldIndex >= 1 && mCurrentIndex == 0) {
                            mPhotoView.setWantPictureCenterCallbacks(true);
                            mSkipUpdateCurrentPhoto = true;
                        }
                    }
                    if (!mSkipUpdateCurrentPhoto) {
                        if (item != null) {
                            MediaItem photo = mModel.getMediaItem(0);
                            if (photo != null)
                                updateCurrentPhoto(photo);
                        }
                        updateBars();
                    }
                    // Reset the timeout for the bars after a swipe
                    /// M: [DEBUG.ADD] @{
                    Log.i(TAG, "<onPhotoChanged> refreshHidingMessage");
                    /// @}
                    refreshHidingMessage();
                }

                @Override
                public void onLoadingFinished(boolean loadingFailed) {
                    /// M: [BUG.ADD] @{
                    mLoadingFinished = true;
                    // Refresh bottom controls when data loading done
                    refreshBottomControlsWhenReady();
                    /// @}
                    if (!mModel.isEmpty()) {
                        MediaItem photo = mModel.getMediaItem(0);
                        if (photo != null)
                            updateCurrentPhoto(photo);
                    } else if (mIsActive) {
                        // We only want to finish the PhotoPage if there is no
                        // deletion that the user can undo.
                        if (mMediaSet.getNumberOfDeletions() == 0) {
                            /// M: [BUG.ADD] pause PhotoView before finish PhotoPage @{
                            mPhotoView.pause();
                            /// @}
                            mActivity.getStateManager().finishState(PhotoPage.this);
                        }
                    }
                }

                @Override
                public void onLoadingStarted() {
                    /// M: [BUG.ADD] @{
                    mLoadingFinished = false;
                    /// @}
                }
            });
        } else {
            // Get default media set by the URI
            MediaItem mediaItem = (MediaItem) mActivity.getDataManager().getMediaObject(itemPath);
            /// M: [BUG.ADD] fix JE when mediaItem is deleted@{
            if (mediaItem == null) {
                Toast.makeText(((Activity) mActivity), R.string.no_such_item, Toast.LENGTH_LONG).show();
                mPhotoView.pause();
                mActivity.getStateManager().finishState(this);
                return;
            }
            /// @}
            /// M: [BUG.ADD] @{
            // no PhotoDataAdapter style loading in SinglePhotoDataAdapter
            mLoadingFinished = true;
            /// @}
            mModel = new SinglePhotoDataAdapter(mActivity, mPhotoView, mediaItem);
            mPhotoView.setModel(mModel);
            updateCurrentPhoto(mediaItem);
            mShowSpinner = false;
        }

        mPhotoView.setFilmMode(mStartInFilmstrip && mMediaSet.getMediaItemCount() > 1);
        RelativeLayout galleryRoot = (RelativeLayout) ((Activity) mActivity)
                .findViewById(mAppBridge != null ? R.id.content : R.id.gallery_root);
        if (galleryRoot != null) {
            if (mSecureAlbum == null) {
                mBottomControls = new PhotoPageBottomControls(this, mActivity, galleryRoot);
            }
        }
        /// M: [BUG.MODIFY] set change listener to current GLRootView @{
        // onResume also need to set this listener, so modify it.
        /*((GLRootView) mActivity.getGLRoot()).setOnSystemUiVisibilityChangeListener(
            new View.OnSystemUiVisibilityChangeListener() {
            @Override
                public void onSystemUiVisibilityChange(int visibility) {
                    int diff = mLastSystemUiVis ^ visibility;
                    mLastSystemUiVis = visibility;
                    if ((diff & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
                            && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                        /// M: [BUG.MODIFY] Don't need show bars in camera preview. @{
                        /*showBars();*/
        /*wantBars();
        /// @}
        }
        }
        });*/
        setOnSystemUiVisibilityChangeListener();
        /// @}
        /// M: [FEATURE.ADD] VTSP: share as video @{
        initAnimatedContentSharer();
        /// @}
        /// M: [FEATURE.ADD] add backward controller for layer @{
        mPhotoView.setBackwardControllerForLayerManager(mBackwardContollerForLayer);
        /// @}
    }

    @Override
    public void onPictureCenter(boolean isCamera) {
        isCamera = isCamera || (mHasCameraScreennailOrPlaceholder && mAppBridge == null);
        mPhotoView.setWantPictureCenterCallbacks(false);
        mHandler.removeMessages(MSG_ON_CAMERA_CENTER);
        mHandler.removeMessages(MSG_ON_PICTURE_CENTER);
        mHandler.sendEmptyMessage(isCamera ? MSG_ON_CAMERA_CENTER : MSG_ON_PICTURE_CENTER);
    }

    @Override
    public boolean canDisplayBottomControls() {
        /// M: [FEATURE.MODIFY] @{
        /* return mIsActive && !mPhotoView.canUndo();
         */
        boolean visible = mIsActive && !mPhotoView.canUndo() && mShowBottomControls;
        return visible;
        /// @}
    }

    @Override
    public boolean canDisplayBottomControl(int control) {
        if (mCurrentPhoto == null) {
            return false;
        }
        switch (control) {
        case R.id.photopage_bottom_control_edit:
            return mHaveImageEditor && mShowBars && !mReadOnlyView && !mPhotoView.getFilmMode()
                    && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_EDIT) != 0
                    && mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE;
        case R.id.photopage_bottom_control_panorama:
            return mIsPanorama;
        case R.id.photopage_bottom_control_tiny_planet:
            return mHaveImageEditor && mShowBars && mIsPanorama360 && !mPhotoView.getFilmMode();
        default:
            return false;
        }
    }

    @Override
    public void onBottomControlClicked(int control) {
        switch (control) {
        case R.id.photopage_bottom_control_edit:
            /// M: [BUG.ADD] disable editing photo when file not exists or sdcard is full. @{
            if (mModel == null) {
                return;
            }

            MediaItem current = mModel.getMediaItem(0);
            if (current == null) {
                return;
            }

            File srcFile = new File(current.getFilePath());
            if (!srcFile.exists()) {
                Log.i(TAG, "<onBottomControlClicked> abort editing photo when not exists!");
                return;
            }
            if (!isSpaceEnough(srcFile)) {
                Log.i(TAG, "<onBottomControlClicked> no enough space, abort edit");
                Toast.makeText(mActivity, mActivity.getString(R.string.storage_not_enough), Toast.LENGTH_SHORT)
                        .show();
                return;
            }
            /// @}
            launchPhotoEditor();
            return;
        case R.id.photopage_bottom_control_panorama:
            mActivity.getPanoramaViewHelper().showPanorama(mCurrentPhoto.getContentUri());
            return;
        case R.id.photopage_bottom_control_tiny_planet:
            launchTinyPlanet();
            return;
        default:
            return;
        }
    }

    @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
    private void setupNfcBeamPush() {
        if (!ApiHelper.HAS_SET_BEAM_PUSH_URIS)
            return;

        NfcAdapter adapter = NfcAdapter.getDefaultAdapter(mActivity);
        if (adapter != null) {
            adapter.setBeamPushUris(null, mActivity);
            /// M: [BEHAVIOR.MODIFY] nfc @{
            if (FeatureConfig.SUPPORT_MTK_BEAM_PLUS) {
                adapter.setMtkBeamPushUrisCallback(new CreateBeamUrisCallback() {
                    @Override
                    public Uri[] createBeamUris(NfcEvent event) {
                        return mNfcPushUris;
                    }
                }, mActivity);
            } else {
                adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
                    @Override
                    public Uri[] createBeamUris(NfcEvent event) {
                        return mNfcPushUris;
                    }
                }, mActivity);
            }
            /*adapter.setBeamPushUrisCallback(new CreateBeamUrisCallback() {
             @Override
             public Uri[] createBeamUris(NfcEvent event) {
             return mNfcPushUris;
             }
             }, mActivity);*/
            /// @}
        }
    }

    private void setNfcBeamPushUri(Uri uri) {
        /// M: [BUG.MODIFY] @{
        /*mNfcPushUris[0] = uri;*/
        if (mShareUriFromChooserView != null) {
            mNfcPushUris[0] = mShareUriFromChooserView;
            mShareUriFromChooserView = null;
        } else {
            mNfcPushUris[0] = uri;
        }
        Log.d(TAG, "<setNfcBeamPushUri> uri " + mNfcPushUris[0]);
        /// @}
    }

    private static Intent createShareIntent(MediaObject mediaObject) {
        int type = mediaObject.getMediaType();
        return new Intent(Intent.ACTION_SEND).setType(MenuExecutor.getMimeType(type))
                .putExtra(Intent.EXTRA_STREAM, mediaObject.getContentUri())
                .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }

    private static Intent createSharePanoramaIntent(Uri contentUri) {
        return new Intent(Intent.ACTION_SEND).setType(GalleryUtils.MIME_TYPE_PANORAMA360)
                .putExtra(Intent.EXTRA_STREAM, contentUri).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }

    private void overrideTransitionToEditor() {
        ((Activity) mActivity).overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
    }

    private void launchTinyPlanet() {
        // Deep link into tiny planet
        MediaItem current = mModel.getMediaItem(0);
        Intent intent = new Intent(FilterShowActivity.TINY_PLANET_ACTION);
        intent.setClass(mActivity, FilterShowActivity.class);
        intent.setDataAndType(current.getContentUri(), current.getMimeType())
                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN, mActivity.isFullscreen());
        mActivity.startActivityForResult(intent, REQUEST_EDIT);
        overrideTransitionToEditor();
    }

    private void launchCamera() {
        mRecenterCameraOnResume = false;
        GalleryUtils.startCameraActivity(mActivity);
    }

    private void launchPhotoEditor() {
        /// M: [BUG.ADD] abort editing photo if loading fail @{
        if (mModel != null && mModel.getLoadingState(0) == PhotoView.Model.LOADING_FAIL) {
            Log.i(TAG, "<launchPhotoEditor> abort editing photo if loading fail!");
            Toast.makeText(mActivity, mActivity.getString(R.string.cannot_load_image), Toast.LENGTH_SHORT).show();
            return;
        }
        /// @}
        MediaItem current = mModel.getMediaItem(0);
        if (current == null || (current.getSupportedOperations() & MediaObject.SUPPORT_EDIT) == 0) {
            return;
        }

        Intent intent = new Intent(ACTION_NEXTGEN_EDIT);

        /// M: [BUG.MODIFY] create new task when launch photo editor from camera
        // gallery and photo editor use same task stack @{
        /*
        intent.setDataAndType(current.getContentUri(), current.getMimeType())
              .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        */
        intent.setDataAndType(current.getContentUri(), current.getMimeType())
                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_ACTIVITY_CLEAR_TOP
                        | Intent.FLAG_ACTIVITY_NEW_TASK);
        /// @}
        if (mActivity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
                .size() == 0) {
            intent.setAction(Intent.ACTION_EDIT);
        }
        intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN, mActivity.isFullscreen());
        /// M: [FEATURE.ADD] @{
        // for special image, no need to delete origin image when save, such as continuous shot
        ExtItem extItem = current.getExtItem();
        if (extItem != null && !extItem.isDeleteOriginFileAfterEdit()) {
            // if current photo is last image in continuous shot group, not
            // set NEED_SAVE_AS as true
            if (mModel instanceof PhotoDataAdapter) {
                int size = ((PhotoDataAdapter) mModel).getTotalCount();
                MediaData md = current.getMediaData();
                if (size == 1 && md.mediaType == MediaData.MediaType.NORMAL
                        && md.subType == MediaData.SubType.CONSHOT) {
                    intent.putExtra(FilterShowActivity.NEED_SAVE_AS, false);
                    Log.i(TAG, "<launchPhotoEditor> edit the last image in continuous shot group,"
                            + " not set NEED_SAVE_AS as true");
                } else {
                    intent.putExtra(FilterShowActivity.NEED_SAVE_AS, true);
                }
            } else {
                intent.putExtra(FilterShowActivity.NEED_SAVE_AS, true);
            }
        }
        /// @}

        /// M: [BUG.MODIFY] @{
        // Make ChooserActivity and GalleryActivity in different tasks.
        /*
         * ((Activity)mActivity).startActivityForResult(Intent.createChooser(intent
         * , null), REQUEST_EDIT);
         */
        ((Activity) mActivity).startActivityForResult(
                Intent.createChooser(intent, null).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK), REQUEST_EDIT);
        /// @}

        overrideTransitionToEditor();
    }

    private void launchSimpleEditor() {
        MediaItem current = mModel.getMediaItem(0);
        if (current == null || (current.getSupportedOperations() & MediaObject.SUPPORT_EDIT) == 0) {
            return;
        }

        Intent intent = new Intent(ACTION_SIMPLE_EDIT);

        intent.setDataAndType(current.getContentUri(), current.getMimeType())
                .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        if (mActivity.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY)
                .size() == 0) {
            intent.setAction(Intent.ACTION_EDIT);
        }
        intent.putExtra(FilterShowActivity.LAUNCH_FULLSCREEN, mActivity.isFullscreen());
        ((Activity) mActivity).startActivityForResult(Intent.createChooser(intent, null), REQUEST_EDIT);
        overrideTransitionToEditor();
    }

    private void requestDeferredUpdate() {
        mDeferUpdateUntil = SystemClock.uptimeMillis() + DEFERRED_UPDATE_MS;
        if (!mDeferredUpdateWaiting) {
            mDeferredUpdateWaiting = true;
            mHandler.sendEmptyMessageDelayed(MSG_UPDATE_DEFERRED, DEFERRED_UPDATE_MS);
        }
    }

    private void updateUIForCurrentPhoto() {
        if (mCurrentPhoto == null)
            return;

        // If by swiping or deletion the user ends up on an action item
        // and zoomed in, zoom out so that the context of the action is
        // more clear
        if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_ACTION) != 0
                && !mPhotoView.getFilmMode()) {
            mPhotoView.setWantPictureCenterCallbacks(true);
        }
        /// M: [BUG.ADD] @{
        // To avoid share old file, set share intent here
        if (mIsActive && !(mCurrentPhoto instanceof SnailItem || mCurrentPhoto instanceof EmptyAlbumImage)) {
            Intent shareIntent = createShareIntent(mCurrentPhoto);
            mActionBar.setShareIntents(null, shareIntent, PhotoPage.this);
        }
        /// @}
        updateMenuOperations();
        refreshBottomControlsWhenReady();
        if (mShowDetails) {
            mDetailsHelper.reloadDetails();
        }
        if ((mSecureAlbum == null) && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_SHARE) != 0) {
            mCurrentPhoto.getPanoramaSupport(mUpdateShareURICallback);
        }
        /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
        // After delete all medias in camera folder, show EmptyAlbumImage,
        // set film mode as false forced.
        if (mLaunchFromCamera && mCurrentPhoto != null
                && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_BACK) != 0) {
            mPhotoView.setFilmMode(false);
        }
        /// @}
        /// M: [BEHAVIOR.ADD] @{
        updateScaleGesture();
        /// @}
    }

    private void updateCurrentPhoto(MediaItem photo) {
        /// M: [BUG.MODIFY] @{
        /*if (mCurrentPhoto == photo) return;*/
        // Modify for update support operation menu display
        // if photo.getDataVersion() != mCurrentVersion, means the mediaItem has been updated
        if (mCurrentPhoto == photo && photo.getDataVersion() == mCurrentVersion) {
            return;
        }
        mCurrentVersion = photo.getDataVersion();
        /// @}
        mCurrentPhoto = photo;
        if (mPhotoView.getFilmMode()) {
            requestDeferredUpdate();
        } else {
            updateUIForCurrentPhoto();
        }
    }

    private void updateMenuOperations() {
        Menu menu = mActionBar.getMenu();

        // it could be null if onCreateActionBar has not been called yet
        if (menu == null)
            return;

        MenuItem item = menu.findItem(R.id.action_slideshow);
        if (item != null) {
            item.setVisible((mSecureAlbum == null) && canDoSlideShow());
        }
        if (mCurrentPhoto == null)
            return;

        int supportedOperations = mCurrentPhoto.getSupportedOperations();
        if (mReadOnlyView) {
            /// M: [BUG.MODIFY] @{
            /*supportedOperations ^= MediaObject.SUPPORT_EDIT;*/
            // when mReadOnlyView == true, set SUPPORT_EDIT as false
            supportedOperations &= ~MediaObject.SUPPORT_EDIT;
            /// @}
        }
        if (mSecureAlbum != null) {
            supportedOperations &= MediaObject.SUPPORT_DELETE;
        } else {
            mCurrentPhoto.getPanoramaSupport(mUpdatePanoramaMenuItemsCallback);
            if (!mHaveImageEditor) {
                supportedOperations &= ~MediaObject.SUPPORT_EDIT;
            }
        }
        /// M: [FEATURE.ADD] @{
        if (MediaObject.MEDIA_TYPE_IMAGE == mCurrentPhoto.getMediaType()) {
            if (true == FeatureConfig.SUPPORT_PQ) {
                supportedOperations |= MediaObject.SUPPORT_PQ;
            }
            if (true == FeatureConfig.SUPPORT_IMAGE_DC_ENHANCE) {
                supportedOperations |= MediaObject.SUPPORT_DC;
                MenuItem dcItem = menu.findItem(R.id.m_action_image_dc);
                ImageDC.setMenuItemTile(dcItem);
            }
        }
        /// @}
        /// M: [BUG.ADD] KK native judge mime type, no need AP judge @{
        if (mCurrentPhoto.getMimeType() == null) {
            supportedOperations &= ~MediaObject.SUPPORT_TRIM;
        }
        /// @}
        /// M: [BUG.ADD] @{
        // reget print system operation
        PrintHelper printHelper = new PrintHelper(mActivity.getAndroidContext());
        if (!printHelper.systemSupportsPrint()) {
            supportedOperations &= ~MediaObject.SUPPORT_PRINT;
        }
        /// @}
        /// M: [BUG.ADD] can not set as wallpaper when no thumbnail @{
        mSupportedOperations = supportedOperations;
        /// @}
        MenuExecutor.updateMenuOperation(menu, supportedOperations);

        /// M: [FEATURE.ADD] HotKnot @{
        boolean canShareByHotKnot = (supportedOperations & MediaObject.SUPPORT_SHARE) != 0;
        mActivity.getHotKnot().updateMenu(menu, R.id.action_share, R.id.action_hotknot, canShareByHotKnot);
        /// @}
        /// M: [BUG.ADD] supported operations is zero(camera preview), close menu @{
        if (supportedOperations == 0) {
            menu.close();
        }
        /// @}
    }

    private boolean canDoSlideShow() {
        if (mMediaSet == null || mCurrentPhoto == null) {
            return false;
        }
        if (mCurrentPhoto.getMediaType() != MediaObject.MEDIA_TYPE_IMAGE) {
            return false;
        }
        return true;
    }

    //////////////////////////////////////////////////////////////////////////
    //  Action Bar show/hide management
    //////////////////////////////////////////////////////////////////////////

    private void showBars() {
        /// M: [PERF.ADD] for performance auto test@{
        if (mDisableBarChanges) {
            return;
        }
        /// @}
        /// M: [BUG.ADD] automatic layer visibility change @{
        onActionBarVisibilityChange(true);
        /// @}
        if (mShowBars)
            return;
        mShowBars = true;
        mOrientationManager.unlockOrientation();
        mActionBar.show();
        mActivity.getGLRoot().setLightsOutMode(false);
        /// M: [FEATURE.MODIFY] automatic layer visibility change @{
        // refreshHidingMessage();
        if (mAllowAutoHideByHost) {
            refreshHidingMessage();
        }
        /// @}
        refreshBottomControlsWhenReady();
    }

    private void hideBars() {
        /// M: [DEBUG.ADD] for performance auto test@{
        if (mDisableBarChanges) {
            return;
        }
        /// @}
        /// M: [FEATURE.ADD] automatic layer visibility change @{
        onActionBarVisibilityChange(false);
        /// @}
        if (!mShowBars)
            return;
        mShowBars = false;
        mActionBar.hide();
        mActivity.getGLRoot().setLightsOutMode(true);
        mHandler.removeMessages(MSG_HIDE_BARS);
        refreshBottomControlsWhenReady();
    }

    private void refreshHidingMessage() {
        mHandler.removeMessages(MSG_HIDE_BARS);
        if (!mIsMenuVisible && !mPhotoView.getFilmMode()) {
            mHandler.sendEmptyMessageDelayed(MSG_HIDE_BARS, HIDE_BARS_TIMEOUT);
        }
    }

    private boolean canShowBars() {
        // No bars if we are showing camera preview.
        if (mAppBridge != null && mCurrentIndex == 0 && !mPhotoView.getFilmMode())
            return false;

        // No bars if it's not allowed.
        if (!mActionBarAllowed)
            return false;

        Configuration config = mActivity.getResources().getConfiguration();
        if (config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH) {
            return false;
        }

        return true;
    }

    private void wantBars() {
        if (canShowBars())
            showBars();
    }

    private void toggleBars() {
        if (mShowBars) {
            hideBars();
        } else {
            if (canShowBars())
                showBars();
        }
    }

    private void updateBars() {
        if (!canShowBars()) {
            hideBars();
        }
        /// M: [BEHAVIOR.ADD] @{
        // Show title at the action bar
        updateActionBarTitle();
        /// @}
    }

    @Override
    protected void onBackPressed() {
        /// M: [BUG.MODIFY] don't need show bars in camera preview @{
        /*showBars();*/
        wantBars();
        /// @}
        /// M: [FEATURE.ADD] add for Stereo feature @{
        if (mPhotoView != null && mPhotoView.onBackPressed()) {
            return;
        }
        /// @}
        if (mShowDetails) {
            hideDetails();
        } else if (mAppBridge == null || !switchWithCaptureAnimation(-1)) {
            // We are leaving this page. Set the result now.
            setResult();
            if (mStartInFilmstrip && !mPhotoView.getFilmMode()) {
                mPhotoView.setFilmMode(true);
            } else if (mTreatBackAsUp) {
                onUpPressed();
            } else {
                super.onBackPressed();
            }
        }
    }

    private void onUpPressed() {
        if ((mStartInFilmstrip || mAppBridge != null) && !mPhotoView.getFilmMode()) {
            mPhotoView.setFilmMode(true);
            return;
        }

        /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
        // After delete all medias in camera folder, press up key,
        // it will exit PhotoPage, and return to camera
        if (mLaunchFromCamera && mCurrentPhoto != null
                && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_BACK) != 0) {
            super.onBackPressed();
            return;
        }
        // After there is only one image in camera folder or more, press up key,
        // it will switch to film strip mode
        if (mLaunchFromCamera && mMediaSet.getMediaItemCount() >= 1 && !mPhotoView.getFilmMode()) {
            mPhotoView.setFilmMode(true);
            return;
        }
        /// @}

        if (mActivity.getStateManager().getStateCount() > 1) {
            setResult();
            super.onBackPressed();
            return;
        }

        if (mOriginalSetPathString == null)
            return;

        /// M: [FEATURE.MODIFY] [Camera independent from Gallery] @{
        // Launch from camera, and press up key, enter GalleryActivity directly
        /*if (mAppBridge == null) {*/
        if (mAppBridge == null && !mLaunchFromCamera) {
            /// @}
            // We're in view mode so set up the stacks on our own.
            Bundle data = new Bundle(getData());
            data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
            data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
                    mActivity.getDataManager().getTopSetPath(DataManager.INCLUDE_ALL));
            /// M: [FEATURE.ADD] @{
            if (null == mCurrentPhoto) {
                mActivity.getStateManager().switchState(this, AlbumPage.class, data);
                return;
            }
            MediaData md = mCurrentPhoto.getMediaData();
            if (md != null && md.mediaType == MediaData.MediaType.NORMAL
                    && md.subType == MediaData.SubType.CONSHOT) {
                PlatformHelper.switchToContainerPage(mActivity, md, false, data);
                return;
            } else {
                /// @}
                mActivity.getStateManager().switchState(this, AlbumPage.class, data);
            }
        } else {
            GalleryUtils.startGalleryActivity(mActivity);
        }
    }

    private void setResult() {
        Intent result = null;
        result = new Intent();
        result.putExtra(KEY_RETURN_INDEX_HINT, mCurrentIndex);
        setStateResult(Activity.RESULT_OK, result);
    }

    //////////////////////////////////////////////////////////////////////////
    //  AppBridge.Server interface
    //////////////////////////////////////////////////////////////////////////

    @Override
    public void setCameraRelativeFrame(Rect frame) {
        mPhotoView.setCameraRelativeFrame(frame);
    }

    @Override
    public boolean switchWithCaptureAnimation(int offset) {
        return mPhotoView.switchWithCaptureAnimation(offset);
    }

    @Override
    public void setSwipingEnabled(boolean enabled) {
        mPhotoView.setSwipingEnabled(enabled);
    }

    @Override
    public void notifyScreenNailChanged() {
        mScreenNailItem.setScreenNail(mAppBridge.attachScreenNail());
        mScreenNailSet.notifyChange();
    }

    @Override
    public void addSecureAlbumItem(boolean isVideo, int id) {
        mSecureAlbum.addMediaItem(isVideo, id);
    }

    @Override
    protected boolean onCreateActionBar(Menu menu) {
        mActionBar.createActionBarMenu(R.menu.photo, menu);
        /// M: [FEATURE.ADD] menu extension @{
        mPhotoView.onCreateOptionsMenu(menu);
        /// @}
        mHaveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
        /// M: [FEATURE.ADD] HotKnot @{
        mActivity.getHotKnot().updateMenu(menu, R.id.action_share, R.id.action_hotknot, false);
        /// @}
        updateMenuOperations();
        /// M: [BUG.MODIFY] @{
        /* mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : ""); */
        // Show title at the action bar
        updateActionBarTitle();
        /// @}
        return true;
    }

    private MenuExecutor.ProgressListener mConfirmDialogListener = new MenuExecutor.ProgressListener() {
        @Override
        public void onProgressUpdate(int index) {
        }

        @Override
        public void onProgressComplete(int result) {
        }

        @Override
        public void onConfirmDialogShown() {
            mHandler.removeMessages(MSG_HIDE_BARS);
        }

        @Override
        public void onConfirmDialogDismissed(boolean confirmed) {
            refreshHidingMessage();
        }

        @Override
        public void onProgressStart() {
        }
    };

    private void switchToGrid() {
        /// M: [BUG.MODIFY] @{
        // For case 1: AlbumSetPage >> AlbumPage >> AlbumSetPage >> (AlbumPage , PhotoPage)
        // For case 2: ContainerPage >> PhotoPage >> film mode >> grid mode
        /*  if (mActivity.getStateManager().hasStateClass(AlbumPage.class)) {
         */
        if (mActivity.getStateManager().hasStateClassInNearPosition(AlbumPage.class)
                || mActivity.getStateManager().hasStateClassInNearPosition(ContainerPage.class)) {
            /// @}
            onUpPressed();
        } else {
            if (mOriginalSetPathString == null)
                return;
            Bundle data = new Bundle(getData());
            data.putString(AlbumPage.KEY_MEDIA_PATH, mOriginalSetPathString);
            data.putString(AlbumPage.KEY_PARENT_MEDIA_PATH,
                    mActivity.getDataManager().getTopSetPath(DataManager.INCLUDE_ALL));

            // We only show cluster menu in the first AlbumPage in stack
            // TODO: Enable this when running from the camera app
            boolean inAlbum = mActivity.getStateManager().hasStateClass(AlbumPage.class);
            data.putBoolean(AlbumPage.KEY_SHOW_CLUSTER_MENU, !inAlbum && mAppBridge == null);

            data.putBoolean(PhotoPage.KEY_APP_BRIDGE, mAppBridge != null);

            // Account for live preview being first item
            mActivity.getTransitionStore().put(KEY_RETURN_INDEX_HINT,
                    mAppBridge != null ? mCurrentIndex - 1 : mCurrentIndex);

            if (mHasCameraScreennailOrPlaceholder && mAppBridge != null) {
                mActivity.getStateManager().startState(AlbumPage.class, data);
            } else {
                mActivity.getStateManager().switchState(this, AlbumPage.class, data);
            }
        }
    }

    @Override
    protected boolean onItemSelected(MenuItem item) {
        if (mModel == null)
            return true;
        refreshHidingMessage();
        MediaItem current = mModel.getMediaItem(0);

        // This is a shield for monkey when it clicks the action bar
        // menu when transitioning from filmstrip to camera
        if (current instanceof SnailItem)
            return true;
        // TODO: We should check the current photo against the MediaItem
        // that the menu was initially created for. We need to fix this
        // after PhotoPage being refactored.
        if (current == null) {
            // item is not ready, ignore
            return true;
        }
        int currentIndex = mModel.getCurrentIndex();
        Path path = current.getPath();

        DataManager manager = mActivity.getDataManager();
        int action = item.getItemId();
        /// M: [BUG.ADD] show toast before PhotoDataAdapter finishing loading to avoid JE @{
        if (action != android.R.id.home && !mLoadingFinished && mSetPathString != null) {
            Toast.makeText(mActivity, mActivity.getString(R.string.please_wait), Toast.LENGTH_SHORT).show();
            return true;
        }
        /// @}
        String confirmMsg = null;
        switch (action) {
        case android.R.id.home: {
            onUpPressed();
            return true;
        }
        case R.id.action_slideshow: {
            Bundle data = new Bundle();
            /// M: [BUG.MODIFY] fix bug: slideshow doesn't play again
            // when finish playing the last picture @{
            String mediaSetPath = mMediaSet.getPath().toString();
            if (mSnailSetPath != null) {
                mediaSetPath = mediaSetPath.replace(mSnailSetPath + ",", "");
                Log.i(TAG, "<onItemSelected> action_slideshow | mediaSetPath: " + mediaSetPath);
            }
            /*data.putString(SlideshowPage.KEY_SET_PATH, mMediaSet.getPath().toString());*/
            data.putString(SlideshowPage.KEY_SET_PATH, mediaSetPath);
            /// @}
            data.putString(SlideshowPage.KEY_ITEM_PATH, path.toString());
            /// M: [BUG.ADD] currentIndex-- if it is in camera folder @{
            if (mHasCameraScreennailOrPlaceholder) {
                currentIndex--;
            }
            /// @}

            data.putInt(SlideshowPage.KEY_PHOTO_INDEX, currentIndex);
            data.putBoolean(SlideshowPage.KEY_REPEAT, true);
            mActivity.getStateManager().startStateForResult(SlideshowPage.class, REQUEST_SLIDESHOW, data);
            return true;
        }
        case R.id.action_crop: {
            /// M: [BUG.ADD] disable cropping photo when file not exists or sdcard is full. @{
            File srcFile = new File(current.getFilePath());
            if (!srcFile.exists()) {
                Log.i(TAG, "<onItemSelected> abort cropping photo when not exists!");
                return true;
            }
            if (!isSpaceEnough(srcFile)) {
                Log.i(TAG, "<onItemSelected> abort cropping photo when no enough space!");
                Toast.makeText(mActivity, mActivity.getString(R.string.storage_not_enough), Toast.LENGTH_SHORT)
                        .show();
                return true;
            }
            /// @}
            Activity activity = mActivity;
            Intent intent = new Intent(CropActivity.CROP_ACTION);
            intent.setClass(activity, CropActivity.class);
            intent.setDataAndType(manager.getContentUri(path), current.getMimeType())
                    .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
            activity.startActivityForResult(intent,
                    PicasaSource.isPicasaImage(current) ? REQUEST_CROP_PICASA : REQUEST_CROP);
            return true;
        }
        case R.id.action_trim: {
            Intent intent = new Intent(mActivity, TrimVideo.class);
            intent.setData(manager.getContentUri(path));
            // We need the file path to wrap this into a RandomAccessFile.
            intent.putExtra(KEY_MEDIA_ITEM_PATH, current.getFilePath());
            /// M: [FEATURE.ADD] SlideVideo@{
            if (FeatureConfig.SUPPORT_SLIDE_VIDEO_PLAY) {
                intent.putExtra(TrimVideo.KEY_COME_FROM_GALLERY, true);
            }
            /// @}
            mActivity.startActivityForResult(intent, REQUEST_TRIM);
            return true;
        }
        case R.id.action_mute: {
            /// M: [BUG.ADD] disable muting video when file not exists or sdcard is full. @{
            File srcFile = new File(current.getFilePath());
            if (!srcFile.exists()) {
                Log.i(TAG, "<onItemSelected> abort muting video when not exists!");
                return true;
            }
            if (!isSpaceEnough(srcFile)) {
                Log.i(TAG, "<onItemSelected> abort muting video when no enough space!");
                Toast.makeText(mActivity, mActivity.getString(R.string.storage_not_enough), Toast.LENGTH_SHORT)
                        .show();
                return true;
            }
            /// @}
            mMuteVideo = new MuteVideo(current.getFilePath(), manager.getContentUri(path), mActivity);
            mMuteVideo.muteInBackground();
            /// M: [FEATURE.ADD] SlideVideo@{
            mMuteVideo.setMuteDoneListener(new MuteDoneListener() {
                public void onMuteDone(Uri uri) {
                    redirectCurrentMedia(uri, false);
                }
            });
            /// @}
            return true;
        }
        case R.id.action_edit: {
            /// M: [BUG.ADD] disable editing photo when file not exists or sdcard is full. @{
            File srcFile = new File(current.getFilePath());
            if (!srcFile.exists()) {
                Log.i(TAG, "<onItemSelected> abort editing photo when not exists!");
                return true;
            }
            if (!isSpaceEnough(srcFile)) {
                Log.i(TAG, "<onItemSelected> abort editing photo when no enough space!");
                Toast.makeText(mActivity, mActivity.getString(R.string.storage_not_enough), Toast.LENGTH_SHORT)
                        .show();
                return true;
            }
            /// @}
            launchPhotoEditor();
            return true;
        }
        /// M: [FEATURE.ADD] @{
        case R.id.m_action_picture_quality: {
            Activity activity = (Activity) mActivity;
            Intent intent = new Intent(PictureQualityActivity.ACTION_PQ);
            intent.setClass(activity, PictureQualityActivity.class);
            intent.setData(manager.getContentUri(path));
            Bundle pqBundle = new Bundle();
            pqBundle.putString("PQUri", manager.getContentUri(path).toString());
            pqBundle.putString("PQMineType", current.getMimeType());
            pqBundle.putInt("PQViewWidth", mPhotoView.getWidth());
            pqBundle.putInt("PQViewHeight", mPhotoView.getHeight());
            intent.putExtras(pqBundle);
            Log.i(TAG, "<onItemSelected>startActivity PQ");
            activity.startActivityForResult(intent, REQUEST_PQ);
            return true;
        }
        case R.id.m_action_image_dc: {
            ImageDC.resetStatus((Context) mActivity);
            ImageDC.setMenuItemTile(item);
            path.clearObject();
            mActivity.getDataManager().forceRefreshAll();
            Log.d(TAG, "< onStateResult > forceRefreshAll~~");
            return true;
        }
        /// @}
        case R.id.action_simple_edit: {
            launchSimpleEditor();
            return true;
        }
        case R.id.action_details: {
            if (mShowDetails) {
                hideDetails();
            } else {
                showDetails();
            }
            return true;
        }
        case R.id.print: {
            mActivity.printSelectedImage(manager.getContentUri(path));
            return true;
        }
        case R.id.action_delete:
            confirmMsg = mActivity.getResources().getQuantityString(R.plurals.delete_selection, 1);
        case R.id.action_setas:
        case R.id.action_rotate_ccw:
        case R.id.action_rotate_cw:
        case R.id.action_show_on_map:
            mSelectionManager.deSelectAll();
            mSelectionManager.toggle(path);
            mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
            return true;
        /// M: [FEATURE.ADD] DRM & HotKnot @{
        case R.id.m_action_protect_info:
            Log.d(TAG, "<onItemSelected> ProtectionInfo: do action_protection_info");
            DrmHelper.showProtectionInfoDialog((Activity) mActivity, manager.getContentUri(path));
            return true;
        case R.id.action_hotknot:
            Log.d(TAG, "<onItemSelected> HotKnot: do action_hotknot");
            // for continuous shot, may share a group image, so getContentUris()
            Uri[] uris = null;
            ExtItem extItem = mCurrentPhoto.getExtItem();
            if (extItem != null) {
                uris = extItem.getContentUris();
            }
            if (uris != null) {
                mActivity.getHotKnot().sendZip(uris);
            } else {
                extHotKnot();
            }
            return true;
        /// @}
        /// M: [FEATURE.ADD] entry to export as video @{
        case R.id.action_export:
            mAnimatedContentSharer.exportCurrentPhoto();
            return true;
        /// @}
        /// M: [FEATURE.ADD] Support BlueTooth print feature.@{
        case R.id.action_print:
            mSelectionManager.deSelectAll();
            mSelectionManager.toggle(path);
            mMenuExecutor.onMenuClicked(item, confirmMsg, mConfirmDialogListener);
            return true;
        /// @}
        default:
            /// M: [FEATURE.ADD] menu extension @{
            // return false;
            return mPhotoView.onOptionsItemSelected(item);
        /// @}
        }
    }

    private void hideDetails() {
        mShowDetails = false;
        mDetailsHelper.hide();
    }

    private void showDetails() {
        mShowDetails = true;
        if (mDetailsHelper == null) {
            mDetailsHelper = new DetailsHelper(mActivity, mRootPane, new MyDetailsSource());
            mDetailsHelper.setCloseListener(new CloseListener() {
                @Override
                public void onClose() {
                    hideDetails();
                }
            });
        }
        mDetailsHelper.show();
    }

    ////////////////////////////////////////////////////////////////////////////
    //  Callbacks from PhotoView
    ////////////////////////////////////////////////////////////////////////////
    @Override
    /// M: [BUG.MODIFY] @{
    /* public void onSingleTapUp(int x, int y) {*/
    public void onSingleTapConfirmed(int x, int y) {
        Log.i(TAG, "<onSingleTapConfirmed>");
        /// @}
        if (mAppBridge != null) {
            if (mAppBridge.onSingleTapUp(x, y))
                return;
        }

        MediaItem item = mModel.getMediaItem(0);
        if (item == null || item == mScreenNailItem) {
            // item is not ready or it is camera preview, ignore
            return;
        }

        int supported = item.getSupportedOperations();
        /// M: [BUG.MARK] play video in onSingleTapUp @{
        /* boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0); */
        /// @}
        boolean unlock = ((supported & MediaItem.SUPPORT_UNLOCK) != 0);
        boolean goBack = ((supported & MediaItem.SUPPORT_BACK) != 0);
        boolean launchCamera = ((supported & MediaItem.SUPPORT_CAMERA_SHORTCUT) != 0);

        /// M: [BEHAVIOR.ADD] [Camera independent from Gallery] @{
        // Play video from secure camera, go into key guard.
        if (mPlaySecureVideo) {
            unlock = true;
        }
        /// @}
        /// M: [BUG.MARK] play video in onSingleTapUp @{
        /*
        if (playVideo) {
        // determine if the point is at center (1/6) of the photo view.
        // (The position of the "play" icon is at center (1/6) of the photo)
        int w = mPhotoView.getWidth();
        int h = mPhotoView.getHeight();
        playVideo = (Math.abs(x - w / 2) * 12 <= w)
            && (Math.abs(y - h / 2) * 12 <= h);
        }
            
        if (playVideo) {
        if (mSecureAlbum == null) {
            playVideo(mActivity, item.getPlayUri(), item.getName());
        } else {
            mActivity.getStateManager().finishState(this);
        }
        } else
        */
        /// @}
        if (goBack) {
            /// M: [BUG.ADD] [Camera independent from Gallery] @{
            // Don't need show bars when back to camera preview
            onActionBarAllowed(false);
            /// @}
            onBackPressed();
        } else if (unlock) {
            /// M: [BEHAVIOR.ADD] [Camera independent from Gallery] @{
            mPlaySecureVideo = false;
            /// @}
            Intent intent = new Intent(mActivity, GalleryActivity.class);
            /// M: [BUG.ADD] secure camera come here, use new task @{
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK
                    | Intent.FLAG_ACTIVITY_CLEAR_TASK);
            /// @}
            /// M: [BUG.MARK] @{
            // In secure camera, user can not skip key guard
            /*intent.putExtra(GalleryActivity.KEY_DISMISS_KEYGUARD, true);*/
            /// @}
            mActivity.startActivity(intent);
        } else if (launchCamera) {
            launchCamera();
        } else {
            /// M: [BUG.MODIFY] @{
            /*toggleBars();*/
            // if we are starting video player, give up toggleBars() to avoid view junk
            if (!mIsStartingVideoPlayer) {
                toggleBars();
            }
            /// @}
        }
    }

    @Override
    public void onActionBarAllowed(boolean allowed) {
        mActionBarAllowed = allowed;
        mHandler.sendEmptyMessage(MSG_UPDATE_ACTION_BAR);
    }

    @Override
    public void onActionBarWanted() {
        mHandler.sendEmptyMessage(MSG_WANT_BARS);
    }

    @Override
    public void onFullScreenChanged(boolean full) {
        Message m = mHandler.obtainMessage(MSG_ON_FULL_SCREEN_CHANGED, full ? 1 : 0, 0);
        m.sendToTarget();
    }

    // How we do delete/undo:
    //
    // When the user choose to delete a media item, we just tell the
    // FilterDeleteSet to hide that item. If the user choose to undo it, we
    // again tell FilterDeleteSet not to hide it. If the user choose to commit
    // the deletion, we then actually delete the media item.
    @Override
    public void onDeleteImage(Path path, int offset) {
        onCommitDeleteImage(); // commit the previous deletion
        mDeletePath = path;
        mDeleteIsFocus = (offset == 0);
        /// M: [BUG.MODIFY] @{
        // mCurrentIndex would always be 0 if you never slide medias after you enter
        // Gallery by clicking one media from other applications like file manager.
        // Another example can be found in ALPS00419381
        // mMediaSet.addDeletion(path, mCurrentIndex + offset);
        mMediaSet.addDeletion(path, mModel.getCurrentIndex() + offset);
        /// @}
    }

    @Override
    public void onUndoDeleteImage() {
        if (mDeletePath == null)
            return;
        // If the deletion was done on the focused item, we want the model to
        // focus on it when it is undeleted.
        if (mDeleteIsFocus)
            mModel.setFocusHintPath(mDeletePath);
        mMediaSet.removeDeletion(mDeletePath);
        mDeletePath = null;
    }

    @Override
    public void onCommitDeleteImage() {
        if (mDeletePath == null)
            return;
        mMenuExecutor.startSingleItemAction(R.id.action_delete, mDeletePath);
        mDeletePath = null;
    }

    public void playVideo(Activity activity, Uri uri, String title) {
        try {
            Intent intent = new Intent(Intent.ACTION_VIEW).setDataAndType(uri, "video/*")
                    .putExtra(Intent.EXTRA_TITLE, title).putExtra(MovieActivity.KEY_TREAT_UP_AS_BACK, true)
                    /// M: [BUG.ADD] @{
                    .putExtra(MovieActivity.KEY_COME_FROM_CAMERA, mAppBridge != null);
            intent.putExtra(FeatureHelper.EXTRA_ENABLE_VIDEO_LIST, true);
            /// @}
            activity.startActivityForResult(intent, REQUEST_PLAY_VIDEO);
        } catch (ActivityNotFoundException e) {
            Toast.makeText(activity, activity.getString(R.string.video_err), Toast.LENGTH_SHORT).show();
        }
    }

    private void setCurrentPhotoByIntent(Intent intent) {
        if (intent == null)
            return;
        Path path = mApplication.getDataManager().findPathByUri(intent.getData(), intent.getType());
        if (path != null) {
            Path albumPath = mApplication.getDataManager().getDefaultSetOf(path);
            if (albumPath == null) {
                return;
            }
            if (!albumPath.equalsIgnoreCase(mOriginalSetPathString)) {
                // If the edited image is stored in a different album, we need
                // to start a new activity state to show the new image
                Bundle data = new Bundle(getData());
                data.putString(KEY_MEDIA_SET_PATH, albumPath.toString());
                data.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
                mActivity.getStateManager().startState(SinglePhotoPage.class, data);
                return;
            }
            mModel.setCurrentPhoto(path, mCurrentIndex);
        }
    }

    @Override
    protected void onStateResult(int requestCode, int resultCode, Intent data) {
        /// M: [BUG.ADD] mark we are not starting video player, to re-enable toggleBars() @{
        mIsStartingVideoPlayer = false;
        /// @}
        /// M: [FEATURE.ADD] @{
        if (mExtActivityResultListener != null) {
            mExtActivityResultListener.onActivityResult(requestCode, resultCode, data);
            mExtActivityResultListener = null;
        }
        /// @}
        /// M: [BUG.MODIFY] @{
        /*if (resultCode == Activity.RESULT_CANCELED) {*/
        if (resultCode == Activity.RESULT_CANCELED && requestCode != REQUEST_PLAY_VIDEO) {
            /// @}
            // This is a reset, not a canceled
            return;
        }
        mRecenterCameraOnResume = false;
        switch (requestCode) {
        case REQUEST_EDIT:
            setCurrentPhotoByIntentEx(data);
            break;
        /// M: [FEATURE.ADD] SlideVideo @{
        case REQUEST_TRIM:
            if (data != null) {
                redirectCurrentMedia(data.getData(), false);
            }
            break;
        /// @}
        case REQUEST_CROP:
            if (resultCode == Activity.RESULT_OK) {
                setCurrentPhotoByIntentEx(data);
            }
            break;
        case REQUEST_CROP_PICASA: {
            if (resultCode == Activity.RESULT_OK) {
                Context context = mActivity.getAndroidContext();
                String message = context.getString(R.string.crop_saved,
                        context.getString(R.string.folder_edited_online_photos));
                Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
            }
            break;
        }
        case REQUEST_SLIDESHOW: {
            if (data == null)
                break;
            String path = data.getStringExtra(SlideshowPage.KEY_ITEM_PATH);
            int index = data.getIntExtra(SlideshowPage.KEY_PHOTO_INDEX, 0);
            if (path != null) {
                mModel.setCurrentPhoto(Path.fromString(path), index);
            }
            break;
        }
        /// M: [FEATURE.ADD] added for Stereo feature.@{
        case BottomControlLayer.REQUEST_REFOCUS:
        case BottomControlLayer.REQUEST_FANCY_COLOR:
        case BottomControlLayer.REQUEST_BACKGROUND:
        case BottomControlLayer.REQUEST_COPY_PAST:
            if (resultCode == Activity.RESULT_OK) {
                setRefocusCurrentPhotoByIntent(data);
            }
            break;
        /// @}
        }
    }

    @Override
    public void onPause() {
        /// M: [DEBUG.ADD] @{
        Log.i(TAG, "<onPause> begin");
        /// @}
        /// M: [BUG.ADD] @{
        // Avoid to set ActionBar visibility in some cases
        mNotSetActionBarVisibiltyWhenResume = mModel.isCamera(0) && !mPhotoView.getFilmMode();
        Log.i(TAG, "<onPause> mNotSetActionBarVisibiltyWhenResume = " + mNotSetActionBarVisibiltyWhenResume);
        /// @}
        super.onPause();
        mIsActive = false;
        if (mMuteVideo != null) {
            mMuteVideo.setMuteHasPaused(true);
        }
        /// M: [FEATURE.ADD] VTSP: share as video @{
        if (mAnimatedContentSharer != null) {
            // mAnimatedContentSharer == null if onCreate() returned before
            // call initAnimatedContentSharer()
            mAnimatedContentSharer.onPause();
        }
        /// @}

        mActivity.getGLRoot().unfreeze();
        mHandler.removeMessages(MSG_UNFREEZE_GLROOT);

        DetailsHelper.pause();
        // Hide the detail dialog on exit
        if (mShowDetails)
            hideDetails();
        if (mModel != null) {
            mModel.pause();
        }
        mPhotoView.pause();
        mHandler.removeMessages(MSG_HIDE_BARS);
        mHandler.removeMessages(MSG_REFRESH_BOTTOM_CONTROLS);
        refreshBottomControlsWhenReady();
        mActionBar.removeOnMenuVisibilityListener(mMenuVisibilityListener);
        if (mShowSpinner) {
            mActionBar.disableAlbumModeMenu(true);
        }
        onCommitDeleteImage();
        mMenuExecutor.pause();
        if (mMediaSet != null) {
            mMediaSet.clearDeletion();
            /// M: [BUG.ADD] @{
            // ContentListener had been removed,so should reset deletion
            mMediaSet.resetDeletion();
            /// @}
        }
        /// M: [FEATURE.ADD] Remove listener while leave PhotoPage @{
        PQBroadcastReceiver.setListener(null);
        /// @}
        /// M: [DEBUG.ADD] @{
        Log.i(TAG, "<onPause> end");
        /// @}
    }

    @Override
    public void onCurrentImageUpdated() {
        mActivity.getGLRoot().unfreeze();
    }

    @Override
    public void onFilmModeChanged(boolean enabled) {
        refreshBottomControlsWhenReady();
        if (mShowSpinner) {
            if (enabled) {
                mActionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
            } else {
                mActionBar.disableAlbumModeMenu(true);
            }
        }
        if (enabled) {
            mHandler.removeMessages(MSG_HIDE_BARS);
            UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY, "FilmstripPage");
        } else {
            refreshHidingMessage();
            if (mAppBridge == null || mCurrentIndex > 0) {
                UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_GALLERY, "SinglePhotoPage");
            } else {
                UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_CAMERA, "Unknown"); // TODO
            }
        }
        /// M: [BEHAVIOR.ADD] @{
        // Show title at the action bar
        updateActionBarTitle();
        /// @}
    }

    private void transitionFromAlbumPageIfNeeded() {
        TransitionStore transitions = mActivity.getTransitionStore();

        int albumPageTransition = transitions.get(KEY_ALBUMPAGE_TRANSITION, MSG_ALBUMPAGE_NONE);

        if (albumPageTransition == MSG_ALBUMPAGE_NONE && mAppBridge != null && mRecenterCameraOnResume) {
            // Generally, resuming the PhotoPage when in Camera should
            // reset to the capture mode to allow quick photo taking
            mCurrentIndex = 0;
            mPhotoView.resetToFirstPicture();
        } else {
            int resumeIndex = transitions.get(KEY_INDEX_HINT, -1);
            if (resumeIndex >= 0) {
                if (mHasCameraScreennailOrPlaceholder) {
                    // Account for preview/placeholder being the first item
                    resumeIndex++;
                }
                if (resumeIndex < mMediaSet.getMediaItemCount()) {
                    mCurrentIndex = resumeIndex;
                    mModel.moveTo(mCurrentIndex);
                }
            }
        }

        if (albumPageTransition == MSG_ALBUMPAGE_RESUMED) {
            mPhotoView.setFilmMode(mStartInFilmstrip || mAppBridge != null);
        } else if (albumPageTransition == MSG_ALBUMPAGE_PICKED) {
            mPhotoView.setFilmMode(false);
        }
    }

    @Override
    protected void onResume() {
        /// M: [DEBUG.ADD] @{
        Log.i(TAG, "<onResume> begin");
        /// @}
        super.onResume();
        if (mMuteVideo != null) {
            mMuteVideo.setMuteHasPaused(false);
            mMuteVideo.needPlayMuteVideo();
        }
        if (mModel == null) {
            /// M: [BUG.ADD] pause PhotoView before finish PhotoPage @{
            mPhotoView.pause();
            /// @}
            mActivity.getStateManager().finishState(this);
            return;
        }
        transitionFromAlbumPageIfNeeded();

        mActivity.getGLRoot().freeze();
        mIsActive = true;
        /// M: [FEATURE.ADD] VTSP: share as video @{
        mAnimatedContentSharer.onResume();
        /// @}
        setContentPane(mRootPane);

        mModel.resume();
        mPhotoView.resume();
        /// M: [BUG.MARK] @{
        /*mActionBar.setDisplayOptions(
         ((mSecureAlbum == null) && (mSetPathString != null)), false);*/
        /// @}
        mActionBar.addOnMenuVisibilityListener(mMenuVisibilityListener);
        /// M: [BEHAVIOR.ADD] @{
        // Show title at the action bar
        updateActionBarTitle();
        /// @}
        /// M: [BUG.MARK] do this in onLoadingFinished @{
        /* refreshBottomControlsWhenReady();*/
        /// @}
        if (mShowSpinner && mPhotoView.getFilmMode()) {
            mActionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
        }
        if (!mShowBars) {
            mActionBar.hide();
            /// M: [BUG.MODIFY] @{
            /*mActivity.getGLRoot().setLightsOutMode(true);*/
            if (mAppBridge != null && mCurrentIndex == 0 && !mPhotoView.getFilmMode()) {
                mActivity.getGLRoot().setLightsOutMode(false);
            } else {
                mActivity.getGLRoot().setLightsOutMode(true);
            }
            /// @}
        }
        boolean haveImageEditor = GalleryUtils.isEditorAvailable(mActivity, "image/*");
        if (haveImageEditor != mHaveImageEditor) {
            mHaveImageEditor = haveImageEditor;
            updateMenuOperations();
        }

        mRecenterCameraOnResume = true;
        mHandler.sendEmptyMessageDelayed(MSG_UNFREEZE_GLROOT, UNFREEZE_GLROOT_TIMEOUT);

        /// M: [BUG.ADD] allow action bar auto-hide @{
        refreshHidingMessage();
        /// @}
        /// M: [BUG.ADD] set change listener to current GLRootView @{
        setOnSystemUiVisibilityChangeListener();
        /// @}
        /// M: [BUG.ADD] @{
        // update share intent and other UI when comes back from paused status
        updateUIForCurrentPhoto();
        /// @}
        /// M: [FEATURE.ADD] Set listener for add PQ effect @{
        PQBroadcastReceiver.setListener(new PQEffect());
        /// @}
        /// M: [DEBUG.ADD] @{
        Log.i(TAG, "<onResume> end");
        /// @}
    }

    @Override
    protected void onDestroy() {
        if (mAppBridge != null) {
            mAppBridge.setServer(null);
            mScreenNailItem.setScreenNail(null);
            mAppBridge.detachScreenNail();
            mAppBridge = null;
            mScreenNailSet = null;
            mScreenNailItem = null;
        }
        mActivity.getGLRoot().setOrientationSource(null);
        if (mBottomControls != null)
            mBottomControls.cleanup();

        // Remove all pending messages.
        /// M: [BUG.MODIFY] @{
        //mHandler.removeCallbacksAndMessages(null);
        if (mHandler != null) {
            mHandler.removeCallbacksAndMessages(null);
        }
        /// @}
        /// M: [FEATURE.ADD] @{
        mPhotoView.destroy();
        /// @}
        /// M: [BUG.ADD] clear mNfcPushUris[0] for NFC when leave photo page @{
        mNfcPushUris[0] = null;
        /// @}
        // / M: [BUG.ADD] @{
        // Google bug fix,mute dialog should be dismiss before gallery activity
        // destroyed.
        if (mMuteVideo != null) {
            mMuteVideo.cancelMute();
        }
        // / @}
        super.onDestroy();
    }

    private class MyDetailsSource implements DetailsSource {

        @Override
        public MediaDetails getDetails() {
            return mModel.getMediaItem(0).getDetails();
        }

        @Override
        public int size() {
            /// M: [FEATURE.MODIFY] do not count camera item when show detail @{
            //return mMediaSet != null ? mMediaSet.getMediaItemCount() : 1;
            return mMediaSet != null
                    ? (mHasCameraScreennailOrPlaceholder ? mMediaSet.getMediaItemCount() - 1
                            : mMediaSet.getMediaItemCount())
                    : 1;
            /// @}
        }

        @Override
        public int setIndex() {
            /// M: [FEATURE.MODIFY] do not count camera item when show detail @{
            //return mModel.getCurrentIndex();
            return mHasCameraScreennailOrPlaceholder ? mModel.getCurrentIndex() - 1 : mModel.getCurrentIndex();
            /// @}
        }
    }

    @Override
    public void onAlbumModeSelected(int mode) {
        if (mode == GalleryActionBar.ALBUM_GRID_MODE_SELECTED) {
            switchToGrid();
        }
    }

    @Override
    public void refreshBottomControlsWhenReady() {
        if (mBottomControls == null) {
            return;
        }
        MediaObject currentPhoto = mCurrentPhoto;
        if (currentPhoto == null) {
            mHandler.obtainMessage(MSG_REFRESH_BOTTOM_CONTROLS, 0, 0, currentPhoto).sendToTarget();
        } else {
            currentPhoto.getPanoramaSupport(mRefreshBottomControlsCallback);
        }
    }

    private void updatePanoramaUI(boolean isPanorama360) {
        Menu menu = mActionBar.getMenu();

        // it could be null if onCreateActionBar has not been called yet
        if (menu == null) {
            return;
        }

        MenuExecutor.updateMenuForPanorama(menu, isPanorama360, isPanorama360);

        if (isPanorama360) {
            MenuItem item = menu.findItem(R.id.action_share);
            if (item != null) {
                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
                item.setTitle(mActivity.getResources().getString(R.string.share_as_photo));
            }
        } else if ((mCurrentPhoto.getSupportedOperations() & MediaObject.SUPPORT_SHARE) != 0) {
            MenuItem item = menu.findItem(R.id.action_share);
            if (item != null) {
                item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
                item.setTitle(mActivity.getResources().getString(R.string.share));
            }
        }
    }

    @Override
    public void onUndoBarVisibilityChanged(boolean visible) {
        refreshBottomControlsWhenReady();
    }

    /// M: [FEATURE.MARK] @{
    /*    @Override
        public boolean onShareTargetSelected(ShareActionProvider source, Intent intent) {
    final long timestampMillis = mCurrentPhoto.getDateInMs();
    final String mediaType = getMediaTypeString(mCurrentPhoto);
    UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY,
            UsageStatistics.ACTION_SHARE,
            mediaType,
                    timestampMillis > 0
                    ? System.currentTimeMillis() - timestampMillis
                    : -1);
    /// M: [FEATURE.ADD] VTSP: share as video @{
    mAnimatedContentSharer.onShareTargetSelected(intent);
    /// @}
    return false;
        }*/
    /// @}

    private static String getMediaTypeString(MediaItem item) {
        if (item.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO) {
            return "Video";
        } else if (item.getMediaType() == MediaObject.MEDIA_TYPE_IMAGE) {
            return "Photo";
        } else {
            return "Unknown:" + item.getMediaType();
        }
    }

    //********************************************************************
    //*                              MTK                                 *
    //********************************************************************

    public static final String KEY_LAUNCH_FROM_CAMERA = "isCamera";
    public boolean mLoadingFinished = false;
    private boolean mLaunchFromCamera = false;
    private long mCurrentVersion;
    private static final int SHOW_TOAST_DURATION = 500;
    private static final int STORAGE_CAPACITY_BASE = 1024;
    private static final int PLAY_ICON_POSITION_BASE = 12;
    private static final int VIDEO_MUTE_MIN_SPACE_GMO = 9;
    private static final int VIDEO_MUTE_MIN_SPACE_DEFAULT = 48;
    // add for stereo feature.
    private boolean mShowBottomControls = true;
    // [Camera independent from Gallery] add for play video from secure camera.
    private boolean mPlaySecureVideo = false;

    /**
     * Add for performance test case, enable/disable action bar change.
     * @param disable enable/disable action bar change
     */
    public void disableBarChanges(boolean disable) {
        mDisableBarChanges = disable;
    }

    private void updateScaleGesture() {
        if (mCurrentPhoto == null) {
            return;
        }
        // when current photo is video & slide video is disabled, disable scale
        if (mCurrentPhoto.getMediaType() == MediaObject.MEDIA_TYPE_VIDEO
                && !FeatureConfig.SUPPORT_SLIDE_VIDEO_PLAY) {
            mPhotoView.setScalingEnabled(false);
            /// M: [FEATURE.ADD] [Camera independent from Gallery] @{
            // After delete all medias in camera folder, show EmptyAlbumImage,
            // in this case, it's not allowed to scale and enter film mode.
        } else if ((mLaunchFromCamera && mCurrentPhoto != null
                && (mCurrentPhoto.getSupportedOperations() & MediaItem.SUPPORT_BACK) != 0)
                // Lock pattern, launch from secure camera, it's not allowed to
                // film mode and delete photo. After pull out photo to delete, can not
                // delete data immediately. So camera preview thumbnail can not be
                // updated immediately.
                || mSecureAlbum != null) {
            Log.i(TAG, "<updateScaleGesture> setScalingEnabled(false)");
            mPhotoView.setScalingEnabled(false);
            /// @}
        } else {
            mPhotoView.setScalingEnabled(true);
        }
    }

    /// M: [FEATURE.ADD] VTSP: share as video @{
    AnimatedContentSharer mAnimatedContentSharer;

    private void initAnimatedContentSharer() {
        // animation sharer should be null here. here just for test
        if (mAnimatedContentSharer == null) {
            mAnimatedContentSharer = new AnimatedContentSharer(mActivity, PhotoPlayFacade.getMediaCenter(),
                    new AnimatedContentSharer.IMediaDataGetter() {
                        public MediaData getMediaData() {
                            if (mCurrentPhoto != null) {
                                return mCurrentPhoto.getMediaData();
                            }
                            return null;
                        }

                        public String getLocalizedFolderName() {
                            String albumName = "";
                            if (mCurrentPhoto instanceof LocalMediaItem) {
                                albumName = LocalAlbum.getLocalizedName(mActivity.getResources(),
                                        ((LocalMediaItem) mCurrentPhoto).bucketId, mMediaSet.getName());
                            }
                            return albumName;
                        }

                        public void redirectCurrentMedia(Uri uri, boolean fromActivityResult) {
                            PhotoPage.this.redirectCurrentMedia(uri, fromActivityResult);
                        }

                        /// M: [BUG.ADD] @{
                        public void setShareUri(Uri uri) {
                            Log.d(TAG, "<initAnimatedContentSharer> setShareUri() uri " + uri);
                            mShareUriFromChooserView = uri;
                        }
                        /// @}
                    });
            mAnimatedContentSharer.registerShareHooker(new SlowMotionSharer.ShareHooker());
        }
    }
    /// @}

    protected void onSaveState(Bundle outState) {
        // keep record of current index and current photo
        mData.putInt(KEY_INDEX_HINT, mCurrentIndex);
        if (mCurrentPhoto != null) {
            Path photoPath = mCurrentPhoto.getPath();
            if (photoPath != null) {
                mData.putString(KEY_MEDIA_ITEM_PATH, photoPath.toString());
            }
        }
    }

    private void redirectCurrentMedia(Uri uri, boolean fromActivityResult) {
        Log.d(TAG, "<redirectCurrentMedia> uri=" + uri + ", fromActivity=" + fromActivityResult);
        if (uri == null) {
            Log.e(TAG, "<redirectCurrentMedia> redirect current media, null uri");
            return;
        }
        final Intent intent = new Intent().setData(uri);

        if (fromActivityResult) {
            setCurrentPhotoByIntentEx(intent);
        } else {
            // switch photo to avoid un-predicated jump error
            // the switch method need sth. about camera, so I just wait camera's patch first
            Path path = mApplication.getDataManager().findPathByUri(intent.getData(), intent.getType());
            Log.d(TAG, "<redirectCurrentMedia> type=" + intent.getType() + ", path=" + path);
            if (path != null) {
                mData.putString(PhotoPage.KEY_MEDIA_ITEM_PATH, path.toString());
                mData.putBoolean(PhotoPage.KEY_START_IN_FILMSTRIP, mPhotoView.getFilmMode());
                mData.putInt(PhotoPage.KEY_INDEX_HINT, mCurrentIndex);
                Log.d(TAG, "<redirectCurrentMedia> mSetPathString=" + mSetPathString);
                mHandler.post(new Runnable() {
                    public void run() {
                        StateManager stateManager = mActivity.getStateManager();
                        // the following condition is logically same to say
                        // if (!PhotoPage.this.isDestroyed()) {
                        if ((stateManager.getStateCount() > 0) && (stateManager.getTopState() == PhotoPage.this)) {
                            mActivity.getStateManager().switchState(PhotoPage.this, SinglePhotoPage.class, mData);
                        }
                    }
                });
            }
        }
    }

    private LayerManager.IBackwardContoller.IOnActivityResultListener mExtActivityResultListener;

    private boolean mIsBackwardToggle;
    private boolean mAllowAutoHideByHost = true;

    private LayerManager.IBackwardContoller mBackwardContollerForLayer = new LayerManager.IBackwardContoller() {
        public void toggleBars(boolean visibility) {
            mAllowAutoHideByHost = !visibility;
            mIsBackwardToggle = true;
            if (visibility) {
                mHandler.removeMessages(MSG_HIDE_BARS);
                if (canShowBars()) {
                    showBars();
                }
            } else {
                hideBars();
            }
            mIsBackwardToggle = false;
        }

        public void redirectCurrentMedia(Uri uri, boolean fromActivityResult) {
            PhotoPage.this.redirectCurrentMedia(uri, fromActivityResult);
        }

        public void startActivityForResult(Intent intent, int requestCode,
                IOnActivityResultListener resultListener) {
            switch (requestCode) {
            case BottomControlLayer.REQUEST_REFOCUS:
            case BottomControlLayer.REQUEST_FANCY_COLOR:
            case BottomControlLayer.REQUEST_BACKGROUND:
                mActivity.startActivityForResult(intent, requestCode);
                break;
            case BottomControlLayer.REQUEST_COPY_PAST:
                Bundle data = new Bundle();
                data.putString("COPY_DEST", intent.getStringExtra("COPY_DEST"));
                GLRoot root = mActivity.getGLRoot();
                root.lockRenderThread();
                mActivity.getStateManager().startStateForResult(StereoPickingAlbumPage.class,
                        BottomControlLayer.REQUEST_COPY_PAST, data);
                root.unlockRenderThread();
                break;
            default:
                break;
            }
            mExtActivityResultListener = resultListener;
        }

        public void notifyDataChange(MediaData mediaData) {
            if (mModel instanceof PhotoDataAdapter) {
                ((PhotoDataAdapter) (mModel)).notifyDataChange(mediaData);
            }
        }

        @Override
        public void refreshBottomControls(boolean showBottomControls) {
            mShowBottomControls = showBottomControls;
            Log.d(TAG, " mShowBottomControls = " + mShowBottomControls);
            refreshBottomControlsWhenReady();
        }

        @Override
        public boolean getBottomMenuVisibility() {
            return mBottomControls.getContainerVisibility();
        }

    };

    private boolean onActionBarVisibilityChange(boolean newVisibility) {
        if (mIsBackwardToggle) {
            return false;
        }
        if (newVisibility) {
            return mPhotoView.onActionBarVisibilityChange(newVisibility);
        } else {
            mPhotoView.onActionBarVisibilityChange(false);
            return false;
        }
    }

    public boolean fresh(boolean newVisibility) {
        if (mIsBackwardToggle) {
            return false;
        }
        if (newVisibility) {
            return mPhotoView.freshLayers(newVisibility);
        } else {
            mPhotoView.freshLayers(false);
            return false;
        }
    }

    /// M: [BUG.ADD] Slideshow doesn't play again
    // when finish playing the last picture @{
    private Path mSnailSetPath;
    /// @}

    /// M: [BUG.ADD] disable mute when sdcard is full. @{
    /**
     * get available space which storage source video is in.
     *
     * @return the available sapce size, -1 means max storage size.
     */
    private long getAvailableSpace(String path) {
        // Here just use one directory to stat fs.
        StatFs stat = new StatFs(path);
        long availableSize = stat.getAvailableBlocks() * (long) stat.getBlockSize();
        Log.i(TAG, "<getAvailableSpace> path " + path + ", availableSize(MB) "
                + (availableSize / STORAGE_CAPACITY_BASE / STORAGE_CAPACITY_BASE));
        return availableSize;
    }

    /**
     * calculate the space for video muted is enough or not lowStorageThreshold
     * is reserve space. ram optimize projec is 9M, the others is 48M.
     */
    private boolean isSpaceEnough(File srcFile) {
        long spaceNeed;
        long lowStorageThreshold;
        if (FeatureConfig.IS_GMO_RAM_OPTIMIZE) {
            lowStorageThreshold = VIDEO_MUTE_MIN_SPACE_GMO * STORAGE_CAPACITY_BASE * STORAGE_CAPACITY_BASE;
        } else {
            lowStorageThreshold = VIDEO_MUTE_MIN_SPACE_DEFAULT * STORAGE_CAPACITY_BASE * STORAGE_CAPACITY_BASE;
        }
        spaceNeed = srcFile.length() + lowStorageThreshold;
        if (getAvailableSpace(srcFile.getPath()) < spaceNeed) {
            Log.i(TAG, "<isSpaceEnough> space is not enough!!!");
            return false;
        } else {
            return true;
        }
    }
    /// @}

    // Show photo name on ActionBar when not film mode
    // and show album name onActionBar when film mode
    private void updateActionBarTitle() {
        if (mPhotoView == null || mActionBar == null) {
            return;
        }

        /// M: [BUG.ADD] @{
        try {
            if (mActivity.getStateManager().getTopState() != this) {
                return;
            }
        } catch (AssertionError e) {
            Log.v(TAG, "no state in State Manager when updates actionbar title");
            return;
        }
        /// @}

        if (mPhotoView.getFilmMode()) {
            mActionBar.setDisplayOptions(((mSecureAlbum == null) && (mSetPathString != null)), false);
            mActionBar.setTitle(mMediaSet != null ? mMediaSet.getName() : "");
            /// M: [BUG.ADD] call to reload spinner @{
            if (mShowSpinner) {
                mActionBar.enableAlbumModeMenu(GalleryActionBar.ALBUM_FILMSTRIP_MODE_SELECTED, this);
            }
            /// @}
        } else {
            mActionBar.setDisplayOptions(((mSecureAlbum == null) && (mSetPathString != null)), true);
            mActionBar.setTitle(mCurrentPhoto != null ? mCurrentPhoto.getName() : "");
        }
    }

    @Override
    public void onSingleTapUp(int x, int y) {
        Log.i(TAG, "<onSingleTapUp>");
        if (mAppBridge != null && mAppBridge.onSingleTapUp(x, y)) {
            return;
        }
        MediaItem item = mModel.getMediaItem(0);
        if (item == null || item == mScreenNailItem) {
            // item is not ready or it is camera preview, ignore
            return;
        }
        int supported = item.getSupportedOperations();
        boolean playVideo = ((supported & MediaItem.SUPPORT_PLAY) != 0);

        if (playVideo) {
            // determine if the point is at center (1/6) of the photo view.
            // (The position of the "play" icon is at center (1/6) of the photo)
            int w = mPhotoView.getWidth();
            int h = mPhotoView.getHeight();
            playVideo = (Math.abs(x - w / 2) * PLAY_ICON_POSITION_BASE <= w)
                    && (Math.abs(y - h / 2) * PLAY_ICON_POSITION_BASE <= h);
        }

        if (playVideo) {
            if (mSecureAlbum == null) {
                /// M: [FEATURE.MODIFY] slide video @{
                // playVideo(mActivity, item.getPlayUri(), item.getName());
                if (!FeatureConfig.SUPPORT_SLIDE_VIDEO_PLAY) {
                    // mark we are starting video player
                    // to avoid calling toggleBars() which leads to view junk
                    mIsStartingVideoPlayer = true;
                    playVideo(mActivity, item.getPlayUri(), item.getName());
                }
                /// @}
            } else {
                /// M: [BEHAVIOR.MODIFY] [Camera independent from Gallery] @{
                // Play video from secure camera, go into key guard.
                /*mPhotoView.pause();
                 mActivity.getStateManager().finishState(this);*/
                mPlaySecureVideo = true;
                /// @}
            }
        }
    }

    /// M: [FEATURE.ADD] added for ImageRefocus.@{
    private void setRefocusCurrentPhotoByIntent(Intent intent) {
        if (intent == null) {
            Log.i(TAG, "<setRefocusCurrentPhotoByIntent> intent is null, return");
            return;
        }
        Path path = mApplication.getDataManager().findPathByUri(intent.getData(), intent.getType());
        if (null != path) {
            String string = path.toString();
            if (null != string) {
                mModel.setCurrentPhoto(Path.fromString(string), mCurrentIndex);
            }
        }
    }
    /// @}

    private void setCurrentPhotoByIntentEx(Intent intent) {
        if (intent == null) {
            Log.i(TAG, "<setCurrentPhotoByIntentEx> inetnt is null, return");
            return;
        }
        Path photoEditPath = mApplication.getDataManager().findPathByUri(intent.getData(), intent.getType());
        if (photoEditPath != null) {
            String string = photoEditPath.toString();
            if (string != null) {
                // mark the cache for edited image out of date
                ImageCacheService.sForceObsoletePath = string;
                mModel.setCurrentPhoto(Path.fromString(string), mCurrentIndex);
            }
        }
        Log.d(TAG, "<setCurrentPhotoByIntentEx> intent.getData()=" + intent.getData());
        Intent shareIntent = createShareIntent(intent.getData(), MediaObject.MEDIA_TYPE_IMAGE);
        if (mActionBar != null) {
            mActionBar.setShareIntents(null, shareIntent, PhotoPage.this);
        }
    }

    private static Intent createShareIntent(Uri contentUri, int type) {
        return new Intent(Intent.ACTION_SEND).setType(MenuExecutor.getMimeType(type))
                .putExtra(Intent.EXTRA_STREAM, contentUri).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
    }

    /// M: [BUG.ADD] can not set as wallpaper when no thumbnail@{
    // note: mSupportedOperations should only be set by updateMenuOperations()
    // and only be get in onPrepareOptionsMenu()
    private int mSupportedOperations = 0;

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        if (mSupportedOperations == 0) {
            return super.onPrepareOptionsMenu(menu);
        }
        if (mModel != null && mModel.getLoadingState(0) == PhotoView.Model.LOADING_FAIL) {
            int supportedOperations = mSupportedOperations;
            supportedOperations &= ~(MediaObject.SUPPORT_SETAS | MediaObject.SUPPORT_CROP | MediaObject.SUPPORT_EDIT
                    | MediaObject.SUPPORT_PRINT | MediaObject.SUPPORT_ROTATE);
            MenuExecutor.updateMenuOperation(mActionBar.getMenu(), supportedOperations);
        }
        mPhotoView.onPrepareOptionsMenu(menu);
        return super.onPrepareOptionsMenu(menu);
    }
    /// @}

    @Override
    public boolean onChooseActivity(ActivityChooserModel host, Intent intent) {
        Log.d(TAG, "<onChooseActivity>");
        /// M: [BUG.ADD] show toast before PhotoDataAdapter finishing loading @{
        if (!mLoadingFinished) {
            Toast.makeText(mActivity, mActivity.getString(R.string.please_wait), Toast.LENGTH_SHORT).show();
            Log.i(TAG, "<onChooseActivity> not finish loading, show toast, return");
            return true;
        }
        /// @}
        final long timestampMillis = mCurrentPhoto.getDateInMs();
        final String mediaType = getMediaTypeString(mCurrentPhoto);
        UsageStatistics.onEvent(UsageStatistics.COMPONENT_GALLERY, UsageStatistics.ACTION_SHARE, mediaType,
                timestampMillis > 0 ? System.currentTimeMillis() - timestampMillis : -1);
        /// M: [FEATURE.ADD] VTSP: share as video @{
        String errMsg = getErrMsgWhenStartActivityFail(host, intent);
        mAnimatedContentSharer.setErrMsgWhenStartActivityFail(errMsg);
        mAnimatedContentSharer.onShareTargetSelected(intent);
        /// @}
        return true;
    }

    private String getErrMsgWhenStartActivityFail(ActivityChooserModel host, Intent intent) {
        int count = host.getActivityCount();
        for (int i = 0; i < count; i++) {
            ResolveInfo rInfo = host.getActivity(i);
            if (rInfo != null && rInfo.activityInfo != null && rInfo.activityInfo.name != null
                    && rInfo.activityInfo.name.equals(intent.getComponent().getClassName())) {
                String res = mActivity.getString(
                        com.android.internal.R.string.activitychooserview_choose_application_error,
                        rInfo.loadLabel(mActivity.getPackageManager()));
                Log.i(TAG, "<getErrMsgWhenStartActivityFail> return " + res);
                return res;
            }
        }
        Log.i(TAG, "<getErrMsgWhenStartActivityFail> return null");
        return null;
    }

    // variable for a temporary solution to handle cases when we enter/return a PhotoPage
    // with camera item at the head but we never intend to see the camera at the "entering" time
    // e.g. save an edited photo; mute a slide video; export as video/gif
    // note that this variable should only be used in UI thread
    private boolean mIsAppBridgeFullScreenChangeEnabled = true;
    private WeakReference<Toast> mEmptyAlbumToast = null;

    private void showEmptyAlbumToast(int toastLength) {
        Toast toast;
        if (mEmptyAlbumToast != null) {
            toast = mEmptyAlbumToast.get();
            if (toast != null) {
                toast.show();
                return;
            }
        }
        toast = Toast.makeText(mActivity, R.string.empty_album, SHOW_TOAST_DURATION);
        mEmptyAlbumToast = new WeakReference<Toast>(toast);
        toast.show();
    }

    // whether we are starting video player
    private boolean mIsStartingVideoPlayer;

    private void setOnSystemUiVisibilityChangeListener() {
        ((GLRootView) mActivity.getGLRoot())
                .setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
                    @Override
                    public void onSystemUiVisibilityChange(int visibility) {
                        int diff = mLastSystemUiVis ^ visibility;
                        mLastSystemUiVis = visibility;
                        if ((diff & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0
                                && (visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
                            /// M: [BUG.MODIFY] Don't need show bars in camera preview. @{
                            /*showBars();*/
                            wantBars();
                            /// @}
                        }
                    }
                });
    }

    private SlowMotionSharer mSlowMotionSharer;

    private void extHotKnot() {
        final MediaData mediaData = mCurrentPhoto.getMediaData();
        if (mediaData == null || !mediaData.isSlowMotion) {
            Uri contentUri = mCurrentPhoto.getContentUri();
            mActivity.getHotKnot().sendUri(contentUri, mCurrentPhoto.getMimeType());
            return;
        }
        if (mediaData.uri == null) {
            mediaData.uri = mCurrentPhoto.getContentUri();
        }

        if (mSlowMotionSharer == null) {
            mSlowMotionSharer = new SlowMotionSharer(new SlowMotionSharer.IShareContext() {
                public boolean isCancelled() {
                    // share context needs redefining
                    return false; // shareContext.isCancelled();
                }

                public Activity getActivity() {
                    return mActivity;
                }
            });
        }

        Job<Void> job = new Job<Void>() {
            public Void run(JobContext jc) {
                final List<MediaData> mediaDatas = new ArrayList<MediaData>();
                mediaDatas.add(mediaData);
                mSlowMotionSharer.share(mediaDatas);
                final ArrayList<Uri> uris = mSlowMotionSharer.getShareUris();
                mActivity.runOnUiThread(new Runnable() {
                    public void run() {
                        Log.i(TAG, "<extHotKnot> start share");
                        mActivity.getHotKnot().sendUri(uris.get(0), mCurrentPhoto.getMimeType());
                    }
                });
                return null;
            }
        };

        mActivity.getThreadPool().submit(job);
    }

    /// M: [FEATURE.ADD] Call onPQEffect, while receive PQBroadcast. @{
    /**
     * Request decode bitmap again when receive PQ broadcast.
     */
    public class PQEffect implements PQBroadcastReceiver.PQListener {

        @Override
        public void onPQEffect() {
            if (mModel.getMediaItem(0) != null) {
                MediaItem current = mModel.getMediaItem(0);
                Path path = current.getPath();
                Log.i(TAG, "<onPQEffect> current = " + current.getFilePath());
                path.clearObject();
                mActivity.getDataManager().forceRefreshAll();
            }
        }
    }
    /// @}
}