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 this fragment will only show the loading spinner */ 110 private final boolean mOnlyShowSpinner; 111 112 /** Whether or not the progress bar is showing valid information about the progress stated */ 113 private boolean mProgressBarNeeded = true; 114 115 private View mPhotoPreviewAndProgress; 116 117 public PhotoViewFragment() { 118 mPosition = -1; 119 mOnlyShowSpinner = false; 120 mProgressBarNeeded = true; 121 } 122 123 public PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter, 124 boolean onlyShowSpinner) { 125 mIntent = intent; 126 mPosition = position; 127 mAdapter = adapter; 128 mOnlyShowSpinner = onlyShowSpinner; 129 mProgressBarNeeded = true; 130 } 131 132 @Override 133 public void onAttach(Activity activity) { 134 super.onAttach(activity); 135 mCallback = (PhotoViewActivity) activity; 136 if (mCallback == null) { 137 throw new IllegalArgumentException( 138 "Activity must be a derived class of PhotoViewActivity"); 139 } 140 141 if (sPhotoSize == null) { 142 final DisplayMetrics metrics = new DisplayMetrics(); 143 final WindowManager wm = 144 (WindowManager) getActivity().getSystemService(Context.WINDOW_SERVICE); 145 final ImageUtils.ImageSize imageSize = ImageUtils.sUseImageSize; 146 wm.getDefaultDisplay().getMetrics(metrics); 147 switch (imageSize) { 148 case EXTRA_SMALL: { 149 // Use a photo that's 80% of the "small" size 150 sPhotoSize = (Math.min(metrics.heightPixels, metrics.widthPixels) * 800) / 1000; 151 break; 152 } 153 154 case SMALL: 155 case NORMAL: 156 default: { 157 sPhotoSize = Math.min(metrics.heightPixels, metrics.widthPixels); 158 break; 159 } 160 } 161 } 162 } 163 164 @Override 165 public void onDetach() { 166 mCallback = null; 167 super.onDetach(); 168 } 169 170 @Override 171 public void onCreate(Bundle savedInstanceState) { 172 super.onCreate(savedInstanceState); 173 174 if (savedInstanceState != null) { 175 final Bundle state = savedInstanceState.getBundle(STATE_INTENT_KEY); 176 if (state != null) { 177 mIntent = new Intent().putExtras(state); 178 } 179 } 180 181 if (mIntent != null) { 182 mResolvedPhotoUri = mIntent.getStringExtra(Intents.EXTRA_RESOLVED_PHOTO_URI); 183 mThumbnailUri = mIntent.getStringExtra(Intents.EXTRA_THUMBNAIL_URI); 184 } 185 } 186 187 @Override 188 public View onCreateView(LayoutInflater inflater, ViewGroup container, 189 Bundle savedInstanceState) { 190 final View view = inflater.inflate(R.layout.photo_fragment_view, container, false); 191 192 mPhotoView = (PhotoView) view.findViewById(R.id.photo_view); 193 mPhotoView.setMaxInitialScale(mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1)); 194 mPhotoView.setOnClickListener(this); 195 mPhotoView.setFullScreen(mFullScreen, false); 196 mPhotoView.enableImageTransforms(true); 197 198 mPhotoPreviewAndProgress = view.findViewById(R.id.photo_preview); 199 mPhotoPreviewImage = (ImageView) view.findViewById(R.id.photo_preview_image); 200 final ProgressBar indeterminate = 201 (ProgressBar) view.findViewById(R.id.indeterminate_progress); 202 final ProgressBar determinate = 203 (ProgressBar) view.findViewById(R.id.determinate_progress); 204 mPhotoProgressBar = new ProgressBarWrapper(determinate, indeterminate, true); 205 mEmptyText = (TextView) view.findViewById(R.id.empty_text); 206 mRetryButton = (ImageView) view.findViewById(R.id.retry_button); 207 208 // Don't call until we've setup the entire view 209 setViewVisibility(); 210 211 return view; 212 } 213 214 @Override 215 public void onResume() { 216 mCallback.addScreenListener(this); 217 mCallback.addCursorListener(this); 218 219 getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); 220 221 super.onResume(); 222 } 223 224 @Override 225 public void onPause() { 226 super.onPause(); 227 // Remove listeners 228 mCallback.removeCursorListener(this); 229 mCallback.removeScreenListener(this); 230 resetPhotoView(); 231 } 232 233 @Override 234 public void onDestroyView() { 235 // Clean up views and other components 236 if (mPhotoView != null) { 237 mPhotoView.clear(); 238 mPhotoView = null; 239 } 240 241 super.onDestroyView(); 242 } 243 244 @Override 245 public void onSaveInstanceState(Bundle outState) { 246 super.onSaveInstanceState(outState); 247 248 if (mIntent != null) { 249 outState.putParcelable(STATE_INTENT_KEY, mIntent.getExtras()); 250 } 251 } 252 253 @Override 254 public Loader<Bitmap> onCreateLoader(int id, Bundle args) { 255 if(mOnlyShowSpinner) { 256 return null; 257 } 258 switch (id) { 259 case LOADER_ID_PHOTO: 260 return new PhotoBitmapLoader(getActivity(), mResolvedPhotoUri); 261 case LOADER_ID_THUMBNAIL: 262 return new PhotoBitmapLoader(getActivity(), mThumbnailUri); 263 default: 264 return null; 265 } 266 } 267 268 @Override 269 public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) { 270 // If we don't have a view, the fragment has been paused. We'll get the cursor again later. 271 if (getView() == null) { 272 return; 273 } 274 275 final int id = loader.getId(); 276 switch (id) { 277 case LOADER_ID_PHOTO: 278 if (data != null) { 279 bindPhoto(data); 280 mPhotoPreviewAndProgress.setVisibility(View.GONE); 281 mProgressBarNeeded = false; 282 } else { 283 // Received a null result for the full size image. Instead attempt to load the 284 // thumbnail 285 getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null, this); 286 } 287 break; 288 case LOADER_ID_THUMBNAIL: 289 mProgressBarNeeded = false; 290 if (isPhotoBound()) { 291 // There is need to do anything with the thumbnail image, as the full size 292 // image is being shown. 293 mPhotoPreviewAndProgress.setVisibility(View.GONE); 294 return; 295 } else if (data == null) { 296 // no preview, show default 297 mPhotoPreviewImage.setVisibility(View.VISIBLE); 298 mPhotoPreviewImage.setImageResource(R.drawable.default_image); 299 } else { 300 bindPhoto(data); 301 getLoaderManager().initLoader(LOADER_ID_PHOTO, null, this); 302 } 303 break; 304 default: 305 break; 306 } 307 308 if (mProgressBarNeeded == false) { 309 // Hide the progress bar as it isn't needed anymore. 310 mPhotoProgressBar.setVisibility(View.GONE); 311 } 312 313 mCallback.setViewActivated(); 314 setViewVisibility(); 315 } 316 317 /** 318 * Binds an image to the photo view. 319 */ 320 private void bindPhoto(Bitmap bitmap) { 321 if (mPhotoView != null) { 322 mPhotoView.bindPhoto(bitmap); 323 } 324 } 325 326 /** 327 * Resets the photo view to it's default state w/ no bound photo. 328 */ 329 private void resetPhotoView() { 330 if (mPhotoView != null) { 331 mPhotoView.bindPhoto(null); 332 } 333 } 334 335 @Override 336 public void onLoaderReset(Loader<Bitmap> loader) { 337 // Do nothing 338 } 339 340 @Override 341 public void onClick(View v) { 342 mCallback.toggleFullScreen(); 343 } 344 345 @Override 346 public void onFullScreenChanged(boolean fullScreen) { 347 setViewVisibility(); 348 } 349 350 @Override 351 public void onViewActivated() { 352 if (!mCallback.isFragmentActive(this)) { 353 // we're not in the foreground; reset our view 354 resetViews(); 355 } else { 356 mCallback.onFragmentVisible(this); 357 } 358 } 359 360 /** 361 * Reset the views to their default states 362 */ 363 public void resetViews() { 364 if (mPhotoView != null) { 365 mPhotoView.resetTransformations(); 366 } 367 } 368 369 @Override 370 public boolean onInterceptMoveLeft(float origX, float origY) { 371 if (!mCallback.isFragmentActive(this)) { 372 // we're not in the foreground; don't intercept any touches 373 return false; 374 } 375 376 return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY)); 377 } 378 379 @Override 380 public boolean onInterceptMoveRight(float origX, float origY) { 381 if (!mCallback.isFragmentActive(this)) { 382 // we're not in the foreground; don't intercept any touches 383 return false; 384 } 385 386 return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY)); 387 } 388 389 /** 390 * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}. 391 */ 392 public boolean isPhotoBound() { 393 return (mPhotoView != null && mPhotoView.isPhotoBound()); 394 } 395 396 /** 397 * Sets view visibility depending upon whether or not we're in "full screen" mode. 398 */ 399 private void setViewVisibility() { 400 final boolean fullScreen = mCallback.isFragmentFullScreen(this); 401 final boolean hide = fullScreen; 402 403 setFullScreen(hide); 404 } 405 406 /** 407 * Sets full-screen mode for the views. 408 */ 409 public void setFullScreen(boolean fullScreen) { 410 mFullScreen = fullScreen; 411 } 412 413 @Override 414 public void onCursorChanged(Cursor cursor) { 415 if (cursor.moveToPosition(mPosition) && !isPhotoBound()) { 416 final LoaderManager manager = getLoaderManager(); 417 final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO); 418 if (fakeLoader == null) { 419 return; 420 } 421 422 final PhotoBitmapLoader loader = 423 (PhotoBitmapLoader) fakeLoader; 424 mResolvedPhotoUri = mAdapter.getPhotoUri(cursor); 425 loader.setPhotoUri(mResolvedPhotoUri); 426 loader.forceLoad(); 427 } 428 } 429 430 public ProgressBarWrapper getPhotoProgressBar() { 431 return mPhotoProgressBar; 432 } 433 434 public TextView getEmptyText() { 435 return mEmptyText; 436 } 437 438 public ImageView getRetryButton() { 439 return mRetryButton; 440 } 441 442 public boolean isProgressBarNeeded() { 443 return mProgressBarNeeded; 444 } 445} 446