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..88a809e 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,7 @@ 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' 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 +54,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 284eca0..ca4e82b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="9" + android:versionName="4.1.0"> diff --git a/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java b/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java index e5204a4..db8374b 100644 --- a/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/AuthenticateActivity.java @@ -5,29 +5,30 @@ 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; import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; -import org.json.JSONException; -import org.json.JSONObject; +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 java.util.Arrays; public class AuthenticateActivity extends AppCompatActivity { private String TAG = "AuthenticateActivity"; - private EditText mUnameEt; - private EditText mPasswdEt; - private WebApiConnection mWac; - private LogManager mLm; - private SdServiceConnection mConnection; private OsdUtil mUtil; final Handler serverStatusHandler = new Handler(); - private String TOKEN_ID = "webApiAuthToken"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -37,113 +38,88 @@ public class AuthenticateActivity extends AppCompatActivity { 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); + 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(); + updateUi(); } @Override protected void onStop() { Log.d(TAG, "onStop()"); super.onStop(); - mUtil.unbindFromServer(getApplicationContext(), mConnection); } + // Called after the Firebase Auth UI has completed + private ActivityResultLauncher signInLauncher = registerForActivityResult( + new FirebaseAuthUIActivityResultContract(), + (result) -> { + Log.i(TAG, "FirebaseAuthUIActivityResult - " + result.toString()); + updateUi(); + }); - private void waitForConnection() { - // We want the UI to update as soon as it is displayed, but it takes a finite time for - // the mConnection to bind to the service, so we delay half a second to give it chance - // to connect before trying to update the UI for the first time (it happens again periodically using the uiTimer) - if (mConnection.mBound) { - Log.v(TAG, "waitForConnection - Bound!"); - initialiseServiceConnection(); - } else { - Log.v(TAG, "waitForConnection - waiting..."); - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - waitForConnection(); - } - }, 100); - } - } +// ... - private void 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); + LinearLayout loginLl = (LinearLayout) findViewById(R.id.login_ui); + LinearLayout logoutLl = (LinearLayout) findViewById(R.id.logout_ui); // Check if we are already logged in - if (storedAuthToken == null || storedAuthToken.length() == 0) { - Log.v(TAG, "Not Logged in - showing log in UI"); + FirebaseAuth auth = FirebaseAuth.getInstance(); + if (auth.getCurrentUser() == null) { + Log.i(TAG, "Not Logged in - showing log in UI"); loginLl.setVisibility(View.VISIBLE); logoutLl.setVisibility(View.GONE); } else { - Log.v(TAG, "Already Logged in - showing Log Out prompt"); + Log.i(TAG, "Already Logged in - showing Log Out prompt - " + auth.getCurrentUser().toString()); 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"); - } + TextView tv2 = (TextView) findViewById(R.id.userIdTv); + tv2.setText(auth.getCurrentUser().getDisplayName()); + tv2 = (TextView) findViewById(R.id.usernameTv); + tv2.setText(auth.getCurrentUser().getEmail()); } } @@ -158,91 +134,45 @@ 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); + 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); + } + }; + + View.OnClickListener onLogout = new View.OnClickListener() { + @Override + public void onClick(View view) { + Log.v(TAG, "onLogout"); + AuthUI.getInstance() + .signOut(getApplicationContext()) + .addOnCompleteListener(new OnCompleteListener() { + public void onComplete(@NonNull Task task) { + // user is now signed out 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 onRegister = - new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.d(TAG, "onRegisterBtn"); - //Intent i; - //i = new Intent(getApplicationContext(), RemoteDbActivity.class); - //i.putExtra("url", "https://osdapi.ddns.net/static/register.html"); - //startActivity(i); - String url = "https://osdapi.ddns.net/static/register.html"; - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - startActivity(i); - } - }; - - View.OnClickListener onResetPassword = - new View.OnClickListener() { - @Override - public void onClick(View view) { - Log.d(TAG, "onResetPasswordBtn"); - //Intent i; - //i = new Intent(getApplicationContext(), RemoteDbActivity.class); - //i.putExtra("url", "https://osdapi.ddns.net/static/register.html"); - //startActivity(i); - String url = "https://osdapi.ddns.net/static/request_password_reset.html"; - Intent i = new Intent(Intent.ACTION_VIEW); - i.setData(Uri.parse(url)); - startActivity(i); - } - }; - - - private void saveAuthToken(String tokenStr) { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - prefs.edit().putString(TOKEN_ID, tokenStr).commit(); - mWac.setStoredToken(tokenStr); - } - - public String getAuthToken() { - SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - String authToken = prefs.getString(TOKEN_ID, null); - return authToken; - } - + }); + } + }; } \ 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..abb0146 100644 --- a/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/EditEventActivity.java @@ -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); diff --git a/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java index c87e22a..eac035a 100644 --- a/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java +++ b/app/src/main/java/uk/org/openseizuredetector/ReportSeizureActivity.java @@ -64,7 +64,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 = diff --git a/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java b/app/src/main/java/uk/org/openseizuredetector/WebApiConnection.java index d188b08..49a608c 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,11 @@ 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.OnFailureListener; +import com.google.android.gms.tasks.OnSuccessListener; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.firestore.DocumentReference; +import com.google.firebase.firestore.FirebaseFirestore; import org.json.JSONArray; import org.json.JSONException; @@ -35,6 +42,8 @@ public class WebApiConnection { private String mAuthToken; private Context mContext; private OsdUtil mUtil; + FirebaseFirestore mDb; + RequestQueue mQueue; public interface JSONObjectCallback { @@ -53,6 +62,16 @@ public class WebApiConnection { mContext = context; mQueue = Volley.newRequestQueue(context); mUtil = new OsdUtil(mContext, new Handler()); + // Check if we are already logged in + FirebaseAuth auth = FirebaseAuth.getInstance(); + if (auth != null) { + Log.i(TAG,"Firebase Logged in OK"); + mDb = FirebaseFirestore.getInstance(); + } else { + Log.e(TAG,"Firebase not logged in"); + mDb = null; + } + } public void close() { @@ -60,174 +79,62 @@ public class WebApiConnection { 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. - */ - 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); + FirebaseAuth auth = FirebaseAuth.getInstance(); + if (auth != null) { + Log.v(TAG,"isLoggedIn(): Firebase Logged in OK"); + return(false); } else { - return (true); + Log.v(TAG,"isLoggedIn(): Firebase not logged in"); + 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. 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); + String userId = null; + if (FirebaseAuth.getInstance().getCurrentUser() == null) { + Log.e(TAG,"ERROR: createEvent() - not logged in"); + return false; + } else { + userId = FirebaseAuth.getInstance().getCurrentUser().getUid(); } - 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); + 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); - StringRequest req = new StringRequest(Request.Method.POST, urlStr, - new Response.Listener() { + mDb.collection("Events") + .add(event) + .addOnSuccessListener(new OnSuccessListener() { @Override - public void onResponse(String response) { - Log.v(TAG, "Response is: " + response); + public void onSuccess(DocumentReference documentReference) { + Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId()); mServerConnectionOk = true; - callback.accept(response); + callback.accept("OK"); } - }, - new Response.ErrorListener() { + }) + .addOnFailureListener(new OnFailureListener() { @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); - } + public void onFailure(@NonNull Exception e) { + Log.w(TAG, "Error adding document", e); + 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); + }); + return(true); } public boolean getEvent(Long eventId, JSONObjectCallback callback) { diff --git a/app/src/main/res/layout/activity_authenticate.xml b/app/src/main/res/layout/activity_authenticate.xml index fc0f4dd..2a1514d 100644 --- a/app/src/main/res/layout/activity_authenticate.xml +++ b/app/src/main/res/layout/activity_authenticate.xml @@ -31,27 +31,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - -