15f0775af25a40843a2c68d67d694945d88514555Andrew Sappersteinpackage com.android.ex.photo;
25f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
3bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.app.Activity;
4bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.app.ActivityManager;
5bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.content.Context;
6bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.content.Intent;
7bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.content.res.Resources;
8bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.database.Cursor;
9bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.graphics.drawable.Drawable;
10bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.net.Uri;
115f0775af25a40843a2c68d67d694945d88514555Andrew Sappersteinimport android.os.Build;
12bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.os.Bundle;
13bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.os.Handler;
1490382f446827fc956839b6294fb13dda6040583cAdam Coppimport android.os.Process;
15dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarcoimport android.support.annotation.IdRes;
16dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarcoimport android.support.annotation.Nullable;
17bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.support.v4.app.Fragment;
18bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.support.v4.app.FragmentManager;
19bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.support.v4.app.LoaderManager;
20bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.support.v4.content.Loader;
21bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.support.v4.view.ViewPager.OnPageChangeListener;
22bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.text.TextUtils;
2355ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Bordeimport android.util.DisplayMetrics;
24bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.util.Log;
25cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sappersteinimport android.view.Menu;
26bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.MenuItem;
275f0775af25a40843a2c68d67d694945d88514555Andrew Sappersteinimport android.view.View;
28bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.ViewPropertyAnimator;
29bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.ViewTreeObserver.OnGlobalLayoutListener;
30e3bc739bfab134619ff59cd139454a8a1c6a5506Yizhen Fuimport android.view.WindowManager;
319ea7077cea45d719026934d33b9deecf4c2eca93Shri Bordeimport android.view.accessibility.AccessibilityManager;
32bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.AlphaAnimation;
33bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.Animation;
34bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.Animation.AnimationListener;
35bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.AnimationSet;
36bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.ScaleAnimation;
37bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.view.animation.TranslateAnimation;
38bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport android.widget.ImageView;
395f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
40bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.ActionBarInterface.OnMenuVisibilityListener;
41bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.PhotoViewPager.InterceptType;
42bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.PhotoViewPager.OnInterceptTouchListener;
43bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.adapters.PhotoPagerAdapter;
44bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.fragments.PhotoViewFragment;
45bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.loaders.PhotoBitmapLoader;
46bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
47bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.loaders.PhotoPagerLoader;
48bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport com.android.ex.photo.provider.PhotoContract;
4955ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Bordeimport com.android.ex.photo.util.ImageUtils;
509ea7077cea45d719026934d33b9deecf4c2eca93Shri Bordeimport com.android.ex.photo.util.Util;
515f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
52bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport java.util.HashMap;
53bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport java.util.HashSet;
54bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport java.util.Map;
55bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodimport java.util.Set;
56bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
57bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood/**
58bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * This class implements all the logic of the photo view activity. An activity should use this class
59bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * calling through from relevant activity methods to the methods of the same name here.
60bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood *
61bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * To customize the photo viewer activity, you should subclass this and implement your
62bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * customizations here. Then subclass {@link PhotoViewActivity} and override just
63bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * {@link PhotoViewActivity#createController createController} to instantiate your controller
64bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood * subclass.
65bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood */
66bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwoodpublic class PhotoViewController implements
67bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        LoaderManager.LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener,
68bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        OnMenuVisibilityListener, PhotoViewCallbacks  {
695f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
70bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
71bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * Defines the interface between the Activity and this class.
72bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     *
73bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * The activity itself must delegate all appropriate method calls into this class, to the
74bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * methods of the same name.
75bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
76bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public interface ActivityInterface {
77bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public Context getContext();
78bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public Context getApplicationContext();
79bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public Intent getIntent();
80bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void setContentView(int resId);
81bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public View findViewById(int id);
82bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public Resources getResources();
83bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public FragmentManager getSupportFragmentManager();
84bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public LoaderManager getSupportLoaderManager();
85bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public ActionBarInterface getActionBarInterface();
86bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public boolean onOptionsItemSelected(MenuItem item);
87bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void finish();
88bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void overridePendingTransition(int enterAnim, int exitAnim);
89bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public PhotoViewController getController();
905f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein    }
915f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
92bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String TAG = "PhotoViewController";
93bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
9405f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde    private final static String STATE_INITIAL_URI_KEY =
9505f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            "com.android.ex.PhotoViewFragment.INITIAL_URI";
96bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_CURRENT_URI_KEY =
97bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.CURRENT_URI";
98bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_CURRENT_INDEX_KEY =
99bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.CURRENT_INDEX";
100bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_FULLSCREEN_KEY =
101bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.FULLSCREEN";
102bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_ACTIONBARTITLE_KEY =
103bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.ACTIONBARTITLE";
104bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_ACTIONBARSUBTITLE_KEY =
105bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.ACTIONBARSUBTITLE";
106bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final static String STATE_ENTERANIMATIONFINISHED_KEY =
107bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            "com.android.ex.PhotoViewFragment.SCALEANIMATIONFINISHED";
108bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
109bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected final static String ARG_IMAGE_URI = "image_uri";
110bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
111bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static final int LOADER_PHOTO_LIST = 100;
112bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
113bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Count used when the real photo count is unknown [but, may be determined] */
114bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static final int ALBUM_COUNT_UNKNOWN = -1;
115bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
116bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static final int ENTER_ANIMATION_DURATION_MS = 250;
117bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static final int EXIT_ANIMATION_DURATION_MS = 250;
118bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
119bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Argument key for the dialog message */
120bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static final String KEY_MESSAGE = "dialog_message";
121bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
122bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public static int sMemoryClass;
12355ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde    public static int sMaxPhotoSize; // The maximum size (either width or height)
124bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
125bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final ActivityInterface mActivity;
126bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
127bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private int mLastFlags;
1285f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
129e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian    private final View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener;
130e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian
131bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The URI of the photos we're viewing; may be {@code null} */
132bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private String mPhotosUri;
13305f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde    /** The uri of the initial photo */
13405f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde    private String mInitialPhotoUri;
135bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The index of the currently viewed photo */
136bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private int mCurrentPhotoIndex;
137bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The uri of the currently viewed photo */
138bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private String mCurrentPhotoUri;
139bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The query projection to use; may be {@code null} */
140bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private String[] mProjection;
141bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The total number of photos; only valid if {@link #mIsEmpty} is {@code false}. */
142bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected int mAlbumCount = ALBUM_COUNT_UNKNOWN;
143bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** {@code true} if the view is empty. Otherwise, {@code false}. */
144bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mIsEmpty;
145bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** the main root view */
146bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected View mRootView;
147bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Background image that contains nothing, so it can be alpha faded from
148bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * transparent to black without affecting any other views. */
149dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @Nullable
150bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected View mBackground;
151bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The main pager; provides left/right swipe between photos */
152bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected PhotoViewPager mViewPager;
153bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The temporary image so that we can quickly scale up the fullscreen thumbnail */
154dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @Nullable
155bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected ImageView mTemporaryImage;
156bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Adapter to create pager views */
157bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected PhotoPagerAdapter mAdapter;
158bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Whether or not we're in "full screen" mode */
159bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mFullScreen;
160bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The listeners wanting full screen state for each screen position */
161bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final Map<Integer, OnScreenListener>
162bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mScreenListeners = new HashMap<Integer, OnScreenListener>();
163bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The set of listeners wanting full screen state */
164bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>();
165bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** When {@code true}, restart the loader when the activity becomes active */
166bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private boolean mKickLoader;
167bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Don't attempt operations that may trigger a fragment transaction when the activity is
168bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * destroyed */
169bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private boolean mIsDestroyedCompat;
170bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** Whether or not this activity is paused */
171bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mIsPaused = true;
172bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The maximum scale factor applied to images when they are initially displayed */
173bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected float mMaxInitialScale;
174bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The title in the actionbar */
175bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected String mActionBarTitle;
176bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /** The subtitle in the actionbar */
177bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected String mActionBarSubtitle;
178bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
179bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private boolean mEnterAnimationFinished;
180bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mScaleAnimationEnabled;
181bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected int mAnimationStartX;
182bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected int mAnimationStartY;
183bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected int mAnimationStartWidth;
184bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected int mAnimationStartHeight;
185bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1868650d3cda75562489551ee04bff9145a49a6f16fKen Thai    /** Whether lights out should invoked based on timer */
1878650d3cda75562489551ee04bff9145a49a6f16fKen Thai    protected boolean mIsTimerLightsOutEnabled;
188bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mActionBarHiddenInitially;
189bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean mDisplayThumbsFullScreen;
190bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1919ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde    private final AccessibilityManager mAccessibilityManager;
1929ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde
193bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected BitmapCallback mBitmapCallback;
194bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected final Handler mHandler = new Handler();
195bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
196bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // TODO Find a better way to do this. We basically want the activity to display the
197bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // "loading..." progress until the fragment takes over and shows it's own "loading..."
198bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // progress [located in photo_header_view.xml]. We could potentially have all status displayed
199bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // by the activity, but, that gets tricky when it comes to screen rotation. For now, we
200bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // track the loading by this variable which is fragile and may cause phantom "loading..."
201bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    // text.
202bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private long mEnterFullScreenDelayTime;
203bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
204272ce63c04f61a7c22edaa119eb791b061a60adaTavis Bohne    private int lastAnnouncedTitle = -1;
205e3bc739bfab134619ff59cd139454a8a1c6a5506Yizhen Fu
206bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public PhotoViewController(ActivityInterface activity) {
207bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity = activity;
208e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian
209e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian        // View.OnSystemUiVisibilityChangeListener is an API that was introduced in API level 11.
210e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
211e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian            mSystemUiVisibilityChangeListener = null;
212e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian        } else {
213e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian            mSystemUiVisibilityChangeListener = new View.OnSystemUiVisibilityChangeListener() {
214e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                @Override
215e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                public void onSystemUiVisibilityChange(int visibility) {
216e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
217e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                            visibility == 0 && mLastFlags == 3846) {
218bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        setFullScreen(false /* fullscreen */, true /* setDelayedRunnable */);
219e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                    }
220e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian                }
221e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian            };
222e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian        }
2239ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde
2249ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        mAccessibilityManager = (AccessibilityManager)
2259ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                activity.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
2265f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein    }
2275f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
228bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public PhotoPagerAdapter createPhotoPagerAdapter(Context context,
229bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
230bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return new PhotoPagerAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen);
231bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
232bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
233cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public PhotoViewController.ActivityInterface getActivity() {
234cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein        return mActivity;
235cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    }
236cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
237bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onCreate(Bundle savedInstanceState) {
23855ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde        initMaxPhotoSize();
239bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final ActivityManager mgr = (ActivityManager) mActivity.getApplicationContext().
240bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                getSystemService(Activity.ACTIVITY_SERVICE);
241bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        sMemoryClass = mgr.getMemoryClass();
242bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
243bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Intent intent = mActivity.getIntent();
244bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // uri of the photos to view; optional
245bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (intent.hasExtra(Intents.EXTRA_PHOTOS_URI)) {
246bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mPhotosUri = intent.getStringExtra(Intents.EXTRA_PHOTOS_URI);
247bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
2488650d3cda75562489551ee04bff9145a49a6f16fKen Thai
2498650d3cda75562489551ee04bff9145a49a6f16fKen Thai        mIsTimerLightsOutEnabled = intent.getBooleanExtra(
2508650d3cda75562489551ee04bff9145a49a6f16fKen Thai                Intents.EXTRA_ENABLE_TIMER_LIGHTS_OUT, true);
2518650d3cda75562489551ee04bff9145a49a6f16fKen Thai
252bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (intent.getBooleanExtra(Intents.EXTRA_SCALE_UP_ANIMATION, false)) {
253bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mScaleAnimationEnabled = true;
254bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mAnimationStartX = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_X, 0);
255bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mAnimationStartY = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_Y, 0);
256bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mAnimationStartWidth = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_WIDTH, 0);
257bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mAnimationStartHeight = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_HEIGHT, 0);
258bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
259bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActionBarHiddenInitially = intent.getBooleanExtra(
2609ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                Intents.EXTRA_ACTION_BAR_HIDDEN_INITIALLY, false)
2619ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                && !Util.isTouchExplorationEnabled(mAccessibilityManager);
262bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mDisplayThumbsFullScreen = intent.getBooleanExtra(
263bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                Intents.EXTRA_DISPLAY_THUMBS_FULLSCREEN, false);
264bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
265bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // projection for the query; optional
266bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // If not set, the default projection is used.
267bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // This projection must include the columns from the default projection.
268bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (intent.hasExtra(Intents.EXTRA_PROJECTION)) {
269bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mProjection = intent.getStringArrayExtra(Intents.EXTRA_PROJECTION);
270bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
271bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mProjection = null;
272bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
273bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
274bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Set the max initial scale, defaulting to 1x
275bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mMaxInitialScale = intent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
276bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoUri = null;
277bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoIndex = -1;
278bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
279bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // We allow specifying the current photo by either index or uri.
280bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // This is because some users may have live datasets that can change,
281bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // adding new items to either the beginning or end of the set. For clients
282bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // that do not need that capability, ability to specify the current photo
283bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // by index is offered as a convenience.
284bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (intent.hasExtra(Intents.EXTRA_PHOTO_INDEX)) {
285bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mCurrentPhotoIndex = intent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
286bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
287bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (intent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI)) {
28805f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            mInitialPhotoUri = intent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
28905f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            mCurrentPhotoUri = mInitialPhotoUri;
290bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
291bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mIsEmpty = true;
292bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
293bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (savedInstanceState != null) {
29405f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            mInitialPhotoUri = savedInstanceState.getString(STATE_INITIAL_URI_KEY);
295bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mCurrentPhotoUri = savedInstanceState.getString(STATE_CURRENT_URI_KEY);
296bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mCurrentPhotoIndex = savedInstanceState.getInt(STATE_CURRENT_INDEX_KEY);
2979ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false)
2989ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                    && !Util.isTouchExplorationEnabled(mAccessibilityManager);
299bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarTitle = savedInstanceState.getString(STATE_ACTIONBARTITLE_KEY);
300bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarSubtitle = savedInstanceState.getString(STATE_ACTIONBARSUBTITLE_KEY);
301bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mEnterAnimationFinished = savedInstanceState.getBoolean(
302bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    STATE_ENTERANIMATIONFINISHED_KEY, false);
303bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
304bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mFullScreen = mActionBarHiddenInitially;
305bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
306bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
307dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        mActivity.setContentView(getContentViewId());
308bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
309bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Create the adapter and add the view pager
310bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mAdapter = createPhotoPagerAdapter(mActivity.getContext(),
311bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        mActivity.getSupportFragmentManager(), null, mMaxInitialScale);
312bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Resources resources = mActivity.getResources();
313dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        mRootView = findViewById(getRootViewId());
314bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
315bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mRootView.setOnSystemUiVisibilityChangeListener(getSystemUiVisibilityChangeListener());
316bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
317dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        mBackground = getBackground();
318dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        mTemporaryImage = getTemporaryImage();
319bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
320bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager.setAdapter(mAdapter);
321bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager.setOnPageChangeListener(this);
322bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager.setOnInterceptTouchListener(this);
323bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin));
324bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
325bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mBitmapCallback = new BitmapCallback();
326bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (!mScaleAnimationEnabled || mEnterAnimationFinished) {
327bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // We are not running the scale up animation. Just let the fragments
328bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // display and handle the animation.
329bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
330bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Make the background opaque immediately so that we don't see the activity
331bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // behind this one.
332dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasBackground()) {
333dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setVisibility(View.VISIBLE);
334dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
335bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
336bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Attempt to load the initial image thumbnail. Once we have the
337bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // image, animate it up. Once the animation is complete, we can kick off
338bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // loading the ViewPager. After the primary fullres image is loaded, we will
339bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // make our temporary image invisible and display the ViewPager.
340bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mViewPager.setVisibility(View.GONE);
341bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            Bundle args = new Bundle();
34205f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            args.putString(ARG_IMAGE_URI, mInitialPhotoUri);
343bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActivity.getSupportLoaderManager().initLoader(
344bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    BITMAP_LOADER_THUMBNAIL, args, mBitmapCallback);
345bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
346bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
347bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mEnterFullScreenDelayTime =
348bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                resources.getInteger(R.integer.reenter_fullscreen_delay_time_in_millis);
349bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
350bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final ActionBarInterface actionBar = mActivity.getActionBarInterface();
351bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (actionBar != null) {
352bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            actionBar.setDisplayHomeAsUpEnabled(true);
353bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            actionBar.addOnMenuVisibilityListener(this);
354bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            actionBar.setDisplayOptionsShowTitle();
355bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Set the title and subtitle immediately here, rather than waiting
356bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // for the fragment to be initialized.
357bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setActionBarTitles(actionBar);
358bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
359bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
360bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (!mScaleAnimationEnabled) {
361bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setLightsOutMode(mFullScreen);
362bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
363bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Keep lights out mode as false. This is to prevent jank cause by concurrent
364bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // animations during the enter animation.
365bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setLightsOutMode(false);
366bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
367bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
368bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
36955ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde    private void initMaxPhotoSize() {
37055ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde        if (sMaxPhotoSize == 0) {
37155ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            final DisplayMetrics metrics = new DisplayMetrics();
37255ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            final WindowManager wm = (WindowManager)
37355ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    mActivity.getContext().getSystemService(Context.WINDOW_SERVICE);
37455ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize;
37555ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            wm.getDefaultDisplay().getMetrics(metrics);
37655ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            switch (imageSize) {
37755ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                case EXTRA_SMALL:
37855ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    // Use a photo that's 80% of the "small" size
37955ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    sMaxPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000;
38055ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    break;
38155ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                case SMALL:
38255ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    // Fall through.
38355ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                case NORMAL:
38455ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    // Fall through.
38555ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                default:
38655ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    sMaxPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels);
38755ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde                    break;
38855ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde            }
38955ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde        }
39055ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde    }
39155ac8ea9519e981bfdbd83c82615c72cfa78d84cShri Borde
392cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public boolean onCreateOptionsMenu(Menu menu) {
393cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein        return true;
394cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    }
395cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
396cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public boolean onPrepareOptionsMenu(Menu menu) {
397cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein        return true;
398cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    }
399cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
400cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public void onActivityResult(int requestCode, int resultCode, Intent data) {}
401cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
402cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    protected View findViewById(int id) {
403bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mActivity.findViewById(id);
404bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
405bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
406dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
407dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns the android id of the viewer's root view. Subclasses should override this method if
408dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * they provide their own layout.
409dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
410dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @IdRes
411dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected int getRootViewId() {
412dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco      return R.id.photo_activity_root_view;
413dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
414dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
415dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
416dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns the android layout id of the root layout that should be inflated for the viewer.
417dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Subclasses should override this method if they provide their own layout.
418dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
419dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @IdRes
420dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected int getContentViewId() {
421dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        return R.layout.photo_activity_view;
422dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
423dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
424dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
425dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns the android view for the viewer's background view, if it has one. Subclasses should
426dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * override this if they have a different (or no) background view.
427dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
428dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @Nullable
429dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected View getBackground() {
430dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        return findViewById(R.id.photo_activity_background);
431dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
432dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
433dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
434dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns whether or not the view has a background object. Subclasses should override this if
435dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * they do not contain a background object.
436dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
437dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected boolean hasBackground() {
438dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        return mBackground != null;
439dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
440dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
441dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
442dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns the android view for the viewer's temporary image, if it has one. Subclasses should
443dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * override this if they have a different (or no) temporary image view.
444dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
445dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    @Nullable
446dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected ImageView getTemporaryImage() {
447dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        return (ImageView) findViewById(R.id.photo_activity_temporary_image);
448dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
449dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
450dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    /**
451dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * Returns whether or not the view has a temporary image view. Subclasses should override this
452dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     * if they do not use a temporary image.
453dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco     */
454dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    protected boolean hasTemporaryImage() {
455dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        return mTemporaryImage != null;
456dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco    }
457dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
458cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public void onStart() {}
459cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
460bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onResume() {
461bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setFullScreen(mFullScreen, false);
462bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
463bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mIsPaused = false;
464bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mKickLoader) {
465bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mKickLoader = false;
466bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
467bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
468bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
469bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
470bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onPause() {
471bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mIsPaused = true;
472bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
473bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
474cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein    public void onStop() {}
475cd95597f2dfc0760a105a87d16664e56ca1a4afeAndrew Sapperstein
476bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onDestroy() {
477bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mIsDestroyedCompat = true;
478bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
479bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
480bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private boolean isDestroyedCompat() {
481bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mIsDestroyedCompat;
482bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
483bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
484bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean onBackPressed() {
485bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // If we are in fullscreen mode, and the default is not full screen, then
486bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // switch back to actionBar display mode.
487bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mFullScreen && !mActionBarHiddenInitially) {
488bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            toggleFullScreen();
489bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
490bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (mScaleAnimationEnabled) {
491bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                runExitAnimation();
492bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
493bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                return false;
494bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
495bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
496bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return true;
497bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
498bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
499bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onSaveInstanceState(Bundle outState) {
50005f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde        outState.putString(STATE_INITIAL_URI_KEY, mInitialPhotoUri);
501bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putString(STATE_CURRENT_URI_KEY, mCurrentPhotoUri);
502bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putInt(STATE_CURRENT_INDEX_KEY, mCurrentPhotoIndex);
503bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
504bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putString(STATE_ACTIONBARTITLE_KEY, mActionBarTitle);
505bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putString(STATE_ACTIONBARSUBTITLE_KEY, mActionBarSubtitle);
506bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        outState.putBoolean(STATE_ENTERANIMATIONFINISHED_KEY, mEnterAnimationFinished);
507bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
508bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
509bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean onOptionsItemSelected(MenuItem item) {
510bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood       switch (item.getItemId()) {
511bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood          case android.R.id.home:
512bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood             mActivity.finish();
513bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood             return true;
514bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood          default:
515bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood             return false;
516bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood       }
517bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
518bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
519bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
520bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void addScreenListener(int position, OnScreenListener listener) {
521bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mScreenListeners.put(position, listener);
522bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
523bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
524bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
525bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void removeScreenListener(int position) {
526bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mScreenListeners.remove(position);
527bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
528bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
529bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
530bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public synchronized void addCursorListener(CursorChangedListener listener) {
531bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCursorListeners.add(listener);
532bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
533bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
534bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
535bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public synchronized void removeCursorListener(CursorChangedListener listener) {
536bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCursorListeners.remove(listener);
537bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
538bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
539bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
540bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean isFragmentFullScreen(Fragment fragment) {
541bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mViewPager == null || mAdapter == null || mAdapter.getCount() == 0) {
542bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return mFullScreen;
543bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
544bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mFullScreen || (mViewPager.getCurrentItem() != mAdapter.getItemPosition(fragment));
545bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
546bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
547bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
548bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void toggleFullScreen() {
549bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setFullScreen(!mFullScreen, true);
550bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
551bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
552bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onPhotoRemoved(long photoId) {
553bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Cursor data = mAdapter.getCursor();
554bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (data == null) {
555bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Huh?! How would this happen?
556bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return;
557bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
558bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
559bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int dataCount = data.getCount();
560bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (dataCount <= 1) {
561bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActivity.finish();
562bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return;
563bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
564bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
565bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
566bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
567bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
568bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
569bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
570bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (id == LOADER_PHOTO_LIST) {
571bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return new PhotoPagerLoader(mActivity.getContext(), Uri.parse(mPhotosUri), mProjection);
572bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
573bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return null;
574bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
575bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
576bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
577bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public Loader<BitmapResult> onCreateBitmapLoader(int id, Bundle args, String uri) {
578bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        switch (id) {
579bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            case BITMAP_LOADER_AVATAR:
580bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            case BITMAP_LOADER_THUMBNAIL:
581bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            case BITMAP_LOADER_PHOTO:
582bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                return new PhotoBitmapLoader(mActivity.getContext(), uri);
583bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            default:
584bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                return null;
585bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
586bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
587bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
588bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
589bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
590bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int id = loader.getId();
591bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (id == LOADER_PHOTO_LIST) {
592bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (data == null || data.getCount() == 0) {
593bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mIsEmpty = true;
594bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mAdapter.swapCursor(null);
595bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
596bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mAlbumCount = data.getCount();
597bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                if (mCurrentPhotoUri != null) {
598bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    int index = 0;
599bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    // Clear query params. Compare only the path.
600bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    final int uriIndex = data.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
601bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    final Uri currentPhotoUri;
602bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
603bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        currentPhotoUri = Uri.parse(mCurrentPhotoUri).buildUpon()
604bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                                .clearQuery().build();
605bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    } else {
606bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        currentPhotoUri = Uri.parse(mCurrentPhotoUri).buildUpon()
607bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                                .query(null).build();
608bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    }
609f39584b0cd6b5cd47a76854efffe437366693b3eGeorge Ruban                    // Rewind data cursor to the start if it has already advanced.
610f39584b0cd6b5cd47a76854efffe437366693b3eGeorge Ruban                    data.moveToPosition(-1);
611bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    while (data.moveToNext()) {
612bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        final String uriString = data.getString(uriIndex);
613bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        final Uri uri;
614bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
615bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            uri = Uri.parse(uriString).buildUpon().clearQuery().build();
616bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        } else {
617bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            uri = Uri.parse(uriString).buildUpon().query(null).build();
618bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        }
619bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        if (currentPhotoUri != null && currentPhotoUri.equals(uri)) {
620bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            mCurrentPhotoIndex = index;
621bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            break;
622bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        }
623bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        index++;
624bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    }
625bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
626bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
627bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // We're paused; don't do anything now, we'll get re-invoked
628bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // when the activity becomes active again
629bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                if (mIsPaused) {
630bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    mKickLoader = true;
631bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    mAdapter.swapCursor(null);
632bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    return;
633bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
634bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                boolean wasEmpty = mIsEmpty;
635bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mIsEmpty = false;
636bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
637bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mAdapter.swapCursor(data);
638bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                if (mViewPager.getAdapter() == null) {
639bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    mViewPager.setAdapter(mAdapter);
640bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
641bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                notifyCursorListeners(data);
642bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
643bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // Use an index of 0 if the index wasn't specified or couldn't be found
644bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                if (mCurrentPhotoIndex < 0) {
645bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    mCurrentPhotoIndex = 0;
646bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
647bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
648bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mViewPager.setCurrentItem(mCurrentPhotoIndex, false);
649bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                if (wasEmpty) {
650bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    setViewActivated(mCurrentPhotoIndex);
651bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
652bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
653bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Update the any action items
654bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            updateActionItems();
655bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
656bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
657bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
658bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
659bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
660bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // If the loader is reset, remove the reference in the adapter to this cursor
661bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (!isDestroyedCompat()) {
662bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // This will cause a fragment transaction which can't happen if we're destroyed,
663bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // but we don't care in that case because we're destroyed anyways.
664bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mAdapter.swapCursor(null);
665bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
666bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
667bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
668bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void updateActionItems() {
669bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Do nothing, but allow extending classes to do work
670bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
671bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
672bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private synchronized void notifyCursorListeners(Cursor data) {
673bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // tell all of the objects listening for cursor changes
674bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // that the cursor has changed
675bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        for (CursorChangedListener listener : mCursorListeners) {
676bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            listener.onCursorChanged(data);
677bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
678bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
679bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
680bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
681bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
682bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (positionOffset < 0.0001) {
683bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            OnScreenListener before = mScreenListeners.get(position - 1);
684bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (before != null) {
685bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                before.onViewUpNext();
686bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
687bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            OnScreenListener after = mScreenListeners.get(position + 1);
688bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (after != null) {
689bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                after.onViewUpNext();
690bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
691bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
692bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
693bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
694bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
695bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onPageSelected(int position) {
696bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoIndex = position;
697bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setViewActivated(position);
698bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
699bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
700bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
701bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onPageScrollStateChanged(int state) {
702bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
703bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
704bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
705bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean isFragmentActive(Fragment fragment) {
706bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mViewPager == null || mAdapter == null) {
707bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return false;
708bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
709bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mViewPager.getCurrentItem() == mAdapter.getItemPosition(fragment);
710bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
711bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
712bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
713bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onFragmentVisible(PhotoViewFragment fragment) {
714bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Do nothing, we handle this in setViewActivated
715bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
716bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
717bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
718bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public InterceptType onTouchIntercept(float origX, float origY) {
719bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        boolean interceptLeft = false;
720bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        boolean interceptRight = false;
721bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
722bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        for (OnScreenListener listener : mScreenListeners.values()) {
723bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (!interceptLeft) {
724bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                interceptLeft = listener.onInterceptMoveLeft(origX, origY);
725bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
726bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (!interceptRight) {
727bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                interceptRight = listener.onInterceptMoveRight(origX, origY);
728bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
729bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
730bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
731bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (interceptLeft) {
732bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (interceptRight) {
733bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                return InterceptType.BOTH;
734bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
735bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return InterceptType.LEFT;
736bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else if (interceptRight) {
737bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return InterceptType.RIGHT;
738bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
739bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return InterceptType.NONE;
740bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
741bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
742bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
743bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * Updates the title bar according to the value of {@link #mFullScreen}.
744bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
745bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) {
7469ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        if (Util.isTouchExplorationEnabled(mAccessibilityManager)) {
7479ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            // Disallow full screen mode when accessibility is enabled so that the action bar
7489ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            // stays accessible.
7499ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            fullScreen = false;
7509ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            setDelayedRunnable = false;
7519ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        }
7529ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde
753bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final boolean fullScreenChanged = (fullScreen != mFullScreen);
754bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mFullScreen = fullScreen;
755bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
756bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mFullScreen) {
757bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setLightsOutMode(true);
758bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            cancelEnterFullScreenRunnable();
759bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
760bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setLightsOutMode(false);
761bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (setDelayedRunnable) {
762bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                postEnterFullScreenRunnableWithDelay();
763bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
764bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
765bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
766bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (fullScreenChanged) {
767bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            for (OnScreenListener listener : mScreenListeners.values()) {
768bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                listener.onFullScreenChanged(mFullScreen);
769bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
770bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
771bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
772bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
7738650d3cda75562489551ee04bff9145a49a6f16fKen Thai    /**
7748650d3cda75562489551ee04bff9145a49a6f16fKen Thai     * Posts a runnable to enter full screen after mEnterFullScreenDelayTime. This method is a
7758650d3cda75562489551ee04bff9145a49a6f16fKen Thai     * no-op if mIsTimerLightsOutEnabled is set to false.
7768650d3cda75562489551ee04bff9145a49a6f16fKen Thai     */
777bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void postEnterFullScreenRunnableWithDelay() {
7788650d3cda75562489551ee04bff9145a49a6f16fKen Thai        if (mIsTimerLightsOutEnabled) {
7798650d3cda75562489551ee04bff9145a49a6f16fKen Thai            mHandler.postDelayed(mEnterFullScreenRunnable, mEnterFullScreenDelayTime);
7808650d3cda75562489551ee04bff9145a49a6f16fKen Thai        }
781bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
782bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
783bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void cancelEnterFullScreenRunnable() {
784bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mHandler.removeCallbacks(mEnterFullScreenRunnable);
785bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
786bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
787bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected void setLightsOutMode(boolean enabled) {
788bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setImmersiveMode(enabled);
789bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
790bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
791bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private final Runnable mEnterFullScreenRunnable = new Runnable() {
792bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        @Override
793bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void run() {
794bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            setFullScreen(true, true);
795bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
796bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    };
797bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
798bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
799bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void setViewActivated(int position) {
800bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        OnScreenListener listener = mScreenListeners.get(position);
801bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (listener != null) {
802bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            listener.onViewActivated();
803bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
804bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Cursor cursor = getCursorAtProperPosition();
805bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoIndex = position;
806bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // FLAG: get the column indexes once in onLoadFinished().
807bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // That would make this more efficient, instead of looking these up
808bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // repeatedly whenever we want them.
809bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        int uriIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
810bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoUri = cursor.getString(uriIndex);
811bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        updateActionBar();
812272ce63c04f61a7c22edaa119eb791b061a60adaTavis Bohne        if (mAccessibilityManager.isEnabled() && lastAnnouncedTitle != position) {
8139ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            String announcement = getPhotoAccessibilityAnnouncement(position);
8149ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            if (announcement != null) {
8159ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                Util.announceForAccessibility(mRootView, mAccessibilityManager, announcement);
816272ce63c04f61a7c22edaa119eb791b061a60adaTavis Bohne                lastAnnouncedTitle = position;
8179ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            }
8189ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        }
819bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
820bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Restart the timer to return to fullscreen.
821bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        cancelEnterFullScreenRunnable();
822bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        postEnterFullScreenRunnableWithDelay();
823bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
824bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
825bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
826bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * Adjusts the activity title and subtitle to reflect the photo name and count.
827bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
828bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void updateActionBar() {
829bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int position = mViewPager.getCurrentItem() + 1;
830bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final boolean hasAlbumCount = mAlbumCount >= 0;
831bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
832bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Cursor cursor = getCursorAtProperPosition();
833bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (cursor != null) {
834bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // FLAG: We should grab the indexes when we first get the cursor
835bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // and store them so we don't need to do it each time.
836bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME);
837bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarTitle = cursor.getString(photoNameIndex);
838bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
839bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarTitle = null;
840bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
841bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
842bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mIsEmpty || !hasAlbumCount || position <= 0) {
843bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarSubtitle = null;
844bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
845bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            mActionBarSubtitle = mActivity.getResources().getString(
846bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    R.string.photo_view_count, position, mAlbumCount);
847bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
848bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
849bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setActionBarTitles(mActivity.getActionBarInterface());
850bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
851bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
852bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
8539ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde     * Returns a string used as an announcement for accessibility after the user moves to a new
8549ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde     * photo. It will be called after {@link #updateActionBar} has been called.
8559ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde     * @param position the index in the album of the currently active photo
8569ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde     * @return announcement for accessibility
8579ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde     */
8589ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde    protected String getPhotoAccessibilityAnnouncement(int position) {
8599ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        String announcement = mActionBarTitle;
8609ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        if (mActionBarSubtitle != null) {
8619ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde            announcement = mActivity.getContext().getResources().getString(
8629ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde                    R.string.titles, mActionBarTitle, mActionBarSubtitle);
8639ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        }
8649ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde        return announcement;
8659ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde    }
8669ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde
8679ea7077cea45d719026934d33b9deecf4c2eca93Shri Borde    /**
868bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * Sets the Action Bar title to {@link #mActionBarTitle} and the subtitle to
869bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * {@link #mActionBarSubtitle}
870bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
871bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected final void setActionBarTitles(ActionBarInterface actionBar) {
872bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (actionBar == null) {
873bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return;
874bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
875bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        actionBar.setTitle(getInputOrEmpty(mActionBarTitle));
876bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        actionBar.setSubtitle(getInputOrEmpty(mActionBarSubtitle));
877bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
878bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
879bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
880bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * If the input string is non-null, it is returned, otherwise an empty string is returned;
881bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * @param in
882bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * @return
883bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
884bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private static final String getInputOrEmpty(String in) {
885bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (in == null) {
886bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return "";
887bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
888bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return in;
889bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
890bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
891bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    /**
892bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * Utility method that will return the cursor that contains the data
893bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * at the current position so that it refers to the current image on screen.
894bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * @return the cursor at the current position or
895bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     * null if no cursor exists or if the {@link PhotoViewPager} is null.
896bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood     */
897bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public Cursor getCursorAtProperPosition() {
898bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mViewPager == null) {
899bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return null;
900bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
901bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
902bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int position = mViewPager.getCurrentItem();
903bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final Cursor cursor = mAdapter.getCursor();
904bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
905bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (cursor == null) {
906bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return null;
907bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
908bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
909bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        cursor.moveToPosition(position);
910bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
911bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return cursor;
912bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
913bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
914bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public Cursor getCursor() {
915bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return (mAdapter == null) ? null : mAdapter.getCursor();
916bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
917bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
918bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
919bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onMenuVisibilityChanged(boolean isVisible) {
920bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (isVisible) {
921bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            cancelEnterFullScreenRunnable();
922bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
923bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            postEnterFullScreenRunnableWithDelay();
924bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
925bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
926bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
927bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
928bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onNewPhotoLoaded(int position) {
929bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // do nothing
930bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
931bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
932bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected void setPhotoIndex(int index) {
933bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mCurrentPhotoIndex = index;
934bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
935bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
936bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
937bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onFragmentPhotoLoadComplete(PhotoViewFragment fragment, boolean success) {
938dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        if (hasTemporaryImage() && mTemporaryImage.getVisibility() != View.GONE &&
939bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                TextUtils.equals(fragment.getPhotoUri(), mCurrentPhotoUri)) {
940bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (success) {
941bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // The fragment for the current image is now ready for display.
942dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                if (hasTemporaryImage()) {
943dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    mTemporaryImage.setVisibility(View.GONE);
944dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                }
945bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mViewPager.setVisibility(View.VISIBLE);
946bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
947bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // This means that we are unable to load the fragment's photo.
948bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // I'm not sure what the best thing to do here is, but at least if
949bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // we display the viewPager, the fragment itself can decide how to
950bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // display the failure of its own image.
951bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                Log.w(TAG, "Failed to load fragment image");
952dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                if (hasTemporaryImage()) {
953dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    mTemporaryImage.setVisibility(View.GONE);
954dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                }
955bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mViewPager.setVisibility(View.VISIBLE);
956bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
95705f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            mActivity.getSupportLoaderManager().destroyLoader(
95805f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde                    PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL);
959bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
960bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
961bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
962bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    protected boolean isFullScreen() {
963bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mFullScreen;
964bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
965bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
966bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
967bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) {
968bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // do nothing
969bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
970bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
971bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    @Override
972bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public PhotoPagerAdapter getAdapter() {
973bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mAdapter;
974bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
975bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
976bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void onEnterAnimationComplete() {
977bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mEnterAnimationFinished = true;
978bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mViewPager.setVisibility(View.VISIBLE);
979bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        setLightsOutMode(mFullScreen);
980bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
981bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
982bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void onExitAnimationComplete() {
983bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.finish();
984bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.overridePendingTransition(0, 0);
985bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
986bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
987bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void runEnterAnimation() {
988bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int totalWidth = mRootView.getMeasuredWidth();
989bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int totalHeight = mRootView.getMeasuredHeight();
990bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
991bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // FLAG: Need to handle the aspect ratio of the bitmap.  If it's a portrait
992bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // bitmap, then we need to position the view higher so that the middle
993bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // pixels line up.
994dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        if (hasTemporaryImage()) {
995dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            mTemporaryImage.setVisibility(View.VISIBLE);
996dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        }
997bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // We need to take a full screen image, and scale/translate it so that
998bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // it appears at exactly the same location onscreen as it is in the
999bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // prior activity.
1000bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // The final image will take either the full screen width or height (or both).
1001bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1002bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scaleW = (float) mAnimationStartWidth / totalWidth;
1003bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scaleY = (float) mAnimationStartHeight / totalHeight;
1004bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scale = Math.max(scaleW, scaleY);
1005bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1006bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int translateX = calculateTranslate(mAnimationStartX, mAnimationStartWidth,
1007bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                totalWidth, scale);
1008bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int translateY = calculateTranslate(mAnimationStartY, mAnimationStartHeight,
1009bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                totalHeight, scale);
1010bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1011bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int version = android.os.Build.VERSION.SDK_INT;
1012bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1013dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasBackground()) {
1014dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setAlpha(0f);
1015dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.animate().alpha(1f).setDuration(ENTER_ANIMATION_DURATION_MS).start();
1016dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setVisibility(View.VISIBLE);
1017dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
1018bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1019dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasTemporaryImage()) {
1020dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mTemporaryImage.setScaleX(scale);
1021dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mTemporaryImage.setScaleY(scale);
1022dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mTemporaryImage.setTranslationX(translateX);
1023dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mTemporaryImage.setTranslationY(translateY);
1024bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1025dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                Runnable endRunnable = new Runnable() {
1026dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    @Override
1027dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    public void run() {
1028dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                        PhotoViewController.this.onEnterAnimationComplete();
1029dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    }
1030dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                };
1031dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                ViewPropertyAnimator animator = mTemporaryImage.animate().scaleX(1f).scaleY(1f)
1032dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    .translationX(0).translationY(0).setDuration(ENTER_ANIMATION_DURATION_MS);
1033dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                if (version >= Build.VERSION_CODES.JELLY_BEAN) {
1034dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    animator.withEndAction(endRunnable);
1035dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                } else {
1036dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    mHandler.postDelayed(endRunnable, ENTER_ANIMATION_DURATION_MS);
1037bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
1038dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                animator.start();
1039bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1040bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
1041dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasBackground()) {
1042dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
1043dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                alphaAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
1044dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.startAnimation(alphaAnimation);
1045dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setVisibility(View.VISIBLE);
1046dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
1047bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1048dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasTemporaryImage()) {
1049dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                final Animation translateAnimation = new TranslateAnimation(translateX,
1050dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                        translateY, 0, 0);
1051dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                translateAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
1052dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                Animation scaleAnimation = new ScaleAnimation(scale, scale, 0, 0);
1053dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                scaleAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
1054dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
1055dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                AnimationSet animationSet = new AnimationSet(true);
1056dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                animationSet.addAnimation(translateAnimation);
1057dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                animationSet.addAnimation(scaleAnimation);
1058dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                AnimationListener listener = new AnimationListener() {
1059dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    @Override
1060dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    public void onAnimationEnd(Animation arg0) {
1061dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                        PhotoViewController.this.onEnterAnimationComplete();
1062dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    }
1063bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1064dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    @Override
1065dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    public void onAnimationRepeat(Animation arg0) {
1066dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    }
1067dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco
1068dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    @Override
1069dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    public void onAnimationStart(Animation arg0) {
1070dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                    }
1071dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                };
1072dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                animationSet.setAnimationListener(listener);
1073dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mTemporaryImage.startAnimation(animationSet);
1074dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
1075bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1076bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1077bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1078bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void runExitAnimation() {
1079bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        Intent intent = mActivity.getIntent();
1080bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // FLAG: should just fall back to a standard animation if either:
1081bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // 1. images have been added or removed since we've been here, or
1082bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // 2. we are currently looking at some image other than the one we
1083bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // started on.
1084bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1085bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int totalWidth = mRootView.getMeasuredWidth();
1086bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int totalHeight = mRootView.getMeasuredHeight();
1087bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1088bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // We need to take a full screen image, and scale/translate it so that
1089bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // it appears at exactly the same location onscreen as it is in the
1090bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // prior activity.
1091bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // The final image will take either the full screen width or height (or both).
1092bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scaleW = (float) mAnimationStartWidth / totalWidth;
1093bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scaleY = (float) mAnimationStartHeight / totalHeight;
1094bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final float scale = Math.max(scaleW, scaleY);
1095bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1096bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int translateX = calculateTranslate(mAnimationStartX, mAnimationStartWidth,
1097bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                totalWidth, scale);
1098bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int translateY = calculateTranslate(mAnimationStartY, mAnimationStartHeight,
1099bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                totalHeight, scale);
1100bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        final int version = android.os.Build.VERSION.SDK_INT;
1101bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1102dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasBackground()) {
1103dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.animate().alpha(0f).setDuration(EXIT_ANIMATION_DURATION_MS).start();
1104dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setVisibility(View.VISIBLE);
1105dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
1106bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1107bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            Runnable endRunnable = new Runnable() {
1108bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                @Override
1109bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                public void run() {
1110bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    PhotoViewController.this.onExitAnimationComplete();
1111bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
1112bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            };
1113bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // If the temporary image is still visible it means that we have
1114bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // not yet loaded the fullres image, so we need to animate
1115bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // the temporary image out.
1116bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            ViewPropertyAnimator animator = null;
1117dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasTemporaryImage() && mTemporaryImage.getVisibility() == View.VISIBLE) {
1118bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                animator = mTemporaryImage.animate().scaleX(scale).scaleY(scale)
1119bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    .translationX(translateX).translationY(translateY)
1120bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    .setDuration(EXIT_ANIMATION_DURATION_MS);
1121bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
1122bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                animator = mViewPager.animate().scaleX(scale).scaleY(scale)
1123bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    .translationX(translateX).translationY(translateY)
1124bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    .setDuration(EXIT_ANIMATION_DURATION_MS);
1125bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
112605f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            // If the user has swiped to a different photo, fade out the current photo
112705f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            // along with the scale animation.
112805f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            if (!mInitialPhotoUri.equals(mCurrentPhotoUri)) {
112905f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde                animator.alpha(0f);
113005f451df7677dce766b4e6b30ff89e2d3bb0b104Shri Borde            }
1131bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (version >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
1132bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                animator.withEndAction(endRunnable);
1133bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
1134bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mHandler.postDelayed(endRunnable, EXIT_ANIMATION_DURATION_MS);
1135bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1136bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            animator.start();
1137bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        } else {
1138dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasBackground()) {
1139dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                final Animation alphaAnimation = new AlphaAnimation(1f, 0f);
1140dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                alphaAnimation.setDuration(EXIT_ANIMATION_DURATION_MS);
1141dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.startAnimation(alphaAnimation);
1142dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco                mBackground.setVisibility(View.VISIBLE);
1143dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            }
1144bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1145bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            final Animation scaleAnimation = new ScaleAnimation(1f, 1f, scale, scale);
1146bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            scaleAnimation.setDuration(EXIT_ANIMATION_DURATION_MS);
1147bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            AnimationListener listener = new AnimationListener() {
1148bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                @Override
1149bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                public void onAnimationEnd(Animation arg0) {
1150bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    PhotoViewController.this.onExitAnimationComplete();
1151bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
1152bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1153bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                @Override
1154bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                public void onAnimationRepeat(Animation arg0) {
1155bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
1156bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1157bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                @Override
1158bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                public void onAnimationStart(Animation arg0) {
1159bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                }
1160bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            };
1161bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            scaleAnimation.setAnimationListener(listener);
1162bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // If the temporary image is still visible it means that we have
1163bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // not yet loaded the fullres image, so we need to animate
1164bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // the temporary image out.
1165dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            if (hasTemporaryImage() && mTemporaryImage.getVisibility() == View.VISIBLE) {
1166bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mTemporaryImage.startAnimation(scaleAnimation);
1167bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
1168bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                mViewPager.startAnimation(scaleAnimation);
1169bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1170bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1171bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1172bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1173bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private int calculateTranslate(int start, int startSize, int totalSize, float scale) {
1174bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Translation takes precedence over scale.  What this means is that if
1175bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // we want an view's upper left corner to be a particular spot on screen,
1176bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // but that view is scaled to something other than 1, we need to take into
1177bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // account the pixels lost to scaling.
1178bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // So if we have a view that is 200x300, and we want it's upper left corner
1179bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // to be at 50x50, but it's scaled by 50%, we can't just translate it to 50x50.
1180bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // If we were to do that, the view's *visible* upper left corner would be at
1181bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // 100x200.  We need to take into account the difference between the outside
1182bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // size of the view (i.e. the size prior to scaling) and the scaled size.
1183bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // scaleFromEdge is the difference between the visible left edge and the
1184bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // actual left edge, due to scaling.
1185bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // scaleFromTop is the difference between the visible top edge, and the
1186bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // actual top edge, due to scaling.
1187bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        int scaleFromEdge = Math.round((totalSize - totalSize * scale) / 2);
1188bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1189bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // The imageView is fullscreen, regardless of the aspect ratio of the actual image.
1190bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // This means that some portion of the imageView will be blank.  We need to
1191bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // take into account the size of the blank area so that the actual image
1192bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // lines up with the starting image.
1193bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        int blankSize = Math.round((totalSize * scale - startSize) / 2);
1194bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1195bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return start - scaleFromEdge - blankSize;
1196bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1197bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1198bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private void initTemporaryImage(Drawable drawable) {
1199bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (mEnterAnimationFinished) {
1200bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Forget this, we've already run the animation.
1201bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return;
1202bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1203dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        if (hasTemporaryImage()) {
1204dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco            mTemporaryImage.setImageDrawable(drawable);
1205dcdb96ea1e7be078ec55be804d326ef2176235faAndrew DiMarco        }
1206bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        if (drawable != null) {
1207bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // We have not yet run the enter animation. Start it now.
1208bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            int totalWidth = mRootView.getMeasuredWidth();
1209bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            if (totalWidth == 0) {
1210bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // the measure pass has not yet finished.  We can't properly
1211bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // run out animation until that is done. Listen for the layout
1212bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // to occur, then fire the animation.
1213bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                final View base = mRootView;
1214bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                base.getViewTreeObserver().addOnGlobalLayoutListener(
1215bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        new OnGlobalLayoutListener() {
1216bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    @Override
1217bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    public void onGlobalLayout() {
1218bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        int version = android.os.Build.VERSION.SDK_INT;
1219bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        if (version >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
1220bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            base.getViewTreeObserver().removeOnGlobalLayoutListener(this);
1221bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        } else {
1222bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            base.getViewTreeObserver().removeGlobalOnLayoutListener(this);
1223bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        }
1224bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        runEnterAnimation();
1225bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    }
1226bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                });
1227bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            } else {
1228bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                // initiate the animation
1229bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                runEnterAnimation();
1230bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1231bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1232bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        // Kick off the photo list loader
1233bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
1234bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1235bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1236bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void showActionBar() {
1237bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.getActionBarInterface().show();
1238bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1239bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1240bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public void hideActionBar() {
1241bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        mActivity.getActionBarInterface().hide();
1242bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1243bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1244bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean isScaleAnimationEnabled() {
1245bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mScaleAnimationEnabled;
1246bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1247bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1248bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public boolean isEnterAnimationFinished() {
1249bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mEnterAnimationFinished;
1250bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1251bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1252bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    public View getRootView() {
1253bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        return mRootView;
1254bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1255bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1256bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    private class BitmapCallback implements LoaderManager.LoaderCallbacks<BitmapResult> {
1257bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1258bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        @Override
1259bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public Loader<BitmapResult> onCreateLoader(int id, Bundle args) {
1260bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            String uri = args.getString(ARG_IMAGE_URI);
1261bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            switch (id) {
1262bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                case PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL:
1263bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    return onCreateBitmapLoader(PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL,
1264bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            args, uri);
1265bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                case PhotoViewCallbacks.BITMAP_LOADER_AVATAR:
1266bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    return onCreateBitmapLoader(PhotoViewCallbacks.BITMAP_LOADER_AVATAR,
1267bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                            args, uri);
1268bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1269bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            return null;
1270bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1271bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1272bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        @Override
1273bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void onLoadFinished(Loader<BitmapResult> loader, BitmapResult result) {
1274bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            Drawable drawable = result.getDrawable(mActivity.getResources());
1275bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            final ActionBarInterface actionBar = mActivity.getActionBarInterface();
1276bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            switch (loader.getId()) {
1277bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                case PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL:
1278bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    // We just loaded the initial thumbnail that we can display
1279bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    // while waiting for the full viewPager to get initialized.
1280bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    initTemporaryImage(drawable);
1281bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    break;
1282bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                case PhotoViewCallbacks.BITMAP_LOADER_AVATAR:
1283bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    if (drawable == null) {
1284bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        actionBar.setLogo(null);
1285bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    } else {
1286bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                        actionBar.setLogo(drawable);
1287bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    }
1288bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                    break;
1289bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            }
1290bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1291bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
1292bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        @Override
1293bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        public void onLoaderReset(Loader<BitmapResult> loader) {
1294bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            // Do nothing
1295bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood        }
1296bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood    }
1297bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood
12985f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein    public void setImmersiveMode(boolean enabled) {
12995f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        int flags = 0;
13005f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        final int version = Build.VERSION.SDK_INT;
1301f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian        final boolean manuallyUpdateActionBar = version < Build.VERSION_CODES.JELLY_BEAN;
13021b641422773c48c8fe4f3b6bc6570f7e098124f2Matt Keoshkerian        if (enabled &&
1303bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                (!isScaleAnimationEnabled() || isEnterAnimationFinished())) {
13041b641422773c48c8fe4f3b6bc6570f7e098124f2Matt Keoshkerian            // Turning on immersive mode causes an animation. If the scale animation is enabled and
13051b641422773c48c8fe4f3b6bc6570f7e098124f2Matt Keoshkerian            // the enter animation isn't yet complete, then an immersive mode animation should not
13061b641422773c48c8fe4f3b6bc6570f7e098124f2Matt Keoshkerian            // occur, since two concurrent animations are very janky.
13071b641422773c48c8fe4f3b6bc6570f7e098124f2Matt Keoshkerian
130890382f446827fc956839b6294fb13dda6040583cAdam Copp            // Disable immersive mode for seconary users to prevent b/12015090 (freezing crash)
130990382f446827fc956839b6294fb13dda6040583cAdam Copp            // This is fixed in KK_MR2 but there is no way to differentiate between  KK and KK_MR2.
131090382f446827fc956839b6294fb13dda6040583cAdam Copp            if (version > Build.VERSION_CODES.KITKAT ||
131190382f446827fc956839b6294fb13dda6040583cAdam Copp                    version == Build.VERSION_CODES.KITKAT && !kitkatIsSecondaryUser()) {
13125f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
13135f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
13145f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
13155f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
13165f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_FULLSCREEN
13175f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_IMMERSIVE;
13185f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.JELLY_BEAN) {
1319f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                // Clients that use the scale animation should set the following system UI flags to
1320f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                // prevent janky animations on exit when the status bar is hidden:
1321f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                //     View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_STABLE
1322f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                // As well, client should ensure `android:fitsSystemWindows` is set on the root
1323f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                // content view.
13245f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_LOW_PROFILE
13255f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1326f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
1327f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                        | View.SYSTEM_UI_FLAG_FULLSCREEN;
13285f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
13295f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
13305f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.HONEYCOMB) {
13315f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.STATUS_BAR_HIDDEN;
13325f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            }
13335f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
13345f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            if (manuallyUpdateActionBar) {
1335bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                hideActionBar();
13365f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            }
13375f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        } else {
13385f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            if (version >= Build.VERSION_CODES.KITKAT) {
13395f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
13405f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
1341f5a30690749a354cbbf8c8d9ad4fd5ed84e7d41eMatt Keoshkerian                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
13425f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.JELLY_BEAN) {
13435f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
13445f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                        | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
13455f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
13465f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.SYSTEM_UI_FLAG_VISIBLE;
13475f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            } else if (version >= Build.VERSION_CODES.HONEYCOMB) {
13485f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein                flags = View.STATUS_BAR_VISIBLE;
13495f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            }
13505f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
13515f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            if (manuallyUpdateActionBar) {
1352bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood                showActionBar();
13535f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            }
13545f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        }
13555f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
13565f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        if (version >= Build.VERSION_CODES.HONEYCOMB) {
13575f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein            mLastFlags = flags;
1358bb137c8e2ed363e6f5e2c0f14719483d27e8c062Mathew Inwood            getRootView().setSystemUiVisibility(flags);
13595f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein        }
13605f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein    }
13615f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein
1362e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian    /**
136390382f446827fc956839b6294fb13dda6040583cAdam Copp     * Return true iff the app is being run as a secondary user on kitkat.
136490382f446827fc956839b6294fb13dda6040583cAdam Copp     *
136590382f446827fc956839b6294fb13dda6040583cAdam Copp     * This is a hack which we only know to work on kitkat.
136690382f446827fc956839b6294fb13dda6040583cAdam Copp     */
136790382f446827fc956839b6294fb13dda6040583cAdam Copp    private boolean kitkatIsSecondaryUser() {
136890382f446827fc956839b6294fb13dda6040583cAdam Copp        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT) {
136990382f446827fc956839b6294fb13dda6040583cAdam Copp            throw new IllegalStateException("kitkatIsSecondary user is only callable on KitKat");
137090382f446827fc956839b6294fb13dda6040583cAdam Copp        }
137190382f446827fc956839b6294fb13dda6040583cAdam Copp        return Process.myUid() > 100000;
137290382f446827fc956839b6294fb13dda6040583cAdam Copp    }
137390382f446827fc956839b6294fb13dda6040583cAdam Copp
137490382f446827fc956839b6294fb13dda6040583cAdam Copp    /**
1375e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian     * Note: This should only be called when API level is 11 or above.
1376e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian     */
1377e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian    public View.OnSystemUiVisibilityChangeListener getSystemUiVisibilityChangeListener() {
1378e9825b89f8d8a61c747c5dc8aeaac9004a07d755Matt Keoshkerian        return mSystemUiVisibilityChangeListener;
13795f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein    }
13805f0775af25a40843a2c68d67d694945d88514555Andrew Sapperstein}
1381