• 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;
19 
20 import android.app.ActionBar;
21 import android.app.ActionBar.OnMenuVisibilityListener;
22 import android.app.Activity;
23 import android.app.ActivityManager;
24 import android.content.Context;
25 import android.content.Intent;
26 import android.content.res.Resources;
27 import android.database.Cursor;
28 import android.net.Uri;
29 import android.os.Build;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.support.v4.app.Fragment;
33 import android.support.v4.app.FragmentActivity;
34 import android.support.v4.app.LoaderManager;
35 import android.support.v4.content.Loader;
36 import android.support.v4.view.ViewPager.OnPageChangeListener;
37 import android.text.TextUtils;
38 import android.view.MenuItem;
39 import android.view.View;
40 
41 import com.android.ex.photo.PhotoViewPager.InterceptType;
42 import com.android.ex.photo.PhotoViewPager.OnInterceptTouchListener;
43 import com.android.ex.photo.adapters.PhotoPagerAdapter;
44 import com.android.ex.photo.fragments.PhotoViewFragment;
45 import com.android.ex.photo.loaders.PhotoPagerLoader;
46 import com.android.ex.photo.provider.PhotoContract;
47 
48 import java.util.HashMap;
49 import java.util.HashSet;
50 import java.util.Map;
51 import java.util.Set;
52 
53 /**
54  * Activity to view the contents of an album.
55  */
56 public class PhotoViewActivity extends FragmentActivity implements
57         LoaderManager.LoaderCallbacks<Cursor>, OnPageChangeListener, OnInterceptTouchListener,
58         OnMenuVisibilityListener, PhotoViewCallbacks {
59 
60     private final static String STATE_ITEM_KEY =
61             "com.google.android.apps.plus.PhotoViewFragment.ITEM";
62     private final static String STATE_FULLSCREEN_KEY =
63             "com.google.android.apps.plus.PhotoViewFragment.FULLSCREEN";
64     private final static String STATE_ACTIONBARTITLE_KEY =
65             "com.google.android.apps.plus.PhotoViewFragment.ACTIONBARTITLE";
66     private final static String STATE_ACTIONBARSUBTITLE_KEY =
67             "com.google.android.apps.plus.PhotoViewFragment.ACTIONBARSUBTITLE";
68 
69     private static final int LOADER_PHOTO_LIST = 1;
70 
71     /** Count used when the real photo count is unknown [but, may be determined] */
72     public static final int ALBUM_COUNT_UNKNOWN = -1;
73 
74     /** Argument key for the dialog message */
75     public static final String KEY_MESSAGE = "dialog_message";
76 
77     public static int sMemoryClass;
78 
79     /** The URI of the photos we're viewing; may be {@code null} */
80     private String mPhotosUri;
81     /** The URI of the initial photo to display */
82     private String mInitialPhotoUri;
83     /** The index of the currently viewed photo */
84     private int mPhotoIndex;
85     /** The query projection to use; may be {@code null} */
86     private String[] mProjection;
87     /** The total number of photos; only valid if {@link #mIsEmpty} is {@code false}. */
88     private int mAlbumCount = ALBUM_COUNT_UNKNOWN;
89     /** {@code true} if the view is empty. Otherwise, {@code false}. */
90     private boolean mIsEmpty;
91     /** the main root view */
92     protected View mRootView;
93     /** The main pager; provides left/right swipe between photos */
94     protected PhotoViewPager mViewPager;
95     /** Adapter to create pager views */
96     protected PhotoPagerAdapter mAdapter;
97     /** Whether or not we're in "full screen" mode */
98     private boolean mFullScreen;
99     /** The listeners wanting full screen state for each screen position */
100     private Map<Integer, OnScreenListener>
101             mScreenListeners = new HashMap<Integer, OnScreenListener>();
102     /** The set of listeners wanting full screen state */
103     private Set<CursorChangedListener> mCursorListeners = new HashSet<CursorChangedListener>();
104     /** When {@code true}, restart the loader when the activity becomes active */
105     private boolean mRestartLoader;
106     /** Whether or not this activity is paused */
107     private boolean mIsPaused = true;
108     /** The maximum scale factor applied to images when they are initially displayed */
109     private float mMaxInitialScale;
110     /** The title in the actionbar */
111     private String mActionBarTitle;
112     /** The subtitle in the actionbar */
113     private String mActionBarSubtitle;
114 
115     private final Handler mHandler = new Handler();
116     // TODO Find a better way to do this. We basically want the activity to display the
117     // "loading..." progress until the fragment takes over and shows it's own "loading..."
118     // progress [located in photo_header_view.xml]. We could potentially have all status displayed
119     // by the activity, but, that gets tricky when it comes to screen rotation. For now, we
120     // track the loading by this variable which is fragile and may cause phantom "loading..."
121     // text.
122     private long mEnterFullScreenDelayTime;
123 
createPhotoPagerAdapter(Context context, android.support.v4.app.FragmentManager fm, Cursor c, float maxScale)124     protected PhotoPagerAdapter createPhotoPagerAdapter(Context context,
125             android.support.v4.app.FragmentManager fm, Cursor c, float maxScale) {
126         return new PhotoPagerAdapter(context, fm, c, maxScale);
127     }
128 
129     @Override
onCreate(Bundle savedInstanceState)130     protected void onCreate(Bundle savedInstanceState) {
131         super.onCreate(savedInstanceState);
132 
133         final ActivityManager mgr = (ActivityManager) getApplicationContext().
134                 getSystemService(Activity.ACTIVITY_SERVICE);
135         sMemoryClass = mgr.getMemoryClass();
136 
137         Intent mIntent = getIntent();
138 
139         int currentItem = -1;
140         if (savedInstanceState != null) {
141             currentItem = savedInstanceState.getInt(STATE_ITEM_KEY, -1);
142             mFullScreen = savedInstanceState.getBoolean(STATE_FULLSCREEN_KEY, false);
143             mActionBarTitle = savedInstanceState.getString(STATE_ACTIONBARTITLE_KEY);
144             mActionBarSubtitle = savedInstanceState.getString(STATE_ACTIONBARSUBTITLE_KEY);
145         }
146 
147         // uri of the photos to view; optional
148         if (mIntent.hasExtra(Intents.EXTRA_PHOTOS_URI)) {
149             mPhotosUri = mIntent.getStringExtra(Intents.EXTRA_PHOTOS_URI);
150         }
151 
152         // projection for the query; optional
153         // If not set, the default projection is used.
154         // This projection must include the columns from the default projection.
155         if (mIntent.hasExtra(Intents.EXTRA_PROJECTION)) {
156             mProjection = mIntent.getStringArrayExtra(Intents.EXTRA_PROJECTION);
157         } else {
158             mProjection = null;
159         }
160 
161         // Set the current item from the intent if wasn't in the saved instance
162         if (currentItem < 0) {
163             if (mIntent.hasExtra(Intents.EXTRA_PHOTO_INDEX)) {
164                 currentItem = mIntent.getIntExtra(Intents.EXTRA_PHOTO_INDEX, -1);
165             }
166             if (mIntent.hasExtra(Intents.EXTRA_INITIAL_PHOTO_URI)) {
167                 mInitialPhotoUri = mIntent.getStringExtra(Intents.EXTRA_INITIAL_PHOTO_URI);
168             }
169         }
170         // Set the max initial scale, defaulting to 1x
171         mMaxInitialScale = mIntent.getFloatExtra(Intents.EXTRA_MAX_INITIAL_SCALE, 1.0f);
172 
173         // If we still have a negative current item, set it to zero
174         mPhotoIndex = Math.max(currentItem, 0);
175         mIsEmpty = true;
176 
177         setContentView(R.layout.photo_activity_view);
178 
179         // Create the adapter and add the view pager
180         mAdapter =
181                 createPhotoPagerAdapter(this, getSupportFragmentManager(), null, mMaxInitialScale);
182         final Resources resources = getResources();
183         mRootView = findViewById(R.id.photo_activity_root_view);
184         mViewPager = (PhotoViewPager) findViewById(R.id.photo_view_pager);
185         mViewPager.setAdapter(mAdapter);
186         mViewPager.setOnPageChangeListener(this);
187         mViewPager.setOnInterceptTouchListener(this);
188         mViewPager.setPageMargin(resources.getDimensionPixelSize(R.dimen.photo_page_margin));
189 
190         // Kick off the loader
191         getSupportLoaderManager().initLoader(LOADER_PHOTO_LIST, null, this);
192 
193         mEnterFullScreenDelayTime =
194                 resources.getInteger(R.integer.reenter_fullscreen_delay_time_in_millis);
195 
196         final ActionBar actionBar = getActionBar();
197         if (actionBar != null) {
198             actionBar.setDisplayHomeAsUpEnabled(true);
199             actionBar.addOnMenuVisibilityListener(this);
200             final int showTitle = ActionBar.DISPLAY_SHOW_TITLE;
201             actionBar.setDisplayOptions(showTitle, showTitle);
202             setActionBarTitles(actionBar);
203         }
204     }
205 
206     @Override
onResume()207     protected void onResume() {
208         super.onResume();
209         setFullScreen(mFullScreen, false);
210 
211         mIsPaused = false;
212         if (mRestartLoader) {
213             mRestartLoader = false;
214             getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
215         }
216     }
217 
218     @Override
onPause()219     protected void onPause() {
220         mIsPaused = true;
221 
222         super.onPause();
223     }
224 
225     @Override
onBackPressed()226     public void onBackPressed() {
227         // If in full screen mode, toggle mode & eat the 'back'
228         if (mFullScreen) {
229             toggleFullScreen();
230         } else {
231             super.onBackPressed();
232         }
233     }
234 
235     @Override
onSaveInstanceState(Bundle outState)236     public void onSaveInstanceState(Bundle outState) {
237         super.onSaveInstanceState(outState);
238 
239         outState.putInt(STATE_ITEM_KEY, mViewPager.getCurrentItem());
240         outState.putBoolean(STATE_FULLSCREEN_KEY, mFullScreen);
241         outState.putString(STATE_ACTIONBARTITLE_KEY, mActionBarTitle);
242         outState.putString(STATE_ACTIONBARSUBTITLE_KEY, mActionBarSubtitle);
243     }
244 
245     @Override
onOptionsItemSelected(MenuItem item)246     public boolean onOptionsItemSelected(MenuItem item) {
247        switch (item.getItemId()) {
248           case android.R.id.home:
249              finish();
250           default:
251              return super.onOptionsItemSelected(item);
252        }
253     }
254 
255     @Override
addScreenListener(int position, OnScreenListener listener)256     public void addScreenListener(int position, OnScreenListener listener) {
257         mScreenListeners.put(position, listener);
258     }
259 
260     @Override
removeScreenListener(int position)261     public void removeScreenListener(int position) {
262         mScreenListeners.remove(position);
263     }
264 
265     @Override
addCursorListener(CursorChangedListener listener)266     public synchronized void addCursorListener(CursorChangedListener listener) {
267         mCursorListeners.add(listener);
268     }
269 
270     @Override
removeCursorListener(CursorChangedListener listener)271     public synchronized void removeCursorListener(CursorChangedListener listener) {
272         mCursorListeners.remove(listener);
273     }
274 
275     @Override
isFragmentFullScreen(Fragment fragment)276     public boolean isFragmentFullScreen(Fragment fragment) {
277         if (mViewPager == null || mAdapter == null || mAdapter.getCount() == 0) {
278             return mFullScreen;
279         }
280         return mFullScreen || (mViewPager.getCurrentItem() != mAdapter.getItemPosition(fragment));
281     }
282 
283     @Override
toggleFullScreen()284     public void toggleFullScreen() {
285         setFullScreen(!mFullScreen, true);
286     }
287 
onPhotoRemoved(long photoId)288     public void onPhotoRemoved(long photoId) {
289         final Cursor data = mAdapter.getCursor();
290         if (data == null) {
291             // Huh?! How would this happen?
292             return;
293         }
294 
295         final int dataCount = data.getCount();
296         if (dataCount <= 1) {
297             finish();
298             return;
299         }
300 
301         getSupportLoaderManager().restartLoader(LOADER_PHOTO_LIST, null, this);
302     }
303 
304     @Override
onCreateLoader(int id, Bundle args)305     public Loader<Cursor> onCreateLoader(int id, Bundle args) {
306         if (id == LOADER_PHOTO_LIST) {
307             return new PhotoPagerLoader(this, Uri.parse(mPhotosUri), mProjection);
308         }
309         return null;
310     }
311 
312     @Override
onLoadFinished(Loader<Cursor> loader, Cursor data)313     public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
314         final int id = loader.getId();
315         if (id == LOADER_PHOTO_LIST) {
316             if (data == null || data.getCount() == 0) {
317                 mIsEmpty = true;
318             } else {
319                 mAlbumCount = data.getCount();
320 
321                 if (mInitialPhotoUri != null) {
322                     int index = 0;
323                     int uriIndex = data.getColumnIndex(PhotoContract.PhotoViewColumns.URI);
324                     while (data.moveToNext()) {
325                         String uri = data.getString(uriIndex);
326                         if (TextUtils.equals(uri, mInitialPhotoUri)) {
327                             mInitialPhotoUri = null;
328                             mPhotoIndex = index;
329                             break;
330                         }
331                         index++;
332                     }
333                 }
334 
335                 // We're paused; don't do anything now, we'll get re-invoked
336                 // when the activity becomes active again
337                 // TODO(pwestbro): This shouldn't be necessary, as the loader manager should
338                 // restart the loader
339                 if (mIsPaused) {
340                     mRestartLoader = true;
341                     return;
342                 }
343                 boolean wasEmpty = mIsEmpty;
344                 mIsEmpty = false;
345 
346                 mAdapter.swapCursor(data);
347                 if (mViewPager.getAdapter() == null) {
348                     mViewPager.setAdapter(mAdapter);
349                 }
350                 notifyCursorListeners(data);
351 
352                 // set the selected photo
353                 int itemIndex = mPhotoIndex;
354 
355                 // Use an index of 0 if the index wasn't specified or couldn't be found
356                 if (itemIndex < 0) {
357                     itemIndex = 0;
358                 }
359 
360                 mViewPager.setCurrentItem(itemIndex, false);
361                 if (wasEmpty) {
362                     setViewActivated(itemIndex);
363                 }
364             }
365             // Update the any action items
366             updateActionItems();
367         }
368     }
369 
370     @Override
onLoaderReset(android.support.v4.content.Loader<Cursor> loader)371     public void onLoaderReset(android.support.v4.content.Loader<Cursor> loader) {
372         // If the loader is reset, remove the reference in the adapter to this cursor
373         // TODO(pwestbro): reenable this when b/7075236 is fixed
374         // mAdapter.swapCursor(null);
375     }
376 
updateActionItems()377     protected void updateActionItems() {
378         // Do nothing, but allow extending classes to do work
379     }
380 
notifyCursorListeners(Cursor data)381     private synchronized void notifyCursorListeners(Cursor data) {
382         // tell all of the objects listening for cursor changes
383         // that the cursor has changed
384         for (CursorChangedListener listener : mCursorListeners) {
385             listener.onCursorChanged(data);
386         }
387     }
388 
389     @Override
onPageScrolled(int position, float positionOffset, int positionOffsetPixels)390     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
391     }
392 
393     @Override
onPageSelected(int position)394     public void onPageSelected(int position) {
395         mPhotoIndex = position;
396         setViewActivated(position);
397     }
398 
399     @Override
onPageScrollStateChanged(int state)400     public void onPageScrollStateChanged(int state) {
401     }
402 
403     @Override
isFragmentActive(Fragment fragment)404     public boolean isFragmentActive(Fragment fragment) {
405         if (mViewPager == null || mAdapter == null) {
406             return false;
407         }
408         return mViewPager.getCurrentItem() == mAdapter.getItemPosition(fragment);
409     }
410 
411     @Override
onFragmentVisible(Fragment fragment)412     public void onFragmentVisible(Fragment fragment) {
413         updateActionBar();
414     }
415 
416     @Override
onTouchIntercept(float origX, float origY)417     public InterceptType onTouchIntercept(float origX, float origY) {
418         boolean interceptLeft = false;
419         boolean interceptRight = false;
420 
421         for (OnScreenListener listener : mScreenListeners.values()) {
422             if (!interceptLeft) {
423                 interceptLeft = listener.onInterceptMoveLeft(origX, origY);
424             }
425             if (!interceptRight) {
426                 interceptRight = listener.onInterceptMoveRight(origX, origY);
427             }
428         }
429 
430         if (interceptLeft) {
431             if (interceptRight) {
432                 return InterceptType.BOTH;
433             }
434             return InterceptType.LEFT;
435         } else if (interceptRight) {
436             return InterceptType.RIGHT;
437         }
438         return InterceptType.NONE;
439     }
440 
441     /**
442      * Updates the title bar according to the value of {@link #mFullScreen}.
443      */
setFullScreen(boolean fullScreen, boolean setDelayedRunnable)444     protected void setFullScreen(boolean fullScreen, boolean setDelayedRunnable) {
445         final boolean fullScreenChanged = (fullScreen != mFullScreen);
446         mFullScreen = fullScreen;
447 
448         if (mFullScreen) {
449             setLightsOutMode(true);
450             cancelEnterFullScreenRunnable();
451         } else {
452             setLightsOutMode(false);
453             if (setDelayedRunnable) {
454                 postEnterFullScreenRunnableWithDelay();
455             }
456         }
457 
458         if (fullScreenChanged) {
459             for (OnScreenListener listener : mScreenListeners.values()) {
460                 listener.onFullScreenChanged(mFullScreen);
461             }
462         }
463     }
464 
postEnterFullScreenRunnableWithDelay()465     private void postEnterFullScreenRunnableWithDelay() {
466         mHandler.postDelayed(mEnterFullScreenRunnable, mEnterFullScreenDelayTime);
467     }
468 
cancelEnterFullScreenRunnable()469     private void cancelEnterFullScreenRunnable() {
470         mHandler.removeCallbacks(mEnterFullScreenRunnable);
471     }
472 
setLightsOutMode(boolean enabled)473     protected void setLightsOutMode(boolean enabled) {
474         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
475             int flags = enabled
476                     ? View.SYSTEM_UI_FLAG_LOW_PROFILE
477                     | View.SYSTEM_UI_FLAG_FULLSCREEN
478                     | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
479                     | View.SYSTEM_UI_FLAG_LAYOUT_STABLE
480                     : View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
481                     | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
482 
483             // using mViewPager since we have it and we need a view
484             mViewPager.setSystemUiVisibility(flags);
485         } else {
486             final ActionBar actionBar = getActionBar();
487             if (enabled) {
488                 actionBar.hide();
489             } else {
490                 actionBar.show();
491             }
492             int flags = enabled
493                     ? View.SYSTEM_UI_FLAG_LOW_PROFILE
494                     : View.SYSTEM_UI_FLAG_VISIBLE;
495             mViewPager.setSystemUiVisibility(flags);
496         }
497     }
498 
499     private Runnable mEnterFullScreenRunnable = new Runnable() {
500         @Override
501         public void run() {
502             setFullScreen(true, true);
503         }
504     };
505 
506     @Override
setViewActivated(int position)507     public void setViewActivated(int position) {
508         OnScreenListener listener = mScreenListeners.get(position);
509         if (listener != null) {
510             listener.onViewActivated();
511         }
512     }
513 
514     /**
515      * Adjusts the activity title and subtitle to reflect the photo name and count.
516      */
updateActionBar()517     protected void updateActionBar() {
518         final int position = mViewPager.getCurrentItem() + 1;
519         final boolean hasAlbumCount = mAlbumCount >= 0;
520 
521         final Cursor cursor = getCursorAtProperPosition();
522         if (cursor != null) {
523             final int photoNameIndex = cursor.getColumnIndex(PhotoContract.PhotoViewColumns.NAME);
524             mActionBarTitle = cursor.getString(photoNameIndex);
525         } else {
526             mActionBarTitle = null;
527         }
528 
529         if (mIsEmpty || !hasAlbumCount || position <= 0) {
530             mActionBarSubtitle = null;
531         } else {
532             mActionBarSubtitle =
533                     getResources().getString(R.string.photo_view_count, position, mAlbumCount);
534         }
535         setActionBarTitles(getActionBar());
536     }
537 
538     /**
539      * Sets the Action Bar title to {@link #mActionBarTitle} and the subtitle to
540      * {@link #mActionBarSubtitle}
541      */
setActionBarTitles(ActionBar actionBar)542     private final void setActionBarTitles(ActionBar actionBar) {
543         if (actionBar == null) {
544             return;
545         }
546         actionBar.setTitle(getInputOrEmpty(mActionBarTitle));
547         actionBar.setSubtitle(getInputOrEmpty(mActionBarSubtitle));
548     }
549 
550     /**
551      * If the input string is non-null, it is returned, otherwise an empty string is returned;
552      * @param in
553      * @return
554      */
getInputOrEmpty(String in)555     private static final String getInputOrEmpty(String in) {
556         if (in == null) {
557             return "";
558         }
559         return in;
560     }
561 
562     /**
563      * Utility method that will return the cursor that contains the data
564      * at the current position so that it refers to the current image on screen.
565      * @return the cursor at the current position or
566      * null if no cursor exists or if the {@link PhotoViewPager} is null.
567      */
getCursorAtProperPosition()568     public Cursor getCursorAtProperPosition() {
569         if (mViewPager == null) {
570             return null;
571         }
572 
573         final int position = mViewPager.getCurrentItem();
574         final Cursor cursor = mAdapter.getCursor();
575 
576         if (cursor == null) {
577             return null;
578         }
579 
580         cursor.moveToPosition(position);
581 
582         return cursor;
583     }
584 
getCursor()585     public Cursor getCursor() {
586         return (mAdapter == null) ? null : mAdapter.getCursor();
587     }
588 
589     @Override
onMenuVisibilityChanged(boolean isVisible)590     public void onMenuVisibilityChanged(boolean isVisible) {
591         if (isVisible) {
592             cancelEnterFullScreenRunnable();
593         } else {
594             postEnterFullScreenRunnableWithDelay();
595         }
596     }
597 
598     @Override
onNewPhotoLoaded(int position)599     public void onNewPhotoLoaded(int position) {
600         // do nothing
601     }
602 
isFullScreen()603     protected boolean isFullScreen() {
604         return mFullScreen;
605     }
606 
setPhotoIndex(int index)607     protected void setPhotoIndex(int index) {
608         mPhotoIndex = index;
609     }
610 
611     @Override
onCursorChanged(PhotoViewFragment fragment, Cursor cursor)612     public void onCursorChanged(PhotoViewFragment fragment, Cursor cursor) {
613         // do nothing
614     }
615 
616     @Override
getAdapter()617     public PhotoPagerAdapter getAdapter() {
618         return mAdapter;
619     }
620 }
621