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