package org.greenstone.android.tipple.base; import java.io.BufferedWriter; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.InputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.Locale; import android.location.Criteria; import android.location.LocationManager; import android.os.Environment; import android.preference.PreferenceManager; import android.speech.tts.TextToSpeech; import android.speech.tts.TextToSpeech.OnInitListener; import android.text.method.ScrollingMovementMethod; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.app.Activity; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.res.AssetManager; import android.location.LocationListener; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.Toast; import android.widget.LinearLayout.LayoutParams; import android.widget.TextView; import org.greenstone.android.tipple.R; import org.json1.JSONStringer; import org.json1.JSONWriter; import org.mapsforge.android.maps.MapActivity; import org.mapsforge.android.maps.MapView; public class TippleActivity extends MapActivity implements OnInitListener { private final boolean debug_mode = true; enum TextAudioModeEnum { TEXT_ONLY, TEXT_PLUS_AUDIO, AUDIO_ONLY, AUDIO_PLUS_TEXT }; /* * GPS coords point to ==> uni rec centre * TODO: Rather than hardcode a "starting point" location as shown below, get the last * known location of the user. */ private static final double DEFAULT_NO_GPS_LONGITUDE = 175.3141; private static final double DEFAULT_NO_GPS_LATITUDE = -37.7876; private static boolean first_time_starting = true; // File storage protected static String geodataDirectory; protected static String logDirectory; protected static String audioDirectory; // Preferences public static SharedPreferences sharedPreferences; public static boolean centreOnGPSLocation; public static boolean showScaleBar; public static boolean showSights; public static boolean showUserTrail; public static TextAudioModeEnum textAudioMode; // Logging public static TippleLog log; // Views protected TextView longlat_view_; protected MapView map_view_; protected TextView text_view_; protected FrameLayout text_map_composite_; protected AudioPlayerView audio_player_view_; protected ArrayList user_trail_ = null; protected String user_trail_filename_ = null; // Text to Speech protected TextToSpeech tts_ = null; protected TipLocationManager tip_location_manager_ = null; protected LocationManager location_manager_ = null; protected LocationListener location_listener_ = null; // Request codes protected final int TTS_DATA_CHECK = 0; private static final int SELECT_LOG_FILE = 1; public static String DEFAULT_LOC_FILE = "hamilton"; public static String getGeodataDirectory() { return geodataDirectory; } public static String getLogDirectory() { return logDirectory; } public static String getAudioDirectory() { return audioDirectory; } /** * * @param asset_filename * @param external_storage_filename * @param debug_mode If this is set to true, then we will always push out new files to the SD card on Tipple launch * @return */ protected boolean assetToExternalStorage(String asset_filename, String external_storage_filename, boolean debug_mode) { boolean status = true; try { File external_storage_file = new File(external_storage_filename); if (!external_storage_file.exists() || (debug_mode)) { // Need to create the /sdcard version AssetManager assetManager = getAssets(); InputStream ais = assetManager.open(asset_filename); // ais = asset input stream FileOutputStream fos = new FileOutputStream(external_storage_file); // fos = file output stream byte [] buffer = new byte[1024]; int buflen; while ((buflen = ais.read(buffer))>0) { fos.write(buffer,0,buflen); } ais.close(); fos.close(); } } catch (Exception e) { e.printStackTrace(); status = false; } return status; } protected boolean initTippleStore(String location) { DEFAULT_LOC_FILE = location; // Locate map data directory File external_root_file = Environment.getExternalStorageDirectory(); String external_root = external_root_file.getAbsolutePath(); String tipple_store_dir = external_root + "/tipple-store"; geodataDirectory = tipple_store_dir + "/geodata/"; logDirectory = tipple_store_dir + "/logs/"; audioDirectory = tipple_store_dir + "/audio/"; File tipple_store_file = new File(tipple_store_dir); if (!tipple_store_file.exists()) { if (!tipple_store_file.mkdir()) { String error = "Failed to create directory: " + tipple_store_dir; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } } File geodataDirFile = new File(geodataDirectory); if (!geodataDirFile.exists()) { if (!geodataDirFile.mkdir()) { String error = "Failed to create directory: " + geodataDirectory; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } } File logDirFile = new File(logDirectory); if (!logDirFile.exists()) { if (!logDirFile.mkdir()) { String error = "Failed to create directory: " + logDirectory; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } } File audioDirFile = new File(audioDirectory); if (!audioDirFile.exists()) { if (!audioDirFile.mkdir()) { String error = "Failed to create directory: " + audioDirectory; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } } // dig out the audio files String[] audio_filenames = { "BeepMorseSonar.wav","Ding.wav","DingExtended.wav" }; for (String audio_filename: audio_filenames) { String audio_asset_filename = "audio/" + audio_filename; String audio_exstore_filename = audioDirectory + audio_filename; if (!assetToExternalStorage(audio_asset_filename,audio_exstore_filename,debug_mode)) { String error = "Failed to transfer asset " + audio_asset_filename + " to " + audio_exstore_filename; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } } String loc_asset_filename = "geodata/" + DEFAULT_LOC_FILE + ".loc"; String loc_exstore_filename = geodataDirectory + DEFAULT_LOC_FILE + ".loc"; if (!assetToExternalStorage(loc_asset_filename,loc_exstore_filename,debug_mode)) { String error = "Failed to transfer asset " + loc_asset_filename + " to " + loc_exstore_filename; System.err.println(error); Toast.makeText(this, error, Toast.LENGTH_SHORT).show(); return false; } return true; } protected boolean initTippleStore() { return initTippleStore(null); } protected TextView createLongLatView() { // Lat,Long line at top TextView longlat_view = new TextView(this); longlat_view.setText("(Long,Lat): "); LayoutParams longlat_view_layout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 35, 0); longlat_view.setLayoutParams(longlat_view_layout); return longlat_view; } protected MapView createMapView(String map_filename) { // Map view in middle MapView map_view = new MapView(this); map_view.setClickable(true); map_view.setBuiltInZoomControls(true); String full_map_filename = geodataDirectory + map_filename; //String full_map_filename = geodataDirectory + "mymap.map"; map_view.setMapFile(new File(full_map_filename)); LayoutParams map_view_layout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1); map_view.setLayoutParams(map_view_layout); return map_view; } protected MapView createMapViewFromAsset(String map_filename) { // Transfer asset to file in dataDirectory if it does not already exist String asset_filename = "geodata" + File.separator + map_filename; String external_storage_filename = geodataDirectory + map_filename; assetToExternalStorage(asset_filename,external_storage_filename,debug_mode); return createMapView(map_filename); } protected TextView createTextView() { // Text view in middle (ultimately on top of map view) TextView text_view = new TextView(this); LayoutParams text_view_layout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1); text_view.setLayoutParams(text_view_layout); text_view.setGravity(Gravity.BOTTOM|Gravity.FILL_HORIZONTAL); text_view.setPadding(12, 12, 12, 12); text_view.setTextColor(0xffffffff); text_view.setBackgroundColor(0xCC000000); text_view.setTextSize(20.0f); text_view.setVerticalScrollBarEnabled(true); text_view.setScrollBarStyle(View.SCROLLBARS_INSIDE_INSET); text_view.setMovementMethod(new ScrollingMovementMethod()); text_view.setVisibility(View.INVISIBLE); return text_view; } protected FrameLayout compositTextViewOnMapView(MapView map_view, TextView text_view) { FrameLayout frame_layout = new FrameLayout(this); LayoutParams view_layout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT, 1); frame_layout.setLayoutParams(view_layout); frame_layout.addView(map_view); frame_layout.addView(text_view); return frame_layout; } protected AudioPlayerView createAudioPlayerView() { // Player at the bottom AudioPlayerView audio_player_view = new AudioPlayerView(this, text_view_); LayoutParams audio_player_layout = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT, 0); audio_player_view.setLayoutParams(audio_player_layout); return audio_player_view; } protected void refreshPreferences() { // Get the preferences and start storing them in our class's variables sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // set the default values, but only if the user hasn't changed the preferences themselves PreferenceManager.setDefaultValues(this, R.xml.preferences, false); //shared_preferences_ = getPreferences(MODE_PRIVATE); showScaleBar = sharedPreferences.getBoolean("showScaleBar", false); centreOnGPSLocation = sharedPreferences.getBoolean("centreOnGPSLoc", false); showSights = sharedPreferences.getBoolean("showSights", true); showUserTrail = sharedPreferences.getBoolean("showUserTrail", false); String textAudioModeStr = sharedPreferences.getString("textAudioMode", "AUDIO_PLUS_TEXT"); textAudioMode = Enum.valueOf(TextAudioModeEnum.class, textAudioModeStr); } @Override protected void onResume() { System.err.println("*** TippleActivity::onResume()"); super.onResume(); refreshPreferences(); audio_player_view_.refreshView(); map_view_.getMapScaleBar().setShowMapScaleBar(showScaleBar); /* * The code below is REALLY bad, but I decided to comment it out instead. * TippleActivity's onResume() gets called many times, and so TipLocationManager * is actually adding lots and lots of tour locations. */ /* if (tip_location_manager_ != null) { if (showSights) { //tip_location_manager_.createTourLocationsOverlay(map_view_,locations_); tip_location_manager_.addTourLocations(); } else { tip_location_manager_.removeTourLocations(); } if (user_trail_filename_ != null) { if (showUserTrail) { //tip_location_manager_.createLogRouteLocationsOverlay(map_view_,user_trail_); tip_location_manager_.addLogRouteLocations(); } else { tip_location_manager_.removeLogRouteLocations(); } } } */ } public void buildAlertMessageNoGps() { final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Your GPS doesn't seem to be enabled. Enable it then re-launch Tipple.") .setCancelable(false) .setPositiveButton("Enable in Settings", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } }); final AlertDialog alert = builder.create(); alert.show(); } protected void onActivityResult(int requestCode, int resultCode, Intent data) { Log.d("TippleActivity::onActivityResult()", "requestCode: " + requestCode); if (requestCode == TTS_DATA_CHECK) { if (resultCode == TextToSpeech.Engine.CHECK_VOICE_DATA_PASS) { if(first_time_starting) { // success, create the TTS instance tts_ = new TextToSpeech(this, (TextToSpeech.OnInitListener) this); tts_.setLanguage(Locale.UK); // now TTS initialized, init TTS aware place-locations String loc_filename = geodataDirectory + "hamilton.loc"; System.err.println("*** loc filename = " + loc_filename); tip_location_manager_ = new TipLocationManager(this, audio_player_view_, tts_, map_view_); tip_location_manager_.createTipLocations(DEFAULT_NO_GPS_LONGITUDE, DEFAULT_NO_GPS_LATITUDE); tip_location_manager_.createMapLocations(); // Set up a callback method for handling GPS location updates connected with our place-locations location_manager_ = (LocationManager)getSystemService(Context.LOCATION_SERVICE); if ( !location_manager_.isProviderEnabled( LocationManager.GPS_PROVIDER ) ) { buildAlertMessageNoGps(); } else { location_listener_ = new TippleLocationListener(this,map_view_,longlat_view_,tip_location_manager_); Criteria location_criteria_ = new Criteria(); location_criteria_.setAccuracy(Criteria.ACCURACY_LOW); location_manager_.requestLocationUpdates(location_manager_.getBestProvider(location_criteria_, true), 0, 0, location_listener_); } tip_location_manager_.addMapLocations(); first_time_starting = false; } } else { // missing data, install it Intent installIntent = new Intent(); installIntent.setAction( TextToSpeech.Engine.ACTION_INSTALL_TTS_DATA); startActivity(installIntent); } } else if (requestCode == SELECT_LOG_FILE) { if (resultCode == RESULT_OK) { if (data != null && data.getStringExtra("logFile") != null) { if (user_trail_filename_ != null) { // have a previously loaded route => scrub it tip_location_manager_.removeLogRouteLocations(); user_trail_filename_ = null; } // set up route overlay user_trail_filename_ = data.getStringExtra("logFile"); user_trail_ = tip_location_manager_.loadUserTrail(user_trail_filename_); tip_location_manager_.createLogRouteLocationsOverlay(user_trail_); } } else if (resultCode == RESULT_CANCELED) { // nothing to do } } } @Override public void onInit(int status) { Log.d("TippleActivity::onInit()", "Status (zero == good) is: " + status); if (status == TextToSpeech.SUCCESS) { Log.d("TippleActivity::onInit()","Text-to-speech engine initialized"); } else if (status == TextToSpeech.ERROR) { Log.e("TippleActivity::onInit()","Text-to-speech engine failed initialized"); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.options_menu, menu); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int item_id = item.getItemId(); if (item_id == R.id.menu_info) { startActivity(new Intent(this, InfoView.class)); return true; } else if (item_id == R.id.menu_preferences) { startActivity(new Intent(this, EditPreferences.class)); return true; } else if (item_id == R.id.menu_logfile) { startActivityForResult(new Intent(this, TippleLogFileBrowser.class), SELECT_LOG_FILE); return true; } else { return false; } } @Override protected void onStop() { System.err.println("*** TippleActivity::onStop()"); super.onStop(); // Is this needed? // Go through an Editor object to make preference changes stick SharedPreferences.Editor editor = sharedPreferences.edit(); editor.commit(); } @Override protected void onPause() { System.err.println("*** TippleActivity::onPause()"); super.onPause(); } @Override protected void onDestroy() { System.err.println("*** TippleActivity::onDestroy()"); super.onDestroy(); tts_.shutdown(); tts_ = null; // Switch off location updates if (location_manager_ != null) { if (location_listener_ != null) { location_manager_.removeUpdates(location_listener_); location_listener_ = null; } location_manager_ = null; user_trail_filename_ = null; } // http://stackoverflow.com/questions/3957253/what-is-this-log-when-i-coded-thread-stop TipLocationManager.stopOverlayThreads(); log.optStopLog(); } }