/* * Copyright (C) 2012 The Serval Project * * This file is part of the Serval Maps Software * * Serval Maps Software 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. * * This source code 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 this source code; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package org.servalproject.maps; import java.io.File; import java.util.TimeZone; import org.servalproject.maps.location.LocationCollector; import org.servalproject.maps.protobuf.BinaryFileWriter; import org.servalproject.maps.provider.PointsOfInterestContract; import org.servalproject.maps.utils.FileUtils; import org.servalproject.maps.utils.HashUtils; import org.servalproject.maps.utils.MediaUtils; import android.app.Activity; import android.content.ContentValues; import android.content.Intent; import android.content.SharedPreferences; import android.database.SQLException; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.TextView; import android.widget.Toast; /** * an activity to solicit information about a POI */ public class NewPoiActivity extends Activity implements OnClickListener{ /* * private class level constants */ private final boolean V_LOG = false; private final String TAG = "NewPoiActivity"; private final int MAX_DESCRIPTION_CHARACTERS = 250; private final int MAX_TITLE_CHARACTERS = 50; private final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100; /* * private class level variables */ private TextView txtCharacters; private double latitude = -1; private double longitude = -1; private String phoneNumber; private String subscriberId; private Uri photoFileUri; /* * (non-Javadoc) * @see android.app.Activity#onCreate(android.os.Bundle) */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.new_poi); // see if a latitude and longitude has been passed across Bundle mBundle = this.getIntent().getExtras(); // if a bundle has been passed // extract the latitude and longitude for use later if(mBundle != null) { latitude = mBundle.getDouble("latitude"); longitude = mBundle.getDouble("longitude"); } else { Location mLocation = LocationCollector.getLocation(); if(mLocation != null) { latitude = mLocation.getLatitude(); longitude = mLocation.getLongitude(); } } // inform user of the character limit txtCharacters = (TextView) findViewById(R.id.new_poi_ui_txt_characters); txtCharacters.setText(Integer.toString(MAX_DESCRIPTION_CHARACTERS)); // watch for changes in the text of the description TextView mTextView = (TextView) findViewById(R.id.new_poi_ui_txt_description); mTextView.addTextChangedListener(descriptionWatcher); mTextView = (TextView) findViewById(R.id.new_poi_ui_txt_latitude); mTextView.setText(Double.toString(latitude)); mTextView = (TextView) findViewById(R.id.new_poi_ui_txt_longitude); mTextView.setText(Double.toString(longitude)); // listen for button presses Button mButton = (Button) findViewById(R.id.new_poi_ui_btn_save); mButton.setOnClickListener(this); mButton = (Button) findViewById(R.id.new_poi_ui_btn_photo); mButton.setOnClickListener(this); mButton = (Button) findViewById(R.id.new_poi_ui_btn_back); mButton.setOnClickListener(this); // get the mesh phone number and sid ServalMaps mApplication = (ServalMaps) getApplicationContext(); phoneNumber = mApplication.getPhoneNumber(); subscriberId = mApplication.getSid(); mApplication = null; // disable the manual entry of geo coordinates SharedPreferences mPreferences = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); boolean mPreference = mPreferences.getBoolean("preferences_map_new_poi_geocoords", false); View mLayout = (View) findViewById(R.id.new_poi_ui_geocord_layout); if(mPreference == false) { mLayout.setVisibility(View.GONE); } else { mLayout.setVisibility(View.VISIBLE); } } // keep track of the number of characters remaining in the description private final TextWatcher descriptionWatcher = new TextWatcher() { @Override public void afterTextChanged(Editable arg0) { // TODO Auto-generated method stub } @Override public void beforeTextChanged(CharSequence arg0, int arg1, int arg2, int arg3) { // TODO Auto-generated method stub } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { int mCharsRemaining = MAX_DESCRIPTION_CHARACTERS - s.length(); txtCharacters.setText(Integer.toString(mCharsRemaining)); } }; /* * (non-Javadoc) * @see android.view.View.OnClickListener#onClick(android.view.View) */ @Override public void onClick(View v) { String mMessage; // work out which element was clicked switch(v.getId()) { case R.id.new_poi_ui_btn_save: // save the new POI if(V_LOG) { Log.v(TAG, "save new poi button touched"); } // validate the title TextView mView = (TextView) findViewById(R.id.new_poi_ui_txt_title); if(TextUtils.isEmpty(mView.getText()) == true) { Toast.makeText(this, R.string.new_poi_toast_title_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } if(mView.getText().length() > MAX_TITLE_CHARACTERS) { mMessage = getString(R.string.new_poi_toast_title_too_long); Toast.makeText(this, String.format(mMessage, MAX_TITLE_CHARACTERS), Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } String mTitle = mView.getText().toString(); // validate the description mView = (TextView) findViewById(R.id.new_poi_ui_txt_description); if(TextUtils.isEmpty(mView.getText()) == true) { Toast.makeText(this, R.string.new_poi_toast_description_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } if(mView.getText().length() > MAX_DESCRIPTION_CHARACTERS) { mMessage = getString(R.string.new_poi_toast_description_too_long); Toast.makeText(this, String.format(mMessage, MAX_DESCRIPTION_CHARACTERS), Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } String mDescription = mView.getText().toString(); // validate the latitude mView = (TextView) findViewById(R.id.new_poi_ui_txt_latitude); if(TextUtils.isEmpty(mView.getText()) == true) { Toast.makeText(this, R.string.new_poi_toast_latitude_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } try { latitude = Double.parseDouble(mView.getText().toString()); } catch (NumberFormatException e) { Toast.makeText(this, R.string.new_poi_toast_latitude_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } // validate the longitude mView = (TextView) findViewById(R.id.new_poi_ui_txt_longitude); if(TextUtils.isEmpty(mView.getText()) == true) { Toast.makeText(this, R.string.new_poi_toast_longitude_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } try { longitude = Double.parseDouble(mView.getText().toString()); } catch (NumberFormatException e) { Toast.makeText(this, R.string.new_poi_toast_longitude_missing, Toast.LENGTH_SHORT).show(); mView.requestFocus(); return; } // add the new POI if(addNewPoi(mTitle, mDescription) == true) { finish(); } break; case R.id.new_poi_ui_btn_photo: // check to see if a photo has to be deleted to be tidy if(photoFileUri != null) { // delete the photo File mPhotoFile = new File(photoFileUri.getPath()); if(!mPhotoFile.delete()) { Log.w(TAG, "unable to delete unecessary photo: " + photoFileUri.toString()); } } // use the inbuilt camera app to take a photo Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); photoFileUri = MediaUtils.getOutputMediaFileUri(MediaUtils.MEDIA_TYPE_IMAGE); // create a file to save the image intent.putExtra(MediaStore.EXTRA_OUTPUT, photoFileUri); // set the image file name // start the image capture Intent startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE); break; case R.id.new_poi_ui_btn_back: // go back to the map // check to see if a photo has to be deleted to be tidy if(photoFileUri != null) { // delete the photo File mPhotoFile = new File(photoFileUri.getPath()); if(!mPhotoFile.delete()) { Log.w(TAG, "unable to delete unecessary photo: " + photoFileUri.toString()); } } finish(); break; default: // unknown view id Log.w(TAG, "unkown view id in onClick: " + v.getId()); } } /* * (non-Javadoc) * @see android.app.Activity#onBackPressed() */ @Override public void onBackPressed() { // fake a press on the back to map button Button mButton = (Button) findViewById(R.id.new_poi_ui_btn_back); this.onClick(mButton); } /* * (non-Javadoc) * @see android.app.Activity#onActivityResult(int, int, android.content.Intent) */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK) { // Image captured and saved to fileUri specified in the Intent // check on the returned intent data if(data == null) { // no return intent so check the uri that was supplied if(FileUtils.isFileReadable(photoFileUri.getPath()) == false) { photoFileUri = null; } } else { if(FileUtils.isFileReadable(data.getData().getPath()) == false) { photoFileUri = null; } } if(photoFileUri == null) { // no photo available, inform user Toast.makeText(this, R.string.new_poi_toast_no_photo, Toast.LENGTH_SHORT).show(); } } else if (resultCode == RESULT_CANCELED) { // User cancelled the image capture photoFileUri = null; } else { // Image capture failed, advise user Toast.makeText(this, R.string.new_poi_toast_no_photo, Toast.LENGTH_SHORT).show(); photoFileUri = null; } // check to see if we need to update the text of the photo if(photoFileUri != null) { Button mButton = (Button) findViewById(R.id.new_poi_ui_btn_photo); mButton.setText(getString(R.string.new_poi_ui_btn_photo_replace)); } } } // add the new POI to the database private boolean addNewPoi(String title, String description) { // add the new POI to the database ContentValues mValues = new ContentValues(); // check on the coordinates if(latitude == -1 || longitude == -1) { // show an error message Toast.makeText(this, R.string.new_poi_toast_location_error, Toast.LENGTH_SHORT).show(); return false; } // add rest of the fields mValues.put(PointsOfInterestContract.Table.PHONE_NUMBER, phoneNumber); mValues.put(PointsOfInterestContract.Table.SUBSCRIBER_ID, subscriberId); mValues.put(PointsOfInterestContract.Table.LATITUDE, latitude); mValues.put(PointsOfInterestContract.Table.LONGITUDE, longitude); mValues.put(PointsOfInterestContract.Table.TIMESTAMP, System.currentTimeMillis()); mValues.put(PointsOfInterestContract.Table.TIMEZONE, TimeZone.getDefault().getID()); mValues.put(PointsOfInterestContract.Table.TITLE, title); mValues.put(PointsOfInterestContract.Table.DESCRIPTION, description); // check to see if a photo is available if(photoFileUri != null) { // process the photo // generate a hash of this poi String mHash = HashUtils.hashPointOfInterestMessage( phoneNumber, latitude, longitude, title, description); // store the name of the photo String mPhotoName = MediaUtils.PHOTO_FILE_PREFIX + mHash + ".jpg"; // rename the file File mPhotoFile = new File(photoFileUri.getPath()); mPhotoName = mPhotoFile.getParent() + File.separator + mPhotoName; Log.v(TAG, mPhotoName); mPhotoFile.renameTo(new File(mPhotoName)); mValues.put(PointsOfInterestContract.Table.PHOTO, new File(mPhotoName).getName()); } try { Uri newRecord = getContentResolver().insert(PointsOfInterestContract.CONTENT_URI, mValues); BinaryFileWriter.writePointOfInterest(this, newRecord.getLastPathSegment()); if(V_LOG) { Log.v(TAG, "new POI record created with id: " + newRecord.getLastPathSegment()); } }catch (SQLException e) { Log.e(TAG, "unable to add new POI record", e); Toast.makeText(this, R.string.new_poi_toast_save_error, Toast.LENGTH_SHORT).show(); return false; } return true; } }