/* * Copyright 2014 Google Inc. All rights reserved. * * 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.google.samples.apps.abelana; import android.app.Activity; import android.app.AlertDialog; import android.app.Fragment; import android.app.FragmentTransaction; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Bitmap; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.Environment; import android.provider.MediaStore; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.ListView; import android.widget.Toast; import com.google.identitytoolkit.IdToken; import com.squareup.picasso.Picasso; import com.squareup.picasso.Target; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.security.SecureRandom; import java.util.ArrayList; import retrofit.Callback; import retrofit.RetrofitError; import retrofit.client.Response; /** * Fragment representing the photo feed, which is the Home tab of the app */ public class FeedFragment extends Fragment { private static final int REQUEST_IMAGE_CAPTURE = 0; private static final int REQUEST_IMAGE_CHOOSE = 1; private static final int MEDIA_TYPE_IMAGE = 3; private Uri mMediaUri; private File mPhotoFile; private final String LOG_TAG = FeedFragment.class.getSimpleName(); public FeedFragment() { } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); inflater.inflate(R.menu.feed, menu); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { final View rootView = inflater.inflate(R.layout.fragment_feed, container, false); //required to display menu with the camera button setHasOptionsMenu(true); final ListView listView = (ListView) rootView.findViewById(R.id.listview_timeline); /* Instantiate the class which has interfaces for the REST APIs. API calls happen off the * UI thread in order to not halt the app. The callback function handles the API response. * The response contains the information for the feed, including photos, names of people * who took the photos, number of likes, and whether the current user liked the photo. This * information is stored in the Data class and is accessed by the FeedAdapter to display it. */ AbelanaClient client = new AbelanaClient(); client.mTimeline.timeline(Data.aTok, "0", new Callback<AbelanaClient.Timeline>() { @Override public void success(AbelanaClient.Timeline timelineResponse, Response response) { Data.mFeedIds = new ArrayList<String>(); Data.mLikes = new ArrayList<Integer>(); Data.mNames = new ArrayList<String>(); Data.mILike = new ArrayList<Boolean>(); Data.mFeedUrls = new ArrayList<String>(); String qualifier = rootView.getResources().getString(R.string.size_qualifier); if (timelineResponse.entries != null) { for (AbelanaClient.TimelineEntry e : timelineResponse.entries) { Data.mFeedIds.add(e.photoid); Data.mFeedUrls.add(AbelanaThings.getImage(e.photoid + qualifier)); Data.mLikes.add(e.likes); Data.mNames.add(e.name); Data.mILike.add(e.ilike); } } //Warm the image cache by pre-loading the next 5 photos int count = 0; for (String url: Data.mFeedUrls) { Picasso.with(getActivity()).load(url).into(new Target() { @Override public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { // cache is now warmed up } @Override public void onBitmapFailed(Drawable errorDrawable) { } @Override public void onPrepareLoad(Drawable placeHolderDrawable) { } }); count++; if (count == 5) break; } //set the adapter for the feed listview listView.setAdapter(new FeedAdapter(getActivity())); } @Override public void failure(RetrofitError error) { error.printStackTrace(); } }); return rootView; } // Lifecycle method to handle menu option actions @Override public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId(); //Allows the user to choose whether to take a new photo, or choose a photo from the Gallery if (id == R.id.action_camera) { AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); builder.setItems(R.array.camera_choices, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialogInterface, int position) { if (position == REQUEST_IMAGE_CAPTURE) { takePicture(); } else if (position == REQUEST_IMAGE_CHOOSE) { Intent choosePhotoIntent = new Intent(Intent.ACTION_GET_CONTENT); choosePhotoIntent.addCategory(Intent.CATEGORY_OPENABLE); choosePhotoIntent.setType("image/*"); startActivityForResult(choosePhotoIntent, REQUEST_IMAGE_CHOOSE); } } }); AlertDialog dialog = builder.create(); dialog.show(); } //Refreshes the feed by re-loading the freed fragment (which in turn calls the API for new data) if (id == R.id.action_refresh) { FragmentTransaction transaction = getFragmentManager().beginTransaction(); transaction.replace(R.id.content_frame, new FeedFragment()) .commit(); } return super.onOptionsItemSelected(item); } private void takePicture() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); //file to save in the cloud mPhotoFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); //Uri to store the photo locally in the Android Gallery mMediaUri = Uri.fromFile(mPhotoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, mMediaUri); if (takePictureIntent.resolveActivity(getActivity().getPackageManager()) != null) { startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE); } } //Lifecycle method called after a photo is taken or an existing image is selected by the user @Override public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == Activity.RESULT_OK) { if (requestCode == REQUEST_IMAGE_CAPTURE) { //add the photo to the Gallery Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); mediaScanIntent.setData(mMediaUri); getActivity().sendBroadcast(mediaScanIntent); Log.v(LOG_TAG, "Photo is called " + mPhotoFile.getName()); //upload the photo to the cloud try { InputStream photoStream = new FileInputStream(mPhotoFile); new AbelanaUpload(photoStream, mPhotoFile.getName()); Toast.makeText(getActivity(), getActivity().getString(R.string.photo_upload_success_message), Toast.LENGTH_SHORT) .show(); } catch (FileNotFoundException e) { e.printStackTrace(); } } else if (requestCode == REQUEST_IMAGE_CHOOSE) { Uri photoUri = data.getData(); try { InputStream photoStream = getActivity().getContentResolver().openInputStream(photoUri); String fileName = generateFileName() + ".jpg"; new AbelanaUpload(photoStream, fileName); Toast.makeText(getActivity(), getActivity().getString(R.string.photo_upload_success_message), Toast.LENGTH_SHORT) .show(); } catch (FileNotFoundException e) { e.printStackTrace(); } } } else if (resultCode != Activity.RESULT_CANCELED) { Toast.makeText(getActivity(), "error!", Toast.LENGTH_LONG).show(); } } /** Create a File for saving an image or video */ private File getOutputMediaFile(int type){ String appName = getActivity().getString(R.string.app_name); if (isExternalStorageAvailable()) { // To be safe, you should check that the SDCard is mounted // using Environment.getExternalStorageState() before doing this. File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES), appName); // This location works best if you want the created images to be shared // between applications and persist after your app has been uninstalled. // Create the storage directory if it does not exist if (! mediaStorageDir.exists()){ if (! mediaStorageDir.mkdirs()){ Log.d(appName, "failed to create directory"); return null; } } /* Create a media file name*/ String fileName = generateFileName(); Log.v(LOG_TAG, "File name is " + fileName); File mediaFile; if (type == MEDIA_TYPE_IMAGE){ mediaFile = new File(mediaStorageDir.getPath() + File.separator + fileName + ".jpg"); Log.d(LOG_TAG, "File: " + Uri.fromFile(mediaFile)); return mediaFile; } else { return null; } } else { return null; } } private String generateFileName() { //Get localID UserInfoStore client = new UserInfoStore(getActivity()); IdToken token = client.getSavedIdToken(); String localId = token.getLocalId(); //Generate 8 digit random number and encode it in base64 SecureRandom rand = new SecureRandom(); StringBuilder rand8Dig = new StringBuilder(8); for (int i=0; i < 8; i++) { rand8Dig.append((char)('0' + rand.nextInt(10))); } String randNum = rand8Dig.toString(); String randNumB64 = Utilities.base64Encoding(randNum); String fileName = localId + '.' + randNumB64; fileName += 'P'; //'P' marks that a photo is public, future versions will allow for private photos return fileName; } private boolean isExternalStorageAvailable() { String state = Environment.getExternalStorageState(); if (state.equals(Environment.MEDIA_MOUNTED)) { return true; } else { return false; } } }