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 
     17 package com.example.android.threadsample;
     18 
     19 import android.content.Context;
     20 import android.content.Intent;
     21 import android.database.Cursor;
     22 import android.graphics.drawable.Drawable;
     23 import android.net.Uri;
     24 import android.os.Bundle;
     25 import android.support.v4.app.Fragment;
     26 import android.support.v4.app.FragmentTransaction;
     27 import android.support.v4.app.LoaderManager;
     28 import android.support.v4.content.CursorLoader;
     29 import android.support.v4.content.Loader;
     30 import android.support.v4.content.LocalBroadcastManager;
     31 import android.support.v4.widget.CursorAdapter;
     32 import android.util.DisplayMetrics;
     33 import android.view.LayoutInflater;
     34 import android.view.View;
     35 import android.view.ViewGroup;
     36 import android.view.ViewGroup.LayoutParams;
     37 import android.widget.AbsListView;
     38 import android.widget.AdapterView;
     39 import android.widget.GridView;
     40 
     41 import java.net.MalformedURLException;
     42 import java.net.URL;
     43 import java.util.concurrent.RejectedExecutionException;
     44 
     45 /**
     46  * PhotoThumbnailFragment displays a GridView of picture thumbnails downloaded from Picasa
     47  */
     48 public class PhotoThumbnailFragment extends Fragment implements
     49         LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
     50 
     51     private static final String STATE_IS_HIDDEN =
     52             "com.example.android.threadsample.STATE_IS_HIDDEN";
     53 
     54     // The width of each column in the grid
     55     private int mColumnWidth;
     56 
     57     // A Drawable for a grid cell that's empty
     58     private Drawable mEmptyDrawable;
     59 
     60     // The GridView for displaying thumbnails
     61     private GridView mGridView;
     62 
     63     // Denotes if the GridView has been loaded
     64     private boolean mIsLoaded;
     65 
     66     // Intent for starting the IntentService that downloads the Picasa featured picture RSS feed
     67     private Intent mServiceIntent;
     68 
     69     // An adapter between a Cursor and the Fragment's GridView
     70     private GridViewAdapter mAdapter;
     71 
     72     // The URL of the Picasa featured picture RSS feed, in String format
     73     private static final String PICASA_RSS_URL =
     74             "http://picasaweb.google.com/data/feed/base/featured?" +
     75             "alt=rss&kind=photo&access=public&slabel=featured&hl=en_US&imgmax=1600";
     76 
     77     private static final String[] PROJECTION =
     78     {
     79         DataProviderContract._ID,
     80         DataProviderContract.IMAGE_THUMBURL_COLUMN,
     81         DataProviderContract.IMAGE_URL_COLUMN
     82     };
     83 
     84     // Constants that define the order of columns in the returned cursor
     85     private static final int IMAGE_THUMBURL_CURSOR_INDEX = 1;
     86     private static final int IMAGE_URL_CURSOR_INDEX = 2;
     87 
     88     // Identifies a particular Loader being used in this component
     89     private static final int URL_LOADER = 0;
     90 
     91     /*
     92      * This callback is invoked when the framework is starting or re-starting the Loader. It
     93      * returns a CursorLoader object containing the desired query
     94      */
     95     @Override
     96     public Loader<Cursor> onCreateLoader(int loaderID, Bundle bundle)
     97     {
     98         /*
     99          * Takes action based on the ID of the Loader that's being created
    100          */
    101         switch (loaderID) {
    102             case URL_LOADER:
    103             // Returns a new CursorLoader
    104             return new CursorLoader(
    105                         getActivity(),                                     // Context
    106                         DataProviderContract.PICTUREURL_TABLE_CONTENTURI,  // Table to query
    107                         PROJECTION,                                        // Projection to return
    108                         null,                                              // No selection clause
    109                         null,                                              // No selection arguments
    110                         null                                               // Default sort order
    111             );
    112             default:
    113                 // An invalid id was passed in
    114                 return null;
    115 
    116         }
    117 
    118     }
    119 
    120     /*
    121      * This callback is invoked when the the Fragment's View is being loaded. It sets up the View.
    122      */
    123     @Override
    124     public View onCreateView(LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
    125 
    126         // Always call the super method first
    127         super.onCreateView(inflater, viewGroup, bundle);
    128 
    129         /*
    130          * Inflates the View from the gridlist layout file, using the layout parameters in
    131          * "viewGroup"
    132          */
    133         View localView = inflater.inflate(R.layout.gridlist, viewGroup, false);
    134 
    135         // Sets the View's data adapter to be a new GridViewAdapter
    136         mAdapter = new GridViewAdapter(getActivity());
    137 
    138         // Gets a handle to the GridView in the layout
    139         mGridView = ((GridView) localView.findViewById(android.R.id.list));
    140 
    141         // Instantiates a DisplayMetrics object
    142         DisplayMetrics localDisplayMetrics = new DisplayMetrics();
    143 
    144         // Gets the current display metrics from the current Window
    145         getActivity().getWindowManager().getDefaultDisplay().getMetrics(localDisplayMetrics);
    146 
    147         /*
    148          * Gets the dp value from the thumbSize resource as an integer in dps. The value can
    149          * be adjusted for specific display sizes, etc. in the dimens.xml file for a particular
    150          * values-<qualifier> directory
    151          */
    152         int pixelSize = getResources().getDimensionPixelSize(R.dimen.thumbSize);
    153 
    154         /*
    155          * Calculates a width scale factor from the pixel width of the current display and the
    156          * desired pixel size
    157          */
    158         int widthScale = localDisplayMetrics.widthPixels / pixelSize;
    159 
    160         // Calculates the grid column width
    161         mColumnWidth = (localDisplayMetrics.widthPixels / widthScale);
    162 
    163         // Sets the GridView's column width
    164         mGridView.setColumnWidth(mColumnWidth);
    165 
    166         // Starts by setting the GridView to have no columns
    167         mGridView.setNumColumns(-1);
    168 
    169         // Sets the GridView's data adapter
    170         mGridView.setAdapter(mAdapter);
    171 
    172         /*
    173          * Sets the GridView's click listener to be this class. As a result, when users click the
    174          * GridView, PhotoThumbnailFragment.onClick() is invoked.
    175          */
    176         mGridView.setOnItemClickListener(this);
    177 
    178         /*
    179          * Sets the "empty" View for the layout. If there's nothing to show, a ProgressBar
    180          * is displayed.
    181          */
    182         mGridView.setEmptyView(localView.findViewById(R.id.progressRoot));
    183 
    184         // Sets a dark background to show when no image is queued to be downloaded
    185         mEmptyDrawable = getResources().getDrawable(R.drawable.imagenotqueued);
    186 
    187         // Initializes the CursorLoader
    188         getLoaderManager().initLoader(URL_LOADER, null, this);
    189 
    190         /*
    191          * Creates a new Intent to send to the download IntentService. The Intent contains the
    192          * URL of the Picasa feature picture RSS feed
    193          */
    194         mServiceIntent =
    195                 new Intent(getActivity(), RSSPullService.class)
    196                         .setData(Uri.parse(PICASA_RSS_URL));
    197 
    198         // If there's no pre-existing state for this Fragment
    199         if (bundle == null) {
    200             // If the data wasn't previously loaded
    201             if (!this.mIsLoaded) {
    202                 // Starts the IntentService to download the RSS feed data
    203                 getActivity().startService(mServiceIntent);
    204             }
    205 
    206         // If this Fragment existed previously, gets its state
    207         } else if (bundle.getBoolean(STATE_IS_HIDDEN, false)) {
    208 
    209             // Begins a transaction
    210             FragmentTransaction localFragmentTransaction =
    211                     getFragmentManager().beginTransaction();
    212 
    213             // Hides the Fragment
    214             localFragmentTransaction.hide(this);
    215 
    216             // Commits the transaction
    217             localFragmentTransaction.commit();
    218         }
    219 
    220         // Returns the View inflated from the layout
    221         return localView;
    222     }
    223 
    224     /*
    225      * This callback is invoked when the Fragment is being destroyed.
    226      */
    227     @Override
    228     public void onDestroyView() {
    229 
    230         // Sets variables to null, to avoid memory leaks
    231         mGridView = null;
    232 
    233         // If the EmptyDrawable contains something, sets those members to null
    234         if (mEmptyDrawable != null) {
    235             this.mEmptyDrawable.setCallback(null);
    236             this.mEmptyDrawable = null;
    237         }
    238 
    239         // Always call the super method last
    240         super.onDestroyView();
    241     }
    242 
    243     /*
    244      * This callback is invoked after onDestroyView(). It clears out variables, shuts down the
    245      * CursorLoader, and so forth
    246      */
    247     @Override
    248     public void onDetach() {
    249 
    250         // Destroys variables and references, and catches Exceptions
    251         try {
    252             getLoaderManager().destroyLoader(0);
    253             if (mAdapter != null) {
    254                 mAdapter.changeCursor(null);
    255                 mAdapter = null;
    256             }
    257         } catch (Throwable localThrowable) {
    258         }
    259 
    260         // Always call the super method last
    261         super.onDetach();
    262         return;
    263     }
    264 
    265     /*
    266      * This is invoked whenever the visibility state of the Fragment changes
    267      */
    268     @Override
    269     public void onHiddenChanged(boolean viewState) {
    270         super.onHiddenChanged(viewState);
    271     }
    272 
    273     /*
    274      * Implements OnItemClickListener.onItemClick() for this View's listener.
    275      * This implementation detects the View that was clicked and retrieves its picture URL.
    276      */
    277     @Override
    278     public void onItemClick(AdapterView<?> adapterView, View view, int viewId, long rowId) {
    279 
    280         // Returns a one-row cursor for the data that backs the View that was clicked.
    281         Cursor cursor = (Cursor) mAdapter.getItem(viewId);
    282 
    283         // Retrieves the urlString from the cursor
    284         String urlString = cursor.getString(IMAGE_URL_CURSOR_INDEX);
    285 
    286         /*
    287          * Creates a new Intent to get the full picture for the thumbnail that the user clicked.
    288          * The full photo is loaded into a separate Fragment
    289          */
    290         Intent localIntent =
    291                 new Intent(Constants.ACTION_VIEW_IMAGE)
    292                 .setData(Uri.parse(urlString));
    293 
    294         // Broadcasts the Intent to receivers in this app. See DisplayActivity.FragmentDisplayer.
    295         LocalBroadcastManager.getInstance(getActivity()).sendBroadcast(localIntent);
    296     }
    297 
    298     /*
    299      * Invoked when the CursorLoader finishes the query. A reference to the Loader and the
    300      * returned Cursor are passed in as arguments
    301      */
    302     @Override
    303     public void onLoadFinished(Loader<Cursor> loader, Cursor returnCursor) {
    304 
    305         /*
    306          *  Changes the adapter's Cursor to be the results of the load. This forces the View to
    307          *  redraw.
    308          */
    309 
    310         mAdapter.changeCursor(returnCursor);
    311     }
    312 
    313     /*
    314      * Invoked when the CursorLoader is being reset. For example, this is called if the
    315      * data in the provider changes and the Cursor becomes stale.
    316      */
    317     @Override
    318     public void onLoaderReset(Loader<Cursor> loader) {
    319 
    320         // Sets the Adapter's backing data to null. This prevents memory leaks.
    321         mAdapter.changeCursor(null);
    322     }
    323 
    324     /*
    325      * This callback is invoked when the system has to destroy the Fragment for some reason. It
    326      * allows the Fragment to save its state, so the state can be restored later on.
    327      */
    328     @Override
    329     public void onSaveInstanceState(Bundle bundle) {
    330 
    331         // Saves the show-hide status of the display
    332         bundle.putBoolean(STATE_IS_HIDDEN, isHidden());
    333 
    334         // Always call the super method last
    335         super.onSaveInstanceState(bundle);
    336     }
    337 
    338     // Sets the state of the loaded flag
    339     public void setLoaded(boolean loadState) {
    340         mIsLoaded = loadState;
    341     }
    342 
    343     /**
    344      * Defines a custom View adapter that extends CursorAdapter. The main reason to do this is to
    345      * display images based on the backing Cursor, rather than just displaying the URLs that the
    346      * Cursor contains.
    347      */
    348     private class GridViewAdapter extends CursorAdapter {
    349 
    350         /**
    351          * Simplified constructor that calls the super constructor with the input Context,
    352          * a null value for Cursor, and no flags
    353          * @param context A Context for this object
    354          */
    355         public GridViewAdapter(Context context) {
    356             super(context, null, false);
    357         }
    358 
    359         /**
    360          *
    361          * Binds a View and a Cursor
    362          *
    363          * @param view An existing View object
    364          * @param context A Context for the View and Cursor
    365          * @param cursor The Cursor to bind to the View, representing one row of the returned query.
    366          */
    367         @Override
    368         public void bindView(View view, Context context, Cursor cursor) {
    369 
    370             // Gets a handle to the View
    371             PhotoView localImageDownloaderView = (PhotoView) view.getTag();
    372 
    373             // Converts the URL string to a URL and tries to retrieve the picture
    374             try {
    375                 // Gets the URL
    376                 URL localURL =
    377                         new URL(
    378                             cursor.getString(IMAGE_THUMBURL_CURSOR_INDEX)
    379                         )
    380                 ;
    381                 /*
    382                  * Invokes setImageURL for the View. If the image isn't already available, this
    383                  * will download and decode it.
    384                  */
    385                 localImageDownloaderView.setImageURL(
    386                             localURL, true, PhotoThumbnailFragment.this.mEmptyDrawable);
    387 
    388             // Catches an invalid URL
    389             } catch (MalformedURLException localMalformedURLException) {
    390                 localMalformedURLException.printStackTrace();
    391 
    392             // Catches errors trying to download and decode the picture in a ThreadPool
    393             } catch (RejectedExecutionException localRejectedExecutionException) {
    394             }
    395         }
    396 
    397         /**
    398          * Creates a new View that shows the contents of the Cursor
    399          *
    400          *
    401          * @param context A Context for the View and Cursor
    402          * @param cursor The Cursor to display. This is a single row of the returned query
    403          * @param viewGroup The viewGroup that's the parent of the new View
    404          * @return the newly-created View
    405          */
    406         @Override
    407         public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
    408             // Gets a new layout inflater instance
    409             LayoutInflater inflater = LayoutInflater.from(context);
    410 
    411             /*
    412              * Creates a new View by inflating the specified layout file. The root ViewGroup is
    413              * the root of the layout file. This View is a FrameLayout
    414              */
    415             View layoutView = inflater.inflate(R.layout.galleryitem, null);
    416 
    417             /*
    418              * Creates a second View to hold the thumbnail image.
    419              */
    420             View thumbView = layoutView.findViewById(R.id.thumbImage);
    421 
    422             /*
    423              * Sets layout parameters for the layout based on the layout parameters of a virtual
    424              * list. In addition, this sets the layoutView's width to be MATCH_PARENT, and its
    425              * height to be the column width?
    426              */
    427             layoutView.setLayoutParams(new AbsListView.LayoutParams(LayoutParams.MATCH_PARENT,
    428                     PhotoThumbnailFragment.this.mColumnWidth));
    429 
    430             // Sets the layoutView's tag to be the same as the thumbnail image tag.
    431             layoutView.setTag(thumbView);
    432             return layoutView;
    433         }
    434 
    435     }
    436 }
    437