diff --git a/.gitignore b/.gitignore index d5fb12a..48d2b95 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ app/release/app-release.apk app/build app/app.iml app/release/output-metadata.json +app/google-services.json *# diff --git a/app/build.gradle b/app/build.gradle index b4db7b7..05b0690 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,5 @@ apply plugin: 'com.android.application' - +apply plugin: 'com.google.gms.google-services' android { compileSdkVersion 31 useLibrary 'org.apache.http.legacy' @@ -37,6 +37,8 @@ dependencies { // Unit testing dependencies implementation 'androidx.constraintlayout:constraintlayout:2.1.3' implementation 'com.google.android.material:material:1.4.0' + implementation 'com.google.firebase:firebase-auth:19.2.0' + implementation 'androidx.test:core:1.4.0' testImplementation 'junit:junit:4.13.2' // Set this dependency if you want to use Mockito testImplementation 'org.mockito:mockito-core:4.3.1' @@ -53,7 +55,10 @@ dependencies { //implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3' testImplementation 'org.robolectric:robolectric:4.7.3' implementation 'com.android.volley:volley:1.2.1' - + implementation platform('com.google.firebase:firebase-bom:29.2.0') + implementation 'com.google.firebase:firebase-analytics' + implementation 'com.firebaseui:firebase-ui-auth:7.2.0' + implementation 'com.google.firebase:firebase-firestore' } repositories { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a164208..99a5e43 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="100" + android:versionName="4.1.1"> diff --git a/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java b/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java index e5204a4..be74ee6 100644 --- a/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java @@ -5,6 +5,9 @@ import android.content.SharedPreferences; import android.net.Uri; import android.os.Handler; import android.preference.PreferenceManager; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.os.Bundle; @@ -15,19 +18,28 @@ import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; +import com.firebase.ui.auth.AuthUI; +import com.firebase.ui.auth.FirebaseAuthUIActivityResultContract; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.FirebaseAuth; + import org.json.JSONException; import org.json.JSONObject; +import java.util.Arrays; + public class AuthenticateActivity extends AppCompatActivity { private String TAG = "AuthenticateActivity"; + private OsdUtil mUtil; private EditText mUnameEt; private EditText mPasswdEt; + private SdServiceConnection mConnection; + final Handler serverStatusHandler = new Handler(); private WebApiConnection mWac; private LogManager mLm; - private SdServiceConnection mConnection; - private OsdUtil mUtil; - final Handler serverStatusHandler = new Handler(); - private String TOKEN_ID = "webApiAuthToken"; + private static final String TOKEN_ID = "webApiAuthToken"; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -36,50 +48,88 @@ public class AuthenticateActivity extends AppCompatActivity { setContentView(R.layout.activity_authenticate); mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler); - if (!mUtil.isServerRunning()) { mUtil.showToast(getString(R.string.error_server_not_running)); finish(); return; } - mConnection = new SdServiceConnection(getApplicationContext()); Button cancelBtn = (Button) findViewById(R.id.cancelBtn); cancelBtn.setOnClickListener(onCancel); - Button OKBtn = (Button) findViewById(R.id.OKBtn); - OKBtn.setOnClickListener(onOK); + Button loginBtn = (Button) findViewById(R.id.loginBtn); + loginBtn.setOnClickListener(onLogin); Button logoutCancelBtn = (Button) findViewById(R.id.logoutCancelBtn); logoutCancelBtn.setOnClickListener(onCancel); - Button logoutBtn = (Button)findViewById(R.id.logoutBtn); + Button logoutBtn = (Button) findViewById(R.id.logoutBtn); logoutBtn.setOnClickListener(onLogout); - Button registerBtn = (Button) findViewById(R.id.RegisterBtn); - registerBtn.setOnClickListener(onRegister); - Button resetPasswordBtn = (Button) findViewById(R.id.ResetPasswordBtn); - resetPasswordBtn.setOnClickListener(onResetPassword); - mUnameEt = (EditText) findViewById(R.id.username); - mPasswdEt = (EditText) findViewById(R.id.password); - //mWac = new WebApiConnection(this, String tokenStr); - //mLm = new LogManager(this); + // Components required only for osdapi backend + if (LogManager.USE_FIREBASE_BACKEND) { } + else { + mConnection = new SdServiceConnection(getApplicationContext()); + + Button registerBtn = (Button) findViewById(R.id.RegisterBtn); + registerBtn.setOnClickListener(onRegister); + Button resetPasswordBtn = (Button) findViewById(R.id.ResetPasswordBtn); + resetPasswordBtn.setOnClickListener(onResetPassword); + + mUnameEt = (EditText) findViewById(R.id.username); + mPasswdEt = (EditText) findViewById(R.id.password); + } + + Button aboutDataSharingBtn = (Button) findViewById(R.id.aboutDataSharingBtn); + aboutDataSharingBtn.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.v(TAG,"aboutDataSharingBtn.onClick()"); + String url = OsdUtil.DATA_SHARING_URL; + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + startActivity(i); + } + } + ); + Button privacyPolicyBtn = (Button) findViewById(R.id.privacyPolicyBtn); + privacyPolicyBtn.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.v(TAG,"privacyPolicyBtn.onClick()"); + String url = OsdUtil.PRIVACY_POLICY_URL; + Intent i = new Intent(Intent.ACTION_VIEW); + i.setData(Uri.parse(url)); + startActivity(i); + } + } + ); + } @Override protected void onStart() { Log.d(TAG, "onStart()"); super.onStart(); - mUtil.bindToServer(getApplicationContext(), mConnection); - waitForConnection(); + if (LogManager.USE_FIREBASE_BACKEND) { + updateUi(); + } else { + mUtil.bindToServer(getApplicationContext(), mConnection); + waitForConnection(); + } } @Override protected void onStop() { Log.d(TAG, "onStop()"); super.onStop(); - mUtil.unbindFromServer(getApplicationContext(), mConnection); - } + if (LogManager.USE_FIREBASE_BACKEND) { + } else { + mUtil.unbindFromServer(getApplicationContext(), mConnection); + } + } private void waitForConnection() { // We want the UI to update as soon as it is displayed, but it takes a finite time for @@ -100,52 +150,65 @@ public class AuthenticateActivity extends AppCompatActivity { } private void initialiseServiceConnection() { + Log.v(TAG,"initialiseServiceConnection()"); mLm = mConnection.mSdServer.mLm; mWac = mConnection.mSdServer.mLm.mWac; updateUi(); } - private void updateUi() { - SharedPreferences prefs; - String storedAuthToken; - LinearLayout loginLl = (LinearLayout)findViewById(R.id.login_ui); - LinearLayout logoutLl = (LinearLayout)findViewById(R.id.logout_ui); - Log.i(TAG, "switchUi()"); - storedAuthToken = getAuthToken(); //mWac.getStoredToken(); - //prefs = PreferenceManager.getDefaultSharedPreferences(this); - //storedAuthToken = (prefs.getString("webApiAuthToken", null)); - Log.v(TAG, "storedAuthToken=" + storedAuthToken); - // Check if we are already logged in - if (storedAuthToken == null || storedAuthToken.length() == 0) { - Log.v(TAG, "Not Logged in - showing log in UI"); - loginLl.setVisibility(View.VISIBLE); - logoutLl.setVisibility(View.GONE); - } else { + + // Called after the Firebase Auth UI has completed + private ActivityResultLauncher signInLauncher = registerForActivityResult( + new FirebaseAuthUIActivityResultContract(), + (result) -> { + Log.i(TAG, "FirebaseAuthUIActivityResult - " + result.toString()); + updateUi(); + }); + +// ... + + + private void updateUi() { + Log.v(TAG,"updateUi()"); + LinearLayout loginLl = (LinearLayout) findViewById(R.id.login_ui); + LinearLayout osdApiLoginLl = (LinearLayout) findViewById(R.id.login_osdapi_ui); + LinearLayout logoutLl = (LinearLayout) findViewById(R.id.logout_ui); + + if (mWac == null) { + Log.i(TAG,"mWac is null - not updating UI"); + return; + } + + if (mWac.isLoggedIn()) { Log.v(TAG, "Already Logged in - showing Log Out prompt"); loginLl.setVisibility(View.GONE); logoutLl.setVisibility(View.VISIBLE); - //TextView tv = (TextView)findViewById(R.id.tokenTv); - //tv.setText("Logged in with Token: "+storedAuthToken); - if (mWac != null) { - mWac.getUserProfile((JSONObject profileObj) -> { - try { - Long userId = profileObj.getLong("id"); - String userName = profileObj.getString("username"); - TextView tv2 = (TextView) findViewById(R.id.userIdTv); - tv2.setText(userId.toString()); - tv2 = (TextView) findViewById(R.id.usernameTv); - tv2.setText(userName); - } catch (JSONException e) { - Log.e(TAG, "Error Parsing profileObj: " + e.getMessage()); - mUtil.showToast("Error Parsing profileObj - this should not happen!!!"); - } - }); - } else { - Log.i(TAG,"UpdateUI - not retrieving profile because mWac is null"); + if (!LogManager.USE_FIREBASE_BACKEND) { + osdApiLoginLl.setVisibility(View.GONE); + } + mWac.getUserProfile((JSONObject profileObj) -> { + try { + String userId = profileObj.getString("id"); + String userName = profileObj.getString("username"); + TextView tv2 = (TextView) findViewById(R.id.userIdTv); + tv2.setText(userId); + tv2 = (TextView) findViewById(R.id.usernameTv); + tv2.setText(userName); + } catch (JSONException e) { + Log.e(TAG, "Error Parsing profileObj: " + e.getMessage()); + mUtil.showToast("Error Parsing profileObj - this should not happen!!!"); + } + }); + } else { + Log.v(TAG,"updateUi() - not logged in.."); + loginLl.setVisibility(View.VISIBLE); + logoutLl.setVisibility(View.GONE); + if (!LogManager.USE_FIREBASE_BACKEND) { + osdApiLoginLl.setVisibility(View.VISIBLE); } - } + } } View.OnClickListener onCancel = @@ -158,45 +221,78 @@ public class AuthenticateActivity extends AppCompatActivity { } }; - View.OnClickListener onOK = + View.OnClickListener onLogin = new View.OnClickListener() { @Override public void onClick(View view) { //m_status=true; - Log.v(TAG, "onOK()"); - String uname = mUnameEt.getText().toString(); - String passwd = mPasswdEt.getText().toString(); - Log.v(TAG,"onOK() - uname="+uname+", passwd="+passwd); - mWac.authenticate(uname, passwd, new WebApiConnection.StringCallback() { - @Override - public void accept(String retVal) { - if (retVal != null) { - Log.d(TAG,"Authentication Success - token is "+retVal); - mUtil.showToast("Login Successful"); - saveAuthToken(retVal); - updateUi(); - } else { - Log.e(TAG,"onOk: Authentication failure for "+uname+", "+passwd); - mUtil.showToast("ERROR: Authentication Failed - Please Try Again"); - mUtil.writeToSysLogFile("AuthActivity - Authorisation failed for "+uname+", "+passwd); + if (LogManager.USE_FIREBASE_BACKEND) { + Log.v(TAG, "onLogin() - using Firebase Login"); + Intent signInIntent = AuthUI.getInstance() + .createSignInIntentBuilder() + .setAvailableProviders(Arrays.asList( + new AuthUI.IdpConfig.GoogleBuilder().build(), + //new AuthUI.IdpConfig.FacebookBuilder().build(), + //new AuthUI.IdpConfig.TwitterBuilder().build(), + //new AuthUI.IdpConfig.MicrosoftBuilder().build(), + //new AuthUI.IdpConfig.YahooBuilder().build(), + //new AuthUI.IdpConfig.AppleBuilder().build(), + new AuthUI.IdpConfig.EmailBuilder().build() + //new AuthUI.IdpConfig.PhoneBuilder().build() + //new AuthUI.IdpConfig.AnonymousBuilder().build())) + )) + // ... options ... + .build(); + signInLauncher.launch(signInIntent); + } else { + // Use Username and password authentication for OSDAPI. + // FIXME - make this work with Google Authentication like we do for Firebase. + String uname = mUnameEt.getText().toString(); + String passwd = mPasswdEt.getText().toString(); + Log.v(TAG,"onOK() - uname="+uname+", passwd="+passwd); + mWac.authenticate(uname, passwd, new WebApiConnection.StringCallback() { + @Override + public void accept(String retVal) { + if (retVal != null) { + Log.d(TAG,"Authentication Success - token is "+retVal); + mUtil.showToast("Login Successful"); + saveAuthToken(retVal); + updateUi(); + } else { + Log.e(TAG,"onOk: Authentication failure for "+uname+", "+passwd); + mUtil.showToast("ERROR: Authentication Failed - Please Try Again"); + mUtil.writeToSysLogFile("AuthActivity - Authorisation failed for "+uname+", "+passwd); + } } - } - }); - //finish(); + }); + } } }; - View.OnClickListener onLogout = - new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.v(TAG, "onLogout"); - //m_status=false; - mWac.logout(); - saveAuthToken(null); - updateUi(); + View.OnClickListener onLogout = new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.v(TAG, "onLogout"); + if (LogManager.USE_FIREBASE_BACKEND) { + AuthUI.getInstance() + .signOut(getApplicationContext()) + .addOnCompleteListener(new OnCompleteListener() { + public void onComplete(@NonNull Task task) { + // user is now signed out + updateUi(); + } + }); + } else { + if (mWac != null) { + mWac.logout(); + saveAuthToken(null); + } else { + Log.e(TAG,"logout() - mWac is null - not doing anything"); + } } - }; + updateUi(); + } + }; View.OnClickListener onRegister = new View.OnClickListener() { @@ -244,5 +340,4 @@ public class AuthenticateActivity extends AppCompatActivity { } - } \ No newline at end of file diff --git a/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java b/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java index 74ecfbf..ccdc01e 100644 --- a/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java @@ -35,7 +35,7 @@ public class EditEventActivity extends AppCompatActivity { private HashMap> mEventSubTypesHashMap = null; private String mEventTypeStr = null; private String mEventSubTypeStr = null; - private Long mEventId; + private String mEventId; private String mEventNotes = ""; //private Date mEventDateTime; private RadioGroup mEventTypeRg; @@ -59,7 +59,7 @@ public class EditEventActivity extends AppCompatActivity { Bundle extras = getIntent().getExtras(); if (extras != null) { - Long eventId = extras.getLong("eventId"); + String eventId = extras.getString("eventId"); mEventId = eventId; Log.v(TAG, "onCreate - mEventId=" + mEventId); } @@ -68,7 +68,7 @@ public class EditEventActivity extends AppCompatActivity { Button cancelBtn = (Button) findViewById(R.id.cancelBtn); cancelBtn.setOnClickListener(onCancel); - Button OKBtn = (Button) findViewById(R.id.OKBtn); + Button OKBtn = (Button) findViewById(R.id.loginBtn); OKBtn.setOnClickListener(onOK); mEventTypeRg = findViewById(R.id.eventTypeRg); @@ -159,10 +159,10 @@ public class EditEventActivity extends AppCompatActivity { mWac.getEvent(mEventId, new WebApiConnection.JSONObjectCallback() { @Override public void accept(JSONObject eventObj) { - Log.v(TAG, "onCreate.getEvent"); + Log.v(TAG, "initialiseServiceConnection.getEvent"); if (eventObj != null) { mEventObj = eventObj; - Log.v(TAG, "onCreate.getEvent: eventObj=" + eventObj.toString()); + Log.v(TAG, "initialiseServiceConnection.getEvent: eventObj=" + eventObj.toString()); updateUi(); // FIXME: modify updateUi to use mEventObj } else { @@ -198,20 +198,27 @@ public class EditEventActivity extends AppCompatActivity { try { if (mEventObj != null) { tv = (TextView) findViewById(R.id.eventIdTv); - tv.setText(String.valueOf(mEventObj.getLong("id"))); + tv.setText(mEventId); tv = (TextView) findViewById(R.id.eventAlarmStateTv); - tv.setText(mEventObj.getString("alarmStateStr")); + String alarmStateStr = mEventObj.getString("osdAlarmState"); + try { + int alarmStateVal = Integer.parseInt(alarmStateStr); + alarmStateStr = mUtil.alarmStatusToString(alarmStateVal); + } catch (Exception e) { + Log.v(TAG,"updateUi: alarmState does not parse to int so displaying it as string: " +alarmStateStr); + } + tv.setText(alarmStateStr); tv = (TextView) findViewById(R.id.eventNotsTv); tv.setText(mEventObj.getString("desc")); tv = (TextView) findViewById(R.id.eventDateTv); try { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - Date dataTime = dateFormat.parse(mEventObj.getString("dataTime")); - dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + String dateStr = mEventObj.getString("dataTime"); + Date dataTime = mUtil.string2date(dateStr); + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); tv.setText(dateFormat.format(dataTime)); - } catch (ParseException e) { + } catch (Exception e) { Log.e(TAG,"updateUI: Error Parsing dataDate "+e.getLocalizedMessage()); tv.setText("---"); } @@ -283,12 +290,12 @@ public class EditEventActivity extends AppCompatActivity { TextView tv = (TextView)findViewById(R.id.eventNotsTv); try { mEventObj.put("desc",tv.getText()); + mEventObj.put("id",mEventId); // Add event Id to event object manually because firestore does not include it by default. } catch (JSONException e) { Log.e(TAG,"Error writing mEventObj: "+e.getMessage()); } Log.v(TAG, "onOK() - eventObj="+mEventObj.toString()); - try { mWac.updateEvent(mEventObj, new WebApiConnection.JSONObjectCallback() { @Override @@ -307,7 +314,7 @@ public class EditEventActivity extends AppCompatActivity { } }); } catch (Exception e) { - Log.e(TAG,"ERROR:"+e.getMessage()); + Log.e(TAG,"onOK() - ERROR: "+e.getMessage()+" : " +e.toString()); e.printStackTrace(); mUtil.showToast("Error Updating Event"); updateUi(); diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManager.java b/app/src/main/java/uk/org/openseizuredetector/LogManager.java index c5df64f..2f905b3 100644 --- a/app/src/main/java/uk/org/openseizuredetector/LogManager.java +++ b/app/src/main/java/uk/org/openseizuredetector/LogManager.java @@ -68,6 +68,7 @@ public class LogManager { static final private String TAG = "LogManager"; //private String mDbName = "osdData"; final static private String mDpTableName = "datapoints"; + final static private String mEventsTableName = "events"; private boolean mLogRemote; private boolean mLogRemoteMobile; private String mAuthToken; @@ -76,13 +77,15 @@ public class LogManager { private static Context mContext; private OsdUtil mUtil; public static WebApiConnection mWac; + public static final boolean USE_FIREBASE_BACKEND = false; private boolean mUploadInProgress; private long mEventDuration = 120; // event duration in seconds - uploads datapoints that cover this time range centred on the event time. public long mDataRetentionPeriod = 1; // Prunes the local db so it only retains data younger than this duration (in days) private long mRemoteLogPeriod = 60; // Period in seconds between uploads to the remote server. private ArrayList mDatapointsToUploadList; - private int mCurrentEventId; + private String mCurrentEventRemoteId; + private long mCurrentEventLocalId = -1; private int mCurrentDatapointId; private long mAutoPrunePeriod = 3600; // Prune the database every hour private boolean mAutoPruneDb; @@ -121,7 +124,12 @@ public class LogManager { mUtil = new OsdUtil(mContext, handler); openDb(); Log.i(TAG, "Starting Remote Database Interface"); - mWac = new WebApiConnection(mContext); + if (USE_FIREBASE_BACKEND) { + mWac = new WebApiConnection_firebase(mContext); + } else { + mWac = new WebApiConnection_osdapi(mContext); + } + mWac.setStoredToken(mAuthToken); if (mLogRemote) { @@ -162,7 +170,7 @@ public class LogManager { try { datapoint.put("id", c.getString(c.getColumnIndex("id"))); datapoint.put("dataTime", c.getString(c.getColumnIndex("dataTime"))); - datapoint.put("status", c.getString(c.getColumnIndex("Status"))); + datapoint.put("status", c.getString(c.getColumnIndex("status"))); datapoint.put("dataJSON", c.getString(c.getColumnIndex("dataJSON"))); datapoint.put("uploaded", c.getString(c.getColumnIndex("uploaded"))); //Log.v(TAG,"cursor2json() - datapoint="+datapoint.toString()); @@ -177,6 +185,41 @@ public class LogManager { return dataPointArray.toString(); } + /** + * Returns a JSON String representing an array of events that are selected from sqlite cursor c. + * + * @param c sqlite cursor pointing to events query result. + * @return JSON String. + * from https://stackoverflow.com/a/20488153/2104584 + */ + private String eventCursor2Json(Cursor c) { + StringBuilder cNames = new StringBuilder(); + for (String n : c.getColumnNames()) { + cNames.append(", ").append(n); + } + c.moveToFirst(); + Log.v(TAG, "eventCursor2Json: size of cursor=" + c.getCount()); + JSONArray eventsArray = new JSONArray(); + int i = 0; + while (!c.isAfterLast()) { + JSONObject event = new JSONObject(); + try { + event.put("id", c.getString(c.getColumnIndex("id"))); + event.put("dataTime", c.getString(c.getColumnIndex("dataTime"))); + event.put("status", c.getString(c.getColumnIndex("status"))); + event.put("uploaded", c.getString(c.getColumnIndex("uploaded"))); + c.moveToNext(); + eventsArray.put(i, event); + i++; + } catch (JSONException e) { + Log.e(TAG, "eventCursor2Json(): error creating JSON Object"); + e.printStackTrace(); + } + } + Log.v(TAG, "eventCursor2JSON(): returning " + eventsArray.toString()); + return eventsArray.toString(); + } + private static boolean openDb() { Log.d(TAG, "openDb"); @@ -187,11 +230,14 @@ public class LogManager { } else { Log.i(TAG, "openDb: mOsdDb has been initialised already so not doing anything"); } - if (!checkTableExists(mOsdDb, mDpTableName)) { - Log.e(TAG, "ERROR - Table " + mDpTableName + " does not exist"); - return false; - } else { - Log.d(TAG, "table " + mDpTableName + " exists ok"); + String[] tableNames = new String[]{mDpTableName, mEventsTableName}; + for (String tableName : tableNames) { + if (!checkTableExists(mOsdDb, tableName)) { + Log.e(TAG, "ERROR - Table " + tableName + " does not exist"); + return false; + } else { + Log.d(TAG, "table " + tableName + " exists ok"); + } } } catch (SQLException e) { Log.e(TAG, "Failed to open Database: " + e.toString()); @@ -221,17 +267,19 @@ public class LogManager { * FIXME - I am sure we should not be using raw SQL Srings to do this! */ public void writeDatapointToLocalDb(SdData sdData) { - Log.v(TAG, "writeDatapointToLocalDb()"); + //Log.v(TAG, "writeDatapointToLocalDb()"); Date curDate = new Date(); SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr = dateFormat.format(curDate); String SQLStr = "SQLStr"; + if (mOsdDb == null) { + Log.e(TAG, "writeDatapointToLocalDb(): mOsdDb is null - doing nothing"); + return; + } try { - //double roiRatio = -1; - //if (sdData.specPower != 0) - // roiRatio = 10. * sdData.roiPower / sdData.specPower; + // Write Datapoint to database SQLStr = "INSERT INTO " + mDpTableName + "(dataTime, status, dataJSON, uploaded)" + " VALUES(" @@ -240,20 +288,55 @@ public class LogManager { + DatabaseUtils.sqlEscapeString(sdData.toJSON(true)) + "," + 0 + ")"; - if (mOsdDb != null) { - mOsdDb.execSQL(SQLStr); - Log.v(TAG, "writeDatapointToLocalDb(): data written to database"); - } else { - Log.e(TAG,"writeDatapointToLocalDb(): mOsdDb is null"); - } + mOsdDb.execSQL(SQLStr); + Log.v(TAG, "writeDatapointToLocalDb(): datapoint written to database"); + if (sdData.alarmState != 0) { + Log.i(TAG, "writeDatapointToLocalDb(): adding event to local DB"); + createLocalEvent(dateStr, sdData.alarmState); + } } catch (SQLException e) { Log.e(TAG, "writeToLocalDb(): Error Writing Data: " + e.toString()); Log.e(TAG, "SQLStr was " + SQLStr); } catch (NullPointerException e) { Log.e(TAG, "writeToLocalDb(): Null Pointer Exception: " + e.toString()); } + } + public boolean createLocalEvent(String dataTime, long status) { + // Expects dataTime to be in format: SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Log.d(TAG,"createLocalEvent() - dataTime="+dataTime+", status="+status); + // Write Datapoint to database + String SQLStr = "INSERT INTO " + mEventsTableName + + "(dataTime, status)" + + " VALUES(" + + "'" + dataTime + "'," + + status + + ")"; + mOsdDb.execSQL(SQLStr); + return true; + } + + /** + * Returns a json representation of locally stored event 'id'. + * + * @param id event id to return + * @return JSON representation of requested event (single element JSON array) + */ + public String getLocalEventById(long id) { + Log.d(TAG, "getLocalEventById() - id=" + id); + Cursor c; + String retVal; + try { + String selectStr = "select * from " + mEventsTableName + " where id=" + id + ";"; + c = mOsdDb.rawQuery(selectStr, null); + retVal = eventCursor2Json(c); + } catch (Exception e) { + Log.d(TAG, "getLocalEventById(): Error Querying Database: " + e.getLocalizedMessage()); + retVal = null; + } + Log.d(TAG, "getLocalEventById() - returning " + retVal); + return (retVal); } @@ -269,9 +352,6 @@ public class LogManager { String retVal; try { String selectStr = "select * from " + mDpTableName + " where id=" + id + ";"; - //String[] selectArgs = new String[]{String.format("%d", id)}; - //c = mOSDDb.getWritableDatabase().query(mDbTableName, null, - // selectStr, selectArgs, null, null, null); c = mOsdDb.rawQuery(selectStr, null); retVal = cursor2Json(c); } catch (Exception e) { @@ -279,7 +359,6 @@ public class LogManager { retVal = null; } return (retVal); - } /** @@ -289,15 +368,17 @@ public class LogManager { * @param eventId - the eventId associated with the uploaded datapoint - the 'uploaded' field is set to this value. * @return True on success or False on failure. */ - public boolean setDatapointToUploaded(int id, int eventId) { + public boolean setDatapointToUploaded(int id, String eventId) { Log.d(TAG, "setDatapointToUploaded() - id=" + id); + if (mOsdDb == null) { + Log.e(TAG,"setDatapointToUploaded() - mOsdDb is null - not doing anything"); + return false; + } ContentValues cv = new ContentValues(); cv.put("uploaded", eventId); int nRowsUpdated = mOsdDb.update(mDpTableName, cv, "id = ?", new String[]{String.format("%d", id)}); - return (nRowsUpdated == 1); - } /** @@ -356,7 +437,7 @@ public class LogManager { String whereClause = getEventWhereClause(includeWarnings); //sqlStr = "SELECT * from " + mDbTableName + " where Status in (" + statusListStr + ") order by dataTime desc;"; String[] columns = {"*"}; - new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs, + new SelectQueryTask(mEventsTableName, columns, whereClause, whereArgs, null, null, "dataTime DESC", (Cursor cursor) -> { Log.v(TAG, "getEventsList - returned " + cursor); if (cursor != null) { @@ -365,7 +446,7 @@ public class LogManager { HashMap event = new HashMap<>(); //event.put("id", cursor.getString(cursor.getColumnIndex("id"))); event.put("dataTime", cursor.getString(cursor.getColumnIndex("dataTime"))); - int status = cursor.getInt(cursor.getColumnIndex("Status")); + int status = cursor.getInt(cursor.getColumnIndex("status")); String statusStr = mUtil.alarmStatusToString(status); event.put("status", statusStr); event.put("uploaded", cursor.getString(cursor.getColumnIndex("uploaded"))); @@ -385,25 +466,48 @@ public class LogManager { */ public int pruneLocalDb() { Log.d(TAG, "pruneLocalDb()"); - int retVal; + int retVal = 0; long currentDateMillis = new Date().getTime(); long endDateMillis = currentDateMillis - 24 * 3600 * 1000 * mDataRetentionPeriod; //long endDateMillis = currentDateMillis - 3600*1000* mDataRetentionPeriod; // Using hours rather than days for testing SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - String endDateStr = dateFormat.format(new Date(endDateMillis)); - try { - String selectStr = "DataTime<=?"; - String[] selectArgs = {endDateStr}; - retVal = mOsdDb.delete(mDpTableName, selectStr, selectArgs); - } catch (Exception e) { - Log.d(TAG, "Error deleting datapoints" + e.toString()); - retVal = 0; + String[] tableNames = new String[]{mDpTableName, mEventsTableName}; + for (String tableName : tableNames) { + Log.i(TAG, "pruneLocalDb - pruning table " + tableName); + try { + String selectStr = "DataTime<=?"; + String[] selectArgs = {endDateStr}; + retVal = mOsdDb.delete(tableName, selectStr, selectArgs); + } catch (Exception e) { + Log.d(TAG, "Error deleting data " + e.toString()); + retVal = 0; + } + Log.d(TAG, String.format("pruneLocalDb() - deleted %d records from table %s", retVal, tableName)); } - Log.d(TAG, String.format("pruneLocalDb() - deleted %d records", retVal)); return (retVal); } + /** + * setEventToUploaded + * + * @param localEventId - local Event ID to change + * @param remoteEventId - the remote eventId associated with the uploaded datapoint - the 'uploaded' field is set to this value. + * @return True on success or False on failure. + */ + public boolean setEventToUploaded(long localEventId, String remoteEventId) { + Log.d(TAG, "setEventToUploaded() - local id=" + localEventId + " remote id="+remoteEventId); + if (mOsdDb == null) { + Log.e(TAG,"setEventToUploaded() - mOsdDb is null - not doing anything"); + return false; + } + ContentValues cv = new ContentValues(); + cv.put("uploaded", remoteEventId); + int nRowsUpdated = mOsdDb.update(mEventsTableName, cv, "id = ?", + new String[]{String.format("%d", localEventId)}); + return (nRowsUpdated == 1); + } + /** * Return the ID of the next event (alarm, warning, fall etc that needs to be uploaded (alarm or warning condition and has not yet been uploaded. @@ -423,7 +527,7 @@ public class LogManager { long endDateMillis = currentDateMillis - 1000 * mEventDuration; SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String endDateStr = dateFormat.format(new Date(endDateMillis)); - String whereClauseUploaded = "uploaded = 0"; + String whereClauseUploaded = "uploaded is null"; String whereClauseDate = "DataTime { - Log.v(TAG, "getEventsList - returned " + cursor); Long recordId = new Long(-1); if (cursor != null) { Log.v(TAG, "getNextEventToUpload - returned " + cursor.getCount() + " records"); @@ -443,9 +546,8 @@ public class LogManager { Log.v(TAG, "getNextEventToUpload() - no events to Upload - exiting"); recordId = new Long(-1); } else { - String recordStr = cursor.getString(3); recordId = cursor.getLong(0); - Log.d(TAG, "getNextEventToUpload(): id=" + recordId + ", recordStr=" + recordStr); + Log.d(TAG, "getNextEventToUpload(): id=" + recordId); } } callback.accept(recordId); @@ -494,13 +596,13 @@ public class LogManager { * @return True on successful start or false if call fails. */ public boolean getLocalEventsCount(boolean includeWarnings, WebApiConnection.LongCallback callback) { - Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings); + //Log.v(TAG, "getLocalEventsCount- includeWarnings=" + includeWarnings); String[] whereArgs = getEventWhereArgs(includeWarnings); String whereClause = getEventWhereClause(includeWarnings); String[] columns = {"*"}; - new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs, + new SelectQueryTask(mEventsTableName, columns, whereClause, whereArgs, null, null, null, (Cursor cursor) -> { - Log.v(TAG, "getLocalEventsCount - returned " + cursor); + //Log.v(TAG, "getLocalEventsCount - returned " + cursor); Long eventCount = Long.valueOf(0); if (cursor != null) { eventCount = Long.valueOf(cursor.getCount()); @@ -517,13 +619,13 @@ public class LogManager { * @return True on successful start or false if call fails. */ public boolean getLocalDatapointsCount(WebApiConnection.LongCallback callback) { - Log.v(TAG, "getLocalDatapointsCount"); + //Log.v(TAG, "getLocalDatapointsCount"); String[] whereArgs = null; String whereClause = null; String[] columns = {"*"}; new SelectQueryTask(mDpTableName, columns, whereClause, whereArgs, null, null, null, (Cursor cursor) -> { - Log.v(TAG, "getLocalDatapointsCount - returned " + cursor); + //Log.v(TAG, "getLocalDatapointsCount - returned " + cursor); Long eventCount = Long.valueOf(0); if (cursor != null) { eventCount = Long.valueOf(cursor.getCount()); @@ -568,7 +670,7 @@ public class LogManager { @Override protected Cursor doInBackground(Void... params) { - Log.v(TAG, "runSelect.doInBackground()"); + //Log.v(TAG, "runSelect.doInBackground()"); Log.v(TAG, "SelectQueryTask.doInBackground: mTable=" + mTable + ", mColumns=" + Arrays.toString(mColumns) + ", mSelection=" + mSelection + ", mSelectionArgs=" + Arrays.toString(mSelectionArgs) + ", mGroupBy=" + mGroupBy + ", mHaving =" + mHaving + ", mOrderBy=" + mOrderBy); @@ -659,17 +761,24 @@ public class LogManager { */ public void uploadSdData() { //int eventId = -1; - Log.v(TAG, "uploadSdData()"); + //Log.v(TAG, "uploadSdData()"); // First try uploading full alarms, and only if we do not have any of those, upload warnings. - boolean warningsArr[] = { false, true }; - for (int n=0; n { if (eventId != -1) { - Log.v(TAG, "uploadSdData() - eventId=" + eventId); - String eventJsonStr = getDatapointById(eventId); - Log.v(TAG, "uploadSdData() - eventJsonStr=" + eventJsonStr); + Log.i(TAG, "uploadSdData() - next Event to Upload eventId=" + eventId); + String eventJsonStr = getLocalEventById(eventId); + Log.v(TAG, "uploadSdData() - event to upload eventJsonStr=" + eventJsonStr); //int eventType; JSONObject eventObj; int eventAlarmStatus; @@ -680,36 +789,42 @@ public class LogManager { eventObj = datapointJsonArr.getJSONObject(0); // We only look at the first (and hopefully only) item in the array. eventAlarmStatus = Integer.parseInt(eventObj.getString("status")); eventDateStr = eventObj.getString("dataTime"); - Log.v(TAG, "uploadSdData - data from local DB is:" + eventJsonStr + ", eventAlarmStatus=" + Log.d(TAG, "uploadSdData - data from local DB is:" + eventJsonStr + ", eventAlarmStatus=" + eventAlarmStatus + ", eventDateStr=" + eventDateStr); } catch (JSONException e) { - Log.e(TAG, "ERROR parsing event JSON Data" + eventJsonStr); + Log.e(TAG, "uploadSdData(): ERROR parsing event JSON Data" + eventJsonStr); e.printStackTrace(); return; } catch (NullPointerException e) { - Log.e(TAG, "ERROR null pointer exception parsing event JSON Data" + eventJsonStr); + Log.e(TAG, "uploadSdData(): ERROR null pointer exception parsing event JSON Data" + eventJsonStr); e.printStackTrace(); return; } try { eventDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(eventDateStr); } catch (ParseException e) { - Log.e(TAG, "Error parsing date " + eventDateStr); + Log.e(TAG, "UploadSdData(): Error parsing date " + eventDateStr); return; } + + Log.i(TAG, "uploadSdData - calling mWac.createEvent"); + mCurrentEventLocalId = eventId; mWac.createEvent(eventAlarmStatus, eventDate, "", this::createEventCallback); } else { - Log.v(TAG, "UploadSdData - no data to upload"); + Log.v(TAG, "uploadSdData - no data to upload "); //(warnings="+warningsVal+")"); + mUploadInProgress = false; } }); } } - // Mark the relevant member variables to show we are not currently doing an upload, so a new one can be + // Mark the relevant member variables to show we are not cuurrently doing an upload, so a new one can be // started if necessary. public void finishUpload() { - mCurrentEventId = -1; + mCurrentEventRemoteId = null; + mCurrentEventLocalId = -1; + mCurrentDatapointId = -1; mDatapointsToUploadList = null; mUploadInProgress = false; } @@ -717,82 +832,97 @@ public class LogManager { // Called by WebApiConnection when a new event record is created. // Once the event is created it queries the local database to find the datapoints associated with the event // and uploads those as a batch of data points. - public void createEventCallback(String eventStr) { - Log.v(TAG, "eventCallback(): " + eventStr); - Date eventDate; - String eventDateStr; - int eventId; - try { - JSONObject eventObj = new JSONObject(eventStr); - eventDateStr = eventObj.getString("dataTime"); - eventId = eventObj.getInt("id"); - } catch (JSONException e) { - Log.e(TAG, "eventCallback() - Error parsing eventStr: " + eventStr); - finishUpload(); - return; - } - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - try { - eventDate = dateFormat.parse(eventDateStr); - } catch (ParseException e) { - Log.e(TAG, "eventCallback() - error parsing date string " + eventDateStr); - finishUpload(); - return; - } - Log.v(TAG, "eventCallback() EventId=" + eventId + ", eventDateStr=" + eventDateStr + ", eventDate=" + eventDate); - long eventDateMillis = eventDate.getTime(); - long startDateMillis = eventDateMillis - 1000 * mEventDuration / 2; - long endDateMillis = eventDateMillis + 1000 * mEventDuration / 2; - dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - getDatapointsByDate( - dateFormat.format(new Date(startDateMillis)), - dateFormat.format(new Date(endDateMillis)), (String datapointsJsonStr) -> { - Log.v(TAG, "eventCallback() - datapointsJsonStr=" + datapointsJsonStr); - JSONArray dataObj; - mDatapointsToUploadList = new ArrayList(); + public void createEventCallback(String eventId) { + Log.v(TAG, "createEventCallback(): " + eventId); + Log.v(TAG, "createEventCallback(): Retrieving remote event details"); + mWac.getEvent(eventId, new WebApiConnection.JSONObjectCallback() { + @Override + public void accept(JSONObject eventObj) { + if (eventObj == null) { + Log.e(TAG,"createEventCallback() - eventObj is null - failed to create event"); + mUtil.showToast("Error Creating Remote Event"); + } else { + Log.v(TAG, "createEventCallback() - eventObj=" + eventObj.toString()); + Date eventDate; + String eventDateStr = ""; try { - //DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - dataObj = new JSONArray(datapointsJsonStr); - for (int i = 0; i < dataObj.length(); i++) { - mDatapointsToUploadList.add(dataObj.getJSONObject(i)); - } + String dateStr= eventObj.getString("dataTime"); + eventDate = mUtil.string2date(dateStr); } catch (JSONException e) { - Log.v(TAG, "Error Creating JSON Object from string " + datapointsJsonStr); - dataObj = null; + Log.e(TAG, "createEventCallback() - Error parsing JSONObject: " + eventObj.toString()); + finishUpload(); + return; + } + if (eventDate != null) { + Log.v(TAG, "createEventCallback() EventId=" + eventId + ", eventDateStr=" + eventDateStr + ", eventDate=" + eventDate); + mUploadInProgress = true; + long eventDateMillis = eventDate.getTime(); + long startDateMillis = eventDateMillis - 1000 * mEventDuration / 2; + long endDateMillis = eventDateMillis + 1000 * mEventDuration / 2; + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + getDatapointsByDate( + dateFormat.format(new Date(startDateMillis)), + dateFormat.format(new Date(endDateMillis)), + (String datapointsJsonStr) -> { + //Log.v(TAG, "createEventCallback() - datapointsJsonStr=" + datapointsJsonStr); + JSONArray dataObj; + mDatapointsToUploadList = new ArrayList(); + try { + //DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + dataObj = new JSONArray(datapointsJsonStr); + Log.v(TAG, "createEventCallback() - datapointsObj length=" + dataObj.length()); + for (int i = 0; i < dataObj.length(); i++) { + mDatapointsToUploadList.add(dataObj.getJSONObject(i)); + } + } catch (JSONException e) { + Log.v(TAG, "createEventCallback(): Error Creating JSON Object from string " + datapointsJsonStr); + dataObj = null; + finishUpload(); + } + // This starts the process of uploading the datapoints, one at a time. + mCurrentEventRemoteId = eventId; + Log.v(TAG, "createEventCallback() - starting datapoints upload with eventId " + mCurrentEventRemoteId + + " Uploading " + mDatapointsToUploadList.size() + " datapoints"); + uploadNextDatapoint(); + + }); + } else { + Log.e(TAG,"createEventCallback() - Error - event date is null - not doing anything"); + mUtil.showToast("Error uploading event - date is null"); finishUpload(); } - // This starts the process of uploading the datapoints, one at a time. - mCurrentEventId = eventId; - mUploadInProgress = true; - Log.v(TAG, "eventCallback() - starting datapoints upload with eventId " + mCurrentEventId); - uploadNextDatapoint(); - - }); + } + } + }); } // takes the next datapoint of the list mDatapointsToUploadList and uploads it to the remote server. // datapointCallback is called when the upload is complete. public void uploadNextDatapoint() { - Log.v(TAG, "uploadDatapoint()"); - if (mDatapointsToUploadList.size() > 0) { - mUploadInProgress = true; - try { - mCurrentDatapointId = mDatapointsToUploadList.get(0).getInt("id"); - } catch (JSONException e) { - Log.e(TAG, "Error reading currentDatapointID from mDatapointsToUploadList[0]" + e.getMessage()); - Log.e(TAG, "Removing mDatapointsToUploadList[0] and trying the next datapoint"); - mDatapointsToUploadList.remove(0); - uploadNextDatapoint(); + //Log.v(TAG, "uploadNextDatapoint()"); + if (mDatapointsToUploadList != null) { + if (mDatapointsToUploadList.size() > 0) { + mUploadInProgress = true; + try { + mCurrentDatapointId = mDatapointsToUploadList.get(0).getInt("id"); + } catch (JSONException e) { + Log.e(TAG, "uploadNextDatapoint(): Error reading currentDatapointID from mDatapointsToUploadList[0]" + e.getMessage()); + Log.e(TAG, "uploadNextDatapoint(): Removing mDatapointsToUploadList[0] and trying the next datapoint"); + mDatapointsToUploadList.remove(0); + uploadNextDatapoint(); + } + + Log.v(TAG, "uploadNextDatapoint() - " + mDatapointsToUploadList.size() + " datapoints to upload. Uploading datapoint ID:" + mCurrentDatapointId); + mWac.createDatapoint(mDatapointsToUploadList.get(0), mCurrentEventRemoteId, this::datapointCallback); + + } else { + Log.i(TAG, "uploadNextDatapoint() - All datapoints uploaded!"); + setEventToUploaded(mCurrentEventLocalId, mCurrentEventRemoteId); + finishUpload(); } - - Log.v(TAG, "uploadDatapoint() - uploading datapoint with local id of " + mCurrentDatapointId); - mWac.createDatapoint(mDatapointsToUploadList.get(0), mCurrentEventId, this::datapointCallback); - } else { - mCurrentEventId = -1; - mCurrentDatapointId = -1; - mUploadInProgress = false; + Log.w(TAG,"uploadNextDatapoint - mDatapointsToUploadList is null - I don't thin this should have happened!"); } } @@ -800,11 +930,15 @@ public class LogManager { // a datapoint based on mDatapointsToUploadList(0) so removes that from the list and calls UploadDatapoint() // to upload the next one. public void datapointCallback(String datapointStr) { - Log.v(TAG, "datapointCallback() " + datapointStr + ", mCurrentEventId=" + mCurrentEventId); - if (mDatapointsToUploadList.size() > 0) { - mDatapointsToUploadList.remove(0); + Log.v(TAG, "datapointCallback() dataPointId="+mCurrentDatapointId+" remote datapointID=" + datapointStr + ", mCurrentEventId=" + mCurrentEventRemoteId); + if (mDatapointsToUploadList != null) { + if (mDatapointsToUploadList.size() > 0) { + mDatapointsToUploadList.remove(0); + } + } else { + Log.w(TAG,"datapointCallback - mDatapointsToUploadList is null - I don't thin this should have happened!"); } - setDatapointToUploaded(mCurrentDatapointId, mCurrentEventId); + setDatapointToUploaded(mCurrentDatapointId, mCurrentEventRemoteId); uploadNextDatapoint(); } @@ -903,9 +1037,19 @@ public class LogManager { String SQLStr = "CREATE TABLE IF NOT EXISTS " + mDpTableName + "(" + "id INTEGER PRIMARY KEY," + "dataTime DATETIME," - + "Status INT," + + "status INT," + "dataJSON TEXT," - + "uploaded INT" + + "uploaded TEXT" // Stores the ID of the datapoint in the remote database if uploaded, otherwise empty + + ");"; + db.execSQL(SQLStr); + Log.i(TAG, "onCreate - TableName=" + mEventsTableName); + SQLStr = "CREATE TABLE IF NOT EXISTS " + mEventsTableName + "(" + + "id INTEGER PRIMARY KEY," + + "dataTime DATETIME," + + "status INT," + + "type TEXT," + + "subType TEXT," + + "uploaded TEXT" // stores the id of the event in the remote dabase if uploaded, otherwise empty + ");"; db.execSQL(SQLStr); } diff --git a/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java index e10ed0c..a7eb350 100644 --- a/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/LogManagerControlActivity.java @@ -11,9 +11,11 @@ import android.os.Bundle; import android.os.CountDownTimer; import android.os.Handler; import android.os.IBinder; + import androidx.core.view.MenuCompat; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; + import android.util.Log; import android.view.Menu; import android.view.MenuItem; @@ -219,14 +221,34 @@ public class LogManagerControlActivity extends AppCompatActivity { // A bit of a hack to display in reverse chronological order for (int i = eventsArray.length() - 1; i >= 0; i--) { JSONObject eventObj = eventsArray.getJSONObject(i); - Long id = eventObj.getLong("id"); - int osdAlarmState = eventObj.getInt("osdAlarmState"); - String dataTime = eventObj.getString("dataTime"); - String typeStr = eventObj.getString("type"); - String subType = eventObj.getString("subType"); - String desc = eventObj.getString("desc"); + Log.v(TAG, "getRemoteEvents() - " + eventObj.toString()); + String id = null; + if (!eventObj.isNull("id")) { + id = eventObj.getString("id"); + } + int osdAlarmState = -1; + if (!eventObj.isNull("osdAlarmState")) { + osdAlarmState = eventObj.getInt("osdAlarmState"); + } + String dataTime = "null"; + if (!eventObj.isNull("dataTime")) { + dataTime = eventObj.getString("dataTime"); + Log.v(TAG, "getRemoteEvents() - dataTime=" + dataTime); + } + String typeStr = "null"; + if (!eventObj.isNull("type")) { + typeStr = eventObj.getString("type"); + } + String subType = "null"; + if (!eventObj.isNull("subType")) { + subType = eventObj.getString("subType"); + } + String desc = "null"; + if (!eventObj.isNull("desc")) { + desc = eventObj.getString("desc"); + } HashMap eventHashMap = new HashMap(); - eventHashMap.put("id", String.valueOf(id)); + eventHashMap.put("id", id); eventHashMap.put("osdAlarmState", String.valueOf(osdAlarmState)); eventHashMap.put("osdAlarmStateStr", mUtil.alarmStatusToString(osdAlarmState)); eventHashMap.put("dataTime", dataTime); @@ -249,7 +271,7 @@ public class LogManagerControlActivity extends AppCompatActivity { private void updateUi() { - Log.i(TAG,"updateUi()"); + Log.i(TAG, "updateUi()"); boolean stopUpdating = true; TextView tv; Button btn; @@ -291,9 +313,9 @@ public class LogManagerControlActivity extends AppCompatActivity { if (mRemoteEventsList != null) { ListView lv = (ListView) findViewById(R.id.remoteEventsLv); ListAdapter adapter = new RemoteEventsAdapter(LogManagerControlActivity.this, mRemoteEventsList, R.layout.log_entry_layout_remote, - new String[]{"id","dataTime", "type", "subType", "osdAlarmStateStr", "desc"}, + new String[]{"id", "dataTime", "type", "subType", "osdAlarmStateStr", "desc"}, new int[]{R.id.event_id_remote_tv, R.id.event_date_remote_tv, R.id.event_type_remote_tv, R.id.event_subtype_remote_tv, - R.id.event_alarmState_remote_tv, R.id.event_notes_remote_tv}); + R.id.event_alarmState_remote_tv, R.id.event_notes_remote_tv}); lv.setAdapter(adapter); //Log.i(TAG,"adapter[0]="+adapter.getItem(0)); //Log.i(TAG,"adapter[3]="+adapter.getItem(3)); @@ -328,14 +350,14 @@ public class LogManagerControlActivity extends AppCompatActivity { } //updateUi(); public void onRadioButtonClicked(View view) { - LinearLayout localDataLl = (LinearLayout) findViewById(R.id.local_data_ll); + LinearLayout localDataLl = (LinearLayout) findViewById(R.id.local_data_ll); LinearLayout sharedDataLl = (LinearLayout) findViewById(R.id.shared_data_ll); LinearLayout syslogLl = (LinearLayout) findViewById(R.id.syslog_ll); // Is the button now checked? boolean checked = ((RadioButton) view).isChecked(); // Check which radio button was clicked - switch(view.getId()) { + switch (view.getId()) { case R.id.local_data_rb: if (checked) { // Switch to the local data view @@ -351,7 +373,7 @@ public class LogManagerControlActivity extends AppCompatActivity { sharedDataLl.setVisibility(View.VISIBLE); syslogLl.setVisibility(View.GONE); } - break; + break; case R.id.syslog_rb: if (checked) { // Switch to the local data view @@ -411,7 +433,6 @@ public class LogManagerControlActivity extends AppCompatActivity { } - View.OnClickListener onAuth = new View.OnClickListener() { @Override @@ -504,7 +525,7 @@ public class LogManagerControlActivity extends AppCompatActivity { public void onItemClick(AdapterView adapter, View v, int position, long id) { Log.v(TAG, "onRemoteEventList Click() - Position=" + position + ", id=" + id);// Confirmation dialog based on: https://stackoverflow.com/a/12213536/2104584 HashMap eventObj = (HashMap) adapter.getItemAtPosition(position); - Long eventId = Long.parseLong(eventObj.get("id")); + String eventId = eventObj.get("id"); Log.d(TAG, "onItemClickListener(): eventId=" + eventId + ", eventObj=" + eventObj); Intent i = new Intent(getApplicationContext(), EditEventActivity.class); i.putExtra("eventId", eventId); @@ -588,9 +609,9 @@ public class LogManagerControlActivity extends AppCompatActivity { @Override public View getView(int position, View convertView, ViewGroup parent) { View v = super.getView(position, convertView, parent); - Map dataItem = (Map)getItem(position); - Log.v(TAG,"getView() "+dataItem.toString()); - switch(dataItem.get("type").toString()) { + Map dataItem = (Map) getItem(position); + Log.v(TAG, "getView() " + dataItem.toString()); + switch (dataItem.get("type").toString()) { case "null": v.setBackgroundColor(Color.parseColor("#ffaaaa")); break; @@ -603,20 +624,17 @@ public class LogManagerControlActivity extends AppCompatActivity { // Convert date format to something more readable. TextView tv = (TextView) v.findViewById(R.id.event_date_remote_tv); - try { - SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - Date dataTime = dateFormat.parse(dataItem.get("dataTime").toString()); - dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); + Date dataTime = null; + String dateStr = (String) dataItem.get("dataTime"); + dataTime = mUtil.string2date(dateStr); + if (dataTime != null) { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss"); tv.setText(dateFormat.format(dataTime)); - } catch (ParseException e) { - Log.e(TAG,"remoteEventsAdapter.getView: Error Parsing dataDate "+e.getLocalizedMessage()); + } else { tv.setText("---"); } - - - return(v); + return (v); } - }; - + } } \ No newline at end of file diff --git a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java index b8f5d2d..bb3a5a9 100644 --- a/app/src/main/java/uk/org/openseizuredetector/MainActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/MainActivity.java @@ -631,7 +631,7 @@ public class MainActivity extends AppCompatActivity { tv.setBackgroundColor(okColour); tv.setTextColor(okTextColour); - if (!mConnection.mSdServer.mLm.mWac.mServerConnectionOk) { + if (!mConnection.mSdServer.mLm.mWac.checkServerConnection()) { // Problem connecting to server tv = (TextView) findViewById(R.id.remoteDbTv); tv.setText(getString(R.string.data_sharing_status) diff --git a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java index 469a2d8..972e053 100644 --- a/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java +++ b/app/src/main/java/uk/org/openseizuredetector/OsdUtil.java @@ -59,6 +59,7 @@ import java.io.File; import java.io.FileWriter; import java.net.InetAddress; import java.net.NetworkInterface; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -453,6 +454,31 @@ public class OsdUtil { } } + /** + * string2date - returns a Date object represented by string dateStr + * It first attempts to parse it as a long integer, in which case it is assumed to + * be a unix timestamp. + * If that fails it attempts to parse it as yyyy-MM-dd'T'HH:mm:ss'Z' format. + * @param dateStr String reprenting a date + * @return Date object or null if parsing fails. + */ + public Date string2date(String dateStr) { + Date dataTime = null; + try { + Long tstamp = Long.parseLong(dateStr); + dataTime = new Date(tstamp); + } catch (NumberFormatException e) { + Log.v(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate as Long: " + e.getLocalizedMessage()+" trying as string"); + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + dataTime = dateFormat.parse(dateStr); + } catch (ParseException e2) { + Log.e(TAG, "remoteEventsAdapter.getView: Error Parsing dataDate " + e2.getLocalizedMessage()); + dataTime = null; + } + } + return(dataTime); + } public final int ALARM_STATUS_WARNING = 1; diff --git a/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java index 04abc4c..2ffa351 100644 --- a/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java @@ -17,6 +17,7 @@ import android.widget.DatePicker; import android.widget.TextView; import android.widget.TimePicker; +import java.text.SimpleDateFormat; import java.util.Calendar; /** @@ -40,6 +41,7 @@ public class ReportSeizureActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { Log.v(TAG, "onCreate()"); super.onCreate(savedInstanceState); + mContext = this; mUtil = new OsdUtil(this, serverStatusHandler); if (!mUtil.isServerRunning()) { mUtil.showToast(getString(R.string.error_server_not_running)); @@ -65,7 +67,7 @@ public class ReportSeizureActivity extends AppCompatActivity { //mLm= new LogManager(mContext); Button okBtn = - (Button) findViewById(R.id.OKBtn); + (Button) findViewById(R.id.loginBtn); okBtn.setOnClickListener(onOk); Button cancelBtn = @@ -142,22 +144,12 @@ public class ReportSeizureActivity extends AppCompatActivity { @Override public void onClick(View view) { Log.v(TAG, "onOk"); + //SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String dateStr=String.format("%4d-%02d-%02d %02d:%02d:30",mYear,mMonth+1,mDay, mHour, mMinute); Log.v(TAG, "onOk() - dateSTr="+dateStr); - mMsg = "Finding Nearest Datapoint to Date/Time "+dateStr+"..."; - mLm.getNearestDatapointToDate(dateStr, (Long id) -> { - mMsg = mMsg + "\nNearest Datapoint is "+id; - Log.v(TAG, "onOK() - nearest datapoint is "+id); - if (id!=-1) { - mLm.setDatapointStatus(id,5); - mMsg = mMsg + "\nSet Datapoint to Manual Alarm Status"; - mUtil.showToast(getString(R.string.createdNewEvent)); - finish(); - } else { - mMsg = mMsg + "\n*** Datapoint not found - not doing anything ***"; - mUtil.showToast(getString(R.string.DatapointNotFound)); - } - }); + mLm.createLocalEvent(dateStr,5); + mUtil.showToast("Seizure Event Created"); + finish(); } }; View.OnClickListener onCancel = diff --git a/app/src/main/java/uk/org/openseizuredetector/SdServer.java b/app/src/main/java/uk/org/openseizuredetector/SdServer.java index e043a71..96421e8 100644 --- a/app/src/main/java/uk/org/openseizuredetector/SdServer.java +++ b/app/src/main/java/uk/org/openseizuredetector/SdServer.java @@ -138,7 +138,7 @@ public class SdServer extends Service implements SdDataReceiver { private long mEventsTimerPeriod = 60; // Number of seconds between checks to see if there are unvalidated remote events. private long mEventDuration = 120; // event duration in seconds - uploads datapoints that cover this time range centred on the event time. public long mDataRetentionPeriod = 1; // Prunes the local db so it only retains data younger than this duration (in days) - private long mRemoteLogPeriod = 60; // Period in seconds between uploads to the remote server. + private long mRemoteLogPeriod = 20; // Period in seconds between uploads to the remote server. private long mAutoPrunePeriod = 3600; // Prune the database every hour private boolean mAutoPruneDb; @@ -1137,7 +1137,7 @@ public class SdServer extends Service implements SdDataReceiver { //writeToSD(); mLm.writeDatapointToLocalDb(mSdData); } else { - Log.e(TAG,"logData() - mLm is null - this should not happen"); + Log.e(TAG, "logData() - mLm is null - this should not happen"); } } } @@ -1241,7 +1241,7 @@ public class SdServer extends Service implements SdDataReceiver { //prefVal = SP.getString("RemoteLogPeriod", "60"); //mRemoteLogPeriod = Integer.parseInt(prefVal); - mRemoteLogPeriod = 60; + //mRemoteLogPeriod = 60; Log.v(TAG, "mRemoteLogPeriod=" + mRemoteLogPeriod); //mOSDUname = SP.getString("OSDUname", ""); @@ -1300,8 +1300,8 @@ public class SdServer extends Service implements SdDataReceiver { Log.i(TAG, "SmsTimer.onFinish() - Last Location is Null so sending first SMS without location."); } } else { - Log.e(TAG,"SmsTImer.onFinish() - mLocationFinder is NULL - this should not happen!"); - mUtil.showToast("Error Finding Location - mLocationFinder is null - please report this issue!"); + Log.e(TAG,"SmsTimer.onFinish - mLocationFinder is null - this should not happen!"); + mUtil.showToast("SmsTimer.onFinish - mLocationFinder is null - this should not happen! - Please report this issue!"); } Log.i(TAG, "SmsTimer.onFinish() - Sending to " + mSMSNumbers.length + " Numbers"); mUtil.writeToSysLogFile("SdServer.SmsTimer.onFinish()"); @@ -1555,28 +1555,26 @@ public class SdServer extends Service implements SdDataReceiver { // Retrieve events from remote database if (mLm.mWac.getEvents((JSONObject remoteEventsObj) -> { Log.v(TAG, "CheckEvents.getEvents.Callback()"); - long firstUnvalidatedEvent; + Boolean haveUnvalidatedEvent = false; if (remoteEventsObj == null) { Log.e(TAG, "CheckEvents.Callback: Error Retrieving events"); } else { try { JSONArray eventsArray = remoteEventsObj.getJSONArray("events"); // A bit of a hack to display in reverse chronological order - firstUnvalidatedEvent = -1; for (int i = eventsArray.length() - 1; i >= 0; i--) { JSONObject eventObj = eventsArray.getJSONObject(i); - Long id = eventObj.getLong("id"); String typeStr = eventObj.getString("type"); //Log.v(TAG,"CheckEventsTimer: id="+id+", typeStr="+typeStr); - if (typeStr.equals("null")) { - firstUnvalidatedEvent = id; + if (typeStr.equals("null") || typeStr.equals("")) { + haveUnvalidatedEvent = true; //Log.v(TAG,"CheckEventsTimer:setting firstUnvalidatedEvent to "+firstUnvalidatedEvent); } } - Log.v(TAG, "CheckEventsTimer.onFinish.callback - firstUnvalidatedEvent = " + - firstUnvalidatedEvent); - if (firstUnvalidatedEvent >= 0) { - showEventNotification(firstUnvalidatedEvent); + Log.v(TAG, "CheckEventsTimer.onFinish.callback - haveUnvalidatedEvent = " + + haveUnvalidatedEvent); + if (haveUnvalidatedEvent) { + showEventNotification(); mNM.cancel(DATASHARE_NOTIFICATION_ID); } else { mNM.cancel(EVENT_NOTIFICATION_ID); @@ -1627,7 +1625,7 @@ public class SdServer extends Service implements SdDataReceiver { /** * Show a notification to tell the user that we have unvalidated events. */ - private void showEventNotification(long eventId) { + private void showEventNotification() { Log.v(TAG, "showEventNotification()"); int iconId; String titleStr; diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java index d188b08..f63e1b2 100644 --- a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java +++ b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java @@ -4,6 +4,8 @@ import android.content.Context; import android.os.Handler; import android.util.Log; +import androidx.annotation.NonNull; + import com.android.volley.AuthFailureError; import com.android.volley.Request; import com.android.volley.RequestQueue; @@ -12,6 +14,18 @@ import com.android.volley.VolleyError; import com.android.volley.VolleyLog; import com.android.volley.toolbox.StringRequest; import com.android.volley.toolbox.Volley; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; +import com.google.firebase.firestore.core.OrderBy; import org.json.JSONArray; import org.json.JSONException; @@ -22,20 +36,19 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; // This class is intended to handle all interactions with the OSD WebAPI -public class WebApiConnection { - public String retVal; - public int retCode; - public boolean mServerConnectionOk = false; - private String mUrlBase = "https://osdApi.ddns.net"; +public abstract class WebApiConnection { + protected Context mContext; + protected OsdUtil mUtil; private String TAG = "WebApiConnection"; private String mAuthToken; - private Context mContext; - private OsdUtil mUtil; - RequestQueue mQueue; + + public interface JSONObjectCallback { public void accept(JSONObject retValObj); @@ -51,237 +64,23 @@ public class WebApiConnection { public WebApiConnection(Context context) { mContext = context; - mQueue = Volley.newRequestQueue(context); mUtil = new OsdUtil(mContext, new Handler()); } public void close() { - Log.i(TAG,"stop()"); - mQueue.stop(); + Log.i(TAG, "stop()"); } - /** - * Attempt to authenticate with the web API using user name uname and password passwd. Calls function callback with either - * the authentication token on success or null on failure. - * - * @param uname - user name - * @param passwd - password - * @param callback - call back function callback(String retVal) - * @return true if request sent, or false if failed to send request. - */ - public boolean authenticate(final String uname, final String passwd, StringCallback callback) { - // NOTE: the 'final' keyword is necessary for uname and passwd to be accessible to getParams below - I don't know why! - // We know that this command works, so we just need the Java equivalent: - // curl -X POST -d 'login=graham4&password=testpwd1' https://osdapi.ddns.net/api/accounts/login/ - // sending the credentials as a JSONObject postData did not work, so try the method from: - // https://protocoderspoint.com/login-and-registration-form-in-android-using-volley-keeping-user-logged-in/#Login_Registration_form_in_android_using_volley_library - String urlStr = mUrlBase + "/api/accounts/login/"; - Log.v(TAG, "urlStr=" + urlStr); - - StringRequest req = new StringRequest(Request.Method.POST, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - String tokenStr = null; - Log.v(TAG, "Response is: " + response); - try { - JSONObject jo = new JSONObject(response); - tokenStr = jo.getString("token"); - mServerConnectionOk = true; - } catch (JSONException e) { - tokenStr = "Error Parsing Rsponse"; - } - setStoredToken(tokenStr); - callback.accept(tokenStr); - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - if (error != null) { - Log.e(TAG, "Login Error: " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "Login Error: Returned null response"); - } - mServerConnectionOk = false; - setStoredToken(null); - callback.accept(null); - } - }) { - // Note, this is overriding part of StringRequest, not one of the sub-classes above! - @Override - protected Map getParams() { - Map params = new HashMap<>(); - // params.put("name",sname); // passing parameters to server - params.put("login", uname); - params.put("password", passwd); - return params; - } - }; - - mQueue.add(req); - return (true); - } - - // Remove the stored token so future calls are not authenticated. - public void logout() { - Log.v(TAG, "logout()"); - setStoredToken(null); - //saveStoredToken(null); - } - - public void setStoredToken(String authToken) { - mAuthToken = authToken; - } - - private String getStoredToken() { - return (mAuthToken); - } - - public boolean isLoggedIn() { - String authToken = getStoredToken(); - //Log.v(TAG, "isLoggedIn(): token=" + authToken); - if (authToken == null || authToken.length() == 0) { - //Log.v(TAG, "isLogged in - not logged in"); - return (false); - } else { - return (true); - } - - } + public abstract boolean isLoggedIn(); // Create a new event in the remote database, based on the provided parameters. - public boolean createEvent(final int osdAlarmState, final Date eventDate, final String eventDesc, StringCallback callback) { - Log.v(TAG, "createEvent()"); - String urlStr = mUrlBase + "/api/events/"; - Log.v(TAG, "urlStr=" + urlStr); - final String authtoken = getStoredToken(); + // passes the newly created documentId to function callback on successful completion, or null on error. + public abstract boolean createEvent(final int osdAlarmState, final Date eventDate, final String eventDesc, StringCallback callback); - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - JSONObject jsonObject = new JSONObject(); - try { - jsonObject.put("osdAlarmState", String.valueOf(osdAlarmState)); - jsonObject.put("dataTime", dateFormat.format(eventDate)); - jsonObject.put("desc", eventDesc); - } catch (JSONException e) { - Log.e(TAG, "Error generating event JSON string"); - } - final String dataStr = jsonObject.toString(); - Log.v(TAG, "createEvent - data=" + dataStr); + // calls function callback with a JSONObject representation of the event with id 'eventId' + public abstract boolean getEvent(String eventId, JSONObjectCallback callback); - StringRequest req = new StringRequest(Request.Method.POST, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - mServerConnectionOk = true; - callback.accept(response); - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - mServerConnectionOk = false; - if (error != null) { - Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); - callback.accept(null); - } else { - Log.e(TAG, "Create Event Error - null respones"); - callback.accept(null); - } - } - }) { - // Note, this is overriding part of StringRequest, not one of the sub-classes above! - @Override - protected Map getParams() { - Map params = new HashMap<>(); - // params.put("name",sname); // passing parameters to server - String authToken = getStoredToken(); - params.put("Authorization: Token " + authToken, authToken); - Log.v(TAG, "getParams: params=" + params.toString()); - return params; - } - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - - @Override - public byte[] getBody() throws AuthFailureError { - try { - return dataStr == null ? null : dataStr.getBytes("utf-8"); - } catch (UnsupportedEncodingException uee) { - VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); - return null; - } - } - }; - - mQueue.add(req); - return (true); - } - - public boolean getEvent(Long eventId, JSONObjectCallback callback) { - //Long eventId=Long.valueOf(285); - Log.v(TAG, "getEvent()"); - String urlStr = mUrlBase + "/api/events/" + eventId; - Log.v(TAG, "getEvent(): urlStr=" + urlStr); - final String authtoken = getStoredToken(); - - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - - StringRequest req = new StringRequest(Request.Method.GET, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - try { - JSONObject retObj = new JSONObject(response); - retObj.put("alarmStateStr", mUtil.alarmStatusToString(retObj.getInt("osdAlarmState"))); - callback.accept(retObj); - } catch (JSONException e) { - Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); - callback.accept(null); - } - mServerConnectionOk = true; - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - if (error != null) { - Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "Create Event Error: returned null response"); - } - mServerConnectionOk = false; - callback.accept(null); - } - }) { - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - }; - mQueue.add(req); - return (true); - } /** * Retrieve all events accessible to the logged in user, and pass them to the callback function as a JSONObject @@ -289,305 +88,11 @@ public class WebApiConnection { * @param callback * @return true on success or false on failure to initiate the request. */ - public boolean getEvents(JSONObjectCallback callback) { - //Long eventId=Long.valueOf(285); - Log.v(TAG, "getEvents()"); - String urlStr = mUrlBase + "/api/events/"; - Log.v(TAG, "getEvents(): urlStr=" + urlStr); - final String authtoken = getStoredToken(); - - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - - StringRequest req = new StringRequest(Request.Method.GET, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - mServerConnectionOk = true; - try { - JSONObject retObj = new JSONObject(); - JSONArray eventArray = new JSONArray(response); - retObj.put("events", eventArray); - callback.accept(retObj); - } catch (JSONException e) { - Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); - callback.accept(null); - } - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - //if ((error != null) && (error.networkResponse != null) && (error.networkResponse.data != null)) {# - mServerConnectionOk = false; - if (error != null) { - if (error.networkResponse != null) { - Log.e(TAG, "getEvents(): Error: " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "getEvents(): Error: - request returned null networkResponse"); - } - } else{ - Log.e(TAG, "getEvents(): Error: - request returned null response"); - } - callback.accept(null); - } - }) { - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - }; - mQueue.add(req); - return (true); - } - - - public boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback) { - Long eventId; - Log.v(TAG, "updateEvent()"); - final String authtoken = getStoredToken(); - - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - try { - eventId = eventObj.getLong("id"); - } catch (JSONException e) { - Log.e(TAG, "updateEvent(): Error reading id from eventObj"); - eventId = Long.valueOf(-1); - } - final String dataStr = eventObj.toString(); - Log.v(TAG, "createEvent - data=" + dataStr); - - - int reqMethod; - String urlStr; - if (eventId != -1) { - Log.v(TAG, "updateEvent() - found eventId " + eventId + ", Updating event record"); - urlStr = mUrlBase + "/api/events/" + eventId + "/"; - Log.v(TAG, "urlStr=" + urlStr); - reqMethod = Request.Method.PUT; - } else { - Log.v(TAG, "updateEvent() - eventId not found - creating new event record"); - urlStr = mUrlBase + "/api/events/"; - Log.v(TAG, "urlStr=" + urlStr); - reqMethod = Request.Method.POST; - } - - StringRequest req = new StringRequest(reqMethod, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - mServerConnectionOk = true; - try { - JSONObject retObj = new JSONObject(response); - callback.accept(retObj); - } catch (JSONException e) { - Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); - callback.accept(null); - } - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - mServerConnectionOk = false; - if (error != null) { - Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "Create Event Error - returned null response"); - } - callback.accept(null); - } - }) { - // Note, this is overriding part of StringRequest, not one of the sub-classes above! - @Override - protected Map getParams() { - Map params = new HashMap<>(); - // params.put("name",sname); // passing parameters to server - String authToken = getStoredToken(); - params.put("Authorization: Token " + authToken, authToken); - Log.v(TAG, "getParams: params=" + params.toString()); - //params.put("eventType", String.valueOf(eventType)); - //params.put("dataTime", dateFormat.format(eventDate)); - //params.put("desc", eventDesc); - return params; - } - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - - @Override - public byte[] getBody() throws AuthFailureError { - try { - return dataStr == null ? null : dataStr.getBytes("utf-8"); - } catch (UnsupportedEncodingException uee) { - VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); - return null; - } - } - }; - - mQueue.add(req); - return (true); - } - - - public boolean createDatapoint(JSONObject dataObj, int eventId, StringCallback callback) { - Log.v(TAG, "createDatapoint()"); - // Create a new event in the remote database, based on the provided parameters. - String urlStr = mUrlBase + "/api/datapoints/"; - Log.v(TAG, "urlStr=" + urlStr); - final String authtoken = getStoredToken(); - - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - - DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - JSONObject jsonObject = new JSONObject(); - try { - //jsonObject.put("userId", -1); - jsonObject.put("eventId", String.valueOf(eventId)); - jsonObject.put("dataTime", dataObj.getString("dataTime")); - jsonObject.put("dataJSON", dataObj.toString()); - } catch (JSONException e) { - Log.e(TAG, "Error generating event JSON string"); - } - final String dataStr = jsonObject.toString(); - Log.v(TAG, "createDatapoint - dataStr=" + dataStr); - - - StringRequest req = new StringRequest(Request.Method.POST, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - mServerConnectionOk = true; - callback.accept(response); - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - mServerConnectionOk = false; - if (error != null) { - Log.e(TAG, "Create Datapoint Error: " + error.toString() + ", message:" + error.getMessage()); - callback.accept(null); - } else { - Log.e(TAG, "Create Datapoint Error - returned null respones"); - callback.accept(null); - } - } - }) { - // Note, this is overriding part of StringRequest, not one of the sub-classes above! - @Override - protected Map getParams() { - Map params = new HashMap<>(); - // params.put("name",sname); // passing parameters to server - String authToken = getStoredToken(); - params.put("Authorization: Token " + authToken, authToken); - Log.v(TAG, "getParams: params=" + params.toString()); - return params; - } - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - - @Override - public byte[] getBody() throws AuthFailureError { - try { - return dataStr == null ? null : dataStr.getBytes("utf-8"); - } catch (UnsupportedEncodingException uee) { - VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); - return null; - } - } - }; - - mQueue.add(req); - return (true); - - } - - /** - * Retieve the user profile of the authenticated user from the server, and return it to the callback function. - * @param callback - function to be called with a JSONObject as a parameter that contains the user profile data. - * @return true if request sent successfully, or else false. - */ - public boolean getUserProfile(JSONObjectCallback callback) { - Log.v(TAG, "getUserProfile()"); - String urlStr = mUrlBase + "/api/accounts/profile/"; - Log.v(TAG, "getUserProfile(): urlStr=" + urlStr); - final String authtoken = getStoredToken(); - - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - - StringRequest req = new StringRequest(Request.Method.GET, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); - try { - JSONObject retObj = new JSONObject(response); - callback.accept(retObj); - } catch (JSONException e) { - Log.e(TAG, "getUserProfile.onRespons(): Error: " + e.getMessage() + "," + e.toString()); - callback.accept(null); - } - mServerConnectionOk = true; - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - if (error != null) { - Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "Create Event Error: returned null response"); - } - mServerConnectionOk = false; - callback.accept(null); - } - }) { - - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - }; - mQueue.add(req); - return (true); - } - + public abstract boolean getEvents(JSONObjectCallback callback); + public abstract boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback); + public abstract boolean createDatapoint(JSONObject dataObj, String eventId, StringCallback callback); /** * Retrieve the file containing the standard event types from the server. @@ -595,88 +100,37 @@ public class WebApiConnection { * * @return true if request sent successfully or else false. */ - public boolean getEventTypes(JSONObjectCallback callback) { - Log.v(TAG, "getEventTypes()"); - String urlStr = mUrlBase + "/static/eventTypes.json"; - Log.v(TAG, "urlStr=" + urlStr); - final String authtoken = getStoredToken(); + public abstract boolean getEventTypes(JSONObjectCallback callback); - if (!isLoggedIn()) { - Log.v(TAG, "not logged in - doing nothing"); - return (false); - } - - StringRequest req = new StringRequest(Request.Method.GET, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "getEventTypes.onResponse(): Response is: " + response); - mServerConnectionOk = true; - try { - JSONObject retObj = new JSONObject(response); - callback.accept(retObj); - } catch (JSONException e) { - Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); - callback.accept(null); - } - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - mServerConnectionOk = false; - if (error != null) { - Log.e(TAG, "getEventTypes.onErrorResponse(): " + error.toString() + ", message:" + error.getMessage()); - } else { - Log.e(TAG, "getEventTypes.onErrorResponse() - returned null response"); - } - callback.accept(null); - } - }) { - // Note, this is overriding part of StringRequest, not one of the sub-classes above! - @Override - public Map getHeaders() throws AuthFailureError { - Map params = new HashMap(); - params.put("Content-Type", "application/json; charset=UTF-8"); - params.put("Authorization", "Token " + getStoredToken()); - return params; - } - }; - - mQueue.add(req); - return (true); - - } /** * Retrieve a trivial file from the server to check we have a good server connection. - * sets mServerConnectionOk. + * sets mServerConnectionOk. + * * @return true if request sent successfully or else false. */ - public boolean checkServerConnection() { - Log.v(TAG, "checkServerConnection()"); - String urlStr = mUrlBase + "/static/test.txt"; - Log.v(TAG, "urlStr=" + urlStr); + public abstract boolean checkServerConnection(); - StringRequest req = new StringRequest(Request.Method.GET, urlStr, - new Response.Listener() { - @Override - public void onResponse(String response) { - Log.v(TAG, "checkServerConnection.onResponse(): Response is: " + response); - mServerConnectionOk = true; - } - }, - new Response.ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - Log.v(TAG, "checkServerConnection.onErrorResponse"); - mServerConnectionOk = false; - } - }); + public abstract boolean getUserProfile(JSONObjectCallback callback); - mQueue.add(req); - return (true); + public boolean authenticate(final String uname, final String passwd, StringCallback callback) { + Log.e(TAG,"WebApiConnection.authenticate(username, password, callback) Not Implemented"); + return false; + } + + // Remove the stored token so future calls are not authenticated. + public void logout() { + Log.v(TAG, "logout()"); + setStoredToken(null); + } + + protected void setStoredToken(String authToken) { + mAuthToken = authToken; + } + + protected String getStoredToken() { + return (mAuthToken); } } diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java new file mode 100644 index 0000000..30a060c --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_firebase.java @@ -0,0 +1,423 @@ +package uk.org.openseizuredetector; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.android.volley.RequestQueue; +import com.android.volley.toolbox.Volley; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.DocumentSnapshot; +import com.google.firebase.firestore.FirebaseFirestore; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.QueryDocumentSnapshot; +import com.google.firebase.firestore.QuerySnapshot; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + + +// This class is intended to handle all interactions with the OSD WebAPI +public class WebApiConnection_firebase extends WebApiConnection { + public String retVal; + public int retCode; + public boolean mServerConnectionOk = false; + private String TAG = "WebApiConnection_firebase"; + private String mAuthToken; + private Context mContext; + private OsdUtil mUtil; + FirebaseFirestore mDb; + + RequestQueue mQueue; + + + public WebApiConnection_firebase(Context context) { + super(context); + loginToFirebase(); + } + + public void loginToFirebase() { + // Check if we are already logged in + FirebaseAuth auth = FirebaseAuth.getInstance(); + mDb = FirebaseFirestore.getInstance(); + if (auth != null) { + if (auth.getCurrentUser() != null) { + Log.i(TAG, "Firebase Logged in OK -" + auth.getCurrentUser().getDisplayName()); + } else { + Log.e(TAG, "Firebase not logged in - no current user"); + } + } else { + Log.e(TAG, "Firebase not logged in"); + } + } + + public void close() { + Log.i(TAG, "stop()"); + mQueue.stop(); + } + + public boolean isLoggedIn() { + FirebaseAuth auth = FirebaseAuth.getInstance(); + if (auth != null) { + if (auth.getCurrentUser() != null) { + //Log.v(TAG, "isLoggedIn(): Firebase Logged in OK"); + return (true); + } else { + //Log.v(TAG, "isLoggedIn(): Current user is null - Firebase not logged in"); + return (false); + } + } else { + //Log.v(TAG, "isLoggedIn(): Firebase not logged in"); + return (false); + } + } + + public boolean getUserProfile(JSONObjectCallback callback) { + Log.v(TAG, "getUserProfile()"); + FirebaseAuth auth = FirebaseAuth.getInstance(); + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } else { + try { + JSONObject retObj = new JSONObject(); + retObj.put("id",auth.getCurrentUser().getUid()); + retObj.put("username", auth.getCurrentUser().getDisplayName()); + retObj.put("email", auth.getCurrentUser().getEmail()); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "Error Creating retObjObj: " + e.getMessage()); + mUtil.showToast("Error Creating retObj - this should not happen!!!"); + return (false); + } + } + return (true); + } + + public String getStoredToken() { + return null; + } + + public void setStoredToken(String s) { + return; + } + + + // Create a new event in the remote database, based on the provided parameters. + // passes the newly created documentId to function callback on successful completion, or null on error. + public boolean createEvent(final int osdAlarmState, final Date eventDate, final String eventDesc, StringCallback callback) { + Log.v(TAG, "createEvent()"); + String userId = null; + + if (mDb == null) { + Log.w(TAG, "createEvent() - mDb is null - not doing anything"); + return false; + } + + if (FirebaseAuth.getInstance().getCurrentUser() == null) { + Log.e(TAG, "ERROR: createEvent() - not logged in"); + return false; + } else { + userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + } + Map event = new HashMap<>(); + event.put("dataTime", eventDate.getTime()); + event.put("osdAlarmState", osdAlarmState); + event.put("desc", eventDesc); + event.put("type", null); + event.put("subType", null); + event.put("userId", userId); + + mDb.collection("Events") + .add(event) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(DocumentReference documentReference) { + Log.d(TAG, "createEvent.onSuccess() - DocumentSnapshot added with ID: " + documentReference.getId()); + mServerConnectionOk = true; + callback.accept(documentReference.getId()); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "createEvent.onFailure() - Error adding document", e); + callback.accept(null); + } + }); + return (true); + } + + // calls function callback with a JSONObject representation of the event with id 'eventId' + public boolean getEvent(String eventId, JSONObjectCallback callback) { + Log.v(TAG, "getEvent()"); + if (mDb == null) { + Log.w(TAG, "getEvent() - mDb is null - not doing anything"); + return false; + } + + DocumentReference docRef = mDb + .collection("Events").document(eventId); + docRef.get().addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + DocumentSnapshot document = task.getResult(); + if (document.exists()) { + Log.d(TAG, "getEvent.onComplete(): DocumentSnapshot data: " + document.getData()); + if (document.getData() == null) { + callback.accept(null); + } else + callback.accept(new JSONObject(document.getData())); + } else { + Log.d(TAG, "No such document"); + callback.accept(null); + } + } else { + Log.d(TAG, "get failed with ", task.getException()); + callback.accept(null); + } + } + }); + + return true; + + } + + /** + * Retrieve all events accessible to the logged in user, and pass them to the callback function as a JSONObject + * + * @param callback + * @return true on success or false on failure to initiate the request. + */ + public boolean getEvents(JSONObjectCallback callback) { + //Long eventId=Long.valueOf(285); + Log.v(TAG, "getEvents()"); + if (mDb == null) { + Log.w(TAG, "getEvents() - mDb is null - not doing anything"); + return false; + } + + if (!isLoggedIn()) { + Log.w(TAG, "getEvents() - not logged in - not doing anything"); + return false; + } + String userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + mDb.collection("Events") //.where("userId", "==", userId) + .whereEqualTo("userId", userId) + .orderBy("dataTime", Query.Direction.ASCENDING) + .get() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + try { + JSONObject retObj = new JSONObject(); + JSONArray eventArray = new JSONArray(); + Log.d(TAG, "getEvents() - returned " + task.getResult().size()); + for (QueryDocumentSnapshot document : task.getResult()) { + Log.d(TAG, "getEvents() - " + document.getId() + " => " + document.getData()); + JSONObject eventObj = new JSONObject(document.getData()); + // Add the event id into the event data because firebase does not include it as part of the document. + eventObj.put("id", document.getId()); + eventArray.put(eventObj); + } + retObj.put("events", eventArray); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEvents.onResponse(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + + } else { + Log.d(TAG, "Error getting documents: ", task.getException()); + callback.accept(null); + } + } + }); + return (true); + } + + public boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback) { + String eventId; + Log.v(TAG, "updateEvent()"); + if (mDb == null) { + Log.w(TAG, "updateEvent() - mDb is null - not doing anything"); + return false; + } + + try { + eventId = eventObj.getString("id"); + } catch (JSONException e) { + Log.e(TAG, "updateEvent(): Error reading id from eventObj"); + eventId = null; + return false; + } + final String dataStr = eventObj.toString(); + Log.v(TAG, "updateEvent - data=" + dataStr); + Map eventMap = new HashMap<>(); + try { + eventMap.put("dataTime", eventObj.getLong("dataTime")); + eventMap.put("osdAlarmState", eventObj.getInt("osdAlarmState")); + eventMap.put("desc", eventObj.getString("desc")); + eventMap.put("type", eventObj.getString("type")); + eventMap.put("subType", eventObj.getString("subType")); + eventMap.put("userId", eventObj.getString("userId")); + } catch (JSONException e) { + Log.e(TAG, "updateEvent(): Error data from eventObj." + e.toString()); + e.printStackTrace(); + return false; + } + Log.v(TAG, "updateEvent - map=" + eventMap.toString()); + + try { + DocumentReference docRef = mDb.collection("Events").document(eventId); + docRef.set(eventMap) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Void aVoid) { + JSONObject retObj; + try { + retObj = new JSONObject("{\"status\":\"OK\"}"); + } catch (Exception e) { + retObj = null; + } + callback.accept(retObj); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "Error updating document", e); + callback.accept(null); + } + }); + return (true); + } catch (Exception e) { + Log.e(TAG, "updateEvent() - ERROR: " + e.toString()); + e.printStackTrace(); + } + return (false); + } + + public boolean createDatapoint(JSONObject dataObj, String eventId, StringCallback callback) { + Log.v(TAG, "createDatapoint()"); + // Create a new event in the remote database, based on the provided parameters. + String userId = null; + if (FirebaseAuth.getInstance().getCurrentUser() == null) { + Log.e(TAG, "ERROR: createDatapoint() - not logged in"); + return false; + } else { + userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); + } + String dataTime; + try { + dataTime = dataObj.getString("dataTime"); + } catch (JSONException e) { + dataTime = ""; + } + Map datapoint = new HashMap<>(); + datapoint.put("dataTime", dataTime); + datapoint.put("dataJSON", dataObj.toString()); + datapoint.put("userId", userId); + datapoint.put("eventId", userId); + + mDb.collection("Datapoints") + .add(datapoint) + .addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(DocumentReference documentReference) { + Log.d(TAG, "createDatapoint.onSuccess() - DocumentSnapshot added with ID: " + documentReference.getId()); + mServerConnectionOk = true; + callback.accept(documentReference.getId()); + } + }) + .addOnFailureListener(new OnFailureListener() { + @Override + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "createDatapoint.onFailure() - Error adding document", e); + callback.accept(null); + } + }); + return (true); + } + + + /** + * Retrieve the file containing the standard event types from the server. + * Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed. + * + * @return true if request sent successfully or else false. + */ + public boolean getEventTypes(JSONObjectCallback callback) { + Log.v(TAG, "getEventTypes()"); + if (mDb == null) { + Log.w(TAG, "getEventTypes() - mDb is null - not doing anything"); + return false; + } + + mDb.collection("EventTypes") + .get() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if (task.isSuccessful()) { + try { + JSONObject retObj = new JSONObject(); + for (QueryDocumentSnapshot document : task.getResult()) { + Log.d(TAG, "getEventTypes.onComplete(): " + document.getId() + " => " + document.getData()); + Log.v(TAG, "getEventTypes.onComplete() - subtypes=" + document.getData().get("subTypes")); + JSONArray subTypesArray = listToJSONArray((List) document.getData().get("subTypes")); + retObj.put(document.getData().get("type").toString(), subTypesArray); + } + Log.d(TAG, "getEventTypes.onComplete() - retObj=" + retObj.toString()); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEventTypes.onResponse(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + } else { + Log.d(TAG, "Error getting documents: ", task.getException()); + callback.accept(null); + } + } + }); + return (true); + + } + + private JSONArray listToJSONArray(List list) { + JSONArray arr = new JSONArray(); + for (Object obj : list) { + arr.put(obj); + } + return arr; + } + + /** + * Retrieve a trivial file from the server to check we have a good server connection. + * sets mServerConnectionOk. + * + * @return true if request sent successfully or else false. + */ + public boolean checkServerConnection() { + //FIXME There must be a Firebase function for this? + mServerConnectionOk = true; + return mServerConnectionOk; + } + +} diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_osdapi.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_osdapi.java new file mode 100644 index 0000000..57aa908 --- /dev/null +++ b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection_osdapi.java @@ -0,0 +1,663 @@ +package uk.org.openseizuredetector; + +import android.content.Context; +import android.os.Handler; +import android.util.Log; + +import com.android.volley.AuthFailureError; +import com.android.volley.Request; +import com.android.volley.RequestQueue; +import com.android.volley.Response; +import com.android.volley.VolleyError; +import com.android.volley.VolleyLog; +import com.android.volley.toolbox.StringRequest; +import com.android.volley.toolbox.Volley; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.UnsupportedEncodingException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + + +// This class is intended to handle all interactions with the OSD WebAPI +public class WebApiConnection_osdapi extends WebApiConnection { + public String retVal; + public int retCode; + public boolean mServerConnectionOk = false; + private String mUrlBase = "https://osdApi.ddns.net"; + private String TAG = "WebApiConnection_osdapi"; + RequestQueue mQueue; + + public WebApiConnection_osdapi(Context context) { + super(context); + mQueue = Volley.newRequestQueue(context); + } + + public void close() { + super.close(); + Log.i(TAG,"stop()"); + mQueue.stop(); + } + + /** + * Attempt to authenticate with the web API using user name uname and password passwd. Calls function callback with either + * the authentication token on success or null on failure. + * + * @param uname - user name + * @param passwd - password + * @param callback - call back function callback(String retVal) + * @return true if request sent, or false if failed to send request. + */ + @Override + public boolean authenticate(final String uname, final String passwd, StringCallback callback) { + // NOTE: the 'final' keyword is necessary for uname and passwd to be accessible to getParams below - I don't know why! + // We know that this command works, so we just need the Java equivalent: + // curl -X POST -d 'login=graham4&password=testpwd1' https://osdapi.ddns.net/api/accounts/login/ + // sending the credentials as a JSONObject postData did not work, so try the method from: + // https://protocoderspoint.com/login-and-registration-form-in-android-using-volley-keeping-user-logged-in/#Login_Registration_form_in_android_using_volley_library + String urlStr = mUrlBase + "/api/accounts/login/"; + Log.v(TAG, "urlStr=" + urlStr); + + StringRequest req = new StringRequest(Request.Method.POST, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + String tokenStr = null; + Log.v(TAG, "Response is: " + response); + try { + JSONObject jo = new JSONObject(response); + tokenStr = jo.getString("token"); + mServerConnectionOk = true; + } catch (JSONException e) { + tokenStr = "Error Parsing Rsponse"; + } + setStoredToken(tokenStr); + callback.accept(tokenStr); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + if (error != null) { + Log.e(TAG, "Login Error: " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "Login Error: Returned null response"); + } + mServerConnectionOk = false; + setStoredToken(null); + callback.accept(null); + } + }) { + // Note, this is overriding part of StringRequest, not one of the sub-classes above! + @Override + protected Map getParams() { + Map params = new HashMap<>(); + // params.put("name",sname); // passing parameters to server + params.put("login", uname); + params.put("password", passwd); + return params; + } + }; + + mQueue.add(req); + return (true); + } + + + + + public boolean isLoggedIn() { + String authToken = getStoredToken(); + Log.v(TAG, "isLoggedIn(): token=" + authToken); + if (authToken == null || authToken.length() == 0) { + Log.v(TAG, "isLogged in - not logged in"); + return (false); + } else { + Log.v(TAG,"isLoggedIn - logged in ok"); + return (true); + } + + } + + + // Create a new event in the remote database, based on the provided parameters. + public boolean createEvent(final int osdAlarmState, final Date eventDate, final String eventDesc, StringCallback callback) { + Log.v(TAG, "createEvent()"); + String urlStr = mUrlBase + "/api/events/"; + Log.v(TAG, "urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + JSONObject jsonObject = new JSONObject(); + try { + jsonObject.put("osdAlarmState", String.valueOf(osdAlarmState)); + jsonObject.put("dataTime", dateFormat.format(eventDate)); + jsonObject.put("desc", eventDesc); + } catch (JSONException e) { + Log.e(TAG, "Error generating event JSON string"); + } + final String dataStr = jsonObject.toString(); + Log.v(TAG, "createEvent - data=" + dataStr); + + StringRequest req = new StringRequest(Request.Method.POST, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "createEvent.onResponse - Response is: " + response); + mServerConnectionOk = true; + // we return just the eventId to be consistent with the firebase version of WebApiConnection. + String retVal = null; + try { + JSONObject retObj = new JSONObject(response); + retVal = retObj.getString("id"); + } catch (JSONException e) { + Log.e(TAG, "createEvent.onResponse(): Error: " + e.getMessage() + "," + e.toString()); + retVal = null; + } + callback.accept(retVal); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + mServerConnectionOk = false; + if (error != null) { + Log.e(TAG, "createEvent Error: " + error.toString() + ", message:" + error.getMessage()); + callback.accept(null); + } else { + Log.e(TAG, "createEvent Error - null response"); + callback.accept(null); + } + } + }) { + // Note, this is overriding part of StringRequest, not one of the sub-classes above! + @Override + protected Map getParams() { + Map params = new HashMap<>(); + // params.put("name",sname); // passing parameters to server + String authToken = getStoredToken(); + params.put("Authorization: Token " + authToken, authToken); + Log.v(TAG, "getParams: params=" + params.toString()); + return params; + } + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + + @Override + public byte[] getBody() throws AuthFailureError { + try { + return dataStr == null ? null : dataStr.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); + return null; + } + } + }; + + mQueue.add(req); + return (true); + } + + public boolean getEvent(String eventId, JSONObjectCallback callback) { + Log.v(TAG, "getEvent()"); + String urlStr = mUrlBase + "/api/events/" + eventId; + Log.v(TAG, "getEvent(): urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + + StringRequest req = new StringRequest(Request.Method.GET, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "Response is: " + response); + try { + JSONObject retObj = new JSONObject(response); + retObj.put("alarmStateStr", mUtil.alarmStatusToString(retObj.getInt("osdAlarmState"))); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + mServerConnectionOk = true; + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + if (error != null) { + Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "Create Event Error: returned null response"); + } + mServerConnectionOk = false; + callback.accept(null); + } + }) { + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + }; + mQueue.add(req); + return (true); + } + + /** + * Retrieve all events accessible to the logged in user, and pass them to the callback function as a JSONObject + * + * @param callback + * @return true on success or false on failure to initiate the request. + */ + public boolean getEvents(JSONObjectCallback callback) { + Log.v(TAG, "getEvents()"); + String urlStr = mUrlBase + "/api/events/"; + Log.v(TAG, "getEvents(): urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + + StringRequest req = new StringRequest(Request.Method.GET, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "Response is: " + response); + mServerConnectionOk = true; + try { + JSONObject retObj = new JSONObject(); + JSONArray eventArray = new JSONArray(response); + retObj.put("events", eventArray); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + //if ((error != null) && (error.networkResponse != null) && (error.networkResponse.data != null)) {# + mServerConnectionOk = false; + if (error != null) { + if (error.networkResponse != null) { + Log.e(TAG, "getEvents(): Error: " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "getEvents(): Error: - request returned null networkResponse"); + } + } else{ + Log.e(TAG, "getEvents(): Error: - request returned null response"); + } + callback.accept(null); + } + }) { + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + }; + mQueue.add(req); + return (true); + } + + + public boolean updateEvent(final JSONObject eventObj, JSONObjectCallback callback) { + String eventId; + Log.v(TAG, "updateEvent()"); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + try { + eventId = eventObj.getString("id"); + } catch (JSONException e) { + Log.e(TAG, "updateEvent(): Error reading id from eventObj"); + eventId = null; + } + final String dataStr = eventObj.toString(); + Log.v(TAG, "updateEvent - data=" + dataStr); + + int reqMethod; + String urlStr; + if (eventId != null) { + Log.v(TAG, "updateEvent() - found eventId " + eventId + ", Updating event record"); + urlStr = mUrlBase + "/api/events/" + eventId + "/"; + Log.v(TAG, "urlStr=" + urlStr); + reqMethod = Request.Method.PUT; + } else { + Log.v(TAG, "updateEvent() - eventId not found - creating new event record"); + urlStr = mUrlBase + "/api/events/"; + Log.v(TAG, "urlStr=" + urlStr); + reqMethod = Request.Method.POST; + } + + StringRequest req = new StringRequest(reqMethod, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "Response is: " + response); + mServerConnectionOk = true; + try { + JSONObject retObj = new JSONObject(response); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + mServerConnectionOk = false; + if (error != null) { + Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "Create Event Error - returned null response"); + } + callback.accept(null); + } + }) { + // Note, this is overriding part of StringRequest, not one of the sub-classes above! + @Override + protected Map getParams() { + Map params = new HashMap<>(); + // params.put("name",sname); // passing parameters to server + String authToken = getStoredToken(); + params.put("Authorization: Token " + authToken, authToken); + Log.v(TAG, "getParams: params=" + params.toString()); + //params.put("eventType", String.valueOf(eventType)); + //params.put("dataTime", dateFormat.format(eventDate)); + //params.put("desc", eventDesc); + return params; + } + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + + @Override + public byte[] getBody() throws AuthFailureError { + try { + return dataStr == null ? null : dataStr.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); + return null; + } + } + }; + + mQueue.add(req); + return (true); + } + + + public boolean createDatapoint(JSONObject dataObj, String eventId, StringCallback callback) { + Log.v(TAG, "createDatapoint()"); + // Create a new event in the remote database, based on the provided parameters. + String urlStr = mUrlBase + "/api/datapoints/"; + Log.v(TAG, "urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + + DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + JSONObject jsonObject = new JSONObject(); + try { + //jsonObject.put("userId", -1); + jsonObject.put("eventId", String.valueOf(eventId)); + jsonObject.put("dataTime", dataObj.getString("dataTime")); + jsonObject.put("dataJSON", dataObj.toString()); + } catch (JSONException e) { + Log.e(TAG, "Error generating event JSON string"); + } + final String dataStr = jsonObject.toString(); + Log.v(TAG, "createDatapoint - dataStr=" + dataStr); + + + StringRequest req = new StringRequest(Request.Method.POST, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "Response is: " + response); + mServerConnectionOk = true; + callback.accept(response); + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + mServerConnectionOk = false; + if (error != null) { + Log.e(TAG, "Create Datapoint Error: " + error.toString() + ", message:" + error.getMessage()); + callback.accept(null); + } else { + Log.e(TAG, "Create Datapoint Error - returned null respones"); + callback.accept(null); + } + } + }) { + // Note, this is overriding part of StringRequest, not one of the sub-classes above! + @Override + protected Map getParams() { + Map params = new HashMap<>(); + // params.put("name",sname); // passing parameters to server + String authToken = getStoredToken(); + params.put("Authorization: Token " + authToken, authToken); + Log.v(TAG, "getParams: params=" + params.toString()); + return params; + } + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + + @Override + public byte[] getBody() throws AuthFailureError { + try { + return dataStr == null ? null : dataStr.getBytes("utf-8"); + } catch (UnsupportedEncodingException uee) { + VolleyLog.wtf("Unsupported Encoding while trying to get the bytes of %s using %s", dataStr, "utf-8"); + return null; + } + } + }; + + mQueue.add(req); + return (true); + + } + + /** + * Retieve the user profile of the authenticated user from the server, and return it to the callback function. + * @param callback - function to be called with a JSONObject as a parameter that contains the user profile data. + * @return true if request sent successfully, or else false. + */ + public boolean getUserProfile(JSONObjectCallback callback) { + Log.v(TAG, "getUserProfile()"); + String urlStr = mUrlBase + "/api/accounts/profile/"; + Log.v(TAG, "getUserProfile(): urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + + StringRequest req = new StringRequest(Request.Method.GET, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "Response is: " + response); + try { + JSONObject retObj = new JSONObject(response); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getUserProfile.onResponse(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + mServerConnectionOk = true; + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + if (error != null) { + Log.e(TAG, "Create Event Error: " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "Create Event Error: returned null response"); + } + mServerConnectionOk = false; + callback.accept(null); + } + }) { + + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + }; + mQueue.add(req); + return (true); + } + + + + + /** + * Retrieve the file containing the standard event types from the server. + * Calls the specified callback function, passing a JSONObject as a parameter when the data has been received and parsed. + * + * @return true if request sent successfully or else false. + */ + public boolean getEventTypes(JSONObjectCallback callback) { + Log.v(TAG, "getEventTypes()"); + String urlStr = mUrlBase + "/static/eventTypes.json"; + Log.v(TAG, "urlStr=" + urlStr); + final String authtoken = getStoredToken(); + + if (!isLoggedIn()) { + Log.v(TAG, "not logged in - doing nothing"); + return (false); + } + + StringRequest req = new StringRequest(Request.Method.GET, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "getEventTypes.onResponse(): Response is: " + response); + mServerConnectionOk = true; + try { + JSONObject retObj = new JSONObject(response); + callback.accept(retObj); + } catch (JSONException e) { + Log.e(TAG, "getEventTypes.onRespons(): Error: " + e.getMessage() + "," + e.toString()); + callback.accept(null); + } + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + mServerConnectionOk = false; + if (error != null) { + Log.e(TAG, "getEventTypes.onErrorResponse(): " + error.toString() + ", message:" + error.getMessage()); + } else { + Log.e(TAG, "getEventTypes.onErrorResponse() - returned null response"); + } + callback.accept(null); + } + }) { + // Note, this is overriding part of StringRequest, not one of the sub-classes above! + @Override + public Map getHeaders() throws AuthFailureError { + Map params = new HashMap(); + params.put("Content-Type", "application/json; charset=UTF-8"); + params.put("Authorization", "Token " + getStoredToken()); + return params; + } + }; + + mQueue.add(req); + return (true); + + } + + /** + * Retrieve a trivial file from the server to check we have a good server connection. + * sets mServerConnectionOk. + * @return true if request sent successfully or else false. + */ + public boolean checkServerConnection() { + Log.v(TAG, "checkServerConnection()"); + String urlStr = mUrlBase + "/static/test.txt"; + Log.v(TAG, "urlStr=" + urlStr); + + StringRequest req = new StringRequest(Request.Method.GET, urlStr, + new Response.Listener() { + @Override + public void onResponse(String response) { + Log.v(TAG, "checkServerConnection.onResponse(): Response is: " + response); + mServerConnectionOk = true; + } + }, + new Response.ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + Log.v(TAG, "checkServerConnection.onErrorResponse"); + mServerConnectionOk = false; + } + }); + + mQueue.add(req); + return (true); + + } + +} diff --git a/app/src/main/res/layout/activity_authenticate.xml b/app/src/main/res/layout/activity_authenticate.xml index fc0f4dd..55cc16e 100644 --- a/app/src/main/res/layout/activity_authenticate.xml +++ b/app/src/main/res/layout/activity_authenticate.xml @@ -31,6 +31,36 @@ android:layout_height="wrap_content" android:orientation="vertical"> + + + +