| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.example.android.threadsample; |
| |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.support.v4.app.FragmentActivity; |
| import android.support.v4.app.FragmentManager; |
| import android.support.v4.app.FragmentManager.OnBackStackChangedListener; |
| import android.support.v4.app.FragmentTransaction; |
| import android.support.v4.content.LocalBroadcastManager; |
| import android.util.Log; |
| import android.view.View; |
| import android.view.WindowManager; |
| |
| /** |
| * This activity displays Picasa's current featured images. It uses a service running |
| * a background thread to download Picasa's "featured image" RSS feed. |
| * <p> |
| * An IntentHandler is used to communicate between the active Fragment and this |
| * activity. This pattern simulates some of the communication used between |
| * activities, and allows this activity to make choices of how to manage the |
| * fragments. |
| */ |
| public class DisplayActivity extends FragmentActivity implements OnBackStackChangedListener { |
| |
| // A handle to the main screen view |
| View mMainView; |
| |
| // An instance of the status broadcast receiver |
| DownloadStateReceiver mDownloadStateReceiver; |
| |
| // Tracks whether Fragments are displaying side-by-side |
| boolean mSideBySide; |
| |
| // Tracks whether navigation should be hidden |
| boolean mHideNavigation; |
| |
| // Tracks whether the app is in full-screen mode |
| boolean mFullScreen; |
| |
| // Tracks the number of Fragments on the back stack |
| int mPreviousStackCount; |
| |
| // Instantiates a new broadcast receiver for handling Fragment state |
| private FragmentDisplayer mFragmentDisplayer = new FragmentDisplayer(); |
| |
| // Sets a tag to use in logging |
| private static final String CLASS_TAG = "DisplayActivity"; |
| |
| /** |
| * Sets full screen mode on the device, by setting parameters in the current |
| * window and View |
| * @param fullscreen |
| */ |
| public void setFullScreen(boolean fullscreen) { |
| // If full screen is set, sets the fullscreen flag in the Window manager |
| getWindow().setFlags( |
| fullscreen ? WindowManager.LayoutParams.FLAG_FULLSCREEN : 0, |
| WindowManager.LayoutParams.FLAG_FULLSCREEN); |
| |
| // Sets the global fullscreen flag to the current setting |
| mFullScreen = fullscreen; |
| |
| // If the platform version is Android 3.0 (Honeycomb) or above |
| if (Build.VERSION.SDK_INT >= 11) { |
| |
| // Sets the View to be "low profile". Status and navigation bar icons will be dimmed |
| int flag = fullscreen ? View.SYSTEM_UI_FLAG_LOW_PROFILE : 0; |
| |
| // If the platform version is Android 4.0 (ICS) or above |
| if (Build.VERSION.SDK_INT >= 14 && fullscreen) { |
| |
| // Hides all of the navigation icons |
| flag |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; |
| } |
| |
| // Applies the settings to the screen View |
| mMainView.setSystemUiVisibility(flag); |
| |
| // If the user requests a full-screen view, hides the Action Bar. |
| if ( fullscreen ) { |
| this.getActionBar().hide(); |
| } else { |
| this.getActionBar().show(); |
| } |
| } |
| } |
| |
| /* |
| * A callback invoked when the task's back stack changes. This allows the app to |
| * move to the previous state of the Fragment being displayed. |
| * |
| */ |
| @Override |
| public void onBackStackChanged() { |
| |
| // Gets the previous global stack count |
| int previousStackCount = mPreviousStackCount; |
| |
| // Gets a FragmentManager instance |
| FragmentManager localFragmentManager = getSupportFragmentManager(); |
| |
| // Sets the current back stack count |
| int currentStackCount = localFragmentManager.getBackStackEntryCount(); |
| |
| // Re-sets the global stack count to be the current count |
| mPreviousStackCount = currentStackCount; |
| |
| /* |
| * If the current stack count is less than the previous, something was popped off the stack |
| * probably because the user clicked Back. |
| */ |
| boolean popping = currentStackCount < previousStackCount; |
| Log.d(CLASS_TAG, "backstackchanged: popping = " + popping); |
| |
| // When going backwards in the back stack, turns off full screen mode. |
| if (popping) { |
| setFullScreen(false); |
| } |
| } |
| |
| /* |
| * This callback is invoked by the system when the Activity is being killed |
| * It saves the full screen status, so it can be restored when the Activity is restored |
| * |
| */ |
| @Override |
| protected void onSaveInstanceState(Bundle outState) { |
| outState.putBoolean(Constants.EXTENDED_FULLSCREEN, mFullScreen); |
| super.onSaveInstanceState(outState); |
| } |
| |
| /* |
| * This callback is invoked when the Activity is first created. It sets up the Activity's |
| * window and initializes the Fragments associated with the Activity |
| */ |
| @Override |
| public void onCreate(Bundle stateBundle) { |
| // Sets fullscreen-related flags for the display |
| getWindow().setFlags( |
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR, |
| WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN |
| | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR); |
| |
| // Calls the super method (required) |
| super.onCreate(stateBundle); |
| |
| // Inflates the main View, which will be the host View for the fragments |
| mMainView = getLayoutInflater().inflate(R.layout.fragmenthost, null); |
| |
| // Sets the content view for the Activity |
| setContentView(mMainView); |
| |
| /* |
| * Creates an intent filter for DownloadStateReceiver that intercepts broadcast Intents |
| */ |
| |
| // The filter's action is BROADCAST_ACTION |
| IntentFilter statusIntentFilter = new IntentFilter( |
| Constants.BROADCAST_ACTION); |
| |
| // Sets the filter's category to DEFAULT |
| statusIntentFilter.addCategory(Intent.CATEGORY_DEFAULT); |
| |
| // Instantiates a new DownloadStateReceiver |
| mDownloadStateReceiver = new DownloadStateReceiver(); |
| |
| // Registers the DownloadStateReceiver and its intent filters |
| LocalBroadcastManager.getInstance(this).registerReceiver( |
| mDownloadStateReceiver, |
| statusIntentFilter); |
| |
| /* |
| * Creates intent filters for the FragmentDisplayer |
| */ |
| |
| // One filter is for the action ACTION_VIEW_IMAGE |
| IntentFilter displayerIntentFilter = new IntentFilter( |
| Constants.ACTION_VIEW_IMAGE); |
| |
| // Adds a data filter for the HTTP scheme |
| displayerIntentFilter.addDataScheme("http"); |
| |
| // Registers the receiver |
| LocalBroadcastManager.getInstance(this).registerReceiver( |
| mFragmentDisplayer, |
| displayerIntentFilter); |
| |
| // Creates a second filter for ACTION_ZOOM_IMAGE |
| displayerIntentFilter = new IntentFilter(Constants.ACTION_ZOOM_IMAGE); |
| |
| // Registers the receiver |
| LocalBroadcastManager.getInstance(this).registerReceiver( |
| mFragmentDisplayer, |
| displayerIntentFilter); |
| |
| // Gets an instance of the support library FragmentManager |
| FragmentManager localFragmentManager = getSupportFragmentManager(); |
| |
| /* |
| * Detects if side-by-side display should be enabled. It's only available on xlarge and |
| * sw600dp devices (for example, tablets). The setting in res/values/ is "false", but this |
| * is overridden in values-xlarge and values-sw600dp. |
| */ |
| mSideBySide = getResources().getBoolean(R.bool.sideBySide); |
| |
| /* |
| * Detects if hiding navigation controls should be enabled. On xlarge andsw600dp, it should |
| * be false, to avoid having the user enter an additional tap. |
| */ |
| mHideNavigation = getResources().getBoolean(R.bool.hideNavigation); |
| |
| /* |
| * Adds the back stack change listener defined in this Activity as the listener for the |
| * FragmentManager. See the method onBackStackChanged(). |
| */ |
| localFragmentManager.addOnBackStackChangedListener(this); |
| |
| // If the incoming state of the Activity is null, sets the initial view to be thumbnails |
| if (null == stateBundle) { |
| |
| // Starts a Fragment transaction to track the stack |
| FragmentTransaction localFragmentTransaction = localFragmentManager |
| .beginTransaction(); |
| |
| // Adds the PhotoThumbnailFragment to the host View |
| localFragmentTransaction.add(R.id.fragmentHost, |
| new PhotoThumbnailFragment(), Constants.THUMBNAIL_FRAGMENT_TAG); |
| |
| // Commits this transaction to display the Fragment |
| localFragmentTransaction.commit(); |
| |
| // The incoming state of the Activity isn't null. |
| } else { |
| |
| // Gets the previous state of the fullscreen indicator |
| mFullScreen = stateBundle.getBoolean(Constants.EXTENDED_FULLSCREEN); |
| |
| // Sets the fullscreen flag to its previous state |
| setFullScreen(mFullScreen); |
| |
| // Gets the previous backstack entry count. |
| mPreviousStackCount = localFragmentManager.getBackStackEntryCount(); |
| } |
| } |
| |
| /* |
| * This callback is invoked when the system is about to destroy the Activity. |
| */ |
| @Override |
| public void onDestroy() { |
| |
| // If the DownloadStateReceiver still exists, unregister it and set it to null |
| if (mDownloadStateReceiver != null) { |
| LocalBroadcastManager.getInstance(this).unregisterReceiver(mDownloadStateReceiver); |
| mDownloadStateReceiver = null; |
| } |
| |
| // Unregisters the FragmentDisplayer instance |
| LocalBroadcastManager.getInstance(this).unregisterReceiver(this.mFragmentDisplayer); |
| |
| // Sets the main View to null |
| mMainView = null; |
| |
| // Must always call the super method at the end. |
| super.onDestroy(); |
| } |
| |
| /* |
| * This callback is invoked when the system is stopping the Activity. It stops |
| * background threads. |
| */ |
| @Override |
| protected void onStop() { |
| |
| // Cancel all the running threads managed by the PhotoManager |
| PhotoManager.cancelAll(); |
| super.onStop(); |
| } |
| |
| /** |
| * This class uses the BroadcastReceiver framework to detect and handle status messages from |
| * the service that downloads URLs. |
| */ |
| private class DownloadStateReceiver extends BroadcastReceiver { |
| |
| private DownloadStateReceiver() { |
| |
| // prevents instantiation by other packages. |
| } |
| /** |
| * |
| * This method is called by the system when a broadcast Intent is matched by this class' |
| * intent filters |
| * |
| * @param context An Android context |
| * @param intent The incoming broadcast Intent |
| */ |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| |
| /* |
| * Gets the status from the Intent's extended data, and chooses the appropriate action |
| */ |
| switch (intent.getIntExtra(Constants.EXTENDED_DATA_STATUS, |
| Constants.STATE_ACTION_COMPLETE)) { |
| |
| // Logs "started" state |
| case Constants.STATE_ACTION_STARTED: |
| if (Constants.LOGD) { |
| |
| Log.d(CLASS_TAG, "State: STARTED"); |
| } |
| break; |
| // Logs "connecting to network" state |
| case Constants.STATE_ACTION_CONNECTING: |
| if (Constants.LOGD) { |
| |
| Log.d(CLASS_TAG, "State: CONNECTING"); |
| } |
| break; |
| // Logs "parsing the RSS feed" state |
| case Constants.STATE_ACTION_PARSING: |
| if (Constants.LOGD) { |
| |
| Log.d(CLASS_TAG, "State: PARSING"); |
| } |
| break; |
| // Logs "Writing the parsed data to the content provider" state |
| case Constants.STATE_ACTION_WRITING: |
| if (Constants.LOGD) { |
| |
| Log.d(CLASS_TAG, "State: WRITING"); |
| } |
| break; |
| // Starts displaying data when the RSS download is complete |
| case Constants.STATE_ACTION_COMPLETE: |
| // Logs the status |
| if (Constants.LOGD) { |
| |
| Log.d(CLASS_TAG, "State: COMPLETE"); |
| } |
| |
| // Finds the fragment that displays thumbnails |
| PhotoThumbnailFragment localThumbnailFragment = |
| (PhotoThumbnailFragment) getSupportFragmentManager().findFragmentByTag( |
| Constants.THUMBNAIL_FRAGMENT_TAG); |
| |
| // If the thumbnail Fragment is hidden, don't change its display status |
| if ((localThumbnailFragment == null) |
| || (!localThumbnailFragment.isVisible())) |
| return; |
| |
| // Indicates that the thumbnail Fragment is visible |
| localThumbnailFragment.setLoaded(true); |
| break; |
| default: |
| break; |
| } |
| } |
| } |
| |
| /** |
| * This class uses the broadcast receiver framework to detect incoming broadcast Intents |
| * and change the currently-visible fragment based on the Intent action. |
| * It adds or replaces Fragments as necessary, depending on how much screen real-estate is |
| * available. |
| */ |
| private class FragmentDisplayer extends BroadcastReceiver { |
| |
| // Default null constructor |
| public FragmentDisplayer() { |
| |
| // Calls the constructor for BroadcastReceiver |
| super(); |
| } |
| /** |
| * Receives broadcast Intents for viewing or zooming pictures, and displays the |
| * appropriate Fragment. |
| * |
| * @param context The current Context of the callback |
| * @param intent The broadcast Intent that triggered the callback |
| */ |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| |
| // Declares a local FragmentManager instance |
| FragmentManager fragmentManager1; |
| |
| // Declares a local instance of the Fragment that displays photos |
| PhotoFragment photoFragment; |
| |
| // Stores a string representation of the URL in the incoming Intent |
| String urlString; |
| |
| // If the incoming Intent is a request is to view an image |
| if (intent.getAction().equals(Constants.ACTION_VIEW_IMAGE)) { |
| |
| // Gets an instance of the support library fragment manager |
| fragmentManager1 = getSupportFragmentManager(); |
| |
| // Gets a handle to the Fragment that displays photos |
| photoFragment = |
| (PhotoFragment) fragmentManager1.findFragmentByTag( |
| Constants.PHOTO_FRAGMENT_TAG |
| ); |
| |
| // Gets the URL of the picture to display |
| urlString = intent.getDataString(); |
| |
| // If the photo Fragment exists from a previous display |
| if (null != photoFragment) { |
| |
| // If the incoming URL is not already being displayed |
| if (!urlString.equals(photoFragment.getURLString())) { |
| |
| // Sets the Fragment to use the URL from the Intent for the photo |
| photoFragment.setPhoto(urlString); |
| |
| // Loads the photo into the Fragment |
| photoFragment.loadPhoto(); |
| } |
| |
| // If the Fragment doesn't already exist |
| } else { |
| // Instantiates a new Fragment |
| photoFragment = new PhotoFragment(); |
| |
| // Sets the Fragment to use the URL from the Intent for the photo |
| photoFragment.setPhoto(urlString); |
| |
| // Starts a new Fragment transaction |
| FragmentTransaction localFragmentTransaction2 = |
| fragmentManager1.beginTransaction(); |
| |
| // If the fragments are side-by-side, adds the photo Fragment to the display |
| if (mSideBySide) { |
| localFragmentTransaction2.add( |
| R.id.fragmentHost, |
| photoFragment, |
| Constants.PHOTO_FRAGMENT_TAG |
| ); |
| /* |
| * If the Fragments are not side-by-side, replaces the current Fragment with |
| * the photo Fragment |
| */ |
| } else { |
| localFragmentTransaction2.replace( |
| R.id.fragmentHost, |
| photoFragment, |
| Constants.PHOTO_FRAGMENT_TAG); |
| } |
| |
| // Don't remember the transaction (sets the Fragment backstack to null) |
| localFragmentTransaction2.addToBackStack(null); |
| |
| // Commits the transaction |
| localFragmentTransaction2.commit(); |
| } |
| |
| // If not in side-by-side mode, sets "full screen", so that no controls are visible |
| if (!mSideBySide) setFullScreen(true); |
| |
| /* |
| * If the incoming Intent is a request to zoom in on an existing image |
| * (Notice that zooming is only supported on large-screen devices) |
| */ |
| } else if (intent.getAction().equals(Constants.ACTION_ZOOM_IMAGE)) { |
| |
| // If the Fragments are being displayed side-by-side |
| if (mSideBySide) { |
| |
| // Gets another instance of the FragmentManager |
| FragmentManager localFragmentManager2 = getSupportFragmentManager(); |
| |
| // Gets a thumbnail Fragment instance |
| PhotoThumbnailFragment localThumbnailFragment = |
| (PhotoThumbnailFragment) localFragmentManager2.findFragmentByTag( |
| Constants.THUMBNAIL_FRAGMENT_TAG); |
| |
| // If the instance exists from a previous display |
| if (null != localThumbnailFragment) { |
| |
| // if the existing instance is visible |
| if (localThumbnailFragment.isVisible()) { |
| |
| // Starts a fragment transaction |
| FragmentTransaction localFragmentTransaction2 = |
| localFragmentManager2.beginTransaction(); |
| |
| /* |
| * Hides the current thumbnail, clears the backstack, and commits the |
| * transaction |
| */ |
| localFragmentTransaction2.hide(localThumbnailFragment); |
| localFragmentTransaction2.addToBackStack(null); |
| localFragmentTransaction2.commit(); |
| |
| // If the existing instance is not visible, display it by going "Back" |
| } else { |
| |
| // Pops the back stack to show the previous Fragment state |
| localFragmentManager2.popBackStack(); |
| } |
| } |
| |
| // Removes controls from the screen |
| setFullScreen(true); |
| } |
| } |
| } |
| } |
| |
| } |