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