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