Merge branch 'goog/jb-ub-mail-ur8' into master

Change-Id: Ie28475b31bb0d5d214c948c498d88e540d6f93a9
diff --git a/res/values/constants.xml b/res/values/constants.xml
index ae480ce..0fefe83 100644
--- a/res/values/constants.xml
+++ b/res/values/constants.xml
@@ -17,5 +17,5 @@
 -->
 
 <resources>
-    <integer name="action_bar_delay_time_in_millis">5000</integer>
+    <integer name="reenter_fullscreen_delay_time_in_millis">5000</integer>
 </resources>
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index c1b8b90..754c99b 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -21,4 +21,5 @@
     <dimen name="photo_crop_stroke_width">1dip</dimen>
     <dimen name="photo_preview_size">200dip</dimen>
     <dimen name="retry_button_size">48dip</dimen>
+    <dimen name="photo_page_margin">32dip</dimen>
 </resources>
diff --git a/src/com/android/ex/photo/PhotoViewActivity.java b/src/com/android/ex/photo/PhotoViewActivity.java
index 417a971..a772424 100644
--- a/src/com/android/ex/photo/PhotoViewActivity.java
+++ b/src/com/android/ex/photo/PhotoViewActivity.java
@@ -23,6 +23,7 @@
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
+import android.content.res.Resources;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
@@ -44,7 +45,9 @@
 import com.android.ex.photo.loaders.PhotoPagerLoader;
 import com.android.ex.photo.provider.PhotoContract;
 
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -58,6 +61,10 @@
             "com.google.android.apps.plus.PhotoViewFragment.ITEM";
     private final static String STATE_FULLSCREEN_KEY =
             "com.google.android.apps.plus.PhotoViewFragment.FULLSCREEN";
+    private final static String STATE_ACTIONBARTITLE_KEY =
+            "com.google.android.apps.plus.PhotoViewFragment.ACTIONBARTITLE";
+    private final static String STATE_ACTIONBARSUBTITLE_KEY =
+            "com.google.android.apps.plus.PhotoViewFragment.ACTIONBARSUBTITLE";
 
     private static final int LOADER_PHOTO_LIST = 1;
 
@@ -89,8 +96,9 @@
     protected PhotoPagerAdapter mAdapter;
     /** Whether or not we're in "full screen" mode */
     private boolean mFullScreen;
-    /** The set of listeners wanting full screen state */
-    private Set<OnScreenListener> mScreenListeners = new HashSet<OnScreenListener>();
+    /** The listeners wanting full screen state for each screen position */
+    private Map<Integer, OnScreenListener>
+            mScreenListeners = new HashMap<Integer, OnScreenListener>();
     /** The set of listeners wanting full screen state */
     private Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>();
     /** When {@code true}, restart the loader when the activity becomes active */
@@ -99,6 +107,11 @@
     private boolean mIsPaused = true;
     /** The maximum scale factor applied to images when they are initially displayed */
     private float mMaxInitialScale;
+    /** The title in the actionbar */
+    private String mActionBarTitle;
+    /** The subtitle in the actionbar */
+    private String mActionBarSubtitle;
+
     private final Handler mHandler = new Handler();
     // TODO Find a better way to do this. We basically want the activity to display the
     // "loading..." progress until the fragment takes over and shows it's own "loading..."
@@ -106,7 +119,7 @@
     // by the activity, but, that gets tricky when it comes to screen rotation. For now, we
     // track the loading by this variable which is fragile and may cause phantom "loading..."
     // text.
-    private long mActionBarHideDelayTime;
+    private long mEnterFullScreenDelayTime;
 
     protected PhotoPagerAdapter createPhotoPagerAdapter(Context context,
             android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
@@ -127,6 +140,8 @@
         if (savedInstanceState != null) {
             currentItem = savedInstanceState.getInt(STATE_ITEM_KEY, -1);
             mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false);
+            mActionBarTitle = savedInstanceState.getString(STATE_ACTIONBARTITLE_KEY);
+            mActionBarSubtitle = savedInstanceState.getString(STATE_ACTIONBARSUBTITLE_KEY);
         }
 
         // uri of the photos to view; optional
@@ -135,7 +150,7 @@
         }
 
         // projection for the query; optional
-        // I.f not set, the default projection is used.
+        // If not set, the default projection is used.
         // This projection must include the columns from the default projection.
         if (mIntent.hasExtra(Intents.EXTRA_PROJECTION)) {
             mProjection = mIntent.getStringArrayExtra(Intents.EXTRA_PROJECTION);
@@ -144,38 +159,46 @@
         }
 
         // Set the current item from the intent if wasn't in the saved instance
-        if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX) && currentItem < 0) {
-            currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
+        if (currentItem < 0) {
+            if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX)) {
+                currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
+            }
+            if (mIntent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI)) {
+                mInitialPhotoUri = mIntent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
+            }
         }
-        if (mIntent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI) && currentItem < 0) {
-            mInitialPhotoUri = mIntent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
-        }
-
         // Set the max initial scale, defaulting to 1x
         mMaxInitialScale = mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
 
-        mPhotoIndex = currentItem;
+        // If we still have a negative current item, set it to zero
+        mPhotoIndex = Math.max(currentItem, 0);
+        mIsEmpty = true;
 
         setContentView(R.layout.photo_activity_view);
 
         // Create the adapter and add the view pager
-        mAdapter = createPhotoPagerAdapter(this, getSupportFragmentManager(),
-            null, mMaxInitialScale);
+        mAdapter =
+                createPhotoPagerAdapter(this, getSupportFragmentManager(), null, mMaxInitialScale);
+        final Resources resources = getResources();
         mRootView = findViewById(R.id.photo_activity_root_view);
         mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
         mViewPager.setOnPageChangeListener(this);
         mViewPager.setOnInterceptTouchListener(this);
+        mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin));
 
         // Kick off the loader
         getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
 
+        mEnterFullScreenDelayTime =
+                resources.getInteger(R.integer.reenter_fullscreen_delay_time_in_millis);
+
         final ActionBar actionBar = getActionBar();
         if (actionBar != null) {
             actionBar.setDisplayHomeAsUpEnabled(true);
-            mActionBarHideDelayTime = getResources().getInteger(
-                    R.integer.action_bar_delay_time_in_millis);
             actionBar.addOnMenuVisibilityListener(this);
-            actionBar.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
+            final int showTitle = ActionBar.DISPLAY_SHOW_TITLE;
+            actionBar.setDisplayOptions(showTitle, showTitle);
+            setActionBarTitles(actionBar);
         }
     }
 
@@ -214,6 +237,8 @@
 
         outState.putInt(STATE_ITEM_KEY, mViewPager.getCurrentItem());
         outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
+        outState.putString(STATE_ACTIONBARTITLE_KEY, mActionBarTitle);
+        outState.putString(STATE_ACTIONBARSUBTITLE_KEY, mActionBarSubtitle);
     }
 
     @Override
@@ -227,13 +252,13 @@
     }
 
     @Override
-    public void addScreenListener(OnScreenListener listener) {
-        mScreenListeners.add(listener);
+    public void addScreenListener(int position, OnScreenListener listener) {
+        mScreenListeners.put(position, listener);
     }
 
     @Override
-    public void removeScreenListener(OnScreenListener listener) {
-        mScreenListeners.remove(listener);
+    public void removeScreenListener(int position) {
+        mScreenListeners.remove(position);
     }
 
     @Override
@@ -314,6 +339,7 @@
                     mRestartLoader = true;
                     return;
                 }
+                boolean wasEmpty = mIsEmpty;
                 mIsEmpty = false;
 
                 mAdapter.swapCursor(data);
@@ -331,7 +357,9 @@
                 }
 
                 mViewPager.setCurrentItem(itemIndex, false);
-                setViewActivated();
+                if (wasEmpty) {
+                    setViewActivated(itemIndex);
+                }
             }
             // Update the any action items
             updateActionItems();
@@ -364,7 +392,7 @@
     @Override
     public void onPageSelected(int position) {
         mPhotoIndex = position;
-        setViewActivated();
+        setViewActivated(position);
     }
 
     @Override
@@ -381,10 +409,7 @@
 
     @Override
     public void onFragmentVisible(Fragment fragment) {
-        if (fragment instanceof PhotoViewFragment) {
-            PhotoViewFragment photoFragment = (PhotoViewFragment)fragment;
-            updateActionBar(photoFragment);
-        }
+        updateActionBar();
     }
 
     @Override
@@ -392,14 +417,13 @@
         boolean interceptLeft = false;
         boolean interceptRight = false;
 
-        for (OnScreenListener listener : mScreenListeners) {
+        for (OnScreenListener listener : mScreenListeners.values()) {
             if (!interceptLeft) {
                 interceptLeft = listener.onInterceptMoveLeft(origX, origY);
             }
             if (!interceptRight) {
                 interceptRight = listener.onInterceptMoveRight(origX, origY);
             }
-            listener.onViewActivated();
         }
 
         if (interceptLeft) {
@@ -422,28 +446,27 @@
 
         if (mFullScreen) {
             setLightsOutMode(true);
-            cancelActionBarHideRunnable();
+            cancelEnterFullScreenRunnable();
         } else {
             setLightsOutMode(false);
             if (setDelayedRunnable) {
-                postActionBarHideRunnableWithDelay();
+                postEnterFullScreenRunnableWithDelay();
             }
         }
 
         if (fullScreenChanged) {
-            for (OnScreenListener listener : mScreenListeners) {
+            for (OnScreenListener listener : mScreenListeners.values()) {
                 listener.onFullScreenChanged(mFullScreen);
             }
         }
     }
 
-    private void postActionBarHideRunnableWithDelay() {
-        mHandler.postDelayed(mActionBarHideRunnable,
-                mActionBarHideDelayTime);
+    private void postEnterFullScreenRunnableWithDelay() {
+        mHandler.postDelayed(mEnterFullScreenRunnable, mEnterFullScreenDelayTime);
     }
 
-    private void cancelActionBarHideRunnable() {
-        mHandler.removeCallbacks(mActionBarHideRunnable);
+    private void cancelEnterFullScreenRunnable() {
+        mHandler.removeCallbacks(mEnterFullScreenRunnable);
     }
 
     protected void setLightsOutMode(boolean enabled) {
@@ -472,7 +495,7 @@
         }
     }
 
-    private Runnable mActionBarHideRunnable = new Runnable() {
+    private Runnable mEnterFullScreenRunnable = new Runnable() {
         @Override
         public void run() {
             setFullScreen(true, true);
@@ -480,8 +503,9 @@
     };
 
     @Override
-    public void setViewActivated() {
-        for (OnScreenListener listener : mScreenListeners) {
+    public void setViewActivated(int position) {
+        OnScreenListener listener = mScreenListeners.get(position);
+        if (listener != null) {
             listener.onViewActivated();
         }
     }
@@ -489,31 +513,49 @@
     /**
      * Adjusts the activity title and subtitle to reflect the photo name and count.
      */
-    protected void updateActionBar(PhotoViewFragment fragment) {
+    protected void updateActionBar() {
         final int position = mViewPager.getCurrentItem() + 1;
-        final String title;
-        final String subtitle;
         final boolean hasAlbumCount = mAlbumCount >= 0;
 
         final Cursor cursor = getCursorAtProperPosition();
-
         if (cursor != null) {
             final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME);
-            title = cursor.getString(photoNameIndex);
+            mActionBarTitle = cursor.getString(photoNameIndex);
         } else {
-            title = null;
+            mActionBarTitle = null;
         }
 
         if (mIsEmpty || !hasAlbumCount || position <= 0) {
-            subtitle = null;
+            mActionBarSubtitle = null;
         } else {
-            subtitle = getResources().getString(R.string.photo_view_count, position, mAlbumCount);
+            mActionBarSubtitle =
+                    getResources().getString(R.string.photo_view_count, position, mAlbumCount);
         }
+        setActionBarTitles(getActionBar());
+    }
 
-        final ActionBar actionBar = getActionBar();
-        actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
-        actionBar.setTitle(title);
-        actionBar.setSubtitle(subtitle);
+    /**
+     * Sets the Action Bar title to {@link #mActionBarTitle} and the subtitle to
+     * {@link #mActionBarSubtitle}
+     */
+    private final void setActionBarTitles(ActionBar actionBar) {
+        if (actionBar == null) {
+            return;
+        }
+        actionBar.setTitle(getInputOrEmpty(mActionBarTitle));
+        actionBar.setSubtitle(getInputOrEmpty(mActionBarSubtitle));
+    }
+
+    /**
+     * If the input string is non-null, it is returned, otherwise an empty string is returned;
+     * @param in
+     * @return
+     */
+    private static final String getInputOrEmpty(String in) {
+        if (in == null) {
+            return "";
+        }
+        return in;
     }
 
     /**
@@ -546,12 +588,17 @@
     @Override
     public void onMenuVisibilityChanged(boolean isVisible) {
         if (isVisible) {
-            cancelActionBarHideRunnable();
+            cancelEnterFullScreenRunnable();
         } else {
-            postActionBarHideRunnableWithDelay();
+            postEnterFullScreenRunnableWithDelay();
         }
     }
 
+    @Override
+    public void onNewPhotoLoaded(int position) {
+        // do nothing
+    }
+
     protected boolean isFullScreen() {
         return mFullScreen;
     }
@@ -560,4 +607,13 @@
         mPhotoIndex = index;
     }
 
+    @Override
+    public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) {
+        // do nothing
+    }
+
+    @Override
+    public PhotoPagerAdapter getAdapter() {
+        return mAdapter;
+    }
 }
diff --git a/src/com/android/ex/photo/PhotoViewCallbacks.java b/src/com/android/ex/photo/PhotoViewCallbacks.java
index a454b48..d71c710 100644
--- a/src/com/android/ex/photo/PhotoViewCallbacks.java
+++ b/src/com/android/ex/photo/PhotoViewCallbacks.java
@@ -3,6 +3,9 @@
 import android.database.Cursor;
 import android.support.v4.app.Fragment;
 
+import com.android.ex.photo.adapters.PhotoPagerAdapter;
+import com.android.ex.photo.fragments.PhotoViewFragment;
+
 public interface PhotoViewCallbacks {
     /**
      * Listener to be invoked for screen events.
@@ -48,15 +51,17 @@
         public void onCursorChanged(Cursor cursor);
     }
 
-    public void addScreenListener(OnScreenListener listener);
+    public void addScreenListener(int position, OnScreenListener listener);
 
-    public void removeScreenListener(OnScreenListener listener);
+    public void removeScreenListener(int position);
 
     public void addCursorListener(CursorChangedListener listener);
 
     public void removeCursorListener(CursorChangedListener listener);
 
-    public void setViewActivated();
+    public void setViewActivated(int position);
+
+    public void onNewPhotoLoaded(int position);
 
     public void toggleFullScreen();
 
@@ -65,4 +70,11 @@
     public void onFragmentVisible(Fragment fragment);
 
     public boolean isFragmentFullScreen(Fragment fragment);
+
+    public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor);
+
+    /**
+     * Returns the adapter associated with this activity.
+     */
+    public PhotoPagerAdapter getAdapter();
 }
diff --git a/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java b/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
index 121ef46..56df485 100644
--- a/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
+++ b/src/com/android/ex/photo/adapters/PhotoPagerAdapter.java
@@ -35,7 +35,8 @@
     protected int mLoadingIndex;
     protected final float mMaxScale;
 
-    public PhotoPagerAdapter(Context context, android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
+    public PhotoPagerAdapter(
+            Context context, android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
         super(context, fm, c);
         mMaxScale = maxScale;
     }
@@ -63,7 +64,7 @@
             .setThumbnailUri(thumbnailUri)
             .setMaxInitialScale(mMaxScale);
 
-        return new PhotoViewFragment(builder.build(), position, this, onlyShowSpinner);
+        return PhotoViewFragment.newInstance(builder.build(), position, onlyShowSpinner);
     }
 
     @Override
diff --git a/src/com/android/ex/photo/fragments/PhotoViewFragment.java b/src/com/android/ex/photo/fragments/PhotoViewFragment.java
index 7725523..afd40ba 100644
--- a/src/com/android/ex/photo/fragments/PhotoViewFragment.java
+++ b/src/com/android/ex/photo/fragments/PhotoViewFragment.java
@@ -17,13 +17,11 @@
 
 package com.android.ex.photo.fragments;
 
-import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
 import android.database.Cursor;
 import android.graphics.Bitmap;
 import android.os.Bundle;
-import android.os.Handler;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.content.Loader;
@@ -52,7 +50,10 @@
  * Displays a photo.
  */
 public class PhotoViewFragment extends Fragment implements
-        LoaderManager.LoaderCallbacks<Bitmap>, OnClickListener, OnScreenListener, CursorChangedListener {
+        LoaderManager.LoaderCallbacks<Bitmap>,
+        OnClickListener,
+        OnScreenListener,
+        CursorChangedListener {
     /**
      * Interface for components that are internally scrollable left-to-right.
      */
@@ -80,6 +81,10 @@
     protected final static String STATE_INTENT_KEY =
             "com.android.mail.photo.fragments.PhotoViewFragment.INTENT";
 
+    private final static String ARG_INTENT = "arg-intent";
+    private final static String ARG_POSITION = "arg-position";
+    private final static String ARG_SHOW_SPINNER = "arg-show-spinner";
+
     // Loader IDs
     protected final static int LOADER_ID_PHOTO = 1;
     protected final static int LOADER_ID_THUMBNAIL = 2;
@@ -101,64 +106,56 @@
     protected ImageView mRetryButton;
     protected ProgressBarWrapper mPhotoProgressBar;
 
-    protected final int mPosition;
+    protected int mPosition;
 
     /** Whether or not the fragment should make the photo full-screen */
     protected boolean mFullScreen;
 
     /** Whether or not this fragment will only show the loading spinner */
-    protected final boolean mOnlyShowSpinner;
+    protected boolean mOnlyShowSpinner;
 
     /** Whether or not the progress bar is showing valid information about the progress stated */
     protected boolean mProgressBarNeeded = true;
 
     protected View mPhotoPreviewAndProgress;
 
+    /** Public no-arg constructor for allowing the framework to handle orientation changes */
     public PhotoViewFragment() {
-        mPosition = -1;
-        mOnlyShowSpinner = false;
-        mProgressBarNeeded = true;
+        // Do nothing.
     }
 
-    public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter,
-            boolean onlyShowSpinner) {
-        mIntent = intent;
-        mPosition = position;
-        mAdapter = adapter;
-        mOnlyShowSpinner = onlyShowSpinner;
-        mProgressBarNeeded = true;
+    /**
+     * Create a {@link PhotoViewFragment}.
+     * @param intent
+     * @param position
+     * @param onlyShowSpinner
+     * @return
+     */
+    public static final PhotoViewFragment newInstance(
+            Intent intent, int position, boolean onlyShowSpinner) {
+        final Bundle b = new Bundle();
+        b.putParcelable(ARG_INTENT, intent);
+        b.putInt(ARG_POSITION, position);
+        b.putBoolean(ARG_SHOW_SPINNER, onlyShowSpinner);
+        final PhotoViewFragment f = new PhotoViewFragment();
+        f.setArguments(b);
+        return f;
     }
 
     @Override
-    public void onAttach(Activity activity) {
-        super.onAttach(activity);
-        mCallback = (PhotoViewCallbacks) activity;
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+        mCallback = (PhotoViewCallbacks) getActivity();
         if (mCallback == null) {
             throw new IllegalArgumentException(
                     "Activity must be a derived class of PhotoViewActivity");
         }
-
-        if (sPhotoSize == null) {
-            final DisplayMetrics metrics = new DisplayMetrics();
-            final WindowManager wm =
-                    (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
-            final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize;
-            wm.getDefaultDisplay().getMetrics(metrics);
-            switch (imageSize) {
-                case EXTRA_SMALL: {
-                    // Use a photo that's 80% of the "small" size
-                    sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000;
-                    break;
-                }
-
-                case SMALL:
-                case NORMAL:
-                default: {
-                    sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels);
-                    break;
-                }
-            }
+        mAdapter = mCallback.getAdapter();
+        if (mAdapter == null) {
+            throw new IllegalStateException("Callback reported null adapter");
         }
+        // Don't call until we've setup the entire view
+        setViewVisibility();
     }
 
     @Override
@@ -170,6 +167,35 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (sPhotoSize == null) {
+            final DisplayMetrics metrics = new DisplayMetrics();
+            final WindowManager wm =
+                    (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE);
+            final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize;
+            wm.getDefaultDisplay().getMetrics(metrics);
+            switch (imageSize) {
+                case EXTRA_SMALL:
+                    // Use a photo that's 80% of the "small" size
+                    sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000;
+                    break;
+                case SMALL:
+                    // Fall through.
+                case NORMAL:
+                    // Fall through.
+                default:
+                    sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels);
+                    break;
+            }
+        }
+
+        final Bundle bundle = getArguments();
+        if (bundle == null) {
+            return;
+        }
+        mIntent = bundle.getParcelable(ARG_INTENT);
+        mPosition = bundle.getInt(ARG_POSITION);
+        mOnlyShowSpinner = bundle.getBoolean(ARG_SHOW_SPINNER);
+        mProgressBarNeeded = true;
 
         if (savedInstanceState != null) {
             final Bundle state = savedInstanceState.getBundle(STATE_INTENT_KEY);
@@ -208,28 +234,30 @@
         mPhotoProgressBar = new ProgressBarWrapper(determinate, indeterminate, true);
         mEmptyText = (TextView) view.findViewById(R.id.empty_text);
         mRetryButton = (ImageView) view.findViewById(R.id.retry_button);
-
-        // Don't call until we've setup the entire view
-        setViewVisibility();
     }
 
     @Override
     public void onResume() {
-        mCallback.addScreenListener(this);
+        super.onResume();
+        mCallback.addScreenListener(mPosition, this);
         mCallback.addCursorListener(this);
 
-        getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this);
+        if (!isPhotoBound()) {
+            mProgressBarNeeded = true;
+            mPhotoPreviewAndProgress.setVisibility(View.VISIBLE);
 
-        super.onResume();
+            getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this);
+            getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this);
+        }
     }
 
     @Override
     public void onPause() {
-        super.onPause();
         // Remove listeners
         mCallback.removeCursorListener(this);
-        mCallback.removeScreenListener(this);
+        mCallback.removeScreenListener(mPosition);
         resetPhotoView();
+        super.onPause();
     }
 
     @Override
@@ -239,7 +267,6 @@
             mPhotoView.clear();
             mPhotoView = null;
         }
-
         super.onDestroyView();
     }
 
@@ -274,50 +301,31 @@
             return;
         }
 
+        // both loaders are started together, they may finish loading in such a
+        // way that the thumbnail is displayed on top of the full image
         final int id = loader.getId();
         switch (id) {
-            case LOADER_ID_PHOTO:
-                if (data != null) {
-                    bindPhoto(data);
-                    enableImageTransforms(true);
-                    mPhotoPreviewAndProgress.setVisibility(View.GONE);
-                    mProgressBarNeeded = false;
-                } else {
-                    // Received a null result for the full size image.  Instead attempt to load the
-                    // thumbnail
-                    Handler handler = new Handler();
-                    handler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null,
-                                                          PhotoViewFragment.this);
-                        }
-                    });
-                }
-                break;
             case LOADER_ID_THUMBNAIL:
-                mProgressBarNeeded = false;
                 if (isPhotoBound()) {
-                    // There is need to do anything with the thumbnail image, as the full size
+                    // There is need to do anything with the thumbnail
+                    // image, as the full size
                     // image is being shown.
-                    mPhotoPreviewAndProgress.setVisibility(View.GONE);
                     return;
-                } else if (data == null) {
+                }
+
+                if (data == null) {
                     // no preview, show default
-                    mPhotoPreviewImage.setVisibility(View.VISIBLE);
                     mPhotoPreviewImage.setImageResource(R.drawable.default_image);
                 } else {
-                    bindPhoto(data);
-                    enableImageTransforms(false);
-                    Handler handler = new Handler();
-                    handler.post(new Runnable() {
-                        @Override
-                        public void run() {
-                            getLoaderManager().initLoader(LOADER_ID_PHOTO, null,
-                                PhotoViewFragment.this);
-                        }
-                    });
+                    // show preview
+                    mPhotoPreviewImage.setImageBitmap(data);
                 }
+                mPhotoPreviewImage.setVisibility(View.VISIBLE);
+                mPhotoPreviewImage.setScaleType(ImageView.ScaleType.CENTER);
+                enableImageTransforms(false);
+                break;
+            case LOADER_ID_PHOTO:
+                bindPhoto(data);
                 break;
             default:
                 break;
@@ -328,7 +336,9 @@
             mPhotoProgressBar.setVisibility(View.GONE);
         }
 
-        mCallback.setViewActivated();
+        if (data != null) {
+            mCallback.onNewPhotoLoaded(mPosition);
+        }
         setViewVisibility();
     }
 
@@ -336,8 +346,13 @@
      * Binds an image to the photo view.
      */
     private void bindPhoto(Bitmap bitmap) {
-        if (mPhotoView != null) {
-            mPhotoView.bindPhoto(bitmap);
+        if (bitmap != null) {
+            if (mPhotoView != null) {
+                mPhotoView.bindPhoto(bitmap);
+            }
+            enableImageTransforms(true);
+            mPhotoPreviewAndProgress.setVisibility(View.GONE);
+            mProgressBarNeeded = false;
         }
     }
 
@@ -379,6 +394,10 @@
             // we're not in the foreground; reset our view
             resetViews();
         } else {
+            if (!isPhotoBound()) {
+                // Restart the loader
+                getLoaderManager().restartLoader(LOADER_ID_THUMBNAIL, null, this);
+            }
             mCallback.onFragmentVisible(this);
         }
     }
@@ -423,10 +442,8 @@
      * Sets view visibility depending upon whether or not we're in "full screen" mode.
      */
     private void setViewVisibility() {
-        final boolean fullScreen = mCallback.isFragmentFullScreen(this);
-        final boolean hide = fullScreen;
-
-        setFullScreen(hide);
+        final boolean fullScreen = mCallback == null ? false : mCallback.isFragmentFullScreen(this);
+        setFullScreen(fullScreen);
     }
 
     /**
@@ -438,7 +455,15 @@
 
     @Override
     public void onCursorChanged(Cursor cursor) {
+        if (mAdapter == null) {
+            // The adapter is set in onAttach(), and is guaranteed to be non-null. We have magically
+            // received an onCursorChanged without attaching to an activity. Ignore this cursor
+            // change.
+            return;
+        }
         if (cursor.moveToPosition(mPosition) && !isPhotoBound()) {
+            mCallback.onCursorChanged(this, cursor);
+
             final LoaderManager manager = getLoaderManager();
             final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO);
             if (fakeLoader == null) {
diff --git a/src/com/android/ex/photo/util/ImageUtils.java b/src/com/android/ex/photo/util/ImageUtils.java
index 250c870..5b1b0d1 100644
--- a/src/com/android/ex/photo/util/ImageUtils.java
+++ b/src/com/android/ex/photo/util/ImageUtils.java
@@ -122,6 +122,8 @@
             // Do nothing - the photo will appear to be missing
         } catch (IllegalArgumentException exception) {
             // Do nothing - the photo will appear to be missing
+        } catch (SecurityException exception) {
+            // Do nothing - the photo will appear to be missing
         } finally {
             try {
                 if (inputStream != null) {