blob: b6754f9f52eadcaaa4dcac04e70436f7454a7529 [file] [log] [blame]
/*
* Copyright (C) 2011 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.hcgallery;
import android.app.ActionBar;
import android.app.Fragment;
import android.content.ClipData;
import android.content.ClipData.Item;
import android.content.ClipDescription;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.ActionMode;
import android.view.DragEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.Toast;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.StringTokenizer;
/** Fragment that shows the content selected from the TitlesFragment.
* When running on a screen size smaller than "large", this fragment is hosted in
* ContentActivity. Otherwise, it appears side by side with the TitlesFragment
* in MainActivity. */
public class ContentFragment extends Fragment {
private View mContentView;
private int mCategory = 0;
private int mCurPosition = 0;
private boolean mSystemUiVisible = true;
private boolean mSoloFragment = false;
// The bitmap currently used by ImageView
private Bitmap mBitmap = null;
// Current action mode (contextual action bar, a.k.a. CAB)
private ActionMode mCurrentActionMode;
/** This is where we initialize the fragment's UI and attach some
* event listeners to UI components.
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mContentView = inflater.inflate(R.layout.content_welcome, null);
final ImageView imageView = (ImageView) mContentView.findViewById(R.id.image);
mContentView.setDrawingCacheEnabled(false);
// Handle drag events when a list item is dragged into the view
mContentView.setOnDragListener(new View.OnDragListener() {
public boolean onDrag(View view, DragEvent event) {
switch (event.getAction()) {
case DragEvent.ACTION_DRAG_ENTERED:
view.setBackgroundColor(
getResources().getColor(R.color.drag_active_color));
break;
case DragEvent.ACTION_DRAG_EXITED:
view.setBackgroundColor(Color.TRANSPARENT);
break;
case DragEvent.ACTION_DRAG_STARTED:
return processDragStarted(event);
case DragEvent.ACTION_DROP:
view.setBackgroundColor(Color.TRANSPARENT);
return processDrop(event, imageView);
}
return false;
}
});
// Show/hide the system status bar when single-clicking a photo.
mContentView.setOnClickListener(new OnClickListener() {
public void onClick(View view) {
if (mCurrentActionMode != null) {
// If we're in an action mode, don't toggle the action bar
return;
}
if (mSystemUiVisible) {
setSystemUiVisible(false);
} else {
setSystemUiVisible(true);
}
}
});
// When long-pressing a photo, activate the action mode for selection, showing the
// contextual action bar (CAB).
mContentView.setOnLongClickListener(new View.OnLongClickListener() {
public boolean onLongClick(View view) {
if (mCurrentActionMode != null) {
return false;
}
mCurrentActionMode = getActivity().startActionMode(
mContentSelectionActionModeCallback);
view.setSelected(true);
return true;
}
});
return mContentView;
}
/** This is where we perform additional setup for the fragment that's either
* not related to the fragment's layout or must be done after the layout is drawn.
*/
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Set member variable for whether this fragment is the only one in the activity
Fragment listFragment = getFragmentManager().findFragmentById(R.id.titles_frag);
mSoloFragment = listFragment == null ? true : false;
if (mSoloFragment) {
// The fragment is alone, so enable up navigation
getActivity().getActionBar().setDisplayHomeAsUpEnabled(true);
// Must call in order to get callback to onOptionsItemSelected()
setHasOptionsMenu(true);
}
// Current position and UI visibility should survive screen rotations.
if (savedInstanceState != null) {
setSystemUiVisible(savedInstanceState.getBoolean("systemUiVisible"));
if (mSoloFragment) {
// Restoring these members is not necessary when this fragment
// is combined with the TitlesFragment, because when the TitlesFragment
// is restored, it selects the appropriate item and sends the event
// to the updateContentAndRecycleBitmap() method itself
mCategory = savedInstanceState.getInt("category");
mCurPosition = savedInstanceState.getInt("listPosition");
updateContentAndRecycleBitmap(mCategory, mCurPosition);
}
}
if (mSoloFragment) {
String title = Directory.getCategory(mCategory).getEntry(mCurPosition).getName();
ActionBar bar = getActivity().getActionBar();
bar.setTitle(title);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// This callback is used only when mSoloFragment == true (see onActivityCreated above)
switch (item.getItemId()) {
case android.R.id.home:
// App icon in Action Bar clicked; go up
Intent intent = new Intent(getActivity(), MainActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); // Reuse the existing instance
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("listPosition", mCurPosition);
outState.putInt("category", mCategory);
outState.putBoolean("systemUiVisible", mSystemUiVisible);
}
/** Toggle whether the system UI (status bar / system bar) is visible.
* This also toggles the action bar visibility.
* @param show True to show the system UI, false to hide it.
*/
void setSystemUiVisible(boolean show) {
mSystemUiVisible = show;
Window window = getActivity().getWindow();
WindowManager.LayoutParams winParams = window.getAttributes();
View view = getView();
ActionBar actionBar = getActivity().getActionBar();
if (show) {
// Show status bar (remove fullscreen flag)
window.setFlags(0, WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Show system bar
view.setSystemUiVisibility(View.STATUS_BAR_VISIBLE);
// Show action bar
actionBar.show();
} else {
// Add fullscreen flag (hide status bar)
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
// Hide system bar
view.setSystemUiVisibility(View.STATUS_BAR_HIDDEN);
// Hide action bar
actionBar.hide();
}
window.setAttributes(winParams);
}
boolean processDragStarted(DragEvent event) {
// Determine whether to continue processing drag and drop based on the
// plain text mime type.
ClipDescription clipDesc = event.getClipDescription();
if (clipDesc != null) {
return clipDesc.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN);
}
return false;
}
boolean processDrop(DragEvent event, ImageView imageView) {
// Attempt to parse clip data with expected format: category||entry_id.
// Ignore event if data does not conform to this format.
ClipData data = event.getClipData();
if (data != null) {
if (data.getItemCount() > 0) {
Item item = data.getItemAt(0);
String textData = (String) item.getText();
if (textData != null) {
StringTokenizer tokenizer = new StringTokenizer(textData, "||");
if (tokenizer.countTokens() != 2) {
return false;
}
int category = -1;
int entryId = -1;
try {
category = Integer.parseInt(tokenizer.nextToken());
entryId = Integer.parseInt(tokenizer.nextToken());
} catch (NumberFormatException exception) {
return false;
}
updateContentAndRecycleBitmap(category, entryId);
// Update list fragment with selected entry.
TitlesFragment titlesFrag = (TitlesFragment)
getFragmentManager().findFragmentById(R.id.titles_frag);
titlesFrag.selectPosition(entryId);
return true;
}
}
}
return false;
}
/**
* Sets the current image visible.
* @param category Index position of the image category
* @param position Index position of the image
*/
void updateContentAndRecycleBitmap(int category, int position) {
mCategory = category;
mCurPosition = position;
if (mCurrentActionMode != null) {
mCurrentActionMode.finish();
}
if (mBitmap != null) {
// This is an advanced call and should be used if you
// are working with a lot of bitmaps. The bitmap is dead
// after this call.
mBitmap.recycle();
}
// Get the bitmap that needs to be drawn and update the ImageView
mBitmap = Directory.getCategory(category).getEntry(position)
.getBitmap(getResources());
((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
}
/** Share the currently selected photo using an AsyncTask to compress the image
* and then invoke the appropriate share intent.
*/
void shareCurrentPhoto() {
File externalCacheDir = getActivity().getExternalCacheDir();
if (externalCacheDir == null) {
Toast.makeText(getActivity(), "Error writing to USB/external storage.",
Toast.LENGTH_SHORT).show();
return;
}
// Prevent media scanning of the cache directory.
final File noMediaFile = new File(externalCacheDir, ".nomedia");
try {
noMediaFile.createNewFile();
} catch (IOException e) {
}
// Write the bitmap to temporary storage in the external storage directory (e.g. SD card).
// We perform the actual disk write operations on a separate thread using the
// {@link AsyncTask} class, thus avoiding the possibility of stalling the main (UI) thread.
final File tempFile = new File(externalCacheDir, "tempfile.jpg");
new AsyncTask<Void, Void, Boolean>() {
/**
* Compress and write the bitmap to disk on a separate thread.
* @return TRUE if the write was successful, FALSE otherwise.
*/
@Override
protected Boolean doInBackground(Void... voids) {
try {
FileOutputStream fo = new FileOutputStream(tempFile, false);
if (!mBitmap.compress(Bitmap.CompressFormat.JPEG, 60, fo)) {
Toast.makeText(getActivity(), "Error writing bitmap data.",
Toast.LENGTH_SHORT).show();
return Boolean.FALSE;
}
return Boolean.TRUE;
} catch (FileNotFoundException e) {
Toast.makeText(getActivity(), "Error writing to USB/external storage.",
Toast.LENGTH_SHORT).show();
return Boolean.FALSE;
}
}
/**
* After doInBackground completes (either successfully or in failure), we invoke an
* intent to share the photo. This code is run on the main (UI) thread.
*/
@Override
protected void onPostExecute(Boolean result) {
if (result != Boolean.TRUE) {
return;
}
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tempFile));
shareIntent.setType("image/jpeg");
startActivity(Intent.createChooser(shareIntent, "Share photo"));
}
}.execute();
}
/**
* The callback for the 'photo selected' {@link ActionMode}. In this action mode, we can
* provide contextual actions for the selected photo. We currently only provide the 'share'
* action, but we could also add clipboard functions such as cut/copy/paste here as well.
*/
private ActionMode.Callback mContentSelectionActionModeCallback = new ActionMode.Callback() {
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
actionMode.setTitle(R.string.photo_selection_cab_title);
MenuInflater inflater = getActivity().getMenuInflater();
inflater.inflate(R.menu.photo_context_menu, menu);
return true;
}
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
switch (menuItem.getItemId()) {
case R.id.menu_share:
shareCurrentPhoto();
actionMode.finish();
return true;
}
return false;
}
public void onDestroyActionMode(ActionMode actionMode) {
mContentView.setSelected(false);
mCurrentActionMode = null;
}
};
}