PhotoViewActivity.java revision 1abd4654c2eeacc7d854a438a9c72d7239278bea
1/* 2 * Copyright (C) 2011 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18package com.android.ex.photo; 19 20import android.app.ActionBar; 21import android.app.Activity; 22import android.app.ActivityManager; 23import android.app.Fragment; 24import android.app.LoaderManager.LoaderCallbacks; 25import android.content.Intent; 26import android.content.Loader; 27import android.database.Cursor; 28import android.net.Uri; 29import android.os.Build; 30import android.os.Bundle; 31import android.os.Handler; 32import android.support.v4.view.ViewPager.OnPageChangeListener; 33import android.view.View; 34 35import com.android.ex.photo.PhotoViewPager.InterceptType; 36import com.android.ex.photo.PhotoViewPager.OnInterceptTouchListener; 37import com.android.ex.photo.adapters.BaseFragmentPagerAdapter.OnFragmentPagerListener; 38import com.android.ex.photo.adapters.PhotoPagerAdapter; 39import com.android.ex.photo.fragments.PhotoViewFragment; 40import com.android.ex.photo.loaders.PhotoPagerLoader; 41import com.android.ex.photo.provider.PhotoContract; 42 43import java.util.HashSet; 44import java.util.Set; 45 46/** 47 * Activity to view the contents of an album. 48 */ 49public class PhotoViewActivity extends Activity implements 50 LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener, 51 OnFragmentPagerListener { 52 53 /** 54 * Listener to be invoked for screen events. 55 */ 56 public static interface OnScreenListener { 57 58 /** 59 * The full screen state has changed. 60 */ 61 public void onFullScreenChanged(boolean fullScreen); 62 63 /** 64 * A new view has been activated and the previous view de-activated. 65 */ 66 public void onViewActivated(); 67 68 /** 69 * Called when a right-to-left touch move intercept is about to occur. 70 * 71 * @param origX the raw x coordinate of the initial touch 72 * @param origY the raw y coordinate of the initial touch 73 * @return {@code true} if the touch should be intercepted. 74 */ 75 public boolean onInterceptMoveLeft(float origX, float origY); 76 77 /** 78 * Called when a left-to-right touch move intercept is about to occur. 79 * 80 * @param origX the raw x coordinate of the initial touch 81 * @param origY the raw y coordinate of the initial touch 82 * @return {@code true} if the touch should be intercepted. 83 */ 84 public boolean onInterceptMoveRight(float origX, float origY); 85 } 86 87 public static interface CursorChangedListener { 88 /** 89 * Called when the cursor that contains the photo list data 90 * is updated. Note that there is no guarantee that the cursor 91 * will be at the proper position. 92 * @param cursor the cursor containing the photo list data 93 */ 94 public void onCursorChanged(Cursor cursor); 95 } 96 97 private final static String STATE_ITEM_KEY = 98 "com.google.android.apps.plus.PhotoViewFragment.ITEM"; 99 private final static String STATE_FULLSCREEN_KEY = 100 "com.google.android.apps.plus.PhotoViewFragment.FULLSCREEN"; 101 102 private static final int LOADER_PHOTO_LIST = 1; 103 104 /** Count used when the real photo count is unknown [but, may be determined] */ 105 public static final int ALBUM_COUNT_UNKNOWN = -1; 106 107 /** Argument key for the dialog message */ 108 public static final String KEY_MESSAGE = "dialog_message"; 109 110 public static int sMemoryClass; 111 112 /** The URI of the photos we're viewing; may be {@code null} */ 113 private String mPhotosUri; 114 /** The index of the currently viewed photo */ 115 private int mPhotoIndex; 116 /** The query projection to use; may be {@code null} */ 117 private String[] mProjection; 118 /** The name of the particular photo being viewed. */ 119 private String mPhotoName; 120 /** The total number of photos; only valid if {@link #mIsEmpty} is {@code false}. */ 121 private int mAlbumCount = ALBUM_COUNT_UNKNOWN; 122 /** {@code true} if the view is empty. Otherwise, {@code false}. */ 123 private boolean mIsEmpty; 124 /** The main pager; provides left/right swipe between photos */ 125 private PhotoViewPager mViewPager; 126 /** Adapter to create pager views */ 127 private PhotoPagerAdapter mAdapter; 128 /** Whether or not we're in "full screen" mode */ 129 private boolean mFullScreen; 130 /** The set of listeners wanting full screen state */ 131 private Set<OnScreenListener> mScreenListeners = new HashSet<OnScreenListener>(); 132 /** The set of listeners wanting full screen state */ 133 private Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>(); 134 /** When {@code true}, restart the loader when the activity becomes active */ 135 private boolean mRestartLoader; 136 /** Whether or not this activity is paused */ 137 private boolean mIsPaused = true; 138 private Handler mActionBarHideHandler; 139 // TODO Find a better way to do this. We basically want the activity to display the 140 // "loading..." progress until the fragment takes over and shows it's own "loading..." 141 // progress [located in photo_header_view.xml]. We could potentially have all status displayed 142 // by the activity, but, that gets tricky when it comes to screen rotation. For now, we 143 // track the loading by this variable which is fragile and may cause phantom "loading..." 144 // text. 145 private long mActionBarHideDelayTime; 146 147 @Override 148 protected void onCreate(Bundle savedInstanceState) { 149 super.onCreate(savedInstanceState); 150 151 final ActivityManager mgr = (ActivityManager) getApplicationContext(). 152 getSystemService(Activity.ACTIVITY_SERVICE); 153 sMemoryClass = mgr.getMemoryClass(); 154 155 Intent mIntent = getIntent(); 156 157 int currentItem = -1; 158 if (savedInstanceState != null) { 159 currentItem = savedInstanceState.getInt(STATE_ITEM_KEY, -1); 160 mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false); 161 } 162 163 // album name; if not set, use a default name 164 if (mIntent.hasExtra(Intents.EXTRA_PHOTO_NAME)) { 165 mPhotoName = mIntent.getStringExtra(Intents.EXTRA_PHOTO_NAME); 166 } else { 167 mPhotoName = getResources().getString(R.string.photo_view_default_title); 168 } 169 170 // uri of the photos to view; optional 171 if (mIntent.hasExtra(Intents.EXTRA_PHOTOS_URI)) { 172 mPhotosUri = mIntent.getStringExtra(Intents.EXTRA_PHOTOS_URI); 173 } 174 175 // projection for the query; optional 176 // I.f not set, the default projection is used. 177 // This projection must include the columns from the default projection. 178 if (mIntent.hasExtra(Intents.EXTRA_PROJECTION)) { 179 mProjection = mIntent.getStringArrayExtra(Intents.EXTRA_PROJECTION); 180 } else { 181 mProjection = null; 182 } 183 184 // Set the current item from the intent if wasn't in the saved instance 185 if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX) && currentItem < 0) { 186 currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1); 187 } 188 mPhotoIndex = currentItem; 189 190 setContentView(R.layout.photo_activity_view); 191 192 // Create the adapter and add the view pager 193 mAdapter = new PhotoPagerAdapter(this, getFragmentManager(), null); 194 mAdapter.setFragmentPagerListener(this); 195 196 mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager); 197 mViewPager.setAdapter(mAdapter); 198 mViewPager.setOnPageChangeListener(this); 199 mViewPager.setOnInterceptTouchListener(this); 200 201 // Kick off the loader 202 getLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this); 203 204 final ActionBar actionBar = getActionBar(); 205 actionBar.setDisplayHomeAsUpEnabled(true); 206 mActionBarHideDelayTime = getResources().getInteger( 207 R.integer.action_bar_delay_time_in_millis); 208 } 209 210 @Override 211 protected void onResume() { 212 super.onResume(); 213 setFullScreen(mFullScreen, false); 214 215 mIsPaused = false; 216 if (mRestartLoader) { 217 mRestartLoader = false; 218 getLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this); 219 } 220 } 221 222 @Override 223 protected void onPause() { 224 mIsPaused = true; 225 226 super.onPause(); 227 } 228 229 @Override 230 public void onBackPressed() { 231 // If in full screen mode, toggle mode & eat the 'back' 232 if (mFullScreen) { 233 toggleFullScreen(); 234 } else { 235 super.onBackPressed(); 236 } 237 } 238 239 @Override 240 public void onSaveInstanceState(Bundle outState) { 241 super.onSaveInstanceState(outState); 242 243 outState.putInt(STATE_ITEM_KEY, mViewPager.getCurrentItem()); 244 outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen); 245 } 246 247 public void addScreenListener(OnScreenListener listener) { 248 mScreenListeners.add(listener); 249 } 250 251 public void removeScreenListener(OnScreenListener listener) { 252 mScreenListeners.remove(listener); 253 } 254 255 public synchronized void addCursorListener(CursorChangedListener listener) { 256 mCursorListeners.add(listener); 257 } 258 259 public synchronized void removeCursorListener(CursorChangedListener listener) { 260 mCursorListeners.remove(listener); 261 } 262 263 public boolean isFragmentFullScreen(Fragment fragment) { 264 if (mViewPager == null || mAdapter == null || mAdapter.getCount() == 0) { 265 return mFullScreen; 266 } 267 return mFullScreen || (mViewPager.getCurrentItem() != mAdapter.getItemPosition(fragment)); 268 } 269 270 public void toggleFullScreen() { 271 setFullScreen(!mFullScreen, true); 272 } 273 274 public void onPhotoRemoved(long photoId) { 275 final Cursor data = mAdapter.getCursor(); 276 if (data == null) { 277 // Huh?! How would this happen? 278 return; 279 } 280 281 final int dataCount = data.getCount(); 282 if (dataCount <= 1) { 283 finish(); 284 return; 285 } 286 287 getLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this); 288 } 289 290 @Override 291 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 292 if (id == LOADER_PHOTO_LIST) { 293 return new PhotoPagerLoader(this, Uri.parse(mPhotosUri), mProjection); 294 } 295 return null; 296 } 297 298 @Override 299 public void onLoadFinished(final Loader<Cursor> loader, final Cursor data) { 300 final int id = loader.getId(); 301 if (id == LOADER_PHOTO_LIST) { 302 if (data == null || data.getCount() == 0) { 303 mIsEmpty = true; 304 } else { 305 mAlbumCount = data.getCount(); 306 307 // Cannot do this directly; need to be out of the loader 308 new Handler().post(new Runnable() { 309 @Override 310 public void run() { 311 // We're paused; don't do anything now, we'll get re-invoked 312 // when the activity becomes active again 313 if (mIsPaused) { 314 mRestartLoader = true; 315 return; 316 } 317 mIsEmpty = false; 318 319 // set the selected photo 320 int itemIndex = mPhotoIndex; 321 322 // Use an index of 0 if the index wasn't specified or couldn't be found 323 if (itemIndex < 0) { 324 itemIndex = 0; 325 } 326 327 mAdapter.swapCursor(data); 328 notifyCursorListeners(data); 329 330 mViewPager.setCurrentItem(itemIndex, false); 331 updateActionBar(); 332 } 333 }); 334 } 335 } 336 } 337 338 private synchronized void notifyCursorListeners(Cursor data) { 339 // tell all of the objects listening for cursor changes 340 // that the cursor has changed 341 for (CursorChangedListener listener : mCursorListeners) { 342 listener.onCursorChanged(data); 343 } 344 } 345 346 @Override 347 public void onLoaderReset(Loader<Cursor> loader) { 348 } 349 350 @Override 351 public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 352 } 353 354 @Override 355 public void onPageSelected(int position) { 356 setViewActivated(); 357 updateActionBar(); 358 mPhotoIndex = position; 359 } 360 361 @Override 362 public void onPageScrollStateChanged(int state) { 363 } 364 365 @Override 366 public void onPageActivated(Fragment fragment) { 367 setViewActivated(); 368 } 369 370 public boolean isFragmentActive(Fragment fragment) { 371 if (mViewPager == null || mAdapter == null) { 372 return false; 373 } 374 return mViewPager.getCurrentItem() == mAdapter.getItemPosition(fragment); 375 } 376 377 public void onFragmentVisible(PhotoViewFragment fragment) { 378 } 379 380 @Override 381 public InterceptType onTouchIntercept(float origX, float origY) { 382 boolean interceptLeft = false; 383 boolean interceptRight = false; 384 385 for (OnScreenListener listener : mScreenListeners) { 386 if (!interceptLeft) { 387 interceptLeft = listener.onInterceptMoveLeft(origX, origY); 388 } 389 if (!interceptRight) { 390 interceptRight = listener.onInterceptMoveRight(origX, origY); 391 } 392 listener.onViewActivated(); 393 } 394 395 if (interceptLeft) { 396 if (interceptRight) { 397 return InterceptType.BOTH; 398 } 399 return InterceptType.LEFT; 400 } else if (interceptRight) { 401 return InterceptType.RIGHT; 402 } 403 return InterceptType.NONE; 404 } 405 406 /** 407 * Updates the title bar according to the value of {@link #mFullScreen}. 408 */ 409 private void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) { 410 final boolean fullScreenChanged = (fullScreen != mFullScreen); 411 mFullScreen = fullScreen; 412 413 if (mFullScreen) { 414 setLightsOutMode(true); 415 if (mActionBarHideHandler == null) { 416 mActionBarHideHandler = new Handler(); 417 } 418 mActionBarHideHandler.removeCallbacks(mActionBarHideRunnable); 419 } else { 420 setLightsOutMode(false); 421 if (setDelayedRunnable) { 422 if (mActionBarHideHandler == null) { 423 mActionBarHideHandler = new Handler(); 424 } 425 mActionBarHideHandler.postDelayed(mActionBarHideRunnable, 426 mActionBarHideDelayTime); 427 } 428 } 429 430 if (fullScreenChanged) { 431 for (OnScreenListener listener : mScreenListeners) { 432 listener.onFullScreenChanged(mFullScreen); 433 } 434 } 435 } 436 437 private void setLightsOutMode(boolean enabled) { 438 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 439 int flags = enabled 440 ? View.SYSTEM_UI_FLAG_LOW_PROFILE 441 | View.SYSTEM_UI_FLAG_FULLSCREEN 442 | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 443 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE 444 : View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN 445 | View.SYSTEM_UI_FLAG_LAYOUT_STABLE; 446 447 // using mViewPager since we have it and we need a view 448 mViewPager.setSystemUiVisibility(flags); 449 } else { 450 final ActionBar actionBar = getActionBar(); 451 if (enabled) { 452 actionBar.hide(); 453 } else { 454 actionBar.show(); 455 } 456 int flags = enabled 457 ? View.SYSTEM_UI_FLAG_LOW_PROFILE 458 : View.SYSTEM_UI_FLAG_VISIBLE; 459 mViewPager.setSystemUiVisibility(flags); 460 } 461 } 462 463 private Runnable mActionBarHideRunnable = new Runnable() { 464 @Override 465 public void run() { 466 PhotoViewActivity.this.setLightsOutMode(true); 467 } 468 }; 469 470 public void setViewActivated() { 471 for (OnScreenListener listener : mScreenListeners) { 472 listener.onViewActivated(); 473 } 474 } 475 476 /** 477 * Adjusts the activity title and subtitle to reflect the photo name and count. 478 */ 479 protected void updateActionBar() { 480 final int position = mViewPager.getCurrentItem() + 1; 481 final String subtitle; 482 final boolean hasAlbumCount = mAlbumCount >= 0; 483 484 final Cursor cursor = getCursorAtProperPosition(); 485 486 if (cursor != null) { 487 final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME); 488 mPhotoName = cursor.getString(photoNameIndex); 489 } 490 491 if (mIsEmpty || !hasAlbumCount || position <= 0) { 492 subtitle = null; 493 } else { 494 subtitle = getResources().getString(R.string.photo_view_count, position, mAlbumCount); 495 } 496 497 final ActionBar actionBar = getActionBar(); 498 499 actionBar.setTitle(mPhotoName); 500 actionBar.setSubtitle(subtitle); 501 } 502 503 /** 504 * Utility method that will return the cursor that contains the data 505 * at the current position so that it refers to the current image on screen. 506 * @return the cursor at the current position or 507 * null if no cursor exists or if the {@link PhotoViewPager} is null. 508 */ 509 public Cursor getCursorAtProperPosition() { 510 if (mViewPager == null) { 511 return null; 512 } 513 514 final int position = mViewPager.getCurrentItem(); 515 final Cursor cursor = mAdapter.getCursor(); 516 517 if (cursor == null) { 518 return null; 519 } 520 521 cursor.moveToPosition(position); 522 523 return cursor; 524 } 525 526 public Cursor getCursor() { 527 return (mAdapter == null) ? null : mAdapter.getCursor(); 528 } 529} 530