content: package com.android.ex.photo;
  
  import android.app.Activity;
  import android.app.ActivityManager;
  import android.content.Context;
  import android.content.Intent;
  import android.content.res.Resources;
  import android.database.Cursor;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.os.Build;
  import android.os.Bundle;
  import android.os.Handler;
  import android.os.Process;
  import androidx.annotation.IdRes;
  import androidx.annotation.Nullable;
  import androidx.fragment.app.Fragment;
  import androidx.fragment.app.FragmentManager;
  import androidx.loader.app.LoaderManager;
  import androidx.loader.content.Loader;
  import androidx.viewpager.widget.ViewPager.OnPageChangeListener;
  import android.text.TextUtils;
  import android.util.DisplayMetrics;
  import android.util.Log;
  import android.view.Menu;
  import android.view.MenuItem;
  import android.view.View;
  import android.view.ViewPropertyAnimator;
  import android.view.ViewTreeObserver.OnGlobalLayoutListener;
  import android.view.WindowManager;
  import android.view.accessibility.AccessibilityManager;
  import android.view.animation.AlphaAnimation;
  import android.view.animation.Animation;
  import android.view.animation.Animation.AnimationListener;
  import android.view.animation.AnimationSet;
  import android.view.animation.ScaleAnimation;
  import android.view.animation.TranslateAnimation;
  import android.widget.ImageView;
  
  import com.android.ex.photo.ActionBarInterface.OnMenuVisibilityListener;
  import com.android.ex.photo.PhotoViewPager.InterceptType;
  import com.android.ex.photo.PhotoViewPager.OnInterceptTouchListener;
  import com.android.ex.photo.adapters.PhotoPagerAdapter;
  import com.android.ex.photo.fragments.PhotoViewFragment;
  import com.android.ex.photo.loaders.PhotoBitmapLoader;
  import com.android.ex.photo.loaders.PhotoBitmapLoaderInterface.BitmapResult;
  import com.android.ex.photo.loaders.PhotoPagerLoader;
  import com.android.ex.photo.provider.PhotoContract;
  import com.android.ex.photo.util.ImageUtils;
  import com.android.ex.photo.util.Util;
  
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Map;
  import java.util.Set;
  
  /**
   * This class implements all the logic of the photo view activity. An activity should use this class
   * calling through from relevant activity methods to the methods of the same name here.
   *
   * To customize the photo viewer activity, you should subclass this and implement your
   * customizations here. Then subclass {@link PhotoViewActivity} and override just
   * {@link PhotoViewActivity#createController createController} to instantiate your controller
   * subclass.
   */
  public class PhotoViewController implements
          LoaderManager.LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener,
          OnMenuVisibilityListener, PhotoViewCallbacks  {
  
      /**
       * Defines the interface between the Activity and this class.
       *
       * The activity itself must delegate all appropriate method calls into this class, to the
       * methods of the same name.
       */
      public interface ActivityInterface {
          public Context getContext();
          public Context getApplicationContext();
          public Intent getIntent();
          public void setContentView(int resId);
          public <T extends View> T findViewById(int id);
          public Resources getResources();
          public FragmentManager getSupportFragmentManager();
          public LoaderManager getSupportLoaderManager();
          public ActionBarInterface getActionBarInterface();
          public boolean onOptionsItemSelected(MenuItem item);
          public void finish();
          public void overridePendingTransition(int enterAnim, int exitAnim);
          public PhotoViewController getController();
      }
  
      private final static String TAG = "PhotoViewController";
  
      private final static String STATE_INITIAL_URI_KEY =
              "com.android.ex.PhotoViewFragment.INITIAL_URI";
      private final static String STATE_CURRENT_URI_KEY =
              "com.android.ex.PhotoViewFragment.CURRENT_URI";
      private final static String STATE_CURRENT_INDEX_KEY =
              "com.android.ex.PhotoViewFragment.CURRENT_INDEX";
      private final static String STATE_FULLSCREEN_KEY =
              "com.android.ex.PhotoViewFragment.FULLSCREEN";
      private final static String STATE_ACTIONBARTITLE_KEY =
              "com.android.ex.PhotoViewFragment.ACTIONBARTITLE";
      private final static String STATE_ACTIONBARSUBTITLE_KEY =
              "com.android.ex.PhotoViewFragment.ACTIONBARSUBTITLE";
      private final static String STATE_ENTERANIMATIONFINISHED_KEY =
              "com.android.ex.PhotoViewFragment.SCALEANIMATIONFINISHED";
  
      protected final static String ARG_IMAGE_URI = "image_uri";
  
      public static final int LOADER_PHOTO_LIST = 100;
  
      /** Count used when the real photo count is unknown [but, may be determined] */
      public static final int ALBUM_COUNT_UNKNOWN = -1;
  
      public static final int ENTER_ANIMATION_DURATION_MS = 250;
      public static final int EXIT_ANIMATION_DURATION_MS = 250;
  
      /** Argument key for the dialog message */
      public static final String KEY_MESSAGE = "dialog_message";
  
      public static int sMemoryClass;
      public static int sMaxPhotoSize; // The maximum size (either width or height)
  
      private final ActivityInterface mActivity;
  
      private int mLastFlags;
  
      private final View.OnSystemUiVisibilityChangeListener mSystemUiVisibilityChangeListener;
  
      /** The URI of the photos we're viewing; may be {@code null} */
      private String mPhotosUri;
      /** The uri of the initial photo */
      private String mInitialPhotoUri;
      /** The index of the currently viewed photo */
      private int mCurrentPhotoIndex;
      /** The uri of the currently viewed photo */
      private String mCurrentPhotoUri;
      /** The query projection to use; may be {@code null} */
      private String[] mProjection;
      /** The total number of photos; only valid if {@link #mIsEmpty} is {@code false}. */
      protected int mAlbumCount = ALBUM_COUNT_UNKNOWN;
      /** {@code true} if the view is empty. Otherwise, {@code false}. */
      protected boolean mIsEmpty;
      /** the main root view */
      protected View mRootView;
      /** Background image that contains nothing, so it can be alpha faded from
       * transparent to black without affecting any other views. */
      @Nullable
      protected View mBackground;
      /** The main pager; provides left/right swipe between photos */
      protected PhotoViewPager mViewPager;
      /** The temporary image so that we can quickly scale up the fullscreen thumbnail */
      @Nullable
      protected ImageView mTemporaryImage;
      /** Adapter to create pager views */
      protected PhotoPagerAdapter mAdapter;
      /** Whether or not we're in "full screen" mode */
      protected boolean mFullScreen;
      /** The listeners wanting full screen state for each screen position */
      private final Map<Integer, OnScreenListener>
              mScreenListeners = new HashMap<Integer, OnScreenListener>();
      /** The set of listeners wanting full screen state */
      private final Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>();
      /** When {@code true}, restart the loader when the activity becomes active */
      private boolean mKickLoader;
      /** Don't attempt operations that may trigger a fragment transaction when the activity is
       * destroyed */
      private boolean mIsDestroyedCompat;
      /** Whether or not this activity is paused */
      protected boolean mIsPaused = true;
      /** The maximum scale factor applied to images when they are initially displayed */
      protected float mMaxInitialScale;
      /** The title in the actionbar */
      protected String mActionBarTitle;
      /** The subtitle in the actionbar */
      protected String mActionBarSubtitle;
  
      private boolean mEnterAnimationFinished;
      protected boolean mScaleAnimationEnabled;
      protected int mAnimationStartX;
      protected int mAnimationStartY;
      protected int mAnimationStartWidth;
      protected int mAnimationStartHeight;
  
      /** Whether lights out should invoked based on timer */
      protected boolean mIsTimerLightsOutEnabled;
      protected boolean mActionBarHiddenInitially;
      protected boolean mDisplayThumbsFullScreen;
  
      private final AccessibilityManager mAccessibilityManager;
  
      protected BitmapCallback mBitmapCallback;
      protected 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..."
      // progress [located in photo_header_view.xml]. We could potentially have all status displayed
      // 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 mEnterFullScreenDelayTime;
  
      private int lastAnnouncedTitle = -1;
  
      public PhotoViewController(ActivityInterface activity) {
          mActivity = activity;
  
          // View.OnSystemUiVisibilityChangeListener is an API that was introduced in API level 11.
          if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
              mSystemUiVisibilityChangeListener = null;
          } else {
              mSystemUiVisibilityChangeListener = new View.OnSystemUiVisibilityChangeListener() {
                  @Override
                  public void onSystemUiVisibilityChange(int visibility) {
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT &&
                              visibility == 0 && mLastFlags == 3846) {
                          setFullScreen(false /* fullscreen */, true /* setDelayedRunnable */);
                      }
                  }
              };
          }
  
          mAccessibilityManager = (AccessibilityManager)
                  activity.getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
      }
  
      public PhotoPagerAdapter createPhotoPagerAdapter(Context context,
              androidx.fragment.app.FragmentManager fm, Cursor c, float maxScale) {
          return new PhotoPagerAdapter(context, fm, c, maxScale, mDisplayThumbsFullScreen);
      }
  
      public PhotoViewController.ActivityInterface getActivity() {
          return mActivity;
      }
  
      public void onCreate(Bundle savedInstanceState) {
          initMaxPhotoSize();
          final ActivityManager mgr = (ActivityManager) mActivity.getApplicationContext().
                  getSystemService(Activity.ACTIVITY_SERVICE);
          sMemoryClass = mgr.getMemoryClass();
  
          final Intent intent = mActivity.getIntent();
          // uri of the photos to view; optional
          if (intent.hasExtra(Intents.EXTRA_PHOTOS_URI)) {
              mPhotosUri = intent.getStringExtra(Intents.EXTRA_PHOTOS_URI);
          }
  
          mIsTimerLightsOutEnabled = intent.getBooleanExtra(
                  Intents.EXTRA_ENABLE_TIMER_LIGHTS_OUT, true);
  
          if (intent.getBooleanExtra(Intents.EXTRA_SCALE_UP_ANIMATION, false)) {
              mScaleAnimationEnabled = true;
              mAnimationStartX = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_X, 0);
              mAnimationStartY = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_Y, 0);
              mAnimationStartWidth = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_WIDTH, 0);
              mAnimationStartHeight = intent.getIntExtra(Intents.EXTRA_ANIMATION_START_HEIGHT, 0);
          }
          mActionBarHiddenInitially = intent.getBooleanExtra(
                  Intents.EXTRA_ACTION_BAR_HIDDEN_INITIALLY, false)
                  && !Util.isTouchExplorationEnabled(mAccessibilityManager);
          mDisplayThumbsFullScreen = intent.getBooleanExtra(
                  Intents.EXTRA_DISPLAY_THUMBS_FULLSCREEN, false);
  
          // projection for the query; optional
          // If not set, the default projection is used.
          // This projection must include the columns from the default projection.
          if (intent.hasExtra(Intents.EXTRA_PROJECTION)) {
              mProjection = intent.getStringArrayExtra(Intents.EXTRA_PROJECTION);
          } else {
              mProjection = null;
          }
  
          // Set the max initial scale, defaulting to 1x
          mMaxInitialScale = intent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
          mCurrentPhotoUri = null;
          mCurrentPhotoIndex = -1;
  
          // We allow specifying the current photo by either index or uri.
          // This is because some users may have live datasets that can change,
          // adding new items to either the beginning or end of the set. For clients
          // that do not need that capability, ability to specify the current photo
          // by index is offered as a convenience.
          if (intent.hasExtra(Intents.EXTRA_PHOTO_INDEX)) {
              mCurrentPhotoIndex = intent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
          }
          if (intent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI)) {
              mInitialPhotoUri = intent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
              mCurrentPhotoUri = mInitialPhotoUri;
          }
          mIsEmpty = true;
  
          if (savedInstanceState != null) {
              mInitialPhotoUri = savedInstanceState.getString(STATE_INITIAL_URI_KEY);
              mCurrentPhotoUri = savedInstanceState.getString(STATE_CURRENT_URI_KEY);
              mCurrentPhotoIndex = savedInstanceState.getInt(STATE_CURRENT_INDEX_KEY);
              mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false)
                      && !Util.isTouchExplorationEnabled(mAccessibilityManager);
              mActionBarTitle = savedInstanceState.getString(STATE_ACTIONBARTITLE_KEY);
              mActionBarSubtitle = savedInstanceState.getString(STATE_ACTIONBARSUBTITLE_KEY);
              mEnterAnimationFinished = savedInstanceState.getBoolean(
                      STATE_ENTERANIMATIONFINISHED_KEY, false);
          } else {
              mFullScreen = mActionBarHiddenInitially;
          }
  
          mActivity.setContentView(getContentViewId());
  
          // Create the adapter and add the view pager
          mAdapter = createPhotoPagerAdapter(mActivity.getContext(),
                          mActivity.getSupportFragmentManager(), null, mMaxInitialScale);
          final Resources resources = mActivity.getResources();
          mRootView = findViewById(getRootViewId());
          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
              mRootView.setOnSystemUiVisibilityChangeListener(getSystemUiVisibilityChangeListener());
          }
          mBackground = getBackground();
          mTemporaryImage = getTemporaryImage();
          mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
          mViewPager.setAdapter(mAdapter);
          mViewPager.setOnPageChangeListener(this);
          mViewPager.setOnInterceptTouchListener(this);
          mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin));
  
          mBitmapCallback = new BitmapCallback();
          if (!mScaleAnimationEnabled || mEnterAnimationFinished) {
              // We are not running the scale up animation. Just let the fragments
              // display and handle the animation.
              mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
              // Make the background opaque immediately so that we don't see the activity
              // behind this one.
              if (hasBackground()) {
                  mBackground.setVisibility(View.VISIBLE);
              }
          } else {
              // Attempt to load the initial image thumbnail. Once we have the
              // image, animate it up. Once the animation is complete, we can kick off
              // loading the ViewPager. After the primary fullres image is loaded, we will
              // make our temporary image invisible and display the ViewPager.
              mViewPager.setVisibility(View.GONE);
              Bundle args = new Bundle();
              args.putString(ARG_IMAGE_URI, mInitialPhotoUri);
              mActivity.getSupportLoaderManager().initLoader(
                      BITMAP_LOADER_THUMBNAIL, args, mBitmapCallback);
          }
  
          mEnterFullScreenDelayTime =
                  resources.getInteger(R.integer.reenter_fullscreen_delay_time_in_millis);
  
          final ActionBarInterface actionBar = mActivity.getActionBarInterface();
          if (actionBar != null) {
              actionBar.setDisplayHomeAsUpEnabled(true);
              actionBar.addOnMenuVisibilityListener(this);
              actionBar.setDisplayOptionsShowTitle();
              // Set the title and subtitle immediately here, rather than waiting
              // for the fragment to be initialized.
              setActionBarTitles(actionBar);
          }
  
          if (!mScaleAnimationEnabled) {
              setLightsOutMode(mFullScreen);
          } else {
              // Keep lights out mode as false. This is to prevent jank cause by concurrent
              // animations during the enter animation.
              setLightsOutMode(false);
          }
      }
  
      private void initMaxPhotoSize() {
          if (sMaxPhotoSize == 0) {
              final DisplayMetrics metrics = new DisplayMetrics();
              final WindowManager wm = (WindowManager)
                      mActivity.getContext().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
                      sMaxPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000;
                      break;
                  case SMALL:
                      // Fall through.
                  case NORMAL:
                      // Fall through.
                  default:
                      sMaxPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels);
                      break;
              }
          }
      }
  
      public boolean onCreateOptionsMenu(Menu menu) {
          return true;
      }
  
      public boolean onPrepareOptionsMenu(Menu menu) {
          return true;
      }
  
      public void onActivityResult(int requestCode, int resultCode, Intent data) {}
  
      protected View findViewById(int id) {
          return mActivity.findViewById(id);
      }
  
      /**
       * Returns the android id of the viewer's root view. Subclasses should override this method if
       * they provide their own layout.
       */
      @IdRes
      protected int getRootViewId() {
        return R.id.photo_activity_root_view;
      }
  
      /**
       * Returns the android layout id of the root layout that should be inflated for the viewer.
       * Subclasses should override this method if they provide their own layout.
       */
      @IdRes
      protected int getContentViewId() {
          return R.layout.photo_activity_view;
      }
  
      /**
       * Returns the android view for the viewer's background view, if it has one. Subclasses should
       * override this if they have a different (or no) background view.
       */
      @Nullable
      protected View getBackground() {
          return findViewById(R.id.photo_activity_background);
      }
  
      /**
       * Returns whether or not the view has a background object. Subclasses should override this if
       * they do not contain a background object.
       */
      protected boolean hasBackground() {
          return mBackground != null;
      }
  
      /**
       * Returns the android view for the viewer's temporary image, if it has one. Subclasses should
       * override this if they have a different (or no) temporary image view.
       */
      @Nullable
      protected ImageView getTemporaryImage() {
          return (ImageView) findViewById(R.id.photo_activity_temporary_image);
      }
  
      /**
       * Returns whether or not the view has a temporary image view. Subclasses should override this
       * if they do not use a temporary image.
       */
      protected boolean hasTemporaryImage() {
          return mTemporaryImage != null;
      }
  
      public void onStart() {}
  
      public void onResume() {
          setFullScreen(mFullScreen, false);
  
          mIsPaused = false;
          if (mKickLoader) {
              mKickLoader = false;
              mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
          }
      }
  
      public void onPause() {
          mIsPaused = true;
      }
  
      public void onStop() {}
  
      public void onDestroy() {
          mIsDestroyedCompat = true;
      }
  
      private boolean isDestroyedCompat() {
          return mIsDestroyedCompat;
      }
  
      public boolean onBackPressed() {
          // If we are in fullscreen mode, and the default is not full screen, then
          // switch back to actionBar display mode.
          if (mFullScreen && !mActionBarHiddenInitially) {
              toggleFullScreen();
          } else {
              if (mScaleAnimationEnabled) {
                  runExitAnimation();
              } else {
                  return false;
              }
          }
          return true;
      }
  
      public void onSaveInstanceState(Bundle outState) {
          outState.putString(STATE_INITIAL_URI_KEY, mInitialPhotoUri);
          outState.putString(STATE_CURRENT_URI_KEY, mCurrentPhotoUri);
          outState.putInt(STATE_CURRENT_INDEX_KEY, mCurrentPhotoIndex);
          outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
          outState.putString(STATE_ACTIONBARTITLE_KEY, mActionBarTitle);
          outState.putString(STATE_ACTIONBARSUBTITLE_KEY, mActionBarSubtitle);
          outState.putBoolean(STATE_ENTERANIMATIONFINISHED_KEY, mEnterAnimationFinished);
      }
  
      public boolean onOptionsItemSelected(MenuItem item) {
         switch (item.getItemId()) {
            case android.R.id.home:
               mActivity.finish();
               return true;
            default:
               return false;
         }
      }
  
      @Override
      public void addScreenListener(int position, OnScreenListener listener) {
          mScreenListeners.put(position, listener);
      }
  
      @Override
      public void removeScreenListener(int position) {
          mScreenListeners.remove(position);
      }
  
      @Override
      public synchronized void addCursorListener(CursorChangedListener listener) {
          mCursorListeners.add(listener);
      }
  
      @Override
      public synchronized void removeCursorListener(CursorChangedListener listener) {
          mCursorListeners.remove(listener);
      }
  
      @Override
      public boolean isFragmentFullScreen(Fragment fragment) {
          if (mViewPager == null || mAdapter == null || mAdapter.getCount() == 0) {
              return mFullScreen;
          }
          return mFullScreen || (mViewPager.getCurrentItem() != mAdapter.getItemPosition(fragment));
      }
  
      @Override
      public void toggleFullScreen() {
          setFullScreen(!mFullScreen, true);
      }
  
      public void onPhotoRemoved(long photoId) {
          final Cursor data = mAdapter.getCursor();
          if (data == null) {
              // Huh?! How would this happen?
              return;
          }
  
          final int dataCount = data.getCount();
          if (dataCount <= 1) {
              mActivity.finish();
              return;
          }
  
          mActivity.getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
      }
  
      @Override
      public Loader<Cursor> onCreateLoader(int id, Bundle args) {
          if (id == LOADER_PHOTO_LIST) {
              return new PhotoPagerLoader(mActivity.getContext(), Uri.parse(mPhotosUri), mProjection);
          }
          return null;
      }
  
      @Override
      public Loader<BitmapResult> onCreateBitmapLoader(int id, Bundle args, String uri) {
          switch (id) {
              case BITMAP_LOADER_AVATAR:
              case BITMAP_LOADER_THUMBNAIL:
              case BITMAP_LOADER_PHOTO:
                  return new PhotoBitmapLoader(mActivity.getContext(), uri);
              default:
                  return null;
          }
      }
  
      @Override
      public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
          final int id = loader.getId();
          if (id == LOADER_PHOTO_LIST) {
              if (data == null || data.getCount() == 0) {
                  mIsEmpty = true;
                  mAdapter.swapCursor(null);
              } else {
                  mAlbumCount = data.getCount();
                  if (mCurrentPhotoUri != null) {
                      int index = 0;
                      // Clear query params. Compare only the path.
                      final int uriIndex = data.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
                      final Uri currentPhotoUri;
                      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                          currentPhotoUri = Uri.parse(mCurrentPhotoUri).buildUpon()
                                  .clearQuery().build();
                      } else {
                          currentPhotoUri = Uri.parse(mCurrentPhotoUri).buildUpon()
                                  .query(null).build();
                      }
                      // Rewind data cursor to the start if it has already advanced.
                      data.moveToPosition(-1);
                      while (data.moveToNext()) {
                          final String uriString = data.getString(uriIndex);
                          final Uri uri;
                          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
                              uri = Uri.parse(uriString).buildUpon().clearQuery().build();
                          } else {
                              uri = Uri.parse(uriString).buildUpon().query(null).build();
                          }
                          if (currentPhotoUri != null && currentPhotoUri.equals(uri)) {
                              mCurrentPhotoIndex = index;
                              break;
                          }
                          index++;
                      }
                  }
  
                  // We're paused; don't do anything now, we'll get re-invoked
                  // when the activity becomes active again
                  if (mIsPaused) {
                      mKickLoader = true;
                      mAdapter.swapCursor(null);
                      return;
                  }
                  boolean wasEmpty = mIsEmpty;
                  mIsEmpty = false;
  
                  mAdapter.swapCursor(data);
                  if (mViewPager.getAdapter() == null) {
                      mViewPager.setAdapter(mAdapter);
                  }
                  notifyCursorListeners(data);
  
                  // Use an index of 0 if the index wasn't specified or couldn't be found
                  if (mCurrentPhotoIndex < 0) {
                      mCurrentPhotoIndex = 0;
                  }
  
                  mViewPager.setCurrentItem(mCurrentPhotoIndex, false);
                  if (wasEmpty) {
                      setViewActivated(mCurrentPhotoIndex);
                  }
              }
              // Update the any action items
              updateActionItems();
          }
      }
  
      @Override
      public void onLoaderReset(androidx.loader.content.Loader<Cursor> loader) {
          // If the loader is reset, remove the reference in the adapter to this cursor
          if (!isDestroyedCompat()) {
              // This will cause a fragment transaction which can't happen if we're destroyed,
              // but we don't care in that case because we're destroyed anyways.
              mAdapter.swapCursor(null);
          }
      }
  
      public void updateActionItems() {
          // Do nothing, but allow extending classes to do work
      }
  
      private synchronized void notifyCursorListeners(Cursor data) {
          // tell all of the objects listening for cursor changes
          // that the cursor has changed
          for (CursorChangedListener listener : mCursorListeners) {
              listener.onCursorChanged(data);
          }
      }
  
      @Override
      public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
          if (positionOffset < 0.0001) {
              OnScreenListener before = mScreenListeners.get(position - 1);
              if (before != null) {
                  before.onViewUpNext();
              }
              OnScreenListener after = mScreenListeners.get(position + 1);
              if (after != null) {
                  after.onViewUpNext();
              }
          }
      }
  
      @Override
      public void onPageSelected(int position) {
          mCurrentPhotoIndex = position;
          setViewActivated(position);
      }
  
      @Override
      public void onPageScrollStateChanged(int state) {
      }
  
      @Override
      public boolean isFragmentActive(Fragment fragment) {
          if (mViewPager == null || mAdapter == null) {
              return false;
          }
          return mViewPager.getCurrentItem() == mAdapter.getItemPosition(fragment);
      }
  
      @Override
      public void onFragmentVisible(PhotoViewFragment fragment) {
          // Do nothing, we handle this in setViewActivated
      }
  
      @Override
      public InterceptType onTouchIntercept(float origX, float origY) {
          boolean interceptLeft = false;
          boolean interceptRight = false;
  
          for (OnScreenListener listener : mScreenListeners.values()) {
              if (!interceptLeft) {
                  interceptLeft = listener.onInterceptMoveLeft(origX, origY);
              }
              if (!interceptRight) {
                  interceptRight = listener.onInterceptMoveRight(origX, origY);
              }
          }
  
          if (interceptLeft) {
              if (interceptRight) {
                  return InterceptType.BOTH;
              }
              return InterceptType.LEFT;
          } else if (interceptRight) {
              return InterceptType.RIGHT;
          }
          return InterceptType.NONE;
      }
  
      /**
       * Updates the title bar according to the value of {@link #mFullScreen}.
       */
      protected void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) {
          if (Util.isTouchExplorationEnabled(mAccessibilityManager)) {
              // Disallow full screen mode when accessibility is enabled so that the action bar
              // stays accessible.
              fullScreen = false;
              setDelayedRunnable = false;
          }
  
          final boolean fullScreenChanged = (fullScreen != mFullScreen);
          mFullScreen = fullScreen;
  
          if (mFullScreen) {
              setLightsOutMode(true);
              cancelEnterFullScreenRunnable();
          } else {
              setLightsOutMode(false);
              if (setDelayedRunnable) {
                  postEnterFullScreenRunnableWithDelay();
              }
          }
  
          if (fullScreenChanged) {
              for (OnScreenListener listener : mScreenListeners.values()) {
                  listener.onFullScreenChanged(mFullScreen);
              }
          }
      }
  
      /**
       * Posts a runnable to enter full screen after mEnterFullScreenDelayTime. This method is a
       * no-op if mIsTimerLightsOutEnabled is set to false.
       */
      private void postEnterFullScreenRunnableWithDelay() {
          if (mIsTimerLightsOutEnabled) {
              mHandler.postDelayed(mEnterFullScreenRunnable, mEnterFullScreenDelayTime);
          }
      }
  
      private void cancelEnterFullScreenRunnable() {
          mHandler.removeCallbacks(mEnterFullScreenRunnable);
      }
  
      protected void setLightsOutMode(boolean enabled) {
          setImmersiveMode(enabled);
      }
  
      private final Runnable mEnterFullScreenRunnable = new Runnable() {
          @Override
          public void run() {
              setFullScreen(true, true);
          }
      };
  
      @Override
      public void setViewActivated(int position) {
          OnScreenListener listener = mScreenListeners.get(position);
          if (listener != null) {
              listener.onViewActivated();
          }
          final Cursor cursor = getCursorAtProperPosition();
          mCurrentPhotoIndex = position;
          // FLAG: get the column indexes once in onLoadFinished().
          // That would make this more efficient, instead of looking these up
          // repeatedly whenever we want them.
          int uriIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
          mCurrentPhotoUri = cursor.getString(uriIndex);
          updateActionBar();
          if (mAccessibilityManager.isEnabled() && lastAnnouncedTitle != position) {
              String announcement = getPhotoAccessibilityAnnouncement(position);
              if (announcement != null) {
                  Util.announceForAccessibility(mRootView, mAccessibilityManager, announcement);
                  lastAnnouncedTitle = position;
              }
          }
  
          // Restart the timer to return to fullscreen.
          cancelEnterFullScreenRunnable();
          postEnterFullScreenRunnableWithDelay();
      }
  
      /**
       * Adjusts the activity title and subtitle to reflect the photo name and count.
       */
      public void updateActionBar() {
          final int position = mViewPager.getCurrentItem() + 1;
          final boolean hasAlbumCount = mAlbumCount >= 0;
  
          final Cursor cursor = getCursorAtProperPosition();
          if (cursor != null) {
              // FLAG: We should grab the indexes when we first get the cursor
              // and store them so we don't need to do it each time.
              final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME);
              mActionBarTitle = cursor.getString(photoNameIndex);
          } else {
              mActionBarTitle = null;
          }
  
          if (mIsEmpty || !hasAlbumCount || position <= 0) {
              mActionBarSubtitle = null;
          } else {
              mActionBarSubtitle = mActivity.getResources().getString(
                      R.string.photo_view_count, position, mAlbumCount);
          }
  
          setActionBarTitles(mActivity.getActionBarInterface());
      }
  
      /**
       * Returns a string used as an announcement for accessibility after the user moves to a new
       * photo. It will be called after {@link #updateActionBar} has been called.
       * @param position the index in the album of the currently active photo
       * @return announcement for accessibility
       */
      protected String getPhotoAccessibilityAnnouncement(int position) {
          String announcement = mActionBarTitle;
          if (mActionBarSubtitle != null) {
              announcement = mActivity.getContext().getResources().getString(
                      R.string.titles, mActionBarTitle, mActionBarSubtitle);
          }
          return announcement;
      }
  
      /**
       * Sets the Action Bar title to {@link #mActionBarTitle} and the subtitle to
       * {@link #mActionBarSubtitle}
       */
      protected final void setActionBarTitles(ActionBarInterface 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;
      }
  
      /**
       * Utility method that will return the cursor that contains the data
       * at the current position so that it refers to the current image on screen.
       * @return the cursor at the current position or
       * null if no cursor exists or if the {@link PhotoViewPager} is null.
       */
      public Cursor getCursorAtProperPosition() {
          if (mViewPager == null) {
              return null;
          }
  
          final int position = mViewPager.getCurrentItem();
          final Cursor cursor = mAdapter.getCursor();
  
          if (cursor == null) {
              return null;
          }
  
          cursor.moveToPosition(position);
  
          return cursor;
      }
  
      public Cursor getCursor() {
          return (mAdapter == null) ? null : mAdapter.getCursor();
      }
  
      @Override
      public void onMenuVisibilityChanged(boolean isVisible) {
          if (isVisible) {
              cancelEnterFullScreenRunnable();
          } else {
              postEnterFullScreenRunnableWithDelay();
          }
      }
  
      @Override
      public void onNewPhotoLoaded(int position) {
          // do nothing
      }
  
      protected void setPhotoIndex(int index) {
          mCurrentPhotoIndex = index;
      }
  
      @Override
      public void onFragmentPhotoLoadComplete(PhotoViewFragment fragment, boolean success) {
          if (hasTemporaryImage() && mTemporaryImage.getVisibility() != View.GONE &&
                  TextUtils.equals(fragment.getPhotoUri(), mCurrentPhotoUri)) {
              if (success) {
                  // The fragment for the current image is now ready for display.
                  if (hasTemporaryImage()) {
                      mTemporaryImage.setVisibility(View.GONE);
                  }
                  mViewPager.setVisibility(View.VISIBLE);
              } else {
                  // This means that we are unable to load the fragment's photo.
                  // I'm not sure what the best thing to do here is, but at least if
                  // we display the viewPager, the fragment itself can decide how to
                  // display the failure of its own image.
                  Log.w(TAG, "Failed to load fragment image");
                  if (hasTemporaryImage()) {
                      mTemporaryImage.setVisibility(View.GONE);
                  }
                  mViewPager.setVisibility(View.VISIBLE);
              }
              mActivity.getSupportLoaderManager().destroyLoader(
                      PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL);
          }
      }
  
      protected boolean isFullScreen() {
          return mFullScreen;
      }
  
      @Override
      public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) {
          // do nothing
      }
  
      @Override
      public PhotoPagerAdapter getAdapter() {
          return mAdapter;
      }
  
      public void onEnterAnimationComplete() {
          mEnterAnimationFinished = true;
          mViewPager.setVisibility(View.VISIBLE);
          setLightsOutMode(mFullScreen);
      }
  
      private void onExitAnimationComplete() {
          mActivity.finish();
          mActivity.overridePendingTransition(0, 0);
      }
  
      private void runEnterAnimation() {
          final int totalWidth = mRootView.getMeasuredWidth();
          final int totalHeight = mRootView.getMeasuredHeight();
  
          // FLAG: Need to handle the aspect ratio of the bitmap.  If it's a portrait
          // bitmap, then we need to position the view higher so that the middle
          // pixels line up.
          if (hasTemporaryImage()) {
              mTemporaryImage.setVisibility(View.VISIBLE);
          }
          // We need to take a full screen image, and scale/translate it so that
          // it appears at exactly the same location onscreen as it is in the
          // prior activity.
          // The final image will take either the full screen width or height (or both).
  
          final float scaleW = (float) mAnimationStartWidth / totalWidth;
          final float scaleY = (float) mAnimationStartHeight / totalHeight;
          final float scale = Math.max(scaleW, scaleY);
  
          final int translateX = calculateTranslate(mAnimationStartX, mAnimationStartWidth,
                  totalWidth, scale);
          final int translateY = calculateTranslate(mAnimationStartY, mAnimationStartHeight,
                  totalHeight, scale);
  
          final int version = android.os.Build.VERSION.SDK_INT;
          if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
              if (hasBackground()) {
                  mBackground.setAlpha(0f);
                  mBackground.animate().alpha(1f).setDuration(ENTER_ANIMATION_DURATION_MS).start();
                  mBackground.setVisibility(View.VISIBLE);
              }
  
              if (hasTemporaryImage()) {
                  mTemporaryImage.setScaleX(scale);
                  mTemporaryImage.setScaleY(scale);
                  mTemporaryImage.setTranslationX(translateX);
                  mTemporaryImage.setTranslationY(translateY);
  
                  Runnable endRunnable = new Runnable() {
                      @Override
                      public void run() {
                          PhotoViewController.this.onEnterAnimationComplete();
                      }
                  };
                  ViewPropertyAnimator animator = mTemporaryImage.animate().scaleX(1f).scaleY(1f)
                      .translationX(0).translationY(0).setDuration(ENTER_ANIMATION_DURATION_MS);
                  if (version >= Build.VERSION_CODES.JELLY_BEAN) {
                      animator.withEndAction(endRunnable);
                  } else {
                      mHandler.postDelayed(endRunnable, ENTER_ANIMATION_DURATION_MS);
                  }
                  animator.start();
              }
          } else {
              if (hasBackground()) {
                  final Animation alphaAnimation = new AlphaAnimation(0f, 1f);
                  alphaAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
                  mBackground.startAnimation(alphaAnimation);
                  mBackground.setVisibility(View.VISIBLE);
              }
  
              if (hasTemporaryImage()) {
                  final Animation translateAnimation = new TranslateAnimation(translateX,
                          translateY, 0, 0);
                  translateAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
                  Animation scaleAnimation = new ScaleAnimation(scale, scale, 0, 0);
                  scaleAnimation.setDuration(ENTER_ANIMATION_DURATION_MS);
  
                  AnimationSet animationSet = new AnimationSet(true);
                  animationSet.addAnimation(translateAnimation);
                  animationSet.addAnimation(scaleAnimation);
                  AnimationListener listener = new AnimationListener() {
                      @Override
                      public void onAnimationEnd(Animation arg0) {
                          PhotoViewController.this.onEnterAnimationComplete();
                      }
  
                      @Override
                      public void onAnimationRepeat(Animation arg0) {
                      }
  
                      @Override
                      public void onAnimationStart(Animation arg0) {
                      }
                  };
                  animationSet.setAnimationListener(listener);
                  mTemporaryImage.startAnimation(animationSet);
              }
          }
      }
  
      private void runExitAnimation() {
          Intent intent = mActivity.getIntent();
          // FLAG: should just fall back to a standard animation if either:
          // 1. images have been added or removed since we've been here, or
          // 2. we are currently looking at some image other than the one we
          // started on.
  
          final int totalWidth = mRootView.getMeasuredWidth();
          final int totalHeight = mRootView.getMeasuredHeight();
  
          // We need to take a full screen image, and scale/translate it so that
          // it appears at exactly the same location onscreen as it is in the
          // prior activity.
          // The final image will take either the full screen width or height (or both).
          final float scaleW = (float) mAnimationStartWidth / totalWidth;
          final float scaleY = (float) mAnimationStartHeight / totalHeight;
          final float scale = Math.max(scaleW, scaleY);
  
          final int translateX = calculateTranslate(mAnimationStartX, mAnimationStartWidth,
                  totalWidth, scale);
          final int translateY = calculateTranslate(mAnimationStartY, mAnimationStartHeight,
                  totalHeight, scale);
          final int version = android.os.Build.VERSION.SDK_INT;
          if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
              if (hasBackground()) {
                  mBackground.animate().alpha(0f).setDuration(EXIT_ANIMATION_DURATION_MS).start();
                  mBackground.setVisibility(View.VISIBLE);
              }
  
              Runnable endRunnable = new Runnable() {
                  @Override
                  public void run() {
                      PhotoViewController.this.onExitAnimationComplete();
                  }
              };
              // If the temporary image is still visible it means that we have
              // not yet loaded the fullres image, so we need to animate
              // the temporary image out.
              ViewPropertyAnimator animator = null;
              if (hasTemporaryImage() && mTemporaryImage.getVisibility() == View.VISIBLE) {
                  animator = mTemporaryImage.animate().scaleX(scale).scaleY(scale)
                      .translationX(translateX).translationY(translateY)
                      .setDuration(EXIT_ANIMATION_DURATION_MS);
              } else {
                  animator = mViewPager.animate().scaleX(scale).scaleY(scale)
                      .translationX(translateX).translationY(translateY)
                      .setDuration(EXIT_ANIMATION_DURATION_MS);
              }
              // If the user has swiped to a different photo, fade out the current photo
              // along with the scale animation.
              if (!mInitialPhotoUri.equals(mCurrentPhotoUri)) {
                  animator.alpha(0f);
              }
              if (version >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                  animator.withEndAction(endRunnable);
              } else {
                  mHandler.postDelayed(endRunnable, EXIT_ANIMATION_DURATION_MS);
              }
              animator.start();
          } else {
              if (hasBackground()) {
                  final Animation alphaAnimation = new AlphaAnimation(1f, 0f);
                  alphaAnimation.setDuration(EXIT_ANIMATION_DURATION_MS);
                  mBackground.startAnimation(alphaAnimation);
                  mBackground.setVisibility(View.VISIBLE);
              }
  
              final Animation scaleAnimation = new ScaleAnimation(1f, 1f, scale, scale);
              scaleAnimation.setDuration(EXIT_ANIMATION_DURATION_MS);
              AnimationListener listener = new AnimationListener() {
                  @Override
                  public void onAnimationEnd(Animation arg0) {
                      PhotoViewController.this.onExitAnimationComplete();
                  }
  
                  @Override
                  public void onAnimationRepeat(Animation arg0) {
                  }
  
                  @Override
                  public void onAnimationStart(Animation arg0) {
                  }
              };
              scaleAnimation.setAnimationListener(listener);
              // If the temporary image is still visible it means that we have
              // not yet loaded the fullres image, so we need to animate
              // the temporary image out.
              if (hasTemporaryImage() && mTemporaryImage.getVisibility() == View.VISIBLE) {
                  mTemporaryImage.startAnimation(scaleAnimation);
              } else {
                  mViewPager.startAnimation(scaleAnimation);
              }
          }
      }
  
      private int calculateTranslate(int start, int startSize, int totalSize, float scale) {
          // Translation takes precedence over scale.  What this means is that if
          // we want an view's upper left corner to be a particular spot on screen,
          // but that view is scaled to something other than 1, we need to take into
          // account the pixels lost to scaling.
          // So if we have a view that is 200x300, and we want it's upper left corner
          // to be at 50x50, but it's scaled by 50%, we can't just translate it to 50x50.
          // If we were to do that, the view's *visible* upper left corner would be at
          // 100x200.  We need to take into account the difference between the outside
          // size of the view (i.e. the size prior to scaling) and the scaled size.
          // scaleFromEdge is the difference between the visible left edge and the
          // actual left edge, due to scaling.
          // scaleFromTop is the difference between the visible top edge, and the
          // actual top edge, due to scaling.
          int scaleFromEdge = Math.round((totalSize - totalSize * scale) / 2);
  
          // The imageView is fullscreen, regardless of the aspect ratio of the actual image.
          // This means that some portion of the imageView will be blank.  We need to
          // take into account the size of the blank area so that the actual image
          // lines up with the starting image.
          int blankSize = Math.round((totalSize * scale - startSize) / 2);
  
          return start - scaleFromEdge - blankSize;
      }
  
      private void initTemporaryImage(Drawable drawable) {
          if (mEnterAnimationFinished) {
              // Forget this, we've already run the animation.
              return;
          }
          if (hasTemporaryImage()) {
              mTemporaryImage.setImageDrawable(drawable);
          }
          if (drawable != null) {
              // We have not yet run the enter animation. Start it now.
              int totalWidth = mRootView.getMeasuredWidth();
              if (totalWidth == 0) {
                  // the measure pass has not yet finished.  We can't properly
                  // run out animation until that is done. Listen for the layout
                  // to occur, then fire the animation.
                  final View base = mRootView;
                  base.getViewTreeObserver().addOnGlobalLayoutListener(
                          new OnGlobalLayoutListener() {
                      @Override
                      public void onGlobalLayout() {
                          int version = android.os.Build.VERSION.SDK_INT;
                          if (version >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
                              base.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                          } else {
                              base.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                          }
                          runEnterAnimation();
                      }
                  });
              } else {
                  // initiate the animation
                  runEnterAnimation();
              }
          }
          // Kick off the photo list loader
          mActivity.getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
      }
  
      public void showActionBar() {
          mActivity.getActionBarInterface().show();
      }
  
      public void hideActionBar() {
          mActivity.getActionBarInterface().hide();
      }
  
      public boolean isScaleAnimationEnabled() {
          return mScaleAnimationEnabled;
      }
  
      public boolean isEnterAnimationFinished() {
          return mEnterAnimationFinished;
      }
  
      public View getRootView() {
          return mRootView;
      }
  
      private class BitmapCallback implements LoaderManager.LoaderCallbacks<BitmapResult> {
  
          @Override
          public Loader<BitmapResult> onCreateLoader(int id, Bundle args) {
              String uri = args.getString(ARG_IMAGE_URI);
              switch (id) {
                  case PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL:
                      return onCreateBitmapLoader(PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL,
                              args, uri);
                  case PhotoViewCallbacks.BITMAP_LOADER_AVATAR:
                      return onCreateBitmapLoader(PhotoViewCallbacks.BITMAP_LOADER_AVATAR,
                              args, uri);
              }
              return null;
          }
  
          @Override
          public void onLoadFinished(Loader<BitmapResult> loader, BitmapResult result) {
              Drawable drawable = result.getDrawable(mActivity.getResources());
              final ActionBarInterface actionBar = mActivity.getActionBarInterface();
              switch (loader.getId()) {
                  case PhotoViewCallbacks.BITMAP_LOADER_THUMBNAIL:
                      // We just loaded the initial thumbnail that we can display
                      // while waiting for the full viewPager to get initialized.
                      initTemporaryImage(drawable);
                      break;
                  case PhotoViewCallbacks.BITMAP_LOADER_AVATAR:
                      if (drawable == null) {
                          actionBar.setLogo(null);
                      } else {
                          actionBar.setLogo(drawable);
                      }
                      break;
              }
          }
  
          @Override
          public void onLoaderReset(Loader<BitmapResult> loader) {
              // Do nothing
          }
      }
  
      public void setImmersiveMode(boolean enabled) {
          int flags = 0;
          final int version = Build.VERSION.SDK_INT;
          final boolean manuallyUpdateActionBar = version < Build.VERSION_CODES.JELLY_BEAN;
          if (enabled &&
                  (!isScaleAnimationEnabled() || isEnterAnimationFinished())) {
              // Turning on immersive mode causes an animation. If the scale animation is enabled and
              // the enter animation isn't yet complete, then an immersive mode animation should not
              // occur, since two concurrent animations are very janky.
  
              // Disable immersive mode for seconary users to prevent b/12015090 (freezing crash)
              // This is fixed in KK_MR2 but there is no way to differentiate between  KK and KK_MR2.
              if (version > Build.VERSION_CODES.KITKAT ||
                      version == Build.VERSION_CODES.KITKAT && !kitkatIsSecondaryUser()) {
                  flags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                          | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                          | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                          | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                          | View.SYSTEM_UI_FLAG_FULLSCREEN
                          | View.SYSTEM_UI_FLAG_IMMERSIVE;
              } else if (version >= Build.VERSION_CODES.JELLY_BEAN) {
                  // Clients that use the scale animation should set the following system UI flags to
                  // prevent janky animations on exit when the status bar is hidden:
                  //     View.SYSTEM_UI_FLAG_VISIBLE | View.SYSTEM_UI_FLAG_STABLE
                  // As well, client should ensure `android:fitsSystemWindows` is set on the root
                  // content view.
                  flags = View.SYSTEM_UI_FLAG_LOW_PROFILE
                          | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                          | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                          | View.SYSTEM_UI_FLAG_FULLSCREEN;
              } else if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                  flags = View.SYSTEM_UI_FLAG_LOW_PROFILE;
              } else if (version >= Build.VERSION_CODES.HONEYCOMB) {
                  flags = View.STATUS_BAR_HIDDEN;
              }
  
              if (manuallyUpdateActionBar) {
                  hideActionBar();
              }
          } else {
              if (version >= Build.VERSION_CODES.KITKAT) {
                  flags = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                          | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                          | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
              } else if (version >= Build.VERSION_CODES.JELLY_BEAN) {
                  flags = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                          | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
              } else if (version >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
                  flags = View.SYSTEM_UI_FLAG_VISIBLE;
              } else if (version >= Build.VERSION_CODES.HONEYCOMB) {
                  flags = View.STATUS_BAR_VISIBLE;
              }
  
              if (manuallyUpdateActionBar) {
                  showActionBar();
              }
          }
  
          if (version >= Build.VERSION_CODES.HONEYCOMB) {
              mLastFlags = flags;
              getRootView().setSystemUiVisibility(flags);
          }
      }
  
      /**
       * Return true iff the app is being run as a secondary user on kitkat.
       *
       * This is a hack which we only know to work on kitkat.
       */
      private boolean kitkatIsSecondaryUser() {
          if (Build.VERSION.SDK_INT != Build.VERSION_CODES.KITKAT) {
              throw new IllegalStateException("kitkatIsSecondary user is only callable on KitKat");
          }
          return Process.myUid() > 100000;
      }
  
      /**
       * Note: This should only be called when API level is 11 or above.
       */
      public View.OnSystemUiVisibilityChangeListener getSystemUiVisibilityChangeListener() {
          return mSystemUiVisibilityChangeListener;
      }
  }
  