Home | History | Annotate | Download | only in hcgallery
      1 /*
      2  * Copyright (C) 2011 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.hcgallery;
     18 
     19 import android.app.ActionBar;
     20 import android.app.Fragment;
     21 import android.content.ClipData;
     22 import android.content.ClipData.Item;
     23 import android.content.ClipDescription;
     24 import android.content.Intent;
     25 import android.graphics.Bitmap;
     26 import android.graphics.Color;
     27 import android.net.Uri;
     28 import android.os.AsyncTask;
     29 import android.os.Bundle;
     30 import android.view.ActionMode;
     31 import android.view.DragEvent;
     32 import android.view.LayoutInflater;
     33 import android.view.Menu;
     34 import android.view.MenuInflater;
     35 import android.view.MenuItem;
     36 import android.view.View;
     37 import android.view.View.OnClickListener;
     38 import android.view.ViewGroup;
     39 import android.view.Window;
     40 import android.view.WindowManager;
     41 import android.widget.ImageView;
     42 import android.widget.Toast;
     43 
     44 import java.io.File;
     45 import java.io.FileNotFoundException;
     46 import java.io.FileOutputStream;
     47 import java.io.IOException;
     48 import java.util.StringTokenizer;
     49 
     50 /** Fragment that shows the content selected from the TitlesFragment.
     51  * When running on a screen size smaller than "large", this fragment is hosted in
     52  * ContentActivity. Otherwise, it appears side by side with the TitlesFragment
     53  * in MainActivity. */
     54 public class ContentFragment extends Fragment {
     55     private View mContentView;
     56     private int mCategory = 0;
     57     private int mCurPosition = 0;
     58     private boolean mSystemUiVisible = true;
     59     private boolean mSoloFragment = false;
     60 
     61     // The bitmap currently used by ImageView
     62     private Bitmap mBitmap = null;
     63 
     64     // Current action mode (contextual action bar, a.k.a. CAB)
     65     private ActionMode mCurrentActionMode;
     66 
     67     /** This is where we initialize the fragment's UI and attach some
     68      * event listeners to UI components.
     69      */
     70     @Override
     71     public View onCreateView(LayoutInflater inflater, ViewGroup container,
     72             Bundle savedInstanceState) {
     73         mContentView = inflater.inflate(R.layout.content_welcome, null);
     74         final ImageView imageView = (ImageView) mContentView.findViewById(R.id.image);
     75         mContentView.setDrawingCacheEnabled(false);
     76 
     77         // Handle drag events when a list item is dragged into the view
     78         mContentView.setOnDragListener(new View.OnDragListener() {
     79             public boolean onDrag(View view, DragEvent event) {
     80                 switch (event.getAction()) {
     81                     case DragEvent.ACTION_DRAG_ENTERED:
     82                         view.setBackgroundColor(
     83                                 getResources().getColor(R.color.drag_active_color));
     84                         break;
     85 
     86                     case DragEvent.ACTION_DRAG_EXITED:
     87                         view.setBackgroundColor(Color.TRANSPARENT);
     88                         break;
     89 
     90                     case DragEvent.ACTION_DRAG_STARTED:
     91                         return processDragStarted(event);
     92 
     93                     case DragEvent.ACTION_DROP:
     94                         view.setBackgroundColor(Color.TRANSPARENT);
     95                         return processDrop(event, imageView);
     96                 }
     97                 return false;
     98             }
     99         });
    100 
    101         // Show/hide the system status bar when single-clicking a photo.
    102         mContentView.setOnClickListener(new OnClickListener() {
    103             public void onClick(View view) {
    104                 if (mCurrentActionMode != null) {
    105                   // If we're in an action mode, don't toggle the action bar
    106                   return;
    107                 }
    108 
    109                 if (mSystemUiVisible) {
    110                   setSystemUiVisible(false);
    111                 } else {
    112                   setSystemUiVisible(true);
    113                 }
    114             }
    115         });
    116 
    117         // When long-pressing a photo, activate the action mode for selection, showing the
    118         // contextual action bar (CAB).
    119         mContentView.setOnLongClickListener(new View.OnLongClickListener() {
    120             public boolean onLongClick(View view) {
    121                 if (mCurrentActionMode != null) {
    122                     return false;
    123                 }
    124 
    125                 mCurrentActionMode = getActivity().startActionMode(
    126                         mContentSelectionActionModeCallback);
    127                 view.setSelected(true);
    128                 return true;
    129             }
    130         });
    131 
    132         return mContentView;
    133     }
    134 
    135     /** This is where we perform additional setup for the fragment that's either
    136      * not related to the fragment's layout or must be done after the layout is drawn.
    137      */
    138     @Override
    139     public void onActivityCreated(Bundle savedInstanceState) {
    140         super.onActivityCreated(savedInstanceState);
    141 
    142         // Set member variable for whether this fragment is the only one in the activity
    143         Fragment listFragment = getFragmentManager().findFragmentById(R.id.titles_frag);
    144         mSoloFragment = listFragment == null ? true : false;
    145 
    146         if (mSoloFragment) {
    147             // The fragment is alone, so enable up navigation
    148             getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
    149             // Must call in order to get callback to onOptionsItemSelected()
    150             setHasOptionsMenu(true);
    151         }
    152 
    153         // Current position and UI visibility should survive screen rotations.
    154         if (savedInstanceState != null) {
    155             setSystemUiVisible(savedInstanceState.getBoolean("systemUiVisible"));
    156             if (mSoloFragment) {
    157                 // Restoring these members is not necessary when this fragment
    158                 // is combined with the TitlesFragment, because when the TitlesFragment
    159                 // is restored, it selects the appropriate item and sends the event
    160                 // to the updateContentAndRecycleBitmap() method itself
    161                 mCategory = savedInstanceState.getInt("category");
    162                 mCurPosition = savedInstanceState.getInt("listPosition");
    163                 updateContentAndRecycleBitmap(mCategory, mCurPosition);
    164             }
    165         }
    166 
    167         if (mSoloFragment) {
    168           String title = Directory.getCategory(mCategory).getEntry(mCurPosition).getName();
    169           ActionBar bar = getActivity().getActionBar();
    170           bar.setTitle(title);
    171         }
    172     }
    173 
    174     @Override
    175     public boolean onOptionsItemSelected(MenuItem item) {
    176         // This callback is used only when mSoloFragment == true (see onActivityCreated above)
    177         switch (item.getItemId()) {
    178         case android.R.id.home:
    179             // App icon in Action Bar clicked; go up
    180             Intent intent = new Intent(getActivity(), MainActivity.class);
    181             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Reuse the existing instance
    182             startActivity(intent);
    183             return true;
    184         default:
    185             return super.onOptionsItemSelected(item);
    186         }
    187     }
    188 
    189     @Override
    190     public void onSaveInstanceState (Bundle outState) {
    191         super.onSaveInstanceState(outState);
    192         outState.putInt("listPosition", mCurPosition);
    193         outState.putInt("category", mCategory);
    194         outState.putBoolean("systemUiVisible", mSystemUiVisible);
    195     }
    196 
    197     /** Toggle whether the system UI (status bar / system bar) is visible.
    198      *  This also toggles the action bar visibility.
    199      * @param show True to show the system UI, false to hide it.
    200      */
    201     void setSystemUiVisible(boolean show) {
    202         mSystemUiVisible = show;
    203 
    204         Window window = getActivity().getWindow();
    205         WindowManager.LayoutParams winParams = window.getAttributes();
    206         View view = getView();
    207         ActionBar actionBar = getActivity().getActionBar();
    208 
    209         if (show) {
    210             // Show status bar (remove fullscreen flag)
    211             window.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
    212             // Show system bar
    213             view.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
    214             // Show action bar
    215             actionBar.show();
    216         } else {
    217             // Add fullscreen flag (hide status bar)
    218             window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
    219                 WindowManager.LayoutParams.FLAG_FULLSCREEN);
    220             // Hide system bar
    221             view.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
    222             // Hide action bar
    223             actionBar.hide();
    224         }
    225         window.setAttributes(winParams);
    226     }
    227 
    228     boolean processDragStarted(DragEvent event) {
    229         // Determine whether to continue processing drag and drop based on the
    230         // plain text mime type.
    231         ClipDescription clipDesc = event.getClipDescription();
    232         if (clipDesc != null) {
    233             return clipDesc.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
    234         }
    235         return false;
    236     }
    237 
    238     boolean processDrop(DragEvent event, ImageView imageView) {
    239         // Attempt to parse clip data with expected format: category||entry_id.
    240         // Ignore event if data does not conform to this format.
    241         ClipData data = event.getClipData();
    242         if (data != null) {
    243             if (data.getItemCount() > 0) {
    244                 Item item = data.getItemAt(0);
    245                 String textData = (String) item.getText();
    246                 if (textData != null) {
    247                     StringTokenizer tokenizer = new StringTokenizer(textData, "||");
    248                     if (tokenizer.countTokens() != 2) {
    249                         return false;
    250                     }
    251                     int category = -1;
    252                     int entryId = -1;
    253                     try {
    254                         category = Integer.parseInt(tokenizer.nextToken());
    255                         entryId = Integer.parseInt(tokenizer.nextToken());
    256                     } catch (NumberFormatException exception) {
    257                         return false;
    258                     }
    259                     updateContentAndRecycleBitmap(category, entryId);
    260                     // Update list fragment with selected entry.
    261                     TitlesFragment titlesFrag = (TitlesFragment)
    262                             getFragmentManager().findFragmentById(R.id.titles_frag);
    263                     titlesFrag.selectPosition(entryId);
    264                     return true;
    265                 }
    266             }
    267         }
    268         return false;
    269     }
    270 
    271     /**
    272      * Sets the current image visible.
    273      * @param category Index position of the image category
    274      * @param position Index position of the image
    275      */
    276     void updateContentAndRecycleBitmap(int category, int position) {
    277         mCategory = category;
    278         mCurPosition = position;
    279 
    280         if (mCurrentActionMode != null) {
    281             mCurrentActionMode.finish();
    282         }
    283 
    284         if (mBitmap != null) {
    285             // This is an advanced call and should be used if you
    286             // are working with a lot of bitmaps. The bitmap is dead
    287             // after this call.
    288             mBitmap.recycle();
    289         }
    290 
    291         // Get the bitmap that needs to be drawn and update the ImageView
    292         mBitmap = Directory.getCategory(category).getEntry(position)
    293                 .getBitmap(getResources());
    294         ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
    295     }
    296 
    297     /** Share the currently selected photo using an AsyncTask to compress the image
    298      * and then invoke the appropriate share intent.
    299      */
    300     void shareCurrentPhoto() {
    301         File externalCacheDir = getActivity().getExternalCacheDir();
    302         if (externalCacheDir == null) {
    303             Toast.makeText(getActivity(), "Error writing to USB/external storage.",
    304                     Toast.LENGTH_SHORT).show();
    305             return;
    306         }
    307 
    308         // Prevent media scanning of the cache directory.
    309         final File noMediaFile = new File(externalCacheDir, ".nomedia");
    310         try {
    311             noMediaFile.createNewFile();
    312         } catch (IOException e) {
    313         }
    314 
    315         // Write the bitmap to temporary storage in the external storage directory (e.g. SD card).
    316         // We perform the actual disk write operations on a separate thread using the
    317         // {@link AsyncTask} class, thus avoiding the possibility of stalling the main (UI) thread.
    318 
    319         final File tempFile = new File(externalCacheDir, "tempfile.jpg");
    320 
    321         new AsyncTask<Void, Void, Boolean>() {
    322             /**
    323              * Compress and write the bitmap to disk on a separate thread.
    324              * @return TRUE if the write was successful, FALSE otherwise.
    325              */
    326             @Override
    327             protected Boolean doInBackground(Void... voids) {
    328                 try {
    329                     FileOutputStream fo = new FileOutputStream(tempFile, false);
    330                     if (!mBitmap.compress(Bitmap.CompressFormat.JPEG, 60, fo)) {
    331                         Toast.makeText(getActivity(), "Error writing bitmap data.",
    332                                 Toast.LENGTH_SHORT).show();
    333                         return Boolean.FALSE;
    334                     }
    335                     return Boolean.TRUE;
    336 
    337                 } catch (FileNotFoundException e) {
    338                     Toast.makeText(getActivity(), "Error writing to USB/external storage.",
    339                             Toast.LENGTH_SHORT).show();
    340                     return Boolean.FALSE;
    341                 }
    342             }
    343 
    344             /**
    345              * After doInBackground completes (either successfully or in failure), we invoke an
    346              * intent to share the photo. This code is run on the main (UI) thread.
    347              */
    348             @Override
    349             protected void onPostExecute(Boolean result) {
    350                 if (result != Boolean.TRUE) {
    351                     return;
    352                 }
    353 
    354                 Intent shareIntent = new Intent(Intent.ACTION_SEND);
    355                 shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
    356                 shareIntent.setType("image/jpeg");
    357                 startActivity(Intent.createChooser(shareIntent, "Share photo"));
    358             }
    359         }.execute();
    360     }
    361 
    362     /**
    363      * The callback for the 'photo selected' {@link ActionMode}. In this action mode, we can
    364      * provide contextual actions for the selected photo. We currently only provide the 'share'
    365      * action, but we could also add clipboard functions such as cut/copy/paste here as well.
    366      */
    367     private ActionMode.Callback mContentSelectionActionModeCallback = new ActionMode.Callback() {
    368         public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
    369             actionMode.setTitle(R.string.photo_selection_cab_title);
    370 
    371             MenuInflater inflater = getActivity().getMenuInflater();
    372             inflater.inflate(R.menu.photo_context_menu, menu);
    373             return true;
    374         }
    375 
    376         public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
    377             return false;
    378         }
    379 
    380         public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
    381             switch (menuItem.getItemId()) {
    382                 case R.id.menu_share:
    383                     shareCurrentPhoto();
    384                     actionMode.finish();
    385                     return true;
    386             }
    387             return false;
    388         }
    389 
    390         public void onDestroyActionMode(ActionMode actionMode) {
    391             mContentView.setSelected(false);
    392             mCurrentActionMode = null;
    393         }
    394     };
    395 }
    396