• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
18 package com.android.ex.photo.fragments;
19 
20 import android.app.Activity;
21 import android.content.Context;
22 import android.content.Intent;
23 import android.database.Cursor;
24 import android.graphics.Bitmap;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.support.v4.app.Fragment;
28 import android.support.v4.app.LoaderManager;
29 import android.support.v4.content.Loader;
30 import android.util.DisplayMetrics;
31 import android.view.LayoutInflater;
32 import android.view.View;
33 import android.view.View.OnClickListener;
34 import android.view.ViewGroup;
35 import android.view.WindowManager;
36 import android.widget.ImageView;
37 import android.widget.ProgressBar;
38 import android.widget.TextView;
39 
40 import com.android.ex.photo.Intents;
41 import com.android.ex.photo.PhotoViewCallbacks;
42 import com.android.ex.photo.PhotoViewCallbacks.CursorChangedListener;
43 import com.android.ex.photo.PhotoViewCallbacks.OnScreenListener;
44 import com.android.ex.photo.R;
45 import com.android.ex.photo.adapters.PhotoPagerAdapter;
46 import com.android.ex.photo.loaders.PhotoBitmapLoader;
47 import com.android.ex.photo.util.ImageUtils;
48 import com.android.ex.photo.views.PhotoView;
49 import com.android.ex.photo.views.ProgressBarWrapper;
50 
51 /**
52  * Displays a photo.
53  */
54 public class PhotoViewFragment extends Fragment implements
55         LoaderManager.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 
interceptMoveLeft(float origX, float origY)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          */
interceptMoveRight(float origX, float origY)77         public boolean interceptMoveRight(float origX, float origY);
78     }
79 
80     protected final static String STATE_INTENT_KEY =
81             "com.android.mail.photo.fragments.PhotoViewFragment.INTENT";
82 
83     // Loader IDs
84     protected final static int LOADER_ID_PHOTO = 1;
85     protected 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     protected String mResolvedPhotoUri;
92     protected String mThumbnailUri;
93     /** The intent we were launched with */
94     protected Intent mIntent;
95     protected PhotoViewCallbacks mCallback;
96     protected PhotoPagerAdapter mAdapter;
97 
98     protected PhotoView mPhotoView;
99     protected ImageView mPhotoPreviewImage;
100     protected TextView mEmptyText;
101     protected ImageView mRetryButton;
102     protected ProgressBarWrapper mPhotoProgressBar;
103 
104     protected final int mPosition;
105 
106     /** Whether or not the fragment should make the photo full-screen */
107     protected boolean mFullScreen;
108 
109     /** Whether or not this fragment will only show the loading spinner */
110     protected final boolean mOnlyShowSpinner;
111 
112     /** Whether or not the progress bar is showing valid information about the progress stated */
113     protected boolean mProgressBarNeeded = true;
114 
115     protected View mPhotoPreviewAndProgress;
116 
PhotoViewFragment()117     public PhotoViewFragment() {
118         mPosition = -1;
119         mOnlyShowSpinner = false;
120         mProgressBarNeeded = true;
121     }
122 
PhotoViewFragment(Intent intent, int position, PhotoPagerAdapter adapter, boolean onlyShowSpinner)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
onAttach(Activity activity)133     public void onAttach(Activity activity) {
134         super.onAttach(activity);
135         mCallback = (PhotoViewCallbacks) 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
onDetach()165     public void onDetach() {
166         mCallback = null;
167         super.onDetach();
168     }
169 
170     @Override
onCreate(Bundle savedInstanceState)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
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)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(false);
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
onResume()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
onPause()225     public void onPause() {
226         super.onPause();
227         // Remove listeners
228         mCallback.removeCursorListener(this);
229         mCallback.removeScreenListener(this);
230         resetPhotoView();
231     }
232 
233     @Override
onDestroyView()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
onSaveInstanceState(Bundle outState)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
onCreateLoader(int id, Bundle args)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
onLoadFinished(Loader<Bitmap> loader, Bitmap data)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                     enableImageTransforms(true);
281                     mPhotoPreviewAndProgress.setVisibility(View.GONE);
282                     mProgressBarNeeded = false;
283                 } else {
284                     // Received a null result for the full size image.  Instead attempt to load the
285                     // thumbnail
286                     Handler handler = new Handler();
287                     handler.post(new Runnable() {
288                         @Override
289                         public void run() {
290                             getLoaderManager().initLoader(LOADER_ID_THUMBNAIL, null,
291                                                           PhotoViewFragment.this);
292                         }
293                     });
294                 }
295                 break;
296             case LOADER_ID_THUMBNAIL:
297                 mProgressBarNeeded = false;
298                 if (isPhotoBound()) {
299                     // There is need to do anything with the thumbnail image, as the full size
300                     // image is being shown.
301                     mPhotoPreviewAndProgress.setVisibility(View.GONE);
302                     return;
303                 } else if (data == null) {
304                     // no preview, show default
305                     mPhotoPreviewImage.setVisibility(View.VISIBLE);
306                     mPhotoPreviewImage.setImageResource(R.drawable.default_image);
307                 } else {
308                     bindPhoto(data);
309                     enableImageTransforms(false);
310                     Handler handler = new Handler();
311                     handler.post(new Runnable() {
312                         @Override
313                         public void run() {
314                             getLoaderManager().initLoader(LOADER_ID_PHOTO, null,
315                                 PhotoViewFragment.this);
316                         }
317                     });
318                 }
319                 break;
320             default:
321                 break;
322         }
323 
324         if (mProgressBarNeeded == false) {
325             // Hide the progress bar as it isn't needed anymore.
326             mPhotoProgressBar.setVisibility(View.GONE);
327         }
328 
329         mCallback.setViewActivated();
330         setViewVisibility();
331     }
332 
333     /**
334      * Binds an image to the photo view.
335      */
bindPhoto(Bitmap bitmap)336     private void bindPhoto(Bitmap bitmap) {
337         if (mPhotoView != null) {
338             mPhotoView.bindPhoto(bitmap);
339         }
340     }
341 
342     /**
343      * Enable or disable image transformations. When transformations are enabled, this view
344      * consumes all touch events.
345      */
enableImageTransforms(boolean enable)346     public void enableImageTransforms(boolean enable) {
347         mPhotoView.enableImageTransforms(enable);
348     }
349 
350     /**
351      * Resets the photo view to it's default state w/ no bound photo.
352      */
resetPhotoView()353     private void resetPhotoView() {
354         if (mPhotoView != null) {
355             mPhotoView.bindPhoto(null);
356         }
357     }
358 
359     @Override
onLoaderReset(Loader<Bitmap> loader)360     public void onLoaderReset(Loader<Bitmap> loader) {
361         // Do nothing
362     }
363 
364     @Override
onClick(View v)365     public void onClick(View v) {
366         mCallback.toggleFullScreen();
367     }
368 
369     @Override
onFullScreenChanged(boolean fullScreen)370     public void onFullScreenChanged(boolean fullScreen) {
371         setViewVisibility();
372     }
373 
374     @Override
onViewActivated()375     public void onViewActivated() {
376         if (!mCallback.isFragmentActive(this)) {
377             // we're not in the foreground; reset our view
378             resetViews();
379         } else {
380             mCallback.onFragmentVisible(this);
381         }
382     }
383 
384     /**
385      * Reset the views to their default states
386      */
resetViews()387     public void resetViews() {
388         if (mPhotoView != null) {
389             mPhotoView.resetTransformations();
390         }
391     }
392 
393     @Override
onInterceptMoveLeft(float origX, float origY)394     public boolean onInterceptMoveLeft(float origX, float origY) {
395         if (!mCallback.isFragmentActive(this)) {
396             // we're not in the foreground; don't intercept any touches
397             return false;
398         }
399 
400         return (mPhotoView != null && mPhotoView.interceptMoveLeft(origX, origY));
401     }
402 
403     @Override
onInterceptMoveRight(float origX, float origY)404     public boolean onInterceptMoveRight(float origX, float origY) {
405         if (!mCallback.isFragmentActive(this)) {
406             // we're not in the foreground; don't intercept any touches
407             return false;
408         }
409 
410         return (mPhotoView != null && mPhotoView.interceptMoveRight(origX, origY));
411     }
412 
413     /**
414      * Returns {@code true} if a photo has been bound. Otherwise, returns {@code false}.
415      */
isPhotoBound()416     public boolean isPhotoBound() {
417         return (mPhotoView != null && mPhotoView.isPhotoBound());
418     }
419 
420     /**
421      * Sets view visibility depending upon whether or not we're in "full screen" mode.
422      */
setViewVisibility()423     private void setViewVisibility() {
424         final boolean fullScreen = mCallback.isFragmentFullScreen(this);
425         final boolean hide = fullScreen;
426 
427         setFullScreen(hide);
428     }
429 
430     /**
431      * Sets full-screen mode for the views.
432      */
setFullScreen(boolean fullScreen)433     public void setFullScreen(boolean fullScreen) {
434         mFullScreen = fullScreen;
435     }
436 
437     @Override
onCursorChanged(Cursor cursor)438     public void onCursorChanged(Cursor cursor) {
439         if (cursor.moveToPosition(mPosition) && !isPhotoBound()) {
440             final LoaderManager manager = getLoaderManager();
441             final Loader<Bitmap> fakeLoader = manager.getLoader(LOADER_ID_PHOTO);
442             if (fakeLoader == null) {
443                 return;
444             }
445 
446             final PhotoBitmapLoader loader =
447                     (PhotoBitmapLoader) fakeLoader;
448             mResolvedPhotoUri = mAdapter.getPhotoUri(cursor);
449             loader.setPhotoUri(mResolvedPhotoUri);
450             loader.forceLoad();
451         }
452     }
453 
getPhotoProgressBar()454     public ProgressBarWrapper getPhotoProgressBar() {
455         return mPhotoProgressBar;
456     }
457 
getEmptyText()458     public TextView getEmptyText() {
459         return mEmptyText;
460     }
461 
getRetryButton()462     public ImageView getRetryButton() {
463         return mRetryButton;
464     }
465 
isProgressBarNeeded()466     public boolean isProgressBarNeeded() {
467         return mProgressBarNeeded;
468     }
469 }
470