Java tutorial
/*------------------------------------------------------------------------------ ** Ident: Fabrice Veniard ** Author: Fabrice Veniard ** Copyright: (c) May 26, 2012 Fabrice Veniard All Rights Reserved. **------------------------------------------------------------------------------ ** Fabrice Veniard | No part of this file may be reproduced ** @f8full | or transmitted in any form or by any ** | means, electronic or mechanical, for the ** H2J Montral | purpose, without the express written ** Qubec | permission of the copyright holder. *------------------------------------------------------------------------------ * * This file is part of casserolesencours. * * casserolesencours 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. * * casserolesencours 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 casserolesencours. If not, see <http://www.gnu.org/licenses/>. * */ package com.f8full.casserolesencours; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileWriter; import java.io.IOException; import java.io.InputStreamReader; import java.net.URLEncoder; import java.text.DateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.concurrent.Future; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; import android.app.Activity; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import android.widget.ToggleButton; import au.com.bytecode.opencsv.CSVReader; import com.alohar.core.Alohar; import com.alohar.user.callback.ALEventListener; import com.alohar.user.callback.ALMotionListener; import com.alohar.user.content.ALMotionManager; import com.alohar.user.content.ALPlaceManager; import com.alohar.user.content.data.ALEvents; import com.alohar.user.content.data.MotionState; //import com.alohar.user.content.data.UserStay; import com.f8full.casserolesencours.R; import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; import com.google.api.client.auth.oauth2.ClientParametersAuthentication; import com.google.api.client.auth.oauth2.TokenResponse; import com.google.api.client.googleapis.GoogleUrl; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; import com.google.api.client.googleapis.services.GoogleClient; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpHeaders; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponseException; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.http.xml.atom.AtomContent; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.jackson.JacksonFactory; import com.google.api.client.util.GenericData; import com.google.api.client.xml.XmlNamespaceDictionary; /*import com.google.api.client.googleapis.services.GoogleClient; import com.google.api.services.docs.DocsClient; import com.google.api.services.docs.DocsUrl; import com.google.api.services.docs.model.DocumentListEntry; import com.google.api.services.docs.model.DocumentListFeed;*/ import org.json.JSONException; import org.json.JSONObject; import org.livinglabmontreal.AtomCategory; import org.livinglabmontreal.AtomRole; import org.livinglabmontreal.AtomScope; import org.livinglabmontreal.OAuth2AccessTokenActivity; import org.livinglabmontreal.OAuth2ClientCredentials; import com.google.api.client.googleapis.services.GoogleClient.Builder; public class CasserolesEnCoursActivity extends Activity implements ALEventListener, ALMotionListener { private static final String PREF_NAME = "casserolesencours"; private static final String PREF_KEY = "aloharuid"; /////////////////////////////////////////////////////////////////////////////////// public static final int APP_ID = 111; //////////////////////////////////////////////////////////////////////////////////////// //Account casserolesencours@gmail.com public static final String API_KEY = "47e49bd099908705ae5ec227de1bab7cdef20b45"; private static final String PREF_REFRESH_TOKEN = "refreshToken"; private static final int REQUEST_OAUTH2_AUTHENTICATE = 0; private static final String SERVICE_URL = "https://www.google.com/fusiontables/api/query/"; /////////////////////////////////////////////////////////////////////////////////////// private static final String PREF_TABLE_ENCID = "tableEncID"; private static final String PREF_TABLE_STATUS = "tableStatus"; private static final String PREF_REGREQUESTTABLE_ENCID = "reqTableID"; private static final String PREF_VIEWONMASTERTABLE_ENCID = "viewOnMasterTableID"; public String mFusionTableEncID; public String mRegisterRequestTableID; public String mViewOnMasterID; String NOTIFIACTION_SERVICE_STRING = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager; Notification mNotification; private ALMotionManager mMotionManager; private boolean mIsStationary = true; private ScheduledThreadPoolExecutor mLocationPollThreadExecutor = new ScheduledThreadPoolExecutor(20); View mAloharAuthLayout, mMainLayout, mProgress; TextView mAccountView, mStatusView; /** The main handler. */ Handler mMainHandler; /** The uid. */ public String mAloharUid; private Future<?> mLocPollFrq0 = null; private ALPlaceManager mPlaceManager; private Future<?> mLocPollFrq1 = null; private Future<?> mLocPollFrq2 = null; private Future<?> mLocPollFrq3 = null; private Alohar mAlohar; private SharedPreferences mPrefs; private ToggleButton mServiceToggleButton; //private EventsManager mEventManager; private EditText mUIDView; GoogleCredential mGOOGCredential;// = new GoogleCredential(); HttpTransport mNetHttpTransport = new NetHttpTransport(); JsonFactory mJaksonJSONFactory = new JacksonFactory(); GoogleClient mGOOGClient;// = new GoogleClient(mNetHttpTransport, mJaksonJSONFactory, SERVICE_URL); private TextView mPollFrequencyText; private Spinner mTimeFilterSpinner; private Spinner mDistanceFilterSpinner; private ArrayAdapter<CharSequence> mTimeFilterAdapter; private ArrayAdapter<CharSequence> mDistanceFilterAdapter; /** Logging level for HTTP requests/responses. */ private static final Level LOGGING_LEVEL = Level.ALL; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Logger.getLogger("com.google.api.client").setLevel(LOGGING_LEVEL); //That ease dev of technical stuff BUT is not wanted on a mid/longer term setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); mTimeFilterSpinner = (Spinner) findViewById(R.id.timeFilterSpinner); mTimeFilterAdapter = ArrayAdapter.createFromResource(this, R.array.timeFilter_choices, android.R.layout.simple_spinner_item); mTimeFilterAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mTimeFilterSpinner.setAdapter(mTimeFilterAdapter); mDistanceFilterSpinner = (Spinner) findViewById(R.id.distanceFilterSpinner); mDistanceFilterAdapter = ArrayAdapter.createFromResource(this, R.array.distanceFilter_choices, android.R.layout.simple_spinner_item); mDistanceFilterAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mDistanceFilterSpinner.setAdapter(mDistanceFilterAdapter); mAloharAuthLayout = findViewById(R.id.auth_layout); mMainLayout = findViewById(R.id.main_layout); mProgress = findViewById(R.id.progress_spin); mAccountView = (TextView) findViewById(R.id.account); mMainHandler = new Handler(); mStatusView = (TextView) findViewById(R.id.service_status); mServiceToggleButton = (ToggleButton) findViewById(R.id.toggle); mUIDView = (EditText) findViewById(R.id.uid); mNotificationManager = (NotificationManager) getSystemService(NOTIFIACTION_SERVICE_STRING); mAlohar = Alohar.init(getApplication()); mPlaceManager = mAlohar.getPlaceManager(); mMotionManager = mAlohar.getMotionManager(); //mEventManager = EventsManager.getInstance(); //register listener //mPlaceManager.registerPlaceEventListener(mEventManager); mMotionManager.registerMotionListener(this); //Alohar original, I'm testing the other one //mPrefs = PreferenceManager.getDefaultSharedPreferences(this); mPrefs = getSharedPreferences(PREF_NAME, MODE_PRIVATE); mAloharUid = mPrefs.getString(PREF_KEY, null); if (mAloharUid == null) { mAlohar.register(APP_ID, API_KEY, this); } mFusionTableEncID = mPrefs.getString(PREF_TABLE_ENCID, null); mRegisterRequestTableID = mPrefs.getString(PREF_REGREQUESTTABLE_ENCID, null); if (mRegisterRequestTableID == null) { ((Button) findViewById(R.id.refreshRegistration)).setEnabled(false); } else { ((Button) findViewById(R.id.refreshRegistration)).setEnabled(true); ((Button) findViewById(R.id.registerTable)).setEnabled(false); } mViewOnMasterID = mPrefs.getString(PREF_VIEWONMASTERTABLE_ENCID, null); if (mViewOnMasterID == null) { ((Button) findViewById(R.id.refreshRegistration)).setEnabled(true); } else { ((Button) findViewById(R.id.refreshRegistration)).setEnabled(false); } if (mAlohar.isServiceRunning()) { //findViewById(R.id.timeFilterSpinner).setClickable(true); mDistanceFilterSpinner.setEnabled(true); } else { //findViewById(R.id.timeFilterSpinner).setClickable(false); mDistanceFilterSpinner.setEnabled(false); } String tableStatus = mPrefs.getString(PREF_TABLE_STATUS, null); if (tableStatus == null) { tableStatus = getString(R.string.geolocationStatusRegRequired); //Setup interface //Done in XML file } else { //setup interface if (tableStatus.equals(getString(R.string.geolocationAnonymizePending))) { ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_orange)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationAnonymizePending)); ((Button) findViewById(R.id.registerAnonymize)).setVisibility(View.GONE); ((Button) findViewById(R.id.checkAnonymize)).setVisibility(View.VISIBLE); } else if (tableStatus.equals(getString(R.string.geolocationAnonymizeProcessed))) { ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_green)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationAnonymizeProcessed)); ((Button) findViewById(R.id.checkAnonymize)).setVisibility(View.GONE); ((Button) findViewById(R.id.registerAnonymize)).setVisibility(View.GONE); ((Button) findViewById(R.id.logWithGoogle)).setVisibility(View.GONE); ((Button) findViewById(R.id.toggleGeolocation)).setVisibility(View.VISIBLE); findViewById(R.id.geolocationServiceStatus).setVisibility(View.VISIBLE); findViewById(R.id.myDataCheckbox).setEnabled(true); } else if (tableStatus.equals(getString(R.string.geolocationStatusRegStart))) { findViewById(R.id.logWithGoogle).setVisibility(View.GONE); findViewById(R.id.registerAnonymize).setVisibility(View.VISIBLE); ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_orange)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationStatusRegStart)); } } ((TextView) findViewById(R.id.tableStatus)).setText(tableStatus); if (mAloharUid != null) { mUIDView.setText(String.valueOf(mAloharUid)); onAuthenClick(mUIDView); } else { mAloharAuthLayout.setVisibility(View.VISIBLE); } mGOOGCredential = new GoogleCredential.Builder().setTransport(mNetHttpTransport) .setJsonFactory(mJaksonJSONFactory)//.build(); .setClientSecrets(OAuth2ClientCredentials.CLIENT_ID, OAuth2ClientCredentials.CLIENT_SECRET).build(); mGOOGCredential.setAccessToken(null); mGOOGCredential.setRefreshToken(mPrefs.getString(PREF_REFRESH_TOKEN, null)); //Logger.getLogger("com.google.api.client").setLevel(LOGGING_LEVEL); Builder truc = GoogleClient.builder(mNetHttpTransport, mJaksonJSONFactory, new GenericUrl(SERVICE_URL)); truc.setHttpRequestInitializer(mGOOGCredential); mGOOGClient = truc.build(); mPollFrequencyText = (TextView) findViewById(R.id.pollFrequencyText); mPollFrequencyText.setText("N/A when stationary"); //Something is wrong with the color, I'll see that cosmetic side later //mPollFrequencyText.setTextColor(R.color.text_violet); mLocationPollThreadExecutor.setKeepAliveTime(0, TimeUnit.SECONDS); RadioGroup pollFrequencyRadioGroup = (RadioGroup) findViewById(R.id.pollFrequencyRadioGroup); pollFrequencyRadioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { @Override public void onCheckedChanged(RadioGroup group, int checkedId) { cancelActiveTasks(); startTaskForId(checkedId, true); } }); findViewById(R.id.frequency0).setEnabled(false); findViewById(R.id.frequency1).setEnabled(false); findViewById(R.id.frequency2).setEnabled(false); findViewById(R.id.frequency3).setEnabled(false); } /* (non-Javadoc) * @see android.app.Activity#onPostCreate(android.os.Bundle) */ @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); //update the title String title = String.format("%s v%s %d", getString(R.string.app_name), getString(R.string.version), mAlohar.version()); setTitle(title); } /* (non-Javadoc) * @see android.app.Activity#onResume() */ @Override protected void onResume() { // TODO Auto-generated method stub super.onResume(); updateServiceStatus(); //update current user stay //UserStay stay = mPlaceManager.getLastKnownStay(); /*if (stay != null) { ((TextView)findViewById(R.id.current_stay)).setText(stay.toString()); }*/ } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_OAUTH2_AUTHENTICATE: if (resultCode == RESULT_OK) { //here I have authorization code final String code = data.getStringExtra("authcode"); //This is not great, my thread pool is polluted mLocationPollThreadExecutor.execute(new Runnable() { public void run() { try { TokenResponse accessTokenResponse = new AuthorizationCodeTokenRequest( new NetHttpTransport(), new JacksonFactory(), new GenericUrl("https://accounts.google.com/o/oauth2/token"), code) .setRedirectUri(OAuth2ClientCredentials.REDIRECT_URI) .setScopes(OAuth2ClientCredentials.SCOPE) .setClientAuthentication(new ClientParametersAuthentication( OAuth2ClientCredentials.CLIENT_ID, OAuth2ClientCredentials.CLIENT_SECRET)) .execute(); mMainHandler.post(new Runnable() { public void run() { findViewById(R.id.logWithGoogle).setVisibility(View.GONE); findViewById(R.id.registerAnonymize).setVisibility(View.VISIBLE); ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_orange)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationStatusRegStart)); setTableStatus(getString(R.string.geolocationStatusRegStart)); } }); setRefreshToken(accessTokenResponse.getRefreshToken()); mGOOGCredential.setFromTokenResponse(accessTokenResponse); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }); } else { //Auth code grabing wnet wrong, relaunch activity (that could give a wonderfull infinite loop on no network access ? //For now on just do nothing } break; } } void setRefreshToken(String refreshToken) { SharedPreferences.Editor editor = mPrefs.edit(); editor.putString(PREF_REFRESH_TOKEN, refreshToken); editor.commit(); } void setViewOnMasterTableID(String viewOnMasterID) { SharedPreferences.Editor editor = mPrefs.edit(); editor.putString(PREF_VIEWONMASTERTABLE_ENCID, viewOnMasterID); editor.commit(); } void setRegRequestTableID(String tableEncID) { SharedPreferences.Editor editor = mPrefs.edit(); editor.putString(PREF_REGREQUESTTABLE_ENCID, tableEncID); editor.commit(); } void setTableEncID(String tableEncID) { SharedPreferences.Editor editor = mPrefs.edit(); editor.putString(PREF_TABLE_ENCID, tableEncID); editor.commit(); } void setTableStatus(String status) { SharedPreferences.Editor editor = mPrefs.edit(); editor.putString(PREF_TABLE_STATUS, status); editor.commit(); } /** * Update service status. */ private void updateServiceStatus() { if (mAlohar.isServiceRunning()) { mServiceToggleButton.setChecked(true); mStatusView.setText("Service is running!"); } else { mServiceToggleButton.setChecked(false); mStatusView.setText("Service stopped"); } } /** * On register click. * * @param v the view */ public void onRegisterClick(View v) { mAloharAuthLayout.setVisibility(View.GONE); mMainLayout.setVisibility(View.GONE); mProgress.setVisibility(View.VISIBLE); mAlohar.register(APP_ID, API_KEY, this); } /** * On authen click. * * @param v the view */ public void onAuthenClick(View v) { String inputUID = mUIDView.getText().toString(); if (inputUID.trim().length() == 0) { toastError("Please give a valid UID"); } else { mAloharUid = inputUID; try { mAlohar.authenticate(mAloharUid, APP_ID, API_KEY, this); } catch (Exception e) { // e.printStackTrace(); } } mProgress.setVisibility(View.VISIBLE); } public void onViewerModeClick(View view) { //TODO: View only my data on the map /*if(mViewOnMasterID == null) { toastMessage(getString(R.string.anonimizeRequired)); return; }*/ String timeFilter = ((Spinner) findViewById(R.id.timeFilterSpinner)).getSelectedItem().toString(); String distanceFilter = ((Spinner) findViewById(R.id.distanceFilterSpinner)).getSelectedItem().toString(); String whereClause = ""; if (distanceFilter.equals("--") == false) //time filtering requested by user { if (mAlohar.getPlaceManager().getCurrentLocation().getLatitude() == 0.0 || mAlohar.getPlaceManager().getCurrentLocation().getLongitude() == 0.0) { //Location not available toastMessage(getString(R.string.locationFilterError)); } else { String WhereClauseDistanceFilter = ""; switch (((Spinner) findViewById(R.id.distanceFilterSpinner)).getSelectedItemPosition()) { case 1: WhereClauseDistanceFilter = "100"; break; case 2: WhereClauseDistanceFilter = "300"; break; case 3: WhereClauseDistanceFilter = "500"; break; case 4: WhereClauseDistanceFilter = "1000"; break; case 5: WhereClauseDistanceFilter = "20000"; break; } //double testLat = 45.5334; //double testLong = -73.5838; //WHERE Pharmacy='yes' AND whereClause += "WHERE ST_INTERSECTS(Location, CIRCLE(LATLNG(" + Double.toString(mAlohar.getPlaceManager().getCurrentLocation().getLatitude()) //+ Double.toString(testLat) + "," + Double.toString(mAlohar.getPlaceManager().getCurrentLocation().getLongitude()) //+ Double.toString(testLong) + ")," + WhereClauseDistanceFilter + ")) "; //37.3242,-121.9806),5000))" } } if (timeFilter.equals("--") == false) //time filtering requested by user { if (whereClause.isEmpty() == false) { whereClause += "AND "; } else { whereClause += "WHERE "; } Calendar cl = Calendar.getInstance(); cl.setTime(new Date()); switch (((Spinner) findViewById(R.id.timeFilterSpinner)).getSelectedItemPosition()) { case 1: cl.add(Calendar.MINUTE, -5); break; case 2: cl.add(Calendar.MINUTE, -15); break; case 3: cl.add(Calendar.MINUTE, -45); break; case 4: cl.add(Calendar.HOUR, -2); break; } whereClause += "Date>='" + DateFormat.getDateTimeInstance().format(cl.getTime()) + "' "; } //1cmlx9aChHUYTWwYivaZucr7NHNsP_ulvEPX1FoM is master table public view ID String fromTableID = "1cmlx9aChHUYTWwYivaZucr7NHNsP_ulvEPX1FoM"; if (((CheckBox) findViewById(R.id.myDataCheckbox)).isChecked()) { fromTableID = mViewOnMasterID; } //excellent, just do a request to grab the locations and pass them around final String SqlQuery = "SELECT Date, Location, Description FROM " + fromTableID + " " + whereClause + "ORDER BY Date DESC LIMIT " + ((EditText) findViewById(R.id.nbIconsMax)).getText(); final boolean myLocationEnabled = !(((CheckBox) findViewById(R.id.worldMapChkBx)).isChecked()); final boolean timeColored = ((CheckBox) findViewById(R.id.timeColoredCheckbox)).isChecked(); new Thread((new Runnable() { public void run() { try { String encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GoogleUrl GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery + "&encid=true"); HttpRequest request = mGOOGClient.getRequestFactory().buildGetRequest(GUrl); HttpHeaders headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request request.setHeaders(headers); HttpResponse response = request.execute(); if (response.getStatusCode() == 200) { //Here I have my data InputStreamReader inputStreamReader = new InputStreamReader(response.getContent()); BufferedReader bufferedStreamReader = new BufferedReader(inputStreamReader); CSVReader reader = new CSVReader(bufferedStreamReader); // The first line is the column names, and the remaining lines are the rows. List<String[]> csvLines = reader.readAll(); List<String> columns = Arrays.asList(csvLines.get(0)); List<String[]> rows = csvLines.subList(1, csvLines.size()); if (rows.size() == 0) { toastMessage("Table vide !"); return; } ArrayList<String> rowData = new ArrayList<String>(); for (String[] row : rows) { String toAdd = ""; for (String cell : row) { //No , in data, or things are gonna go horribly wrong here toAdd += cell + "|"; } //I have this last pesky separator ,it will give me an empty String on the other side rowData.add(toAdd); } Intent mapIntent = new Intent(getApplicationContext(), CasserolesEnCoursViewerActivity.class); mapIntent.putStringArrayListExtra("rowsData", rowData); mapIntent.putExtra("timeColored", timeColored); mapIntent.putExtra("myLocation", myLocationEnabled); if (myLocationEnabled) toastMessage(getString(R.string.lastContributionTapHintLocal)); else toastMessage(getString(R.string.lastContributionTapHintWorld)); //mapIntent.putExtra("relativeTime", ((CheckBox)findViewById(R.id.relativeTimeCheckbox)).isChecked()); startActivity(mapIntent); } } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } })).start(); } /** * On service click. * * @param view the view */ public void onServiceClick(View view) { boolean isChecked = ((ToggleButton) view).isChecked(); if (isChecked) { //turn on mAlohar.startServices(); //StatusView.setText("Service is running!"); ((TextView) findViewById(R.id.geolocationServiceStatus)) .setText(getString(R.string.geolocationStatusOn)); ((TextView) findViewById(R.id.geolocationServiceStatus)) .setTextColor(getResources().getColor(R.color.text_red)); ((TextView) findViewById(R.id.distanceFilterNA)).setVisibility(View.INVISIBLE); findViewById(R.id.FrequencyGroupLayout).setVisibility(View.VISIBLE); mDistanceFilterSpinner.setEnabled(true); //String freq = ((RadioButton)findViewById( ((RadioGroup)findViewById(R.id.pollFrequencyRadioGroup)).getCheckedRadioButtonId() )).getText().toString(); String text = getString(R.string.geolocationStatusOnNotification);//String.format(getString(R.string.geolocationOnNotification), freq); int icon = R.drawable.ic_launcher; CharSequence tickerText = text; long when = System.currentTimeMillis(); mNotification = new Notification(icon, tickerText, when); mNotification.flags |= Notification.FLAG_ONGOING_EVENT; CharSequence contentText = getString(R.string.geolocationOnNotificationNoFreq); CharSequence contentTitle = getString(R.string.app_name); Intent notificationIntent = new Intent(this, CasserolesEnCoursActivity.class); mNotification.contentIntent = PendingIntent.getActivity(this, 0, notificationIntent, Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); mNotification.setLatestEventInfo(this, contentTitle, contentText, mNotification.contentIntent); mNotificationManager.notify(R.layout.main, mNotification); } else { //turn off mAlohar.stopServices(); ((TextView) findViewById(R.id.geolocationServiceStatus)) .setText(getString(R.string.geolocationStatusOff)); ((TextView) findViewById(R.id.geolocationServiceStatus)) .setTextColor(getResources().getColor(R.color.text_green)); ((TextView) findViewById(R.id.distanceFilterNA)).setVisibility(View.VISIBLE); findViewById(R.id.FrequencyGroupLayout).setVisibility(View.INVISIBLE); mDistanceFilterSpinner.setSelection(0); mDistanceFilterSpinner.setEnabled(false); //((RadioGroup) findViewById(R.id.pollFrequencyRadioGroup)).check(-1); cancelActiveTasks(); mLocationPollThreadExecutor.purge(); //That is a hack, some code path must be missed, I'm tracking a thread leak bug when app is in background for a long time mLocationPollThreadExecutor.shutdown(); mLocationPollThreadExecutor = new ScheduledThreadPoolExecutor(20); mIsStationary = true; mNotificationManager.cancel(R.layout.main); } } public void onManifClick(View view) { boolean isChecked = ((ToggleButton) view).isChecked(); final String description; if (isChecked) { description = "MANIF START"; } else { description = "MANIF END"; } new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(true, description); } })).start(); } public void onAntiEmeuteClick(View view) { boolean isChecked = ((ToggleButton) view).isChecked(); final String description; if (isChecked) { description = "Anti Emeute Start"; } else { description = "Anti Emeute END"; } new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(true, description); } })).start(); } public void onSpotDispersionClick(View view) { final String description = "Ordre de dispersion ! :S"; new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(true, description); } })).start(); } public void onSpotFusionClick(View view) { final String description = "Fusion ! :)"; new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(true, description); } })).start(); } public void onSpotReductionClick(View view) { final String description = "reduction ! :s"; new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(true, description); } })).start(); } public void onClearTableClick(View view) { //TextView textView = (TextView) findViewById(R.id.tableInfo); //textView.setText("Tap create"); ((Button) findViewById(R.id.clearTable)).setEnabled(false); ((Button) findViewById(R.id.registerTable)).setEnabled(false); ((Button) findViewById(R.id.registerTable)).setEnabled(false); ((Button) findViewById(R.id.refreshRegistration)).setEnabled(false); ((Button) findViewById(R.id.createTable)).setEnabled(true); mFusionTableEncID = null; mRegisterRequestTableID = null; mViewOnMasterID = null; ((TextView) findViewById(R.id.tableStatus)).setText(R.string.tableStatusNoTable); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_TABLE_ENCID); editor2.remove(PREF_TABLE_STATUS); editor2.remove(PREF_VIEWONMASTERTABLE_ENCID); editor2.remove(PREF_REGREQUESTTABLE_ENCID); editor2.commit(); } public void onCreateTableClick(View view) { new Thread((new Runnable() { public void run() { try { sendCreateQueryToFusionTable(); } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } })).start(); } private void publicizeTable() throws IOException { try { XmlNamespaceDictionary docDic = new XmlNamespaceDictionary(); docDic.set("", "http://www.w3.org/2005/Atom"); docDic.set("gAcl", "http://schemas.google.com/acl/2007"); GenericData data = new GenericData(); AtomCategory category = AtomCategory.newKind("accessRule"); AtomRole role = AtomRole.newRole("reader"); AtomScope scope = AtomScope.newScope("default"); data.put("category", category); data.put("gAcl:role", role); data.put("gAcl:scope", scope); AtomContent content = AtomContent.forEntry(docDic, data); GoogleUrl GUrl = new GoogleUrl( "https://docs.google.com/feeds/default/private/full/" + mFusionTableEncID + "/acl?v=3"); HttpRequest request = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, content); HttpResponse response = request.execute(); if (response.getStatusCode() == 201) { toastMessage(getString(R.string.publicizeDataTableToast)); //Success ! } } catch (HttpResponseException e) { if (e.getStatusCode() == 409) //Conflict { toastMessage(getString(R.string.publicizeDataTableToast)); return; } throw (e); } catch (IOException e) { throw (e); } } public void onAnonymizeClick(View view) { new Thread((new Runnable() { public void run() { try { //publicizeTable(); //Make the data table public -- Removed as it's not nescessary, server will //it will be shared with server for initial data copy and then given back to the user createAndShareRequestTable(); //Create table and share it so it can be found } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } })).start(); } public void onRegisterTableClick(View view) { new Thread((new Runnable() { public void run() { try { //publicizeTable(); //Make the data table public -- Removed as it's not nescessary, server will //it will be shared with server for initial data copy and then given back to the user createAndShareRequestTable(); //Create table and share it so it can be found } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } } })).start(); } public void onAnonymizeRefreshClick(View v) { if (mRegisterRequestTableID == null) return; new Thread((new Runnable() { public void run() { try { //Getting view on master table on which the contributor has write access String SqlQuery = "SELECT Date, RequestedViewOnMasterTable_ID FROM " + mRegisterRequestTableID + " ORDER BY Date ASC"; String encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GoogleUrl GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery + "&encid=true"); HttpRequest request = mGOOGClient.getRequestFactory().buildGetRequest(GUrl); HttpHeaders headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request request.setHeaders(headers); HttpResponse response = request.execute(); if (response.getStatusCode() == 200) { //Here I have my data InputStreamReader inputStreamReader = new InputStreamReader(response.getContent()); BufferedReader bufferedStreamReader = new BufferedReader(inputStreamReader); CSVReader reader = new CSVReader(bufferedStreamReader); // The first line is the column names, and the remaining lines are the rows. List<String[]> csvLines = reader.readAll(); List<String> columns = Arrays.asList(csvLines.get(0)); List<String[]> rows = csvLines.subList(1, csvLines.size()); if (rows.size() < 3 || rows.size() >= 4) { toastMessage(getString(R.string.regNotProcessed)); return; } else { //hack to select the right row for (String[] curRowContent : rows) { //Ref id for length, this is my current ID String potentialID = curRowContent[1]; if (potentialID.length() == "13WOJlilS1fbwVoO9z1H4bVIqWxcxZJeesZL5qGg".length()) { //found ID mViewOnMasterID = potentialID; setViewOnMasterTableID(mViewOnMasterID); break; } } mMainHandler.post(new Runnable() { public void run() { ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_green)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationAnonymizeProcessed)); setTableStatus(getString(R.string.geolocationAnonymizeProcessed)); findViewById(R.id.myDataCheckbox).setEnabled(true); ((Button) findViewById(R.id.checkAnonymize)).setVisibility(View.GONE); ((Button) findViewById(R.id.toggleGeolocation)).setVisibility(View.VISIBLE); findViewById(R.id.geolocationServiceStatus).setVisibility(View.VISIBLE); } }); } } } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } })).start(); } private void createAndShareRequestTable() throws JSONException, HttpResponseException, IOException { String SqlQuery = "CREATE TABLE " + getString(R.string.registerRequestTableName) + " (Date:DATETIME, RequestedViewOnMasterTable_ID:STRING)"; String encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GoogleUrl GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery + "&encid=true"); try { HttpRequest request = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, null); HttpHeaders headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request request.setHeaders(headers); HttpResponse response = request.execute(); if (response.getStatusCode() == 200) { //Table created, insert relevant data //Extract encrypted ID String tableName = "NONAME"; InputStreamReader inputStreamReader = new InputStreamReader(response.getContent()); BufferedReader bufferedStreamReader = new BufferedReader(inputStreamReader); CSVReader reader = new CSVReader(bufferedStreamReader); // The first line is the column names, and the remaining lines are the rows. List<String[]> csvLines = reader.readAll(); List<String> columns = Arrays.asList(csvLines.get(0)); List<String[]> rows = csvLines.subList(1, csvLines.size()); //TextView textView = (TextView) findViewById(R.id.nameField); String regRequestTableIDToShare = rows.get(0)[0]; setRegRequestTableID(regRequestTableIDToShare); mRegisterRequestTableID = regRequestTableIDToShare; //Now insert data SqlQuery = "INSERT INTO " + regRequestTableIDToShare + " (Date, RequestedViewOnMasterTable_ID) VALUES ('" + DateFormat.getDateTimeInstance().format(new Date()) + "', '" + getString(R.string.regTableReqRegisteredTextStart) + " " + getString(R.string.updateRegStatusButtonText) + " " + getString(R.string.regTableReqRegisteredTextEnd) + "')"; encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery); try { HttpRequest requestFillRegTable = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, null); headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request requestFillRegTable.setHeaders(headers); HttpResponse responseFillRegTable = requestFillRegTable.execute(); if (responseFillRegTable.getStatusCode() == 200) { toastMessage("Request table timestamped :)"); } } catch (HttpResponseException e) { throw e; } catch (IOException e) { throw e; } //now share it with user casserolesencours@gmail.com try { // <entry xmlns="http://www.w3.org/2005/Atom" xmlns:gAcl='http://schemas.google.com/acl/2007'> // <category scheme='http://schemas.google.com/g/2005#kind' // term='http://schemas.google.com/acl/2007#accessRule'/> // <gAcl:role value='writer'/> // <gAcl:scope type='user' value='new_writer@example.com'/> // </entry> XmlNamespaceDictionary docDic = new XmlNamespaceDictionary(); docDic.set("", "http://www.w3.org/2005/Atom"); docDic.set("gAcl", "http://schemas.google.com/acl/2007"); GenericData data = new GenericData(); AtomCategory category = AtomCategory.newKind("accessRule"); AtomRole role = AtomRole.newRole("writer"); AtomScope scope = AtomScope.newScope("user", "casserolesencours@gmail.com"); data.put("category", category); data.put("gAcl:role", role); data.put("gAcl:scope", scope); AtomContent content = AtomContent.forEntry(docDic, data); GUrl = new GoogleUrl("https://docs.google.com/feeds/default/private/full/" + regRequestTableIDToShare + "/acl?v=3&send-notification-emails=false"); HttpRequest requestShare = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, content); HttpResponse responseShare = requestShare.execute(); if (responseShare.getStatusCode() == 201) { toastMessage(getString(R.string.registerShareTableOKToast)); mMainHandler.post(new Runnable() { public void run() { ((TextView) findViewById(R.id.geolocationStatus)) .setTextColor(getResources().getColor(R.color.text_orange)); ((TextView) findViewById(R.id.geolocationStatus)) .setText(getString(R.string.geolocationAnonymizePending)); setTableStatus(getString(R.string.geolocationAnonymizePending)); ((Button) findViewById(R.id.registerAnonymize)).setVisibility(View.GONE); ((Button) findViewById(R.id.checkAnonymize)).setVisibility(View.VISIBLE); } }); } } catch (HttpResponseException e) { if (e.getStatusCode() == 409) //Conflict { toastMessage("SHOULD NOT HAPPEN"); } throw (e); } catch (IOException e) { throw (e); } } } catch (HttpResponseException e) { throw e; } catch (IOException e) { throw e; } } private void sendCreateQueryToFusionTable() throws JSONException, HttpResponseException, IOException { String today = DateFormat.getDateInstance().format(new Date()); String todayFileFormatted = today.replace(" ", "_").replace(",", "_"); String SqlQuery = "CREATE TABLE CasserolesEnCours" + todayFileFormatted + " (Date:DATETIME, Location:LOCATION, Manual:STRING, Description:STRING, IsStationary:STRING)"; String encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GoogleUrl GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery + "&encid=true"); try { HttpRequest request = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, null); HttpHeaders headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request request.setHeaders(headers); HttpResponse response = request.execute(); if (response.getStatusCode() == 200) { InputStreamReader inputStreamReader = new InputStreamReader(response.getContent()); BufferedReader bufferedStreamReader = new BufferedReader(inputStreamReader); CSVReader reader = new CSVReader(bufferedStreamReader); // The first line is the column names, and the remaining lines are the rows. List<String[]> csvLines = reader.readAll(); List<String> columns = Arrays.asList(csvLines.get(0)); List<String[]> rows = csvLines.subList(1, csvLines.size()); //TextView textView = (TextView) findViewById(R.id.nameField); mFusionTableEncID = rows.get(0)[0]; setTableEncID(mFusionTableEncID); setTableStatus(getString(R.string.tableStatusPrivate)); //TODO : add request to retrieve NAME of table instead of ID mMainHandler.post(new Runnable() { public void run() { //TextView textView = (TextView) findViewById(R.id.tableInfo); //textView.setText(mFusionTableEncID); ((TextView) findViewById(R.id.tableStatus)).setText(getString(R.string.tableStatusPrivate)); ((Button) findViewById(R.id.clearTable)).setEnabled(true); ((Button) findViewById(R.id.registerTable)).setEnabled(true); ((Button) findViewById(R.id.createTable)).setEnabled(false); } }); toastMessage("Fusion table create request 200 OK :)--" + mFusionTableEncID);//dataList.get(0).getString("DESC")); } } catch (HttpResponseException e) { throw e; } catch (IOException e) { throw e; } } /* (non-Javadoc) * @see com.alohar.user.callback.ALMotionListener#onMotionStateChanged(com.alohar.user.content.data.MotionState, com.alohar.user.content.data.MotionState) */ public void onMotionStateChanged(MotionState oldState, MotionState newState) { if (oldState == MotionState.STATIONARY || (newState != MotionState.BIGMOVEMENT && newState != MotionState.MICROMOVEMENT)) { //LOG IN TABLE final String motionStateSwitch = oldState.name() + "=>" + newState.name(); final String userStateSwitch = "Stationary=" + mMotionManager.isStationary() + "|OnCommute=" + mMotionManager.isOnCommute(); new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, motionStateSwitch); } })).start(); mMainHandler.post(new Runnable() { public void run() { ((TextView) findViewById(R.id.motion_state)).setText(motionStateSwitch); ((TextView) findViewById(R.id.user_state)).setText(userStateSwitch); } }); } if (mIsStationary && mMotionManager.isStationary() == false) //We started to move, enable frequency controls { findViewById(R.id.frequency0).setEnabled(true); findViewById(R.id.frequency1).setEnabled(true); findViewById(R.id.frequency2).setEnabled(true); findViewById(R.id.frequency3).setEnabled(true); RadioGroup pollFrequencyRadioGroup = (RadioGroup) findViewById(R.id.pollFrequencyRadioGroup); if (pollFrequencyRadioGroup.getCheckedRadioButtonId() == -1) { //check(...) inderictly calls startTaskForId(...) ((RadioGroup) findViewById(R.id.pollFrequencyRadioGroup)).check(R.id.frequency2); } else { //START SCHEDULED THREADED ACTIVTY TO PUSH Location TO FUSION TABLE startTaskForId(pollFrequencyRadioGroup.getCheckedRadioButtonId(), false); } ((TextView) findViewById(R.id.pollFrequencyText)).setText("{Polling frequency}:"); //((TextView) findViewById(R.id.pollFrequencyText)).setTextColor(R.color.text_blue); } else if (mIsStationary == false && mMotionManager.isStationary()) //We just stopped, disable { findViewById(R.id.frequency0).setEnabled(false); findViewById(R.id.frequency1).setEnabled(false); findViewById(R.id.frequency2).setEnabled(false); findViewById(R.id.frequency3).setEnabled(false); //((RadioGroup) findViewById(R.id.pollFrequencyRadioGroup)).check(-1); cancelActiveTasks(); mLocationPollThreadExecutor.purge(); //Should I thread this as the Alohar's guy did by posting a new thread on the main handle ? ((TextView) findViewById(R.id.pollFrequencyText)).setText("{N/A when stationary}"); //((TextView) findViewById(R.id.pollFrequencyText)).setTextColor(R.color.text_violet); new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, "Location poll STOP--Purged"); } })).start(); } mIsStationary = mMotionManager.isStationary(); } ///////////////////////////////////////////////////////////////////////////// protected void writeToFusionTable(boolean manual, String desc) { if (mAlohar.getPlaceManager().getCurrentLocation().getLatitude() == 0.0 || mAlohar.getPlaceManager().getCurrentLocation().getLongitude() == 0.0) { toastMessage(getString(R.string.nullocationErrorMessage)); return; } JSONObject newData = new JSONObject(); try { newData.put("DESC", desc); newData.put("LOCATION", getLatLongPosition()); newData.put("DATE", DateFormat.getDateTimeInstance().format(new Date())); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } writeToFusionTable(manual, newData); } protected void writeToFusionTable(boolean manual, JSONObject timeNDescNLoc) { String Location; try { Location = timeNDescNLoc.getString("LOCATION"); } catch (JSONException e1) { return; } if (Location.contains("0.0")) { toastMessage(getString(R.string.nullocationErrorMessage)); return; } JSONObject newData = new JSONObject(); String debugOrder = ""; try { newData.put("DATE", timeNDescNLoc.getString("DATE")); newData.put("LOCATION", timeNDescNLoc.getString("LOCATION")); newData.put("DESC", timeNDescNLoc.getString("DESC")); ///////////////////////////////////////////////////////////// //Additional data newData.put("MANUAL", manual); newData.put("STATIONARY", mMotionManager.isStationary()); //newData.put("COMMUTE", mMotionManager.isOnCommute()); //TODO Second parameter (SD log on ERROR) should come from upper levels pushDataToFusionTable(newData, true); } catch (JSONException e) { // TODO Auto-generated catch block toastError("JSON error while formatting JSON data for query"); return; } } private void pushDataToFusionTable(JSONObject data, boolean logOnError) throws JSONException { ArrayList<JSONObject> dataList = new ArrayList<JSONObject>(); dataList.add(data); try { pushDataToFusionTable(dataList); } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); toastMessage("OAuth login required, redirecting..."); //This last Constant is weird startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); } if (logOnError) { toastMessage("Fusion table INSERT failed...attempt to CSV log..."); //Here I have to generate an error CSV file corresponding to the missed row of data //I add a fine timestamp on the data to avoid threads locking (unique filename), worse case two rows of data //will contains the exact same date string. try { data.put("ERROR_TIMESTAMP", new Date().getTime()); } catch (JSONException e1) { //Things went horribly horribly wrong, losing data for now toastError("Error timestamp JSON write failed...dropping row :("); return; } if (writeToSD_CSVError(data)) { toastMessage("Happy dance ! -- Error CSV log succeded, remember to flush."); //HAPPY DANCE !! } else { //Things went horribly horribly wrong, losing data for now toastError("Error CSV writing failed...dropping row :("); } } else { toastMessage("Fusion table INSERT failed and logOnError==false"); } } catch (IOException e) { if (logOnError) { toastMessage("Fusion table INSERT failed...attempt to CSV log..."); //Here I have to generate an error CSV file corresponding to the missed row of data //I add a fine timestamp on the data to avoid threads locking, worse case two rows of data //will contains the exact same date string. try { data.put("ERROR_TIMESTAMP", new Date().getTime()); } catch (JSONException e1) { //Things went horribly horribly wrong, losing data for now toastError("Error timestamp JSON write failed...dropping row :("); return; } if (writeToSD_CSVError(data)) { toastMessage("Happy dance ! -- Error CSV log succeded, remember to flush."); //HAPPY DANCE !! } else { //Things went horribly horribly wrong, losing data for now toastError("Error CSV writing failed...dropping row :("); } } else { toastMessage("Fusion table INSERT failed and logOnError==false"); } } } private void pushDataToFusionTable(ArrayList<JSONObject> dataList) throws JSONException, HttpResponseException, IOException { if (mViewOnMasterID == null) { toastMessage(getString(R.string.anonimizeRequired)); return; } String SqlQuery = ""; //Can I pass JSON around to input data into the table ? (Like in the request body or something) for (int i = 0; i < dataList.size(); ++i) { //SqlQuery += "INSERT INTO " + mFusionTableEncID + " (Date, Location, Manual, Description, IsStationary) VALUES ('"//fv#casseroles //TODO : the following request when table is registered, in addition to the one the personal table SqlQuery += "INSERT INTO " + mViewOnMasterID + " (Date, Location, Manual, Description, IsStationary, RequestTableID) VALUES ('"//fv#casseroles + dataList.get(i).getString("DATE") + "', '" + dataList.get(i).getString("LOCATION")//"45.5334 -73.5838" + "', '" + dataList.get(i).getBoolean("MANUAL") + "', '" + dataList.get(i).getString("DESC") + "', '" + dataList.get(i).getBoolean("STATIONARY") + "', '" + mRegisterRequestTableID /*+ "', '" + dataList.get(i).getBoolean("COMMUTE")*/ + "')"; if (dataList.size() != 1) { SqlQuery += ";"; } } String encodedQuery = URLEncoder.encode(SqlQuery, "UTF-8"); GoogleUrl GUrl = new GoogleUrl(SERVICE_URL + "?sql=" + encodedQuery);//&access_token=" + credential.getAccessToken()); try { HttpRequest request = mGOOGClient.getRequestFactory().buildPostRequest(GUrl, null); HttpHeaders headers = new HttpHeaders(); headers.setContentLength("0");//Required so that Fusion Table API considers request request.setHeaders(headers); HttpResponse response = request.execute(); if (response.getStatusCode() == 200) { toastMessage("Fusion table update request 200 OK :)--" + dataList.get(0).getString("DESC")); //Does nothing if nothing to flush flushErrorRows(); } } catch (HttpResponseException e) { throw e; } catch (IOException e) { throw e; } } private boolean writeToSD_CSVError(JSONObject data) { File sdCard = getSDCardFile(true);//getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); if (sdCard == null) return false; File dir = new File(sdCard.getAbsolutePath() + "/" + getString(R.string.app_name) + "/OutputErrorLog"); if (!dir.exists()) { dir.mkdirs(); } String today; try { today = data.getString("DATE") + "_" + data.getLong("ERROR_TIMESTAMP"); } catch (JSONException e1) { //Data contains no date or error timestamp, abort return false; } //DateFormat.getDateTimeInstance().format(new Date()); String todayFileFormatted = today.replace(" ", "_").replace(",", "_").replace(":", "_"); File fileCheck = getApplicationContext().getFileStreamPath(todayFileFormatted + "_ErrorRow.csv"); if (fileCheck.exists()) { //This is an error, the file should be unique, but somebody came before us //Let's just restamp the error and try again try { data.put("ERROR_TIMESTAMP", new Date().getTime()); } catch (JSONException e) { return false; } return writeToSD_CSVError(data); } //No else : all path lead to return File file = new File(dir, todayFileFormatted + "_ErrorRow.csv"); //This creates the file if it doesn't already exists FileWriter writer = null; try { writer = new FileWriter(file, true); } catch (IOException e) { return false; } String toWrite = null; try { ////////////////////////////////////////////////////////////// ///DATA DEFINITION toWrite = "\"" + data.getString("DATE") + "\"," + data.getString("LOCATION") + "," + data.getBoolean("MANUAL") + "," + data.getString("DESC") + "," + data.getBoolean("STATIONARY") + /*"," + data.getBoolean("COMMUTE") +*/ "\r\n"; } catch (JSONException e) { // TODO Auto-generated catch block return false; } try { writer.write("Date,Location,Manual,Description,IsStationary\r\n"); //writer.write("Date,Location,Manual,Description,IsStationary,IsOnCommute\r\n"); writer.write(toWrite); writer.flush(); writer.close(); } catch (IOException e) { return false; } return true; } private File getSDCardFile(boolean checkWriteAccess) { boolean externalStorageAvailable = false; boolean externalStorageWriteable = false; String state = Environment.getExternalStorageState(); if (Environment.MEDIA_MOUNTED.equals(state)) { // We can read and write the media externalStorageAvailable = externalStorageWriteable = true; } else if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { // We can only read the media externalStorageAvailable = true; externalStorageWriteable = false; } else { // Something else is wrong. It may be one of many other states, but all we need // to know is we can neither read nor write externalStorageAvailable = externalStorageWriteable = false; } if (externalStorageAvailable == false) return null; else if (checkWriteAccess && !externalStorageWriteable) return null; else return getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); } /** * Toast toastMessage. * * @param message the message */ public void toastMessage(final String message) { mMainHandler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); } }); } private static final int MENU_FLUSH = 1; private static final int MENU_EXIT = 2; private static final int MENU_FLUSHSQL = 3; /* (non-Javadoc) * @see android.app.Activity#onCreateOptionsMenu(android.view.Menu) */ /*@Override public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, MENU_FLUSH, 0, "flush"); menu.add(0, MENU_EXIT, 0, "Exit"); menu.add(0, MENU_FLUSHSQL, 0, "Flush SQL"); return super.onCreateOptionsMenu(menu); }*/ /* (non-Javadoc) * @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem) */ @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_FLUSH: // try to trigger one post every 15 minutes mAlohar.flush(); return true; case MENU_EXIT: mAlohar.stopServices(); mAlohar.teardown(); this.finish(); return true; case MENU_FLUSHSQL: flushErrorRows(); return true; } return super.onOptionsItemSelected(item); } private void flushErrorRows() { File sdCard = getSDCardFile(true);//getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); String truc = sdCard.getAbsolutePath() + "/" + getString(R.string.app_name) + "/OutputErrorLog"; File dir = new File(sdCard.getAbsolutePath() + "/" + getString(R.string.app_name) + "/OutputErrorLog"); if (!dir.exists()) { return; } File[] errorFiles = dir.listFiles(); if (errorFiles.length == 0) { //toastMessage("Nothing to flush"); return; } final ArrayList<JSONObject> dataList = new ArrayList<JSONObject>(); for (int i = 0; i < errorFiles.length; ++i) //I will create a big SQL query string containing every error rows separated by ; { FileInputStream fis; try { fis = new FileInputStream(errorFiles[i]); InputStreamReader inputStreamReader = new InputStreamReader(fis); BufferedReader bufferedStreamReader = new BufferedReader(inputStreamReader); CSVReader reader = new CSVReader(bufferedStreamReader); // The first line is the column names, and the remaining lines are the rows. List<String[]> csvLines; csvLines = reader.readAll(); //List<String> columns = Arrays.asList(csvLines.get(0)); List<String[]> rows = csvLines.subList(1, csvLines.size()); //rows length should be 1 : 1 line of data //Let's construct a JSONObject from the second line JSONObject data = new JSONObject(); ///////////////////////////////////////////// //DATA Definition data.put("DATE", rows.get(0)[0]); data.put("LOCATION", rows.get(0)[1]); data.put("MANUAL", rows.get(0)[2]); data.put("DESC", rows.get(0)[3]); data.put("STATIONARY", rows.get(0)[4]); //data.put("COMMUTE", rows.get(0)[5]); dataList.add(data); } catch (FileNotFoundException e1) { //That should never happen toastError("Can't flush SQL CSV error files"); return; } catch (IOException e) { toastError("Can't flush SQL CSV error files"); return; } catch (JSONException e) { toastError("Can't flush SQL CSV error files"); return; } } new Thread((new Runnable() { public void run() { try { pushDataToFusionTable(dataList); } catch (HttpResponseException e) { if (e.getStatusCode() == 401) { mGOOGCredential.setAccessToken(null); SharedPreferences.Editor editor2 = mPrefs.edit(); editor2.remove(PREF_REFRESH_TOKEN); editor2.commit(); //accountName = null; toastMessage("OAuth login required, please reflush afterwards... redirecting..."); startActivityForResult( new Intent().setClass(getApplicationContext(), OAuth2AccessTokenActivity.class), REQUEST_OAUTH2_AUTHENTICATE); return; } } catch (JSONException e) { toastError("Can't flush SQL CSV error files"); return; } catch (IOException e) { toastError("Can't flush SQL CSV error files"); return; } //if I get there everything went fine with my request, so I can delete files //NOT THREAD SAFE, multiple button clics could lead to nightmare //File sdCard = Environment.getExternalStorageDirectory(); File sdCard = getSDCardFile(true);//getApplicationContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS); File dir = new File( sdCard.getAbsolutePath() + "/" + getString(R.string.app_name) + "/OutputErrorLog"); if (!dir.exists()) { return; } File[] errorFiles = dir.listFiles(); //Reenable button in interface to prevent multiple threads trying to delete files for (int i = 0; i < errorFiles.length; ++i) //I will create a big string containing every error rows separated by ; { errorFiles[i].delete(); //Could return false } toastMessage("SQL flushing successfull " + errorFiles.length + " row(s) uploaded"); } })).start(); } private void cancelActiveTasks() { //mLocPollFrq2 if (mLocPollFrq0 != null) { mLocPollFrq0.cancel(true); mLocPollFrq0 = null; } else if (mLocPollFrq1 != null) { mLocPollFrq1.cancel(true); mLocPollFrq1 = null; } else if (mLocPollFrq2 != null) { mLocPollFrq2.cancel(true); mLocPollFrq2 = null; } else if (mLocPollFrq3 != null) { mLocPollFrq3.cancel(true); mLocPollFrq3 = null; } } private void startTaskForId(int radioButtonID, final boolean manual) { String eventDescTemp = ""; //Will do for now, insure only one task at a time, we could have overlap if wanted later on cancelActiveTasks(); final JSONObject newData = new JSONObject(); //Just a convenient map ? try { newData.put("DESC", "Scheduled poll"); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } //That should be a switch if (radioButtonID == R.id.frequency0) { try { //Retrieve status message from ui in one function only newData.put("DESC", "Scheduled status update. UI says : " + ((EditText) findViewById(R.id.statusText)).getText().toString()); } catch (JSONException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } eventDescTemp = "frequency -- now: 10min"; //Threaded mLocPollFrq0 = mLocationPollThreadExecutor.scheduleAtFixedRate(new Runnable() { public void run() { try { newData.put("DATE", DateFormat.getDateTimeInstance().format(new Date())); newData.put("LOCATION", getLatLongPosition()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, newData); } }, //60000, 60000, TimeUnit.MILLISECONDS); 10, 10, TimeUnit.MINUTES); } else if (radioButtonID == R.id.frequency1) { try { //Retrieve status message from ui in one function only newData.put("DESC", "Scheduled status update. UI says : " + ((EditText) findViewById(R.id.statusText)).getText().toString()); } catch (JSONException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } eventDescTemp = "frequency -- now: 5min"; //Threaded mLocPollFrq1 = mLocationPollThreadExecutor.scheduleAtFixedRate(new Runnable() { public void run() { try { newData.put("DATE", DateFormat.getDateTimeInstance().format(new Date())); newData.put("LOCATION", getLatLongPosition()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, newData); } }, //30000, 30000, TimeUnit.MILLISECONDS); 5, 5, TimeUnit.MINUTES); } else if (radioButtonID == R.id.frequency2) { try { //Retrieve status message from ui in one function only newData.put("DESC", "Scheduled status update. UI says : " + ((EditText) findViewById(R.id.statusText)).getText().toString()); } catch (JSONException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } eventDescTemp = "frequency -- now: 3mins"; //Threaded mLocPollFrq2 = mLocationPollThreadExecutor.scheduleAtFixedRate(new Runnable() { public void run() { try { newData.put("DATE", DateFormat.getDateTimeInstance().format(new Date())); newData.put("LOCATION", getLatLongPosition()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, newData); } }, //15000, 15000, TimeUnit.MILLISECONDS); 3, 3, TimeUnit.MINUTES); } else if (radioButtonID == R.id.frequency3) { try { //Retrieve status message from ui in one function only newData.put("DESC", "Scheduled status update. UI says : " + ((EditText) findViewById(R.id.statusText)).getText().toString()); } catch (JSONException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } eventDescTemp = "frequency -- now: 1min"; //Threaded mLocPollFrq3 = mLocationPollThreadExecutor.scheduleAtFixedRate(new Runnable() { public void run() { try { newData.put("DATE", DateFormat.getDateTimeInstance().format(new Date())); newData.put("LOCATION", getLatLongPosition()); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(false, newData); } }, //5000, 5000, TimeUnit.MILLISECONDS); 1, 1, TimeUnit.MINUTES); } final String eventDesc = eventDescTemp; //This is to thread the input of the changing frequency message, that's why tasks are delayed by their frequency new Thread((new Runnable() { public void run() { //Threaded : httpTransport is Thread safe, hence concurrent access to the web should be handled writeToFusionTable(manual, eventDesc); } })).start(); } private String getLatLongPosition() { //Attempt to align values in CSV file but that failed //DecimalFormat df = new DecimalFormat("#.###############"); //return df.format(mPlaceManager.getCurrentLocation().getLatitude()) + " " + df.format(mPlaceManager.getCurrentLocation().getLongitude()); return mPlaceManager.getCurrentLocation().getLatitude() + " " + mPlaceManager.getCurrentLocation().getLongitude(); } /** * Toast error. * * @param message the message */ public void toastError(final String message) { mMainHandler.post(new Runnable() { public void run() { Toast.makeText(getApplicationContext(), message, Toast.LENGTH_LONG).show(); mAloharAuthLayout.setVisibility(View.VISIBLE); mMainLayout.setVisibility(View.GONE); mProgress.setVisibility(View.GONE); } }); } /* (non-Javadoc) * @see com.alohar.user.callback.ALEventListener#handleEvent(com.alohar.user.content.data.ALEvents, java.lang.Object) */ public void handleEvent(ALEvents event, Object data) { if (event == ALEvents.AUTHENTICATE_CALLBACK || event == ALEvents.REGISTRATION_CALLBACK) { if (data instanceof String) { mAloharUid = (String) data; Log.i("CobraDemo", "######UID=" + mAloharUid); //SharedPreferences prefs = getSharedPreferences(PREF_NAME,MODE_PRIVATE);//PreferenceManager.getDefaultSharedPreferences(this); mPrefs.edit().putString(PREF_KEY, mAloharUid).commit(); } //alohar service is ready to start mMainHandler.post(new Runnable() { public void run() { //switch to main layout mAloharAuthLayout.setVisibility(View.GONE); mMainLayout.setVisibility(View.VISIBLE); mProgress.setVisibility(View.GONE); mAccountView.setText(mAloharUid); } }); } else if (event == ALEvents.GENERAL_ERROR_CALLBACK || event == ALEvents.SERVER_ERROR_CALLBACK) { toastError((String) data); } } }