diff --git a/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java b/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java index 19558d9..11690ce 100644 --- a/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java +++ b/android/app/src/main/java/com/vernu/sms/activities/MainActivity.java @@ -3,18 +3,12 @@ package com.vernu.sms.activities; 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.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; import android.os.Build; import android.os.Bundle; -import android.telephony.SubscriptionInfo; -import android.telephony.SubscriptionManager; import android.util.Log; import android.view.View; import android.widget.Button; @@ -25,61 +19,48 @@ import android.widget.RadioGroup; import android.widget.Switch; import android.widget.TextView; import android.widget.Toast; - import com.google.android.material.snackbar.Snackbar; import com.google.firebase.messaging.FirebaseMessaging; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; -import com.vernu.sms.services.GatewayApiService; +import com.vernu.sms.ApiManager; +import com.vernu.sms.AppConstants; +import com.vernu.sms.BuildConfig; +import com.vernu.sms.TextBeeUtils; +import com.vernu.sms.database.local.AppDatabase; +import com.vernu.sms.database.local.SMS; import com.vernu.sms.R; import com.vernu.sms.dtos.RegisterDeviceInputDTO; import com.vernu.sms.dtos.RegisterDeviceResponseDTO; import com.vernu.sms.helpers.SharedPreferenceHelper; - -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; - +import java.util.Objects; +import java.util.concurrent.Executors; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; public class MainActivity extends AppCompatActivity { private Context mContext; - private Retrofit retrofit; - private GatewayApiService gatewayApiService; - private Switch gatewaySwitch; private EditText apiKeyEditText, fcmTokenEditText; private Button registerDeviceBtn, grantSMSPermissionBtn, scanQRBtn; private ImageButton copyDeviceIdImgBtn; private TextView deviceBrandAndModelTxt, deviceIdTxt; - private RadioGroup defaultSimSlotRadioGroup; - - 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://api.textbee.dev/api/v1/"; + private static final int PERMISSION_REQUEST_CODE = 0; private String deviceId = null; - + private static final String TAG = "MainActivity"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mContext = getApplicationContext(); - - retrofit = new Retrofit.Builder() - .baseUrl(API_BASE_URL) - .addConverterFactory(GsonConverterFactory.create()) - .build(); - gatewayApiService = retrofit.create(GatewayApiService.class); - - deviceId = SharedPreferenceHelper.getSharedPreferenceString(mContext, "DEVICE_ID", ""); - + deviceId = SharedPreferenceHelper.getSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, ""); setContentView(R.layout.activity_main); gatewaySwitch = findViewById(R.id.gatewaySwitch); apiKeyEditText = findViewById(R.id.apiKeyEditText); @@ -87,33 +68,11 @@ public class MainActivity extends AppCompatActivity { registerDeviceBtn = findViewById(R.id.registerDeviceBtn); grantSMSPermissionBtn = findViewById(R.id.grantSMSPermissionBtn); scanQRBtn = findViewById(R.id.scanQRButton); - - deviceBrandAndModelTxt = findViewById(R.id.deviceBrandAndModelTxt); deviceIdTxt = findViewById(R.id.deviceIdTxt); - copyDeviceIdImgBtn = findViewById(R.id.copyDeviceIdImgBtn); - defaultSimSlotRadioGroup = findViewById(R.id.defaultSimSlotRadioGroup); - - try { - getAvailableSimSlots().forEach(subscriptionInfo -> { - RadioButton radioButton = new RadioButton(mContext); - radioButton.setText(subscriptionInfo.getDisplayName().toString()); - radioButton.setId(subscriptionInfo.getSubscriptionId()); - radioButton.setOnClickListener(view -> { - SharedPreferenceHelper.setSharedPreferenceInt(mContext, "PREFERED_SIM", subscriptionInfo.getSubscriptionId()); - }); - radioButton.setChecked(subscriptionInfo.getSubscriptionId() == SharedPreferenceHelper.getSharedPreferenceInt(mContext, "PREFERED_SIM", 0)); - defaultSimSlotRadioGroup.addView(radioButton); - }); - } catch (Exception e) { - Snackbar.make(defaultSimSlotRadioGroup.getRootView(), "Error: " + e.getMessage(), Snackbar.LENGTH_LONG).show(); - Log.e("SIM_SLOT_ERROR", e.getMessage()); - } - - deviceIdTxt.setText(deviceId); deviceBrandAndModelTxt.setText(Build.BRAND + " " + Build.MODEL); @@ -123,14 +82,19 @@ public class MainActivity extends AppCompatActivity { registerDeviceBtn.setText("Update"); } - if (isSMSPermissionGranted(mContext) && isReadPhoneStatePermissionGranted(mContext)) { + String[] missingPermissions = Arrays.stream(AppConstants.requiredPermissions).filter(permission -> !TextBeeUtils.isPermissionGranted(mContext, permission)).toArray(String[]::new); + if (missingPermissions.length == 0) { grantSMSPermissionBtn.setEnabled(false); grantSMSPermissionBtn.setText("SMS Permission Granted"); + renderAvailableSimOptions(); } else { + Snackbar.make(grantSMSPermissionBtn, "Please Grant Required Permissions to continue: " + Arrays.toString(missingPermissions), Snackbar.LENGTH_SHORT).show(); grantSMSPermissionBtn.setEnabled(true); - grantSMSPermissionBtn.setOnClickListener(view -> handleSMSRequestPermission(view)); + grantSMSPermissionBtn.setOnClickListener(this::handleRequestPermissions); } + TextBeeUtils.startStickyNotificationService(mContext); + copyDeviceIdImgBtn.setOnClickListener(view -> { ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); ClipData clip = ClipData.newPlainText("Device ID", deviceId); @@ -138,70 +102,111 @@ public class MainActivity extends AppCompatActivity { Snackbar.make(view, "Copied", Snackbar.LENGTH_LONG).show(); }); - apiKeyEditText.setText(SharedPreferenceHelper.getSharedPreferenceString(mContext, "API_KEY", "")); - - gatewaySwitch.setChecked(SharedPreferenceHelper.getSharedPreferenceBoolean(mContext, "GATEWAY_ENABLED", false)); + apiKeyEditText.setText(SharedPreferenceHelper.getSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_API_KEY_KEY, "")); + gatewaySwitch.setChecked(SharedPreferenceHelper.getSharedPreferenceBoolean(mContext, AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, false)); gatewaySwitch.setOnCheckedChangeListener((compoundButton, isCheked) -> { View view = compoundButton.getRootView(); compoundButton.setEnabled(false); String key = apiKeyEditText.getText().toString(); - RegisterDeviceInputDTO registerDeviceInput = new RegisterDeviceInputDTO(); registerDeviceInput.setEnabled(isCheked); + registerDeviceInput.setAppVersionCode(BuildConfig.VERSION_CODE); + registerDeviceInput.setAppVersionName(BuildConfig.VERSION_NAME); - Call apiCall = gatewayApiService.updateDevice(deviceId, key, registerDeviceInput); + Call apiCall = ApiManager.getApiService().updateDevice(deviceId, key, registerDeviceInput); apiCall.enqueue(new Callback() { @Override public void onResponse(Call call, Response response) { - - if (response.isSuccessful()) { - Snackbar.make(view, "Gateway " + (isCheked ? "enabled" : "disabled"), Snackbar.LENGTH_LONG).show(); - SharedPreferenceHelper.setSharedPreferenceBoolean(mContext, "GATEWAY_ENABLED", isCheked); - compoundButton.setChecked(Boolean.TRUE.equals(response.body().data.get("enabled"))); - } else { + Log.d(TAG, response.toString()); + if (!response.isSuccessful()) { Snackbar.make(view, response.message(), Snackbar.LENGTH_LONG).show(); + compoundButton.setEnabled(true); + return; + } + Snackbar.make(view, "Gateway " + (isCheked ? "enabled" : "disabled"), Snackbar.LENGTH_LONG).show(); + SharedPreferenceHelper.setSharedPreferenceBoolean(mContext, AppConstants.SHARED_PREFS_GATEWAY_ENABLED_KEY, isCheked); + boolean enabled = Boolean.TRUE.equals(Objects.requireNonNull(response.body()).data.get("enabled")); + compoundButton.setChecked(enabled); + if (enabled) { + TextBeeUtils.startStickyNotificationService(mContext); + } else { + TextBeeUtils.stopStickyNotificationService(mContext); } compoundButton.setEnabled(true); } - @Override public void onFailure(Call call, Throwable t) { Snackbar.make(view, "An error occured :(", Snackbar.LENGTH_LONG).show(); compoundButton.setEnabled(true); - } }); - - }); - + // TODO: check gateway status/api key/device validity and update UI accordingly registerDeviceBtn.setOnClickListener(view -> handleRegisterDevice()); - scanQRBtn.setOnClickListener(view -> { IntentIntegrator intentIntegrator = new IntentIntegrator(MainActivity.this); intentIntegrator.setPrompt("Go to textbee.dev/dashboard and click Register Device to generate QR Code"); intentIntegrator.setRequestCode(SCAN_QR_REQUEST_CODE); intentIntegrator.initiateScan(); }); + } + private void renderAvailableSimOptions() { + try { + defaultSimSlotRadioGroup.removeAllViews(); + RadioButton defaultSimSlotRadioBtn = new RadioButton(mContext); + defaultSimSlotRadioBtn.setText("Device Default"); + defaultSimSlotRadioBtn.setId((int)123456); + defaultSimSlotRadioGroup.addView(defaultSimSlotRadioBtn); + TextBeeUtils.getAvailableSimSlots(mContext).forEach(subscriptionInfo -> { + String simInfo = "SIM " + (subscriptionInfo.getSimSlotIndex() + 1) + " (" + subscriptionInfo.getDisplayName() + ")"; + RadioButton radioButton = new RadioButton(mContext); + radioButton.setText(simInfo); + radioButton.setId(subscriptionInfo.getSubscriptionId()); + defaultSimSlotRadioGroup.addView(radioButton); + }); + int preferredSim = SharedPreferenceHelper.getSharedPreferenceInt(mContext, AppConstants.SHARED_PREFS_PREFERRED_SIM_KEY, -1); + if (preferredSim == -1) { + defaultSimSlotRadioGroup.check(defaultSimSlotRadioBtn.getId()); + } else { + defaultSimSlotRadioGroup.check(preferredSim); + } + defaultSimSlotRadioGroup.setOnCheckedChangeListener((radioGroup, i) -> { + RadioButton radioButton = findViewById(i); + if (radioButton == null) { + return; + } + radioButton.setChecked(true); + if("Device Default".equals(radioButton.getText().toString())) { + SharedPreferenceHelper.clearSharedPreference(mContext, AppConstants.SHARED_PREFS_PREFERRED_SIM_KEY); + } else { + SharedPreferenceHelper.setSharedPreferenceInt(mContext, AppConstants.SHARED_PREFS_PREFERRED_SIM_KEY, radioButton.getId()); + } + }); + } catch (Exception e) { + Snackbar.make(defaultSimSlotRadioGroup.getRootView(), "Error: " + e.getMessage(), Snackbar.LENGTH_LONG).show(); + Log.e(TAG, "SIM_SLOT_ERROR "+ e.getMessage()); + } } @Override public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - switch (requestCode) { - case SEND_SMS_PERMISSION_REQUEST_CODE: { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - Toast.makeText(mContext, "Yay! Permission Granted.", Toast.LENGTH_LONG).show(); - } else { - Toast.makeText(mContext, "Permission Denied :(", Toast.LENGTH_LONG).show(); - return; - } - } - } + if (requestCode != PERMISSION_REQUEST_CODE) { + return; + } + boolean allPermissionsGranted = Arrays.stream(permissions).allMatch(permission -> TextBeeUtils.isPermissionGranted(mContext, permission)); + if (allPermissionsGranted) { + Snackbar.make(findViewById(R.id.grantSMSPermissionBtn), "All Permissions Granted", Snackbar.LENGTH_SHORT).show(); + grantSMSPermissionBtn.setEnabled(false); + grantSMSPermissionBtn.setText("Permission Granted"); + renderAvailableSimOptions(); + } else { + Snackbar.make(findViewById(R.id.grantSMSPermissionBtn), "Please Grant Required Permissions to continue", Snackbar.LENGTH_SHORT).show(); + } } private void handleRegisterDevice() { @@ -230,86 +235,63 @@ public class MainActivity extends AppCompatActivity { registerDeviceInput.setModel(Build.MODEL); registerDeviceInput.setBuildId(Build.ID); registerDeviceInput.setOs(Build.VERSION.BASE_OS); + registerDeviceInput.setAppVersionCode(BuildConfig.VERSION_CODE); + registerDeviceInput.setAppVersionName(BuildConfig.VERSION_NAME); - - Call apiCall = gatewayApiService.registerDevice(newKey, registerDeviceInput); + Call apiCall = ApiManager.getApiService().registerDevice(newKey, registerDeviceInput); apiCall.enqueue(new Callback() { @Override public void onResponse(Call call, Response 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(); - deviceId = response.body().data.get("_id").toString(); - deviceIdTxt.setText(deviceId); - SharedPreferenceHelper.setSharedPreferenceString(mContext, "DEVICE_ID", deviceId); - - } else { + Log.d(TAG, response.toString()); + if (!response.isSuccessful()) { Snackbar.make(view, response.message(), Snackbar.LENGTH_LONG).show(); + registerDeviceBtn.setEnabled(true); + registerDeviceBtn.setText("Update"); + return; } + SharedPreferenceHelper.setSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_API_KEY_KEY, newKey); + Snackbar.make(view, "Device Registration Successful :)", Snackbar.LENGTH_LONG).show(); + deviceId = response.body().data.get("_id").toString(); + deviceIdTxt.setText(deviceId); + SharedPreferenceHelper.setSharedPreferenceString(mContext, AppConstants.SHARED_PREFS_DEVICE_ID_KEY, deviceId); registerDeviceBtn.setEnabled(true); registerDeviceBtn.setText("Update"); - } + } @Override public void onFailure(Call 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) && isReadPhoneStatePermissionGranted(mContext)) { + private void handleRequestPermissions(View view) { + boolean allPermissionsGranted = Arrays.stream(AppConstants.requiredPermissions).allMatch(permission -> TextBeeUtils.isPermissionGranted(mContext, permission)); + if (allPermissionsGranted) { Snackbar.make(view, "Already got permissions", Snackbar.LENGTH_SHORT).show(); - } else { - Snackbar.make(view, "Grant SMS Permissions to continue", Snackbar.LENGTH_SHORT).show(); - ActivityCompat.requestPermissions(MainActivity.this, - new String[]{Manifest.permission.SEND_SMS, Manifest.permission.READ_PHONE_STATE - }, SEND_SMS_PERMISSION_REQUEST_CODE); - + return; } + String[] permissionsToRequest = Arrays.stream(AppConstants.requiredPermissions).filter(permission -> !TextBeeUtils.isPermissionGranted(mContext, permission)).toArray(String[]::new); + Snackbar.make(view, "Please Grant Required Permissions to continue", Snackbar.LENGTH_SHORT).show(); + ActivityCompat.requestPermissions(this, permissionsToRequest, PERMISSION_REQUEST_CODE); } @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(); - } + if (intentResult == null || intentResult.getContents() == null) { + Toast.makeText(getBaseContext(), "Canceled", Toast.LENGTH_SHORT).show(); + return; } + String scannedQR = intentResult.getContents(); + apiKeyEditText.setText(scannedQR); + handleRegisterDevice(); } } - private boolean isSMSPermissionGranted(Context context) { - return ContextCompat.checkSelfPermission(context, Manifest.permission.SEND_SMS) == PackageManager.PERMISSION_GRANTED; - } - - private boolean isReadPhoneStatePermissionGranted(Context context) { - return ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED; - } - - private List getAvailableSimSlots() { - - if (ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) { - return new ArrayList<>(); - } - - SubscriptionManager subscriptionManager = SubscriptionManager.from(mContext); - return subscriptionManager.getActiveSubscriptionInfoList(); - - } } \ No newline at end of file diff --git a/android/app/src/main/java/com/vernu/sms/dtos/SMSForwardResponseDTO.java b/android/app/src/main/java/com/vernu/sms/dtos/SMSForwardResponseDTO.java new file mode 100644 index 0000000..47aebf9 --- /dev/null +++ b/android/app/src/main/java/com/vernu/sms/dtos/SMSForwardResponseDTO.java @@ -0,0 +1,9 @@ +package com.vernu.sms.dtos; + +public class SMSForwardResponseDTO { + + public SMSForwardResponseDTO() { + } + + +}