Home | History | Annotate | Download | only in threadsample
      1 /*
      2  * Copyright (C) 2012 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.example.android.threadsample;
     17 
     18 import android.content.BroadcastReceiver;
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.content.IntentFilter;
     22 import android.os.Build;
     23 import android.os.Bundle;
     24 import android.support.v4.app.FragmentActivity;
     25 import android.support.v4.app.FragmentManager;
     26 import android.support.v4.app.FragmentManager.OnBackStackChangedListener;
     27 import android.support.v4.app.FragmentTransaction;
     28 import android.support.v4.content.LocalBroadcastManager;
     29 import android.util.Log;
     30 import android.view.View;
     31 import android.view.WindowManager;
     32 
     33 /**
     34  * This activity displays Picasa's current featured images. It uses a service running
     35  * a background thread to download Picasa's "featured image" RSS feed.
     36  * <p>
     37  * An IntentHandler is used to communicate between the active Fragment and this
     38  * activity. This pattern simulates some of the communication used between
     39  * activities, and allows this activity to make choices of how to manage the
     40  * fragments.
     41  */
     42 public class DisplayActivity extends FragmentActivity implements OnBackStackChangedListener {
     43 
     44     // A handle to the main screen view
     45     View mMainView;
     46 
     47     // An instance of the status broadcast receiver
     48     DownloadStateReceiver mDownloadStateReceiver;
     49 
     50     // Tracks whether Fragments are displaying side-by-side
     51     boolean mSideBySide;
     52 
     53     // Tracks whether navigation should be hidden
     54     boolean mHideNavigation;
     55 
     56     // Tracks whether the app is in full-screen mode
     57     boolean mFullScreen;
     58 
     59     // Tracks the number of Fragments on the back stack
     60     int mPreviousStackCount;
     61 
     62     // Instantiates a new broadcast receiver for handling Fragment state
     63     private FragmentDisplayer mFragmentDisplayer = new FragmentDisplayer();
     64 
     65     // Sets a tag to use in logging
     66     private static final String CLASS_TAG = "DisplayActivity";
     67 
     68     /**
     69      * Sets full screen mode on the device, by setting parameters in the current
     70      * window and View
     71      * @param fullscreen
     72      */
     73     public void setFullScreen(boolean fullscreen) {
     74         // If full screen is set, sets the fullscreen flag in the Window manager
     75         getWindow().setFlags(
     76                 fullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0,
     77                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
     78 
     79         // Sets the global fullscreen flag to the current setting
     80         mFullScreen = fullscreen;
     81 
     82         // If the platform version is Android 3.0 (Honeycomb) or above
     83         if (Build.VERSION.SDK_INT >= 11) {
     84 
     85             // Sets the View to be "low profile". Status and navigation bar icons will be dimmed
     86             int flag = fullscreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0;
     87 
     88             // If the platform version is Android 4.0 (ICS) or above
     89             if (Build.VERSION.SDK_INT >= 14 && fullscreen) {
     90 
     91                 // Hides all of the navigation icons
     92                 flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
     93             }
     94 
     95             // Applies the settings to the screen View
     96             mMainView.setSystemUiVisibility(flag);
     97 
     98             // If the user requests a full-screen view, hides the Action Bar.
     99             if ( fullscreen ) {
    100                 this.getActionBar().hide();
    101             } else {
    102                 this.getActionBar().show();
    103             }
    104         }
    105     }
    106 
    107     /*
    108      * A callback invoked when the task's back stack changes. This allows the app to
    109      * move to the previous state of the Fragment being displayed.
    110      *
    111      */
    112     @Override
    113     public void onBackStackChanged() {
    114 
    115         // Gets the previous global stack count
    116         int previousStackCount = mPreviousStackCount;
    117 
    118         // Gets a FragmentManager instance
    119         FragmentManager localFragmentManager = getSupportFragmentManager();
    120 
    121         // Sets the current back stack count
    122         int currentStackCount = localFragmentManager.getBackStackEntryCount();
    123 
    124         // Re-sets the global stack count to be the current count
    125         mPreviousStackCount = currentStackCount;
    126 
    127         /*
    128          * If the current stack count is less than the previous, something was popped off the stack
    129          * probably because the user clicked Back.
    130          */
    131         boolean popping = currentStackCount < previousStackCount;
    132         Log.d(CLASS_TAG, "backstackchanged: popping = " + popping);
    133 
    134         // When going backwards in the back stack, turns off full screen mode.
    135         if (popping) {
    136             setFullScreen(false);
    137         }
    138     }
    139 
    140     /*
    141      * This callback is invoked by the system when the Activity is being killed
    142      * It saves the full screen status, so it can be restored when the Activity is restored
    143      *
    144      */
    145     @Override
    146     protected void onSaveInstanceState(Bundle outState) {
    147         outState.putBoolean(Constants.EXTENDED_FULLSCREEN, mFullScreen);
    148         super.onSaveInstanceState(outState);
    149     }
    150 
    151     /*
    152      * This callback is invoked when the Activity is first created. It sets up the Activity's
    153      * window and initializes the Fragments associated with the Activity
    154      */
    155     @Override
    156     public void onCreate(Bundle stateBundle) {
    157         // Sets fullscreen-related flags for the display
    158         getWindow().setFlags(
    159                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    160                         | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR,
    161                 WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
    162                         | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR);
    163 
    164         // Calls the super method (required)
    165         super.onCreate(stateBundle);
    166 
    167         // Inflates the main View, which will be the host View for the fragments
    168         mMainView = getLayoutInflater().inflate(R.layout.fragmenthost, null);
    169 
    170         // Sets the content view for the Activity
    171         setContentView(mMainView);
    172 
    173         /*
    174          * Creates an intent filter for DownloadStateReceiver that intercepts broadcast Intents
    175          */
    176 
    177         // The filter's action is BROADCAST_ACTION
    178         IntentFilter statusIntentFilter = new IntentFilter(
    179                 Constants.BROADCAST_ACTION);
    180 
    181         // Sets the filter's category to DEFAULT
    182         statusIntentFilter.addCategory(Intent.CATEGORY_DEFAULT);
    183 
    184         // Instantiates a new DownloadStateReceiver
    185         mDownloadStateReceiver = new DownloadStateReceiver();
    186 
    187         // Registers the DownloadStateReceiver and its intent filters
    188         LocalBroadcastManager.getInstance(this).registerReceiver(
    189                 mDownloadStateReceiver,
    190                 statusIntentFilter);
    191 
    192         /*
    193          * Creates intent filters for the FragmentDisplayer
    194          */
    195 
    196         // One filter is for the action ACTION_VIEW_IMAGE
    197         IntentFilter displayerIntentFilter = new IntentFilter(
    198                 Constants.ACTION_VIEW_IMAGE);
    199 
    200         // Adds a data filter for the HTTP scheme
    201         displayerIntentFilter.addDataScheme("http");
    202 
    203         // Registers the receiver
    204         LocalBroadcastManager.getInstance(this).registerReceiver(
    205                 mFragmentDisplayer,
    206                 displayerIntentFilter);
    207 
    208         // Creates a second filter for ACTION_ZOOM_IMAGE
    209         displayerIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE);
    210 
    211         // Registers the receiver
    212         LocalBroadcastManager.getInstance(this).registerReceiver(
    213                 mFragmentDisplayer,
    214                 displayerIntentFilter);
    215 
    216         // Gets an instance of the support library FragmentManager
    217         FragmentManager localFragmentManager = getSupportFragmentManager();
    218 
    219         /*
    220          * Detects if side-by-side display should be enabled. It's only available on xlarge and
    221          * sw600dp devices (for example, tablets). The setting in res/values/ is "false", but this
    222          * is overridden in values-xlarge and values-sw600dp.
    223          */
    224         mSideBySide = getResources().getBoolean(R.bool.sideBySide);
    225 
    226         /*
    227          * Detects if hiding navigation controls should be enabled. On xlarge andsw600dp, it should
    228          * be false, to avoid having the user enter an additional tap.
    229          */
    230         mHideNavigation = getResources().getBoolean(R.bool.hideNavigation);
    231 
    232         /*
    233          * Adds the back stack change listener defined in this Activity as the listener for the
    234          * FragmentManager. See the method onBackStackChanged().
    235          */
    236         localFragmentManager.addOnBackStackChangedListener(this);
    237 
    238         // If the incoming state of the Activity is null, sets the initial view to be thumbnails
    239         if (null == stateBundle) {
    240 
    241             // Starts a Fragment transaction to track the stack
    242             FragmentTransaction localFragmentTransaction = localFragmentManager
    243                     .beginTransaction();
    244 
    245             // Adds the PhotoThumbnailFragment to the host View
    246             localFragmentTransaction.add(R.id.fragmentHost,
    247                     new PhotoThumbnailFragment(), Constants.THUMBNAIL_FRAGMENT_TAG);
    248 
    249             // Commits this transaction to display the Fragment
    250             localFragmentTransaction.commit();
    251 
    252         // The incoming state of the Activity isn't null.
    253         } else {
    254 
    255             // Gets the previous state of the fullscreen indicator
    256             mFullScreen = stateBundle.getBoolean(Constants.EXTENDED_FULLSCREEN);
    257 
    258             // Sets the fullscreen flag to its previous state
    259             setFullScreen(mFullScreen);
    260 
    261             // Gets the previous backstack entry count.
    262             mPreviousStackCount = localFragmentManager.getBackStackEntryCount();
    263         }
    264     }
    265 
    266     /*
    267      * This callback is invoked when the system is about to destroy the Activity.
    268      */
    269     @Override
    270     public void onDestroy() {
    271 
    272         // If the DownloadStateReceiver still exists, unregister it and set it to null
    273         if (mDownloadStateReceiver != null) {
    274             LocalBroadcastManager.getInstance(this).unregisterReceiver(mDownloadStateReceiver);
    275             mDownloadStateReceiver = null;
    276         }
    277 
    278         // Unregisters the FragmentDisplayer instance
    279         LocalBroadcastManager.getInstance(this).unregisterReceiver(this.mFragmentDisplayer);
    280 
    281         // Sets the main View to null
    282         mMainView = null;
    283 
    284         // Must always call the super method at the end.
    285         super.onDestroy();
    286     }
    287 
    288     /*
    289      * This callback is invoked when the system is stopping the Activity. It stops
    290      * background threads.
    291      */
    292     @Override
    293     protected void onStop() {
    294 
    295         // Cancel all the running threads managed by the PhotoManager
    296         PhotoManager.cancelAll();
    297         super.onStop();
    298     }
    299 
    300     /**
    301      * This class uses the BroadcastReceiver framework to detect and handle status messages from
    302      * the service that downloads URLs.
    303      */
    304     private class DownloadStateReceiver extends BroadcastReceiver {
    305 
    306         private DownloadStateReceiver() {
    307 
    308             // prevents instantiation by other packages.
    309         }
    310         /**
    311          *
    312          * This method is called by the system when a broadcast Intent is matched by this class'
    313          * intent filters
    314          *
    315          * @param context An Android context
    316          * @param intent The incoming broadcast Intent
    317          */
    318         @Override
    319         public void onReceive(Context context, Intent intent) {
    320 
    321             /*
    322              * Gets the status from the Intent's extended data, and chooses the appropriate action
    323              */
    324             switch (intent.getIntExtra(Constants.EXTENDED_DATA_STATUS,
    325                     Constants.STATE_ACTION_COMPLETE)) {
    326 
    327                 // Logs "started" state
    328                 case Constants.STATE_ACTION_STARTED:
    329                     if (Constants.LOGD) {
    330 
    331                         Log.d(CLASS_TAG, "State: STARTED");
    332                     }
    333                     break;
    334                 // Logs "connecting to network" state
    335                 case Constants.STATE_ACTION_CONNECTING:
    336                     if (Constants.LOGD) {
    337 
    338                         Log.d(CLASS_TAG, "State: CONNECTING");
    339                     }
    340                     break;
    341                  // Logs "parsing the RSS feed" state
    342                  case Constants.STATE_ACTION_PARSING:
    343                     if (Constants.LOGD) {
    344 
    345                         Log.d(CLASS_TAG, "State: PARSING");
    346                     }
    347                     break;
    348                 // Logs "Writing the parsed data to the content provider" state
    349                 case Constants.STATE_ACTION_WRITING:
    350                     if (Constants.LOGD) {
    351 
    352                         Log.d(CLASS_TAG, "State: WRITING");
    353                     }
    354                     break;
    355                 // Starts displaying data when the RSS download is complete
    356                 case Constants.STATE_ACTION_COMPLETE:
    357                     // Logs the status
    358                     if (Constants.LOGD) {
    359 
    360                         Log.d(CLASS_TAG, "State: COMPLETE");
    361                     }
    362 
    363                     // Finds the fragment that displays thumbnails
    364                     PhotoThumbnailFragment localThumbnailFragment =
    365                             (PhotoThumbnailFragment) getSupportFragmentManager().findFragmentByTag(
    366                                     Constants.THUMBNAIL_FRAGMENT_TAG);
    367 
    368                     // If the thumbnail Fragment is hidden, don't change its display status
    369                     if ((localThumbnailFragment == null)
    370                             || (!localThumbnailFragment.isVisible()))
    371                         return;
    372 
    373                     // Indicates that the thumbnail Fragment is visible
    374                     localThumbnailFragment.setLoaded(true);
    375                     break;
    376                 default:
    377                     break;
    378             }
    379         }
    380     }
    381 
    382     /**
    383      * This class uses the broadcast receiver framework to detect incoming broadcast Intents
    384      * and change the currently-visible fragment based on the Intent action.
    385      * It adds or replaces Fragments as necessary, depending on how much screen real-estate is
    386      * available.
    387      */
    388     private class FragmentDisplayer extends BroadcastReceiver {
    389 
    390         // Default null constructor
    391         public FragmentDisplayer() {
    392 
    393             // Calls the constructor for BroadcastReceiver
    394             super();
    395         }
    396         /**
    397          * Receives broadcast Intents for viewing or zooming pictures, and displays the
    398          * appropriate Fragment.
    399          *
    400          * @param context The current Context of the callback
    401          * @param intent The broadcast Intent that triggered the callback
    402          */
    403         @Override
    404         public void onReceive(Context context, Intent intent) {
    405 
    406             // Declares a local FragmentManager instance
    407             FragmentManager fragmentManager1;
    408 
    409             // Declares a local instance of the Fragment that displays photos
    410             PhotoFragment photoFragment;
    411 
    412             // Stores a string representation of the URL in the incoming Intent
    413             String urlString;
    414 
    415             // If the incoming Intent is a request is to view an image
    416             if (intent.getAction().equals(Constants.ACTION_VIEW_IMAGE)) {
    417 
    418                 // Gets an instance of the support library fragment manager
    419                 fragmentManager1 = getSupportFragmentManager();
    420 
    421                 // Gets a handle to the Fragment that displays photos
    422                 photoFragment =
    423                         (PhotoFragment) fragmentManager1.findFragmentByTag(
    424                             Constants.PHOTO_FRAGMENT_TAG
    425                 );
    426 
    427                 // Gets the URL of the picture to display
    428                 urlString = intent.getDataString();
    429 
    430                 // If the photo Fragment exists from a previous display
    431                 if (null != photoFragment) {
    432 
    433                     // If the incoming URL is not already being displayed
    434                     if (!urlString.equals(photoFragment.getURLString())) {
    435 
    436                         // Sets the Fragment to use the URL from the Intent for the photo
    437                         photoFragment.setPhoto(urlString);
    438 
    439                         // Loads the photo into the Fragment
    440                         photoFragment.loadPhoto();
    441                     }
    442 
    443                 // If the Fragment doesn't already exist
    444                 } else {
    445                     // Instantiates a new Fragment
    446                     photoFragment = new PhotoFragment();
    447 
    448                     // Sets the Fragment to use the URL from the Intent for the photo
    449                     photoFragment.setPhoto(urlString);
    450 
    451                     // Starts a new Fragment transaction
    452                     FragmentTransaction localFragmentTransaction2 =
    453                             fragmentManager1.beginTransaction();
    454 
    455                     // If the fragments are side-by-side, adds the photo Fragment to the display
    456                     if (mSideBySide) {
    457                         localFragmentTransaction2.add(
    458                                 R.id.fragmentHost,
    459                                 photoFragment,
    460                                 Constants.PHOTO_FRAGMENT_TAG
    461                         );
    462                     /*
    463                      * If the Fragments are not side-by-side, replaces the current Fragment with
    464                      * the photo Fragment
    465                      */
    466                     } else {
    467                         localFragmentTransaction2.replace(
    468                                 R.id.fragmentHost,
    469                                 photoFragment,
    470                                 Constants.PHOTO_FRAGMENT_TAG);
    471                     }
    472 
    473                     // Don't remember the transaction (sets the Fragment backstack to null)
    474                     localFragmentTransaction2.addToBackStack(null);
    475 
    476                     // Commits the transaction
    477                     localFragmentTransaction2.commit();
    478                 }
    479 
    480                 // If not in side-by-side mode, sets "full screen", so that no controls are visible
    481                 if (!mSideBySide) setFullScreen(true);
    482 
    483             /*
    484              * If the incoming Intent is a request to zoom in on an existing image
    485              * (Notice that zooming is only supported on large-screen devices)
    486              */
    487             } else if (intent.getAction().equals(Constants.ACTION_ZOOM_IMAGE)) {
    488 
    489                 // If the Fragments are being displayed side-by-side
    490                 if (mSideBySide) {
    491 
    492                     // Gets another instance of the FragmentManager
    493                     FragmentManager localFragmentManager2 = getSupportFragmentManager();
    494 
    495                     // Gets a thumbnail Fragment instance
    496                     PhotoThumbnailFragment localThumbnailFragment =
    497                             (PhotoThumbnailFragment) localFragmentManager2.findFragmentByTag(
    498                                 Constants.THUMBNAIL_FRAGMENT_TAG);
    499 
    500                     // If the instance exists from a previous display
    501                     if (null != localThumbnailFragment) {
    502 
    503                         // if the existing instance is visible
    504                         if (localThumbnailFragment.isVisible()) {
    505 
    506                             // Starts a fragment transaction
    507                             FragmentTransaction localFragmentTransaction2 =
    508                                     localFragmentManager2.beginTransaction();
    509 
    510                             /*
    511                              * Hides the current thumbnail, clears the backstack, and commits the
    512                              * transaction
    513                              */
    514                             localFragmentTransaction2.hide(localThumbnailFragment);
    515                             localFragmentTransaction2.addToBackStack(null);
    516                             localFragmentTransaction2.commit();
    517 
    518                         // If the existing instance is not visible, display it by going "Back"
    519                         } else {
    520 
    521                             // Pops the back stack to show the previous Fragment state
    522                             localFragmentManager2.popBackStack();
    523                         }
    524                     }
    525 
    526                     // Removes controls from the screen
    527                     setFullScreen(true);
    528                 }
    529             }
    530         }
    531     }
    532 
    533 }
    534