blob: f3abdf2480ea015a7f38f6c41fd6adeb8a172d3d [file] [log] [blame]
/*
* 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);
}
}
}
}
}