Files
android_development/samples/HoneycombGallery/src/com/example/android/hcgallery/ContentFragment.java
Scott Main ddd0d0c656 rewrite HC Gallery app to support phones. In summary, this change:
- moves tablet-style layouts to layout-large* directories
- adds ContentActivity to host the ContentFragment when on phones
- adds an OnItemSelectedListener interface to TitlesFragment, which MainActivity
  implements in order to receive callbacks on click events to the list item and then
  pass the selected item to the ContentFragment in the manner appropriate for the
  current configuration... Specifically, when in two-pane mode, it updates the ContentFragment
  directly, and when in single-pane mode, it starts the ContentActivity with intent data
  about the selected item, which then updates the ContentFragment
- Change CameraSample.java to CameraActivity.java for name conventions
- Moves all menu strings into string resources
- Fixes camera sample to properly handle front-facing camera on other devices (was broken
  on nexus s and on g-slate)
- Fixes camera sample to handle resume state after the camera has changed (for example, when
  switched to a different camera, it would crash on resume)
- Moves various code around between classes as appropriate for the fragment handling the
  action. For example, move the ActionBar.TabListener implementation to the TitlesFragment
  (was originally impemented by the MainActivity)
- Adds logic to support devices without camera and properly declare the camera in manifest as such
- Maintains the state of hidden titles list across restarts

Change-Id: I27a39a68dee37325c0c3607aa0b56ab6c134d026
2011-09-06 18:24:49 -07:00

396 lines
15 KiB
Java

/*
* 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;
}
};
}