Starting to convert to Google Firebase backend. Authentication working and starting on WebApiConnection
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -7,5 +7,6 @@ app/release/app-release.apk
|
|||||||
app/build
|
app/build
|
||||||
app/app.iml
|
app/app.iml
|
||||||
app/release/output-metadata.json
|
app/release/output-metadata.json
|
||||||
|
app/google-services.json
|
||||||
*#
|
*#
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'com.google.gms.google-services'
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 31
|
compileSdkVersion 31
|
||||||
useLibrary 'org.apache.http.legacy'
|
useLibrary 'org.apache.http.legacy'
|
||||||
@@ -37,6 +37,7 @@ dependencies {
|
|||||||
// Unit testing dependencies
|
// Unit testing dependencies
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||||
implementation 'com.google.android.material:material:1.4.0'
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
|
implementation 'com.google.firebase:firebase-auth:19.2.0'
|
||||||
testImplementation 'junit:junit:4.13.2'
|
testImplementation 'junit:junit:4.13.2'
|
||||||
// Set this dependency if you want to use Mockito
|
// Set this dependency if you want to use Mockito
|
||||||
testImplementation 'org.mockito:mockito-core:4.3.1'
|
testImplementation 'org.mockito:mockito-core:4.3.1'
|
||||||
@@ -53,7 +54,10 @@ dependencies {
|
|||||||
//implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3'
|
//implementation 'com.github.RohitSurwase.UCE-Handler:uce_handler:1.3'
|
||||||
testImplementation 'org.robolectric:robolectric:4.7.3'
|
testImplementation 'org.robolectric:robolectric:4.7.3'
|
||||||
implementation 'com.android.volley:volley:1.2.1'
|
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 {
|
repositories {
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="uk.org.openseizuredetector"
|
package="uk.org.openseizuredetector"
|
||||||
android:versionCode="97"
|
android:versionCode="9"
|
||||||
android:versionName="4.0.0">
|
android:versionName="4.1.0">
|
||||||
<!-- android:allowBackup="false" -->
|
<!-- android:allowBackup="false" -->
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||||
|
|||||||
@@ -5,29 +5,30 @@ import android.content.SharedPreferences;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import com.firebase.ui.auth.AuthUI;
|
||||||
import org.json.JSONObject;
|
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 {
|
public class AuthenticateActivity extends AppCompatActivity {
|
||||||
private String TAG = "AuthenticateActivity";
|
private String TAG = "AuthenticateActivity";
|
||||||
private EditText mUnameEt;
|
|
||||||
private EditText mPasswdEt;
|
|
||||||
private WebApiConnection mWac;
|
|
||||||
private LogManager mLm;
|
|
||||||
private SdServiceConnection mConnection;
|
|
||||||
private OsdUtil mUtil;
|
private OsdUtil mUtil;
|
||||||
final Handler serverStatusHandler = new Handler();
|
final Handler serverStatusHandler = new Handler();
|
||||||
private String TOKEN_ID = "webApiAuthToken";
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@@ -37,113 +38,88 @@ public class AuthenticateActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
mUtil = new OsdUtil(getApplicationContext(), serverStatusHandler);
|
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 cancelBtn =
|
||||||
(Button) findViewById(R.id.cancelBtn);
|
(Button) findViewById(R.id.cancelBtn);
|
||||||
cancelBtn.setOnClickListener(onCancel);
|
cancelBtn.setOnClickListener(onCancel);
|
||||||
Button OKBtn = (Button) findViewById(R.id.OKBtn);
|
Button loginBtn = (Button) findViewById(R.id.loginBtn);
|
||||||
OKBtn.setOnClickListener(onOK);
|
loginBtn.setOnClickListener(onLogin);
|
||||||
Button logoutCancelBtn =
|
Button logoutCancelBtn =
|
||||||
(Button) findViewById(R.id.logoutCancelBtn);
|
(Button) findViewById(R.id.logoutCancelBtn);
|
||||||
logoutCancelBtn.setOnClickListener(onCancel);
|
logoutCancelBtn.setOnClickListener(onCancel);
|
||||||
Button logoutBtn = (Button)findViewById(R.id.logoutBtn);
|
Button logoutBtn = (Button) findViewById(R.id.logoutBtn);
|
||||||
logoutBtn.setOnClickListener(onLogout);
|
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);
|
Button aboutDataSharingBtn = (Button) findViewById(R.id.aboutDataSharingBtn);
|
||||||
mPasswdEt = (EditText) findViewById(R.id.password);
|
aboutDataSharingBtn.setOnClickListener(
|
||||||
//mWac = new WebApiConnection(this, String tokenStr);
|
new View.OnClickListener() {
|
||||||
//mLm = new LogManager(this);
|
@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
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
Log.d(TAG, "onStart()");
|
Log.d(TAG, "onStart()");
|
||||||
super.onStart();
|
super.onStart();
|
||||||
mUtil.bindToServer(getApplicationContext(), mConnection);
|
updateUi();
|
||||||
waitForConnection();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
Log.d(TAG, "onStop()");
|
Log.d(TAG, "onStop()");
|
||||||
super.onStop();
|
super.onStop();
|
||||||
mUtil.unbindFromServer(getApplicationContext(), mConnection);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Called after the Firebase Auth UI has completed
|
||||||
|
private ActivityResultLauncher<Intent> 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() {
|
private void updateUi() {
|
||||||
SharedPreferences prefs;
|
LinearLayout loginLl = (LinearLayout) findViewById(R.id.login_ui);
|
||||||
String storedAuthToken;
|
LinearLayout logoutLl = (LinearLayout) findViewById(R.id.logout_ui);
|
||||||
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
|
// Check if we are already logged in
|
||||||
if (storedAuthToken == null || storedAuthToken.length() == 0) {
|
FirebaseAuth auth = FirebaseAuth.getInstance();
|
||||||
Log.v(TAG, "Not Logged in - showing log in UI");
|
if (auth.getCurrentUser() == null) {
|
||||||
|
Log.i(TAG, "Not Logged in - showing log in UI");
|
||||||
loginLl.setVisibility(View.VISIBLE);
|
loginLl.setVisibility(View.VISIBLE);
|
||||||
logoutLl.setVisibility(View.GONE);
|
logoutLl.setVisibility(View.GONE);
|
||||||
} else {
|
} 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);
|
loginLl.setVisibility(View.GONE);
|
||||||
logoutLl.setVisibility(View.VISIBLE);
|
logoutLl.setVisibility(View.VISIBLE);
|
||||||
//TextView tv = (TextView)findViewById(R.id.tokenTv);
|
TextView tv2 = (TextView) findViewById(R.id.userIdTv);
|
||||||
//tv.setText("Logged in with Token: "+storedAuthToken);
|
tv2.setText(auth.getCurrentUser().getDisplayName());
|
||||||
if (mWac != null) {
|
tv2 = (TextView) findViewById(R.id.usernameTv);
|
||||||
mWac.getUserProfile((JSONObject profileObj) -> {
|
tv2.setText(auth.getCurrentUser().getEmail());
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -158,91 +134,45 @@ public class AuthenticateActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
View.OnClickListener onOK =
|
View.OnClickListener onLogin =
|
||||||
new View.OnClickListener() {
|
new View.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
//m_status=true;
|
//m_status=true;
|
||||||
Log.v(TAG, "onOK()");
|
Log.v(TAG, "onLogin() - using Firebase Login");
|
||||||
String uname = mUnameEt.getText().toString();
|
Intent signInIntent = AuthUI.getInstance()
|
||||||
String passwd = mPasswdEt.getText().toString();
|
.createSignInIntentBuilder()
|
||||||
Log.v(TAG,"onOK() - uname="+uname+", passwd="+passwd);
|
.setAvailableProviders(Arrays.asList(
|
||||||
mWac.authenticate(uname, passwd, new WebApiConnection.StringCallback() {
|
new AuthUI.IdpConfig.GoogleBuilder().build(),
|
||||||
@Override
|
//new AuthUI.IdpConfig.FacebookBuilder().build(),
|
||||||
public void accept(String retVal) {
|
//new AuthUI.IdpConfig.TwitterBuilder().build(),
|
||||||
if (retVal != null) {
|
//new AuthUI.IdpConfig.MicrosoftBuilder().build(),
|
||||||
Log.d(TAG,"Authentication Success - token is "+retVal);
|
//new AuthUI.IdpConfig.YahooBuilder().build(),
|
||||||
mUtil.showToast("Login Successful");
|
//new AuthUI.IdpConfig.AppleBuilder().build(),
|
||||||
saveAuthToken(retVal);
|
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<Void>() {
|
||||||
|
public void onComplete(@NonNull Task<Void> task) {
|
||||||
|
// user is now signed out
|
||||||
updateUi();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@ public class EditEventActivity extends AppCompatActivity {
|
|||||||
Button cancelBtn =
|
Button cancelBtn =
|
||||||
(Button) findViewById(R.id.cancelBtn);
|
(Button) findViewById(R.id.cancelBtn);
|
||||||
cancelBtn.setOnClickListener(onCancel);
|
cancelBtn.setOnClickListener(onCancel);
|
||||||
Button OKBtn = (Button) findViewById(R.id.OKBtn);
|
Button OKBtn = (Button) findViewById(R.id.loginBtn);
|
||||||
OKBtn.setOnClickListener(onOK);
|
OKBtn.setOnClickListener(onOK);
|
||||||
|
|
||||||
mEventTypeRg = findViewById(R.id.eventTypeRg);
|
mEventTypeRg = findViewById(R.id.eventTypeRg);
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ public class ReportSeizureActivity extends AppCompatActivity {
|
|||||||
//mLm= new LogManager(mContext);
|
//mLm= new LogManager(mContext);
|
||||||
|
|
||||||
Button okBtn =
|
Button okBtn =
|
||||||
(Button) findViewById(R.id.OKBtn);
|
(Button) findViewById(R.id.loginBtn);
|
||||||
okBtn.setOnClickListener(onOk);
|
okBtn.setOnClickListener(onOk);
|
||||||
|
|
||||||
Button cancelBtn =
|
Button cancelBtn =
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import android.content.Context;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.android.volley.AuthFailureError;
|
import com.android.volley.AuthFailureError;
|
||||||
import com.android.volley.Request;
|
import com.android.volley.Request;
|
||||||
import com.android.volley.RequestQueue;
|
import com.android.volley.RequestQueue;
|
||||||
@@ -12,6 +14,11 @@ import com.android.volley.VolleyError;
|
|||||||
import com.android.volley.VolleyLog;
|
import com.android.volley.VolleyLog;
|
||||||
import com.android.volley.toolbox.StringRequest;
|
import com.android.volley.toolbox.StringRequest;
|
||||||
import com.android.volley.toolbox.Volley;
|
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.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -35,6 +42,8 @@ public class WebApiConnection {
|
|||||||
private String mAuthToken;
|
private String mAuthToken;
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private OsdUtil mUtil;
|
private OsdUtil mUtil;
|
||||||
|
FirebaseFirestore mDb;
|
||||||
|
|
||||||
RequestQueue mQueue;
|
RequestQueue mQueue;
|
||||||
|
|
||||||
public interface JSONObjectCallback {
|
public interface JSONObjectCallback {
|
||||||
@@ -53,6 +62,16 @@ public class WebApiConnection {
|
|||||||
mContext = context;
|
mContext = context;
|
||||||
mQueue = Volley.newRequestQueue(context);
|
mQueue = Volley.newRequestQueue(context);
|
||||||
mUtil = new OsdUtil(mContext, new Handler());
|
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() {
|
public void close() {
|
||||||
@@ -60,174 +79,62 @@ public class WebApiConnection {
|
|||||||
mQueue.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.
|
|
||||||
*/
|
|
||||||
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<String>() {
|
|
||||||
@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<String, String> getParams() {
|
|
||||||
Map<String, String> 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() {
|
public boolean isLoggedIn() {
|
||||||
String authToken = getStoredToken();
|
FirebaseAuth auth = FirebaseAuth.getInstance();
|
||||||
//Log.v(TAG, "isLoggedIn(): token=" + authToken);
|
if (auth != null) {
|
||||||
if (authToken == null || authToken.length() == 0) {
|
Log.v(TAG,"isLoggedIn(): Firebase Logged in OK");
|
||||||
//Log.v(TAG, "isLogged in - not logged in");
|
return(false);
|
||||||
return (false);
|
|
||||||
} else {
|
} 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.
|
// 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) {
|
public boolean createEvent(final int osdAlarmState, final Date eventDate, final String eventDesc, StringCallback callback) {
|
||||||
Log.v(TAG, "createEvent()");
|
Log.v(TAG, "createEvent()");
|
||||||
String urlStr = mUrlBase + "/api/events/";
|
String userId = null;
|
||||||
Log.v(TAG, "urlStr=" + urlStr);
|
if (FirebaseAuth.getInstance().getCurrentUser() == null) {
|
||||||
final String authtoken = getStoredToken();
|
Log.e(TAG,"ERROR: createEvent() - not logged in");
|
||||||
|
return false;
|
||||||
if (!isLoggedIn()) {
|
} else {
|
||||||
Log.v(TAG, "not logged in - doing nothing");
|
userId = FirebaseAuth.getInstance().getCurrentUser().getUid();
|
||||||
return (false);
|
|
||||||
}
|
}
|
||||||
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
|
Map<String, Object> event = new HashMap<>();
|
||||||
JSONObject jsonObject = new JSONObject();
|
event.put("dataTime", eventDate.getTime());
|
||||||
try {
|
event.put("osdAlarmState", osdAlarmState);
|
||||||
jsonObject.put("osdAlarmState", String.valueOf(osdAlarmState));
|
event.put("desc", eventDesc);
|
||||||
jsonObject.put("dataTime", dateFormat.format(eventDate));
|
event.put("type", null);
|
||||||
jsonObject.put("desc", eventDesc);
|
event.put("subType", null);
|
||||||
} catch (JSONException e) {
|
event.put("userId", userId);
|
||||||
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,
|
mDb.collection("Events")
|
||||||
new Response.Listener<String>() {
|
.add(event)
|
||||||
|
.addOnSuccessListener(new OnSuccessListener<DocumentReference>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResponse(String response) {
|
public void onSuccess(DocumentReference documentReference) {
|
||||||
Log.v(TAG, "Response is: " + response);
|
Log.d(TAG, "DocumentSnapshot added with ID: " + documentReference.getId());
|
||||||
mServerConnectionOk = true;
|
mServerConnectionOk = true;
|
||||||
callback.accept(response);
|
callback.accept("OK");
|
||||||
}
|
}
|
||||||
},
|
})
|
||||||
new Response.ErrorListener() {
|
.addOnFailureListener(new OnFailureListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onErrorResponse(VolleyError error) {
|
public void onFailure(@NonNull Exception e) {
|
||||||
mServerConnectionOk = false;
|
Log.w(TAG, "Error adding document", e);
|
||||||
if (error != null) {
|
callback.accept(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!
|
return(true);
|
||||||
@Override
|
|
||||||
protected Map<String, String> getParams() {
|
|
||||||
Map<String, String> 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<String, String> getHeaders() throws AuthFailureError {
|
|
||||||
Map<String, String> params = new HashMap<String, String>();
|
|
||||||
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) {
|
public boolean getEvent(Long eventId, JSONObjectCallback callback) {
|
||||||
|
|||||||
@@ -31,27 +31,6 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/username"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="4dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginRight="4dp"
|
|
||||||
android:layout_marginBottom="4dp"
|
|
||||||
android:hint="username" />
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/password"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="4dp"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginRight="4dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:fontFamily="sans-serif"
|
|
||||||
android:hint="password"
|
|
||||||
android:inputType="textPassword" />
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
@@ -66,32 +45,14 @@
|
|||||||
android:text="@string/back" />
|
android:text="@string/back" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/OKBtn"
|
android:id="@+id/loginBtn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/authenticate" />
|
android:text="@string/authenticate" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/RegisterBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/register" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/ResetPasswordBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="@string/reset_password" />
|
|
||||||
</LinearLayout>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -117,8 +78,14 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
android:text="userId" />
|
android:text="userId" />
|
||||||
|
</LinearLayout>
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
|
||||||
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||||
@@ -139,12 +106,6 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!--<TextView
|
|
||||||
android:id="@+id/tokenTv"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/logged_in_with_token" />
|
|
||||||
-->
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@@ -164,40 +125,25 @@
|
|||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/logout" />
|
android:text="@string/logout" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
<!--
|
</LinearLayout>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/aboutDataSharingBtn"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:layout_weight="1"
|
||||||
|
android:text="@string/about_data_sharing" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/createEventBtn"
|
android:id="@+id/privacyPolicyBtn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Create Event" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/createDatapointBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Prune Database" />
|
|
||||||
</LinearLayout>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:layout_weight="1"
|
||||||
|
android:text="@string/privacy_policy" />
|
||||||
<Button
|
|
||||||
android:id="@+id/getLocalEventsBtn"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="getLocalEvents" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
-->
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
android:text="@string/cancel" />
|
android:text="@string/cancel" />
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/OKBtn"
|
android:id="@+id/loginBtn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
|
|||||||
@@ -96,7 +96,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/OKBtn"
|
android:id="@+id/loginBtn"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/okBtnTxt" />
|
android:text="@string/okBtnTxt" />
|
||||||
|
|||||||
@@ -369,7 +369,7 @@
|
|||||||
<string name="check_seizures_message">Please select the events highlighted in pink to say if they are real seizures or false alarms</string>
|
<string name="check_seizures_message">Please select the events highlighted in pink to say if they are real seizures or false alarms</string>
|
||||||
<string name="error_server_not_running">ERROR: OpenSeizureDetector Server is not running - please re-start it</string>
|
<string name="error_server_not_running">ERROR: OpenSeizureDetector Server is not running - please re-start it</string>
|
||||||
<string name="system_logs">System Logs</string>
|
<string name="system_logs">System Logs</string>
|
||||||
<string name="logged_in_as_user_id">Logged in as User Id:</string>
|
<string name="logged_in_as_user_id">Logged in as: </string>
|
||||||
<string name="datasharing_notification_text">Select for more information</string>
|
<string name="datasharing_notification_text">Select for more information</string>
|
||||||
<string name="datasharing_notification_title">OpenSeizureDetector Data Sharing Problem</string>
|
<string name="datasharing_notification_title">OpenSeizureDetector Data Sharing Problem</string>
|
||||||
<string name="datasharing_about_title">OpenSeizureDetector Data Sharing</string>
|
<string name="datasharing_about_title">OpenSeizureDetector Data Sharing</string>
|
||||||
@@ -429,4 +429,6 @@
|
|||||||
and selecting <b>Apps->OpenSeizureDetector->Permissions</b>.
|
and selecting <b>Apps->OpenSeizureDetector->Permissions</b>.
|
||||||
</string>
|
</string>
|
||||||
<string name="permissions_required">Permissions Disclosure</string>
|
<string name="permissions_required">Permissions Disclosure</string>
|
||||||
|
<string name="about_data_sharing">About Data Sharing</string>
|
||||||
|
<string name="privacy_policy">Privacy Policy</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ buildscript {
|
|||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.1.0'
|
classpath 'com.android.tools.build:gradle:7.1.0'
|
||||||
|
classpath 'com.google.gms:google-services:4.3.10'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allprojects {
|
allprojects {
|
||||||
@@ -19,6 +20,7 @@ allprojects {
|
|||||||
url 'https://maven.google.com/'
|
url 'https://maven.google.com/'
|
||||||
name 'Google'
|
name 'Google'
|
||||||
}
|
}
|
||||||
|
google()
|
||||||
//maven { url 'https://jitpack.io' }
|
//maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
tasks.withType(JavaCompile) {
|
tasks.withType(JavaCompile) {
|
||||||
|
|||||||
Reference in New Issue
Block a user