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.fragments; 19 20import android.app.Activity; 21import android.app.Fragment; 22import android.app.LoaderManager; 23import android.app.LoaderManager.LoaderCallbacks; 24import android.content.Context; 25import android.content.Intent; 26import android.content.Loader; 27import android.database.Cursor; 28import android.graphics.Bitmap; 29import android.os.Bundle; 30import android.util.DisplayMetrics; 31import android.view.LayoutInflater; 32import android.view.View; 33import android.view.View.OnClickListener; 34import android.view.ViewGroup; 35import android.view.WindowManager; 36import android.widget.ImageView; 37import android.widget.ProgressBar; 38import android.widget.TextView; 39 40import com.android.ex.photo.Intents; 41import com.android.ex.photo.PhotoViewActivity; 42import com.android.ex.photo.PhotoViewActivity.CursorChangedListener; 43import com.android.ex.photo.PhotoViewActivity.OnScreenListener; 44import com.android.ex.photo.R; 45import com.android.ex.photo.adapters.PhotoPagerAdapter; 46import com.android.ex.photo.loaders.PhotoBitmapLoader; 47import com.android.ex.photo.util.ImageUtils; 48import com.android.ex.photo.views.PhotoView; 49import com.android.ex.photo.views.ProgressBarWrapper; 50 51/** 52 * Displays a photo. 53 */ 54public class PhotoViewFragment extends Fragment implements 55 LoaderCallbacks<Bitmap>, OnClickListener, OnScreenListener, CursorChangedListener { 56 /** 57 * Interface for components that are internally scrollable left-to-right. 58 */ 59 public static interface HorizontallyScrollable { 60 /** 61 * Return {@code true} if the component needs to receive right-to-left 62 * touch movements. 63 * 64 * @param origX the raw x coordinate of the initial touch 65 * @param origY the raw y coordinate of the initial touch 66 */ 67 68 public boolean interceptMoveLeft(float origX, float origY); 69 70 /** 71 * Return {@code true} if the component needs to receive left-to-right 72 * touch movements. 73 * 74 * @param origX the raw x coordinate of the initial touch 75 * @param origY the raw y coordinate of the initial touch 76 */ 77 public boolean interceptMoveRight(float origX, float origY); 78 } 79 80 private final static String STATE_INTENT_KEY = 81 "com.android.mail.photo.fragments.PhotoViewFragment.INTENT"; 82 83 // Loader IDs 84 private final static int LOADER_ID_PHOTO = 1; 85 private final static int LOADER_ID_THUMBNAIL = 2; 86 87 /** The size of the photo */ 88 public static Integer sPhotoSize; 89 90 /** The URL of a photo to display */ 91 private String mResolvedPhotoUri; 92 private String mThumbnailUri; 93 /** The intent we were launched with */ 94 private Intent mIntent; 95 private PhotoViewActivity mCallback; 96 private PhotoPagerAdapter mAdapter; 97 98 private PhotoView mPhotoView; 99 private ImageView mPhotoPreviewImage; 100 private TextView mEmptyText; 101 private ImageView mRetryButton; 102 private ProgressBarWrapper mPhotoProgressBar; 103 104 private final int mPosition; 105 106 /** Whether or not the fragment should make the photo full-screen */ 107 private boolean mFullScreen; 108 109 /** Whether or not the progress bar is showing valid information about the progress stated */ 110 private boolean mProgressBarNeeded = true; 111 112 private View mPhotoPreviewAndProgress; 113 114 public PhotoViewFragment() { 115 mPosition = -1; 116 mProgressBarNeeded = true; 117 } 118 119 public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter) { 120 mIntent = intent; 121 mPosition = position; 122 mAdapter = adapter; 123 mProgressBarNeeded = true; 124 } 125 126 @Override 127 public void onAttach(Activity activity) { 128 super.onAttach(activity); 129 mCallback = (PhotoViewActivity) activity; 130 if (mCallback == null) { 131 throw new IllegalArgumentException( 132 "Activity must be a derived class of PhotoViewActivity"); 133 } 134 135 if (sPhotoSize == null) { 136 final DisplayMetrics metrics = new DisplayMetrics(); 137 final WindowManager wm = 138 (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); 139 final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize; 140 wm.getDefaultDisplay().getMetrics(metrics); 141 switch (imageSize) { 142 case EXTRA_SMALL: { 143 // Use a photo that's 80% of the "small" size 144 sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000; 145 break; 146 } 147 148 case SMALL: 149 case NORMAL: 150 default: { 151 sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels); 152 break; 153 } 154 } 155 } 156 } 157 158 @Override 159 public void onDetach() { 160 mCallback = null; 161 super.onDetach(); 162 } 163 164 @Override 165 public void onCreate(Bundle savedInstanceState) { 166 super.onCreate(savedInstanceState); 167 168 if (savedInstanceState != null) { 169 final Bundle state = savedInstanceState.getBundle(STATE_INTENT_KEY); 170 if (state != null) { 171 mIntent = new Intent().putExtras(state); 172 } 173 } 174 175 if (mIntent != null) { 176 mResolvedPhotoUri = mIntent.getStringExtra(Intents.EXTRA_RESOLVED_PHOTO_URI); 177 mThumbnailUri = mIntent.getStringExtra(Intents.EXTRA_THUMBNAIL_URI); 178 } 179 } 180 181 @Override 182 public View onCreateView(LayoutInflater inflater, ViewGroup container, 183 Bundle savedInstanceState) { 184 final View view = inflater.inflate(R.layout.photo_fragment_view, container, false); 185 186 mPhotoView = (PhotoView) view.findViewById(R.id.photo_view); 187 mPhotoView.setOnClickListener(this); 188 mPhotoView.setFullScreen(mFullScreen, false); 189 mPhotoView.enableImageTransforms(true); 190 191 mPhotoPreviewAndProgress = view.findViewById(R.id.photo_preview); 192 mPhotoPreviewImage = (ImageView) view.findViewById(R.id.photo_preview_image); 193 final ProgressBar indeterminate = 194 (ProgressBar) view.findViewById(R.id.indeterminate_progress); 195 final ProgressBar determinate = 196 (ProgressBar) view.findViewById(R.id.determinate_progress); 197 mPhotoProgressBar = new ProgressBarWrapper(determinate, indeterminate, true); 198 mEmptyText = (TextView) view.findViewById(R.id.empty_text); 199 mRetryButton = (ImageView) view.findViewById(R.id.retry_button); 200 201 // Don't call until we've setup the entire view 202 setViewVisibility(); 203 204 return view; 205 } 206 207 @Override 208 public void onResume() { 209 mCallback.addScreenListener(this); 210 mCallback.addCursorListener(this); 211 212 getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); 213 214 super.onResume(); 215 } 216 217 @Override 218 public void onPause() { 219 super.onPause(); 220 // Remove listeners 221 mCallback.removeCursorListener(this); 222 mCallback.removeScreenListener(this); 223 resetPhotoView(); 224 } 225 226 @Override 227 public void onDestroyView() { 228 // Clean up views and other components 229 if (mPhotoView != null) { 230 mPhotoView.clear(); 231 mPhotoView = null; 232 } 233 234 super.onDestroyView(); 235 } 236 237 @Override 238 public void onSaveInstanceState(Bundle outState) { 239 super.onSaveInstanceState(outState); 240 241 if (mIntent != null) { 242 outState.putParcelable(STATE_INTENT_KEY, mIntent.getExtras()); 243 } 244 } 245 246 @Override 247 public Loader<Bitmap> onCreateLoader(int id, Bundle args) { 248 switch (id) { 249 case LOADER_ID_PHOTO: 250 return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri); 251 case LOADER_ID_THUMBNAIL: 252 return new PhotoBitmapLoader(getActivity(), mThumbnailUri); 253 default: 254 return null; 255 } 256 } 257 258 @Override 259 public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) { 260 // If we don't have a view, the fragment has been paused. We'll get the cursor again later. 261 if (getView() == null) { 262 return; 263 } 264 265 final int id = loader.getId(); 266 switch (id) { 267 case LOADER_ID_PHOTO: 268 if (data != null) { 269 bindPhoto(data); 270 mPhotoPreviewAndProgress.setVisibility(View.GONE); 271 mProgressBarNeeded = false; 272 } 273 break; 274 case LOADER_ID_THUMBNAIL: 275 if (isPhotoBound()) { 276 // There is need to do anything with the thumbnail image, as the full size 277 // image is being shown. 278 mPhotoPreviewAndProgress.setVisibility(View.GONE); 279 mProgressBarNeeded = false; 280 return; 281 } else { 282 // Make the preview image view visible 283 mPhotoPreviewImage.setVisibility(View.VISIBLE); 284 285 if (data == null) { 286 // no preview, show default 287 mPhotoPreviewImage.setImageResource(R.drawable.default_image); 288 mPhotoPreviewImage.setScaleType(ImageView.ScaleType.CENTER); 289 } else { 290 // Show the preview 291 mPhotoPreviewImage.setImageBitmap(data); 292 } 293 // Now load the full size image 294 getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this); 295 } 296 break; 297 default: 298 break; 299 } 300 301 if (mProgressBarNeeded == false) { 302 // Hide the progress bar as it isn't needed anymore. 303 mPhotoProgressBar.setVisibility(View.GONE); 304 } 305 if (data != null) { 306 mCallback.onNewPhotoLoaded(); 307 } 308 309 setViewVisibility(); 310 } 311 312 /** 313 * Binds an image to the photo view. 314 */ 315 private void bindPhoto(Bitmap bitmap) { 316 if (mPhotoView != null) { 317 mPhotoView.bindPhoto(bitmap); 318 } 319 } 320 321 /** 322 * Resets the photo view to it's default state w/ no bound photo. 323 */ 324 private void resetPhotoView() { 325 if (mPhotoView != null) { 326 mPhotoView.bindPhoto(null); 327 } 328 } 329 330 @Override 331 public void onLoaderReset(Loader<Bitmap> loader) { 332 // Do nothing 333 } 334 335 @Override 336 public void onClick(View v) { 337 mCallback.toggleFullScreen(); 338 } 339 340 @Override 341 public void onFullScreenChanged(boolean fullScreen) { 342 setViewVisibility(); 343 } 344 345 @Override 346 public void onViewActivated() { 347 if (!mCallback.isFragmentActive(this)) { 348 // we're not in the foreground; reset our view 349 resetViews(); 350 } else { 351 if (!isPhotoBound()) { 352 // Restart the loader 353 getLoaderManager().restartLoader(LOADER_ID_THUMBNAIL, null, this); 354 } 355 mCallback.onFragmentVisible(this); 356 } 357 } 358 359 /** 360 * Reset the views to their default states 361 */ 362 public void resetViews() { 363 if (mPhotoView != null) { 364 mPhotoView.resetTransformations(); 365 } 366 } 367 368 @Override 369 public boolean onInterceptMoveLeft(float origX, float origY) { 370 if (!mCallback.isFragmentActive(this)) { 371 // we're not in the foreground; don't intercept any touches 372 return false; 373 } 374 375 return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY)); 376 } 377 378 @Override 379 public boolean onInterceptMoveRight(float origX, float origY) { 380 if (!mCallback.isFragmentActive(this)) { 381 // we're not in the foreground; don't intercept any touches 382 return false; 383 } 384 385 return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY)); 386 } 387 388 /** 389 * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}. 390 */ 391 public boolean isPhotoBound() { 392 return (mPhotoView != null && mPhotoView.isPhotoBound()); 393 } 394 395 /** 396 * Sets view visibility depending upon whether or not we're in "full screen" mode. 397 */ 398 private void setViewVisibility() { 399 final boolean fullScreen = mCallback.isFragmentFullScreen(this); 400 final boolean hide = fullScreen; 401 402 setFullScreen(hide); 403 } 404 405 /** 406 * Sets full-screen mode for the views. 407 */ 408 public void setFullScreen(boolean fullScreen) { 409 mFullScreen = fullScreen; 410 } 411 412 @Override 413 public void onCursorChanged(Cursor cursor) { 414 if (cursor.moveToPosition(mPosition) && !isPhotoBound()) { 415 final LoaderManager manager = getLoaderManager(); 416 final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO); 417 if (fakeLoader == null) { 418 return; 419 } 420 421 final PhotoBitmapLoader loader = 422 (PhotoBitmapLoader) fakeLoader; 423 mResolvedPhotoUri = mAdapter.getPhotoUri(cursor); 424 loader.setPhotoUri(mResolvedPhotoUri); 425 loader.forceLoad(); 426 } 427 } 428 429 public ProgressBarWrapper getPhotoProgressBar() { 430 return mPhotoProgressBar; 431 } 432 433 public TextView getEmptyText() { 434 return mEmptyText; 435 } 436 437 public ImageView getRetryButton() { 438 return mRetryButton; 439 } 440 441 public boolean isProgressBarNeeded() { 442 return mProgressBarNeeded; 443 } 444} 445