/*
* Copyright 2015 Lafayette College
*
* This file is part of OpenCVTour.
*
* OpenCVTour is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* OpenCVTour is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenCVTour. If not, see <http://www.gnu.org/licenses/>.
*/
package alicrow.opencvtour;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.location.Location;
import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.io.IOException;
import java.util.ArrayList;
/*
* Activity to edit an item in a tour.
*/
public class EditTourItemActivity extends AppCompatActivity implements View.OnClickListener, AbsListView.MultiChoiceModeListener {
private static final String TAG = "EditTourItemActivity";
/// Request codes for Intents
public static final int EDIT_TOUR_ITEM_REQUEST = 0x0003;
private TourItem _tour_item;
private ArrayList<String> _images_selected = new ArrayList<>();
private Uri _photo_uri; /// uri we told the camera app to save to. We store this so we know where to find the image after the camera app returns
private Menu _context_menu;
private MediaPlayer _player = null;
private MediaRecorder _recorder = null;
private boolean _recording = false;
private boolean _playing = false;
private LocationService.ServiceConnection _connection;
private boolean _service_is_bound = false;
private void bindLocationService() {
_connection = new LocationService.ServiceConnection();
bindService(new Intent(this, LocationService.class), _connection, Context.BIND_AUTO_CREATE);
_service_is_bound = true;
}
private void unbindLocationService() {
if (_service_is_bound) {
unbindService(_connection);
_service_is_bound = false;
}
}
/**
* Adapter to display thumbnails of the TourItem's images
*/
public class TourItemImageAdapter extends BaseAdapter {
private TourItem _item;
public TourItemImageAdapter(TourItem item) {
_item = item;
}
@Override
public int getCount() {
return _item.getImageFilepaths().size();
}
@Override
public Object getItem(int position) {
return _item.getImageFilepaths().get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, final ViewGroup parent) {
Log.v(TAG, "getView called");
final ImageView image_view;
final FrameLayout frame_layout;
if (convertView == null) {
// We don't have an existing view to convert, so we need to create a new view
Log.v(TAG, "creating new ImageView");
frame_layout = (FrameLayout) getLayoutInflater().inflate(R.layout.selectable_image, parent, false);
image_view = (ImageView) frame_layout.findViewById(R.id.image);
image_view.setScaleType(ImageView.ScaleType.CENTER_CROP);
} else {
/// Recycle convertView for better performance
frame_layout = (FrameLayout) convertView;
image_view = (ImageView) frame_layout.findViewById(R.id.image);
}
/// Load image thumbnail
String image_filename = _item.getImageFilepaths().get(position);
int column_width = ((GridView) parent).getRequestedColumnWidth() - Utilities.dp_to_px(8); /// 8dp padding;
Utilities.loadBitmap(image_view, image_filename, column_width, column_width, EditTourItemActivity.this);
return frame_layout;
}
}
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
/// called when an image is selected or deselected.
if(checked)
_images_selected.add(_tour_item.getImageFilepaths().get(position));
else
_images_selected.remove(_tour_item.getImageFilepaths().get(position));
/// If only a single image is selected, the user can set that as the main image, so we add that option to the contextual action bar
if(_images_selected.size() == 1)
_context_menu.findItem(R.id.menu_set_as_main_image).setVisible(true);
else
_context_menu.findItem(R.id.menu_set_as_main_image).setVisible(false);
}
@Override
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
/// Called when a button is clicked on the contextual action bar
switch (item.getItemId()) {
case R.id.menu_delete: {
for(String image : _images_selected) {
_tour_item.removeImage(image);
}
mode.finish();
return true;
}
case R.id.menu_set_as_main_image: {
_tour_item.setMainImage(_images_selected.get(0));
mode.finish();
return true;
}
default:
return false;
}
}
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
Log.d(TAG, "onCreateActionMode called");
Log.d(TAG, _images_selected.size() + " images selected");
mode.getMenuInflater().inflate(R.menu.context_menu_edit_tour_item, menu);
_context_menu = menu;
/// If only a single image is selected, the user can set that as the main image, so we add that option to the contextual action bar
if(_images_selected.size() == 1)
_context_menu.findItem(R.id.menu_set_as_main_image).setVisible(true);
else
_context_menu.findItem(R.id.menu_set_as_main_image).setVisible(false);
return true;
}
@Override
public void onDestroyActionMode(ActionMode mode) {
_images_selected.clear();
}
@Override
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
// Perform updates to the CAB due to an invalidate() request
Log.d(TAG, "onPrepareActionMode called");
return false;
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindLocationService();
}
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume called");
if(_connection != null && _connection.getService() != null)
_connection.getService().startLocationUpdates();
}
@Override
protected void onStop() {
super.onStop();
/// Unless we're just reconfiguring the UI (due to screen rotation or similar), we should stop location updates, since we only need them when this activity is running.
if(!isChangingConfigurations())
if(_connection != null && _connection.getService() != null)
_connection.getService().stopLocationUpdates();
}
@Override
protected void onCreate(Bundle savedInstanceState) {
Log.d(TAG, "onCreate called");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_tour_item);
/// Find the TourItem we're editing, and display its current values.
Bundle bundle = getIntent().getExtras();
short position_in_tour = bundle.getShort("position");
_tour_item = Tour.getCurrentTour().getTourItems().get(position_in_tour);
/// GridView of images in the TourItem
ExpandableHeightGridView gridview = (ExpandableHeightGridView) findViewById(R.id.gridview);
gridview.setAdapter(new TourItemImageAdapter(_tour_item));
gridview.setChoiceMode(GridView.CHOICE_MODE_MULTIPLE_MODAL);
gridview.setMultiChoiceModeListener(this);
gridview.setExpanded(true);
((EditText) findViewById(R.id.edit_tour_item_name)).setText(_tour_item.getName());
((EditText) findViewById(R.id.edit_tour_item_description)).setText(_tour_item.getDescription());
((EditText) findViewById(R.id.edit_tour_item_directions)).setText(_tour_item.getDirections());
if(_tour_item.getLocation() != null) {
((TextView) findViewById(R.id.tour_item_location)).setText("location: " + _tour_item.getLocation().getLatitude() + ", " + _tour_item.getLocation().getLongitude() + ", accuracy: " + _tour_item.getLocation().getAccuracy() + " meters");
}
findViewById(R.id.image_picker).setOnClickListener(this);
findViewById(R.id.get_current_gps_location).setOnClickListener(this);
findViewById(R.id.record_audio).setOnClickListener(this);
findViewById(R.id.play_audio).setOnClickListener(this);
if(_tour_item.hasAudioFile())
findViewById(R.id.play_audio).setVisibility(View.VISIBLE);
else
findViewById(R.id.play_audio).setVisibility(View.INVISIBLE);
bindLocationService();
/// Load stuff from a previous instance
if(savedInstanceState != null) {
if (savedInstanceState.containsKey("_photo_uri")) {
_photo_uri = Uri.parse(savedInstanceState.getString("_photo_uri"));
}
if(savedInstanceState.containsKey("_images_selected")) {
_images_selected = savedInstanceState.getStringArrayList("_images_selected");
}
}
}
/// Applies changes made to the TourItem.
private void applyChanges() {
_tour_item.setName(((EditText) findViewById(R.id.edit_tour_item_name)).getText().toString());
_tour_item.setDescription(((EditText) findViewById(R.id.edit_tour_item_description)).getText().toString());
_tour_item.setDirections(((EditText) findViewById(R.id.edit_tour_item_directions)).getText().toString());
}
@Override
public void onBackPressed() {
/// Apply the changes the user made, and then finish the activity. Without this, all changes would be lost as soon as the user hits the back button.
applyChanges();
setResult(RESULT_OK);
finish();
}
@Override
public void onClick(View v) {
switch(v.getId()) {
case R.id.image_picker:
_photo_uri = Utilities.takePicture(this, false);
break;
case R.id.record_audio: {
_recording = !_recording;
if(_recording) {
((TextView) v).setText("stop recording");
startRecording();
} else {
((TextView) v).setText("record audio");
stopRecording();
}
break;
}
case R.id.play_audio: {
_playing = !_playing;
if(_playing) {
((TextView) v).setText("stop playing");
startPlaying();
} else {
((TextView) v).setText("play audio");
stopPlaying();
}
break;
}
case R.id.get_current_gps_location: {
Location location = _connection.getService().getCurrentLocation();
if (location == null) {
Log.e(TAG, "got null current location");
Toast.makeText(this, "Could not determine location. Make sure you have location enabled on your device, and/or wait a few seconds and try again.", Toast.LENGTH_LONG).show();
} else {
_tour_item.setLocation(location);
((TextView) findViewById(R.id.tour_item_location)).setText("location: " + location.getLatitude() + ", " + location.getLongitude() + ", accuracy: " + location.getAccuracy() + " meters");
}
break;
}
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(resultCode == Activity.RESULT_OK && requestCode == Utilities.REQUEST_IMAGE_CAPTURE) {
_tour_item.addImageFilepath(_photo_uri.getPath());
Log.i(TAG, "saved photo as " + _photo_uri.toString());
GridView gridview = (GridView) findViewById(R.id.gridview);
((TourItemImageAdapter) gridview.getAdapter()).notifyDataSetChanged();
}
}
/// Play the item's audio recording
private void startPlaying() {
_player = new MediaPlayer();
try {
_player.setDataSource(_tour_item.getAudioFilepath());
_player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@Override
public void onCompletion(MediaPlayer mp) {
((TextView) findViewById(R.id.play_audio)).setText("play audio");
_playing = false;
stopPlaying();
}
});
_player.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
mp.start();
}
});
_player.prepareAsync();
} catch (IOException e) {
Log.e(TAG, "prepare() failed");
}
}
private void stopPlaying() {
_player.release();
_player = null;
}
/// Record audio for the item.
private void startRecording() {
_recorder = new MediaRecorder();
_recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
_recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
_recorder.setOutputFile(_tour_item.getAudioFilepath());
_recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
try {
_recorder.prepare();
} catch (IOException e) {
Log.e(TAG, "prepare() failed");
}
_recorder.start();
}
private void stopRecording() {
_recorder.stop();
_recorder.release();
_recorder = null;
findViewById(R.id.play_audio).setVisibility(View.VISIBLE);
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
/// Activity must be destroyed and recreated when the screen orientation changes, so we need to save some things so the activity can be recreated if needed.
/// If the user changed the phone's orientation to take a better photo, our activity will be destroyed and recreated upon returning from the camera app, so we need to save the location of the photo.
if(_photo_uri != null)
outState.putString("_photo_uri", _photo_uri.toString());
outState.putStringArrayList("_images_selected", _images_selected);
}
}