Browse Source

improve device registration flow:

register device by scanning qr
add apiKey for auth
pull/1/head
isra el 4 years ago
parent
commit
5735b0e50e
  1. 2
      .idea/misc.xml
  2. 3
      app/build.gradle
  3. 8
      app/src/main/AndroidManifest.xml
  4. 14
      app/src/main/java/com/vernu/sms/GatewayApiService.java
  5. 191
      app/src/main/java/com/vernu/sms/activities/MainActivity.java
  6. 6
      app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java
  7. 9
      app/src/main/java/com/vernu/sms/dtos/RegisterDeviceResponseDTO.java
  8. 7
      app/src/main/java/com/vernu/sms/dtos/UpdateDeviceResponseDTO.java
  9. 19
      app/src/main/java/com/vernu/sms/services/GatewayApiService.java
  10. 40
      app/src/main/res/layout/activity_main.xml

2
.idea/misc.xml

@ -3,7 +3,7 @@
<component name="DesignSurface">
<option name="filePathToZoomLevelMap">
<map>
<entry key="app/src/main/res/layout/activity_main.xml" value="0.27088470458984376" />
<entry key="app/src/main/res/layout/activity_main.xml" value="0.22209167480468753" />
</map>
</option>
</component>

3
app/build.gradle

@ -8,7 +8,7 @@ android {
defaultConfig {
applicationId "com.vernu.sms"
minSdk 21
minSdk 24
targetSdk 32
versionCode 1
versionName "1.0"
@ -45,4 +45,5 @@ dependencies {
implementation 'com.google.code.gson:gson:2.9.0'
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.journeyapps:zxing-android-embedded:4.1.0'
}

8
app/src/main/AndroidManifest.xml

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.vernu.sms">
<uses-permission android:name="android.permission.SEND_SMS" />
@ -9,7 +10,8 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.SMSGateway">
android:theme="@style/Theme.SMSGateway"
android:usesCleartextTraffic="true" >
<service
android:name=".services.FCMService"
android:exported="false"
@ -27,6 +29,10 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="portrait"
tools:replace="screenOrientation" />
</application>
</manifest>

14
app/src/main/java/com/vernu/sms/GatewayApiService.java

@ -1,14 +0,0 @@
package com.vernu.sms;
import com.vernu.sms.dtos.UpdateDeviceInputDTO;
import com.vernu.sms.dtos.UpdateDeviceResponseDTO;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.PATCH;
import retrofit2.http.Path;
public interface GatewayApiService {
@PATCH("gateway/devices/{deviceId}")
Call<UpdateDeviceResponseDTO> updateDevice(@Path("deviceId") String deviceId, @Body() UpdateDeviceInputDTO body);
}

191
app/src/main/java/com/vernu/sms/activities/MainActivity.java

@ -1,12 +1,14 @@
package com.vernu.sms.activities;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
@ -22,10 +24,12 @@ import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.android.material.snackbar.Snackbar;
import com.google.firebase.messaging.FirebaseMessaging;
import com.vernu.sms.GatewayApiService;
import com.google.zxing.integration.android.IntentIntegrator;
import com.google.zxing.integration.android.IntentResult;
import com.vernu.sms.services.GatewayApiService;
import com.vernu.sms.R;
import com.vernu.sms.dtos.UpdateDeviceInputDTO;
import com.vernu.sms.dtos.UpdateDeviceResponseDTO;
import com.vernu.sms.dtos.RegisterDeviceInputDTO;
import com.vernu.sms.dtos.RegisterDeviceResponseDTO;
import com.vernu.sms.helpers.SharedPreferenceHelper;
import retrofit2.Call;
@ -41,28 +45,38 @@ public class MainActivity extends AppCompatActivity {
private GatewayApiService gatewayApiService;
private Switch gatewaySwitch;
private EditText gatewayKeyEditText, fcmTokenEditText;
private Button updateKeyButton, grantSMSPermissionBtn;
private EditText apiKeyEditText, fcmTokenEditText;
private Button registerDeviceBtn, grantSMSPermissionBtn, scanQRBtn;
private static final int SEND_SMS_PERMISSION_REQUEST_CODE = 0;
private static final int SCAN_QR_REQUEST_CODE = 49374;
private static final String API_BASE_URL = "https://vernu-sms.herokuapp.com/api/v1/";
private String deviceId = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = getApplicationContext();
retrofit = new Retrofit.Builder()
.baseUrl("https://vernu-sms.herokuapp.com/api/v1/")
.baseUrl(API_BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
gatewayApiService = retrofit.create(GatewayApiService.class);
deviceId = SharedPreferenceHelper.getSharedPreferenceString(mContext, "DEVICE_ID", "");
setContentView(R.layout.activity_main);
gatewaySwitch = findViewById(R.id.gatewaySwitch);
gatewayKeyEditText = findViewById(R.id.gatewayKeyEditText);
apiKeyEditText = findViewById(R.id.apiKeyEditText);
fcmTokenEditText = findViewById(R.id.fcmTokenEditText);
updateKeyButton = findViewById(R.id.updateKeyButton);
registerDeviceBtn = findViewById(R.id.registerDeviceBtn);
grantSMSPermissionBtn = findViewById(R.id.grantSMSPermissionBtn);
scanQRBtn = findViewById(R.id.scanQRButton);
if (isSMSPermissionGranted(mContext)) {
grantSMSPermissionBtn.setEnabled(false);
@ -77,7 +91,7 @@ public class MainActivity extends AppCompatActivity {
}
gatewayKeyEditText.setText(SharedPreferenceHelper.getSharedPreferenceString(mContext, "GATEWAY_KEY", ""));
apiKeyEditText.setText(SharedPreferenceHelper.getSharedPreferenceString(mContext, "API_KEY", ""));
gatewaySwitch.setChecked(SharedPreferenceHelper.getSharedPreferenceBoolean(mContext, "GATEWAY_ENABLED", false));
gatewaySwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@ -85,16 +99,16 @@ public class MainActivity extends AppCompatActivity {
public void onCheckedChanged(CompoundButton compoundButton, boolean isCheked) {
View view = compoundButton.getRootView();
compoundButton.setEnabled(false);
String key = gatewayKeyEditText.getText().toString();
String key = apiKeyEditText.getText().toString();
UpdateDeviceInputDTO updateDeviceInput = new UpdateDeviceInputDTO();
updateDeviceInput.setEnabled(isCheked);
RegisterDeviceInputDTO registerDeviceInput = new RegisterDeviceInputDTO();
registerDeviceInput.setEnabled(isCheked);
Call<UpdateDeviceResponseDTO> apiCall = gatewayApiService.updateDevice(key, updateDeviceInput);
apiCall.enqueue(new Callback<UpdateDeviceResponseDTO>() {
Call<RegisterDeviceResponseDTO> apiCall = gatewayApiService.updateDevice(deviceId, key, registerDeviceInput);
apiCall.enqueue(new Callback<RegisterDeviceResponseDTO>() {
@Override
public void onResponse(Call<UpdateDeviceResponseDTO> call, Response<UpdateDeviceResponseDTO> response) {
public void onResponse(Call<RegisterDeviceResponseDTO> call, Response<RegisterDeviceResponseDTO> response) {
if (response.isSuccessful()) {
SharedPreferenceHelper.setSharedPreferenceBoolean(mContext, "GATEWAY_ENABLED", isCheked);
@ -108,7 +122,7 @@ public class MainActivity extends AppCompatActivity {
}
@Override
public void onFailure(Call<UpdateDeviceResponseDTO> call, Throwable t) {
public void onFailure(Call<RegisterDeviceResponseDTO> call, Throwable t) {
Snackbar.make(view, "An error occured :(", Snackbar.LENGTH_LONG).show();
compoundButton.setEnabled(true);
@ -119,65 +133,21 @@ public class MainActivity extends AppCompatActivity {
}
});
updateKeyButton.setOnClickListener(new View.OnClickListener() {
registerDeviceBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String newKey = gatewayKeyEditText.getText().toString();
updateKeyButton.setEnabled(false);
updateKeyButton.setText("Loading...");
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Snackbar.make(view, "Failed to obtain FCM Token :(", Snackbar.LENGTH_LONG).show();
updateKeyButton.setEnabled(true);
updateKeyButton.setText("Update");
return;
}
String token = task.getResult();
fcmTokenEditText.setText(token);
UpdateDeviceInputDTO updateDeviceInput = new UpdateDeviceInputDTO();
updateDeviceInput.setEnabled(true);
updateDeviceInput.setFcmToken(token);
updateDeviceInput.setBrand(Build.BRAND);
updateDeviceInput.setManufacturer(Build.MANUFACTURER);
updateDeviceInput.setModel(Build.MODEL);
updateDeviceInput.setBuildId(Build.ID);
updateDeviceInput.setOs(Build.VERSION.BASE_OS);
Call<UpdateDeviceResponseDTO> apiCall = gatewayApiService.updateDevice(newKey, updateDeviceInput);
apiCall.enqueue(new Callback<UpdateDeviceResponseDTO>() {
@Override
public void onResponse(Call<UpdateDeviceResponseDTO> call, Response<UpdateDeviceResponseDTO> response) {
if (response.isSuccessful()) {
SharedPreferenceHelper.setSharedPreferenceString(mContext, "GATEWAY_KEY", newKey);
Log.e("API_RESP", response.toString());
Snackbar.make(view, "DONE :)", Snackbar.LENGTH_LONG).show();
} else {
Snackbar.make(view, response.message(), Snackbar.LENGTH_LONG).show();
}
updateKeyButton.setEnabled(true);
updateKeyButton.setText("Update");
}
@Override
public void onFailure(Call<UpdateDeviceResponseDTO> call, Throwable t) {
Snackbar.make(view, "An error occured :(", Snackbar.LENGTH_LONG).show();
updateKeyButton.setEnabled(true);
updateKeyButton.setText("Update");
}
});
}
});
handleRegisterDevice();
}
});
scanQRBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this);
intentIntegrator.setPrompt("Go to vernu-sms.vercel.app/dashboard and click Register Device to generate QR Code");
intentIntegrator.setRequestCode(SCAN_QR_REQUEST_CODE);
intentIntegrator.initiateScan();
}
});
@ -200,6 +170,66 @@ public class MainActivity extends AppCompatActivity {
}
private void handleRegisterDevice() {
String newKey = apiKeyEditText.getText().toString();
registerDeviceBtn.setEnabled(false);
registerDeviceBtn.setText("Loading...");
View view = findViewById(R.id.registerDeviceBtn);
FirebaseMessaging.getInstance().getToken()
.addOnCompleteListener(new OnCompleteListener<String>() {
@Override
public void onComplete(@NonNull Task<String> task) {
if (!task.isSuccessful()) {
Snackbar.make(view, "Failed to obtain FCM Token :(", Snackbar.LENGTH_LONG).show();
registerDeviceBtn.setEnabled(true);
registerDeviceBtn.setText("Update");
return;
}
String token = task.getResult();
fcmTokenEditText.setText(token);
RegisterDeviceInputDTO registerDeviceInput = new RegisterDeviceInputDTO();
registerDeviceInput.setEnabled(true);
registerDeviceInput.setFcmToken(token);
registerDeviceInput.setBrand(Build.BRAND);
registerDeviceInput.setManufacturer(Build.MANUFACTURER);
registerDeviceInput.setModel(Build.MODEL);
registerDeviceInput.setBuildId(Build.ID);
registerDeviceInput.setOs(Build.VERSION.BASE_OS);
Call<RegisterDeviceResponseDTO> apiCall = gatewayApiService.registerDevice(newKey, registerDeviceInput);
apiCall.enqueue(new Callback<RegisterDeviceResponseDTO>() {
@Override
public void onResponse(Call<RegisterDeviceResponseDTO> call, Response<RegisterDeviceResponseDTO> response) {
if (response.isSuccessful()) {
SharedPreferenceHelper.setSharedPreferenceString(mContext, "API_KEY", newKey);
Log.e("API_RESP", response.toString());
Snackbar.make(view, "Device Registration Successful :)", Snackbar.LENGTH_LONG).show();
SharedPreferenceHelper.setSharedPreferenceString(mContext, "DEVICE_ID", response.body().data.get("_id").toString());
} else {
Snackbar.make(view, response.message(), Snackbar.LENGTH_LONG).show();
}
registerDeviceBtn.setEnabled(true);
registerDeviceBtn.setText("Update");
}
@Override
public void onFailure(Call<RegisterDeviceResponseDTO> call, Throwable t) {
Snackbar.make(view, "An error occured :(", Snackbar.LENGTH_LONG).show();
registerDeviceBtn.setEnabled(true);
registerDeviceBtn.setText("Update");
}
});
}
});
}
private void handleSMSRequestPermission(View view) {
if (isSMSPermissionGranted(mContext)) {
Snackbar.make(view, "Already got permissions", Snackbar.LENGTH_SHORT).show();
@ -215,6 +245,25 @@ public class MainActivity extends AppCompatActivity {
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == SCAN_QR_REQUEST_CODE) {
IntentResult intentResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, data);
if (intentResult != null) {
if (intentResult.getContents() == null) {
Toast.makeText(getBaseContext(), "Canceled", Toast.LENGTH_SHORT).show();
} else {
String scannedQR = intentResult.getContents();
apiKeyEditText.setText(scannedQR);
handleRegisterDevice();
}
}
}
}
private boolean isSMSPermissionGranted(Context context) {
return ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED;
}

6
app/src/main/java/com/vernu/sms/dtos/UpdateDeviceInputDTO.java → app/src/main/java/com/vernu/sms/dtos/RegisterDeviceInputDTO.java

@ -1,6 +1,6 @@
package com.vernu.sms.dtos;
public class UpdateDeviceInputDTO {
public class RegisterDeviceInputDTO {
private String fcmToken;
private boolean enabled;
private String brand;
@ -13,10 +13,10 @@ public class UpdateDeviceInputDTO {
private String appVersionName;
private String appVersionCode;
public UpdateDeviceInputDTO() {
public RegisterDeviceInputDTO() {
}
public UpdateDeviceInputDTO(String fcmToken) {
public RegisterDeviceInputDTO(String fcmToken) {
this.fcmToken = fcmToken;
}

9
app/src/main/java/com/vernu/sms/dtos/RegisterDeviceResponseDTO.java

@ -0,0 +1,9 @@
package com.vernu.sms.dtos;
import java.util.Map;
public class RegisterDeviceResponseDTO {
public boolean success;
public Map<String, Object> data;
public String error;
}

7
app/src/main/java/com/vernu/sms/dtos/UpdateDeviceResponseDTO.java

@ -1,7 +0,0 @@
package com.vernu.sms.dtos;
public class UpdateDeviceResponseDTO {
public boolean success;
public Object data;
public String error;
}

19
app/src/main/java/com/vernu/sms/services/GatewayApiService.java

@ -0,0 +1,19 @@
package com.vernu.sms.services;
import com.vernu.sms.dtos.RegisterDeviceInputDTO;
import com.vernu.sms.dtos.RegisterDeviceResponseDTO;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.PATCH;
import retrofit2.http.POST;
import retrofit2.http.Path;
import retrofit2.http.Query;
public interface GatewayApiService {
@POST("gateway/devices")
Call<RegisterDeviceResponseDTO> registerDevice(@Query("apiKey") String apiKey, @Body() RegisterDeviceInputDTO body);
@PATCH("gateway/devices/{deviceId}")
Call<RegisterDeviceResponseDTO> updateDevice(@Path("deviceId") String deviceId, @Query("apiKey") String apiKey, @Body() RegisterDeviceInputDTO body);
}

40
app/src/main/res/layout/activity_main.xml

@ -10,30 +10,32 @@
android:id="@+id/gatewaySwitch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="32dp"
android:text="Status"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<EditText
android:id="@+id/gatewayKeyEditText"
android:id="@+id/apiKeyEditText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:hint="key"
android:inputType="text"
android:minHeight="48dp"
android:textIsSelectable="true"
app:layout_constraintEnd_toStartOf="@+id/updateKeyButton"
app:layout_constraintEnd_toStartOf="@+id/registerDeviceBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/gatewaySwitch" />
<Button
android:id="@+id/updateKeyButton"
android:id="@+id/registerDeviceBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Update"
android:text="Register Device"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/gatewayKeyEditText"
app:layout_constraintStart_toEndOf="@+id/apiKeyEditText"
app:layout_constraintTop_toBottomOf="@+id/gatewaySwitch" />
<Button
@ -50,27 +52,41 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="10"
android:enabled="false"
android:gravity="start|top"
android:hint="FCM Token"
android:inputType="textMultiLine"
android:enabled="false"
android:visibility="gone"
android:hint="FCM Token"
app:layout_constraintEnd_toEndOf="@+id/gatewayKeyEditText"
app:layout_constraintStart_toStartOf="@+id/gatewayKeyEditText"
app:layout_constraintTop_toBottomOf="@+id/gatewayKeyEditText" />
app:layout_constraintEnd_toEndOf="@+id/apiKeyEditText"
app:layout_constraintStart_toStartOf="@+id/apiKeyEditText"
app:layout_constraintTop_toBottomOf="@+id/apiKeyEditText" />
<Button
android:id="@+id/scanQRButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="or Scan QR"
app:layout_constraintEnd_toEndOf="@+id/registerDeviceBtn"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/registerDeviceBtn" />
<ScrollView
android:id="@+id/scrollView2"
android:layout_width="409dp"
android:layout_height="606dp"
app:layout_constraintBottom_toTopOf="@+id/grantSMSPermissionBtn"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/fcmTokenEditText">
app:layout_constraintTop_toBottomOf="@+id/scanQRButton"
app:layout_constraintVertical_bias="0.0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"></LinearLayout>
android:orientation="vertical">
</LinearLayout>
</ScrollView>
Loading…
Cancel
Save