blob: f626757fd05faa3935489507201510f1508030a1 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc.
* Licensed to The Android Open Source Project.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.ex.photo;
import android.app.ActionBar;
import android.app.ActionBar.OnMenuVisibilityListener;
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.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.text.TextUtils;
import android.view.MenuItem;
import android.view.View;
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.PhotoPagerLoader;
import com.android.ex.photo.provider.PhotoContract;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* Activity to view the contents of an album.
*/
public class PhotoViewActivity extends FragmentActivity implements
LoaderManager.LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener,
OnMenuVisibilityListener, PhotoViewCallbacks {
private final static String STATE_ITEM_KEY =
"com.google.android.apps.plus.PhotoViewFragment.ITEM";
private final static String STATE_FULLSCREEN_KEY =
"com.google.android.apps.plus.PhotoViewFragment.FULLSCREEN";
private final static String STATE_ACTIONBARTITLE_KEY =
"com.google.android.apps.plus.PhotoViewFragment.ACTIONBARTITLE";
private final static String STATE_ACTIONBARSUBTITLE_KEY =
"com.google.android.apps.plus.PhotoViewFragment.ACTIONBARSUBTITLE";
private static final int LOADER_PHOTO_LIST = 1;
/** Count used when the real photo count is unknown [but, may be determined] */
public static final int ALBUM_COUNT_UNKNOWN = -1;
/** Argument key for the dialog message */
public static final String KEY_MESSAGE = "dialog_message";
public static int sMemoryClass;
/** The URI of the photos we're viewing; may be {@code null} */
private String mPhotosUri;
/** The URI of the initial photo to display */
private String mInitialPhotoUri;
/** The index of the currently viewed photo */
private int mPhotoIndex;
/** 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}. */
private int mAlbumCount = ALBUM_COUNT_UNKNOWN;
/** {@code true} if the view is empty. Otherwise, {@code false}. */
private boolean mIsEmpty;
/** the main root view */
protected View mRootView;
/** The main pager; provides left/right swipe between photos */
protected PhotoViewPager mViewPager;
/** Adapter to create pager views */
protected PhotoPagerAdapter mAdapter;
/** Whether or not we're in "full screen" mode */
private boolean mFullScreen;
/** The listeners wanting full screen state for each screen position */
private Map<Integer, OnScreenListener>
mScreenListeners = new HashMap<Integer, OnScreenListener>();
/** The set of listeners wanting full screen state */
private Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>();
/** When {@code true}, restart the loader when the activity becomes active */
private boolean mRestartLoader;
/** Whether or not this activity is paused */
private boolean mIsPaused = true;
/** The maximum scale factor applied to images when they are initially displayed */
private float mMaxInitialScale;
/** The title in the actionbar */
private String mActionBarTitle;
/** The subtitle in the actionbar */
private String mActionBarSubtitle;
private final Handler mHandler = new Handler();
// TODO Find a better way to do this. We basically want the activity to display the
// "loading..." progress until the fragment takes over and shows it's own "loading..."
// 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;
protected PhotoPagerAdapter createPhotoPagerAdapter(Context context,
android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
return new PhotoPagerAdapter(context, fm, c, maxScale);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityManager mgr = (ActivityManager) getApplicationContext().
getSystemService(Activity.ACTIVITY_SERVICE);
sMemoryClass = mgr.getMemoryClass();
Intent mIntent = getIntent();
int currentItem = -1;
if (savedInstanceState != null) {
currentItem = savedInstanceState.getInt(STATE_ITEM_KEY, -1);
mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false);
mActionBarTitle = savedInstanceState.getString(STATE_ACTIONBARTITLE_KEY);
mActionBarSubtitle = savedInstanceState.getString(STATE_ACTIONBARSUBTITLE_KEY);
}
// uri of the photos to view; optional
if (mIntent.hasExtra(Intents.EXTRA_PHOTOS_URI)) {
mPhotosUri = mIntent.getStringExtra(Intents.EXTRA_PHOTOS_URI);
}
// projection for the query; optional
// If not set, the default projection is used.
// This projection must include the columns from the default projection.
if (mIntent.hasExtra(Intents.EXTRA_PROJECTION)) {
mProjection = mIntent.getStringArrayExtra(Intents.EXTRA_PROJECTION);
} else {
mProjection = null;
}
// Set the current item from the intent if wasn't in the saved instance
if (currentItem < 0) {
if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX)) {
currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
}
if (mIntent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI)) {
mInitialPhotoUri = mIntent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
}
}
// Set the max initial scale, defaulting to 1x
mMaxInitialScale = mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
// If we still have a negative current item, set it to zero
mPhotoIndex = Math.max(currentItem, 0);
mIsEmpty = true;
setContentView(R.layout.photo_activity_view);
// Create the adapter and add the view pager
mAdapter =
createPhotoPagerAdapter(this, getSupportFragmentManager(), null, mMaxInitialScale);
final Resources resources = getResources();
mRootView = findViewById(R.id.photo_activity_root_view);
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));
// Kick off the loader
getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
mEnterFullScreenDelayTime =
resources.getInteger(R.integer.reenter_fullscreen_delay_time_in_millis);
final ActionBar actionBar = getActionBar();
if (actionBar != null) {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.addOnMenuVisibilityListener(this);
final int showTitle = ActionBar.DISPLAY_SHOW_TITLE;
actionBar.setDisplayOptions(showTitle, showTitle);
setActionBarTitles(actionBar);
}
}
@Override
protected void onResume() {
super.onResume();
setFullScreen(mFullScreen, false);
mIsPaused = false;
if (mRestartLoader) {
mRestartLoader = false;
getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
}
}
@Override
protected void onPause() {
mIsPaused = true;
super.onPause();
}
@Override
public void onBackPressed() {
// If in full screen mode, toggle mode & eat the 'back'
if (mFullScreen) {
toggleFullScreen();
} else {
super.onBackPressed();
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(STATE_ITEM_KEY, mViewPager.getCurrentItem());
outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
outState.putString(STATE_ACTIONBARTITLE_KEY, mActionBarTitle);
outState.putString(STATE_ACTIONBARSUBTITLE_KEY, mActionBarSubtitle);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
default:
return super.onOptionsItemSelected(item);
}
}
@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) {
finish();
return;
}
getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
if (id == LOADER_PHOTO_LIST) {
return new PhotoPagerLoader(this, Uri.parse(mPhotosUri), mProjection);
}
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;
} else {
mAlbumCount = data.getCount();
if (mInitialPhotoUri != null) {
int index = 0;
int uriIndex = data.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
while (data.moveToNext()) {
String uri = data.getString(uriIndex);
if (TextUtils.equals(uri, mInitialPhotoUri)) {
mInitialPhotoUri = null;
mPhotoIndex = index;
break;
}
index++;
}
}
// We're paused; don't do anything now, we'll get re-invoked
// when the activity becomes active again
// TODO(pwestbro): This shouldn't be necessary, as the loader manager should
// restart the loader
if (mIsPaused) {
mRestartLoader = true;
return;
}
boolean wasEmpty = mIsEmpty;
mIsEmpty = false;
mAdapter.swapCursor(data);
if (mViewPager.getAdapter() == null) {
mViewPager.setAdapter(mAdapter);
}
notifyCursorListeners(data);
// set the selected photo
int itemIndex = mPhotoIndex;
// Use an index of 0 if the index wasn't specified or couldn't be found
if (itemIndex < 0) {
itemIndex = 0;
}
mViewPager.setCurrentItem(itemIndex, false);
if (wasEmpty) {
setViewActivated(itemIndex);
}
}
// Update the any action items
updateActionItems();
}
}
@Override
public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
// If the loader is reset, remove the reference in the adapter to this cursor
// TODO(pwestbro): reenable this when b/7075236 is fixed
// mAdapter.swapCursor(null);
}
protected 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) {
}
@Override
public void onPageSelected(int position) {
mPhotoIndex = 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) {
updateActionBar();
}
@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) {
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);
}
}
}
private void postEnterFullScreenRunnableWithDelay() {
mHandler.postDelayed(mEnterFullScreenRunnable, mEnterFullScreenDelayTime);
}
private void cancelEnterFullScreenRunnable() {
mHandler.removeCallbacks(mEnterFullScreenRunnable);
}
protected void setLightsOutMode(boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
int flags = enabled
? View.SYSTEM_UI_FLAG_LOW_PROFILE
| View.SYSTEM_UI_FLAG_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE
: View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
// using mViewPager since we have it and we need a view
mViewPager.setSystemUiVisibility(flags);
} else {
final ActionBar actionBar = getActionBar();
if (enabled) {
actionBar.hide();
} else {
actionBar.show();
}
int flags = enabled
? View.SYSTEM_UI_FLAG_LOW_PROFILE
: View.SYSTEM_UI_FLAG_VISIBLE;
mViewPager.setSystemUiVisibility(flags);
}
}
private 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();
}
}
/**
* Adjusts the activity title and subtitle to reflect the photo name and count.
*/
protected void updateActionBar() {
final int position = mViewPager.getCurrentItem() + 1;
final boolean hasAlbumCount = mAlbumCount >= 0;
final Cursor cursor = getCursorAtProperPosition();
if (cursor != null) {
final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME);
mActionBarTitle = cursor.getString(photoNameIndex);
} else {
mActionBarTitle = null;
}
if (mIsEmpty || !hasAlbumCount || position <= 0) {
mActionBarSubtitle = null;
} else {
mActionBarSubtitle =
getResources().getString(R.string.photo_view_count, position, mAlbumCount);
}
setActionBarTitles(getActionBar());
}
/**
* Sets the Action Bar title to {@link #mActionBarTitle} and the subtitle to
* {@link #mActionBarSubtitle}
*/
private final void setActionBarTitles(ActionBar actionBar) {
if (actionBar == null) {
return;
}
actionBar.setTitle(getInputOrEmpty(mActionBarTitle));
actionBar.setSubtitle(getInputOrEmpty(mActionBarSubtitle));
}
/**
* If the input string is non-null, it is returned, otherwise an empty string is returned;
* @param in
* @return
*/
private static final String getInputOrEmpty(String in) {
if (in == null) {
return "";
}
return in;
}
/**
* 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 boolean isFullScreen() {
return mFullScreen;
}
protected void setPhotoIndex(int index) {
mPhotoIndex = index;
}
@Override
public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) {
// do nothing
}
@Override
public PhotoPagerAdapter getAdapter() {
return mAdapter;
}
}