diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/dashboard/DashboardActivity.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/dashboard/DashboardActivity.java index b920744b2..720231f78 100644 --- a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/dashboard/DashboardActivity.java +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/dashboard/DashboardActivity.java @@ -3,6 +3,7 @@ import android.Manifest; import android.content.Intent; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.provider.Settings; @@ -13,6 +14,8 @@ import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.RequiresApi; +import androidx.annotation.StringRes; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -37,7 +40,6 @@ public class DashboardActivity extends BaseActivity { private AuthViewModel authViewModel; private CustomerIORepository customerIORepository; - @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) private final ActivityResultLauncher notificationSettingsRequestLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { if (isNotificationPermissionGranted()) { @@ -50,10 +52,7 @@ public class DashboardActivity extends BaseActivity { if (isGranted) { showPushPermissionGranted(); } else { - MaterialAlertDialogBuilder builder = ViewUtils.createAlertDialog(this); - builder.setMessage(R.string.notification_permission_denied); - builder.setNeutralButton(R.string.open_settings, (dialogInterface, i) -> openNotificationPermissionSettings()); - builder.show(); + showPushPermissionDeniedAlert(R.string.notification_permission_denied); } }); @@ -174,35 +173,39 @@ private void startSimpleFragmentActivity(String fragmentName) { } private void requestNotificationPermission() { - // Push notification permission is only required by API Level 33 (Android 13) and above - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - showPushPermissionGrantedAlert(); - return; - } - - // Ask for notification permission if not granted if (isNotificationPermissionGranted()) { + // Ask for notification permission if not granted showPushPermissionGrantedAlert(); - } else if (shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { - MaterialAlertDialogBuilder builder = ViewUtils.createAlertDialog(this); - builder.setMessage(R.string.notification_permission_failure); - builder.setNeutralButton(R.string.open_settings, (dialogInterface, i) -> openNotificationPermissionSettings()); - builder.show(); + } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU || shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)) { + // If notification permission is not available or denied permanently, show prompt to open settings + showPushPermissionDeniedAlert(R.string.notification_permission_failure); } else { + // Else, request notification permission notificationPermissionRequestLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); } } - @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) private boolean isNotificationPermissionGranted() { - return ContextCompat.checkSelfPermission(DashboardActivity.this, - Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; + // Push notification permission is only required by API Level 33 (Android 13) and above + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + return ContextCompat.checkSelfPermission(DashboardActivity.this, + Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED; + } + + // For Android OS 12 and below, notification enabled status can be checked using NotificationManagerCompat + return NotificationManagerCompat.from(this).areNotificationsEnabled(); } - @RequiresApi(api = Build.VERSION_CODES.TIRAMISU) private void openNotificationPermissionSettings() { - Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); + final Intent intent; + final String packageName = getPackageName(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); + intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); + } else { + intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + packageName)); + } notificationSettingsRequestLauncher.launch(intent); } @@ -218,4 +221,11 @@ private void showPushPermissionGrantedAlert() { builder.setMessage(R.string.notification_permission_success); builder.show(); } + + private void showPushPermissionDeniedAlert(@StringRes int messageResId) { + MaterialAlertDialogBuilder builder = ViewUtils.createAlertDialog(this); + builder.setMessage(messageResId); + builder.setNeutralButton(R.string.open_settings, (dialogInterface, i) -> openNotificationPermissionSettings()); + builder.show(); + } } diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/settings/SettingsActivity.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/settings/SettingsActivity.java index 550f3c70d..197308b08 100644 --- a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/settings/SettingsActivity.java +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/settings/SettingsActivity.java @@ -120,7 +120,10 @@ private void setupViews() { clipboard.setPrimaryClip(clip); }); binding.saveButton.setOnClickListener(view -> saveSettings()); - binding.restoreDefaultsButton.setOnClickListener(view -> updateIOWithConfig(CustomerIOSDKConfig.getDefaultConfigurations())); + binding.restoreDefaultsButton.setOnClickListener(view -> { + updateIOWithConfig(CustomerIOSDKConfig.getDefaultConfigurations()); + saveSettings(); + }); } private void setupObservers() { @@ -147,7 +150,14 @@ private boolean isTrackingURLValid(String url) { Uri uri = Uri.parse(url); String scheme = uri.getScheme(); // Since SDK does not allow tracking URL with empty host or incorrect schemes - return !TextUtils.isEmpty(uri.getAuthority()) && ("http".equals(scheme) || "https".equals(scheme)); + return !TextUtils.isEmpty(uri.getAuthority()) && ("http".equals(scheme) || "https".equals(scheme)) && uri.getPath().endsWith("/"); + } + + @SuppressWarnings("BooleanMethodIsAlwaysInverted") + private > boolean isNumberValid(T number, T min) { + // Compares if the value is not null and greater than or equal to min + // i.e. evaluates number >= min + return number != null && number.compareTo(min) >= 0; } private void updateIOWithConfig(@NonNull CustomerIOSDKConfig config) { @@ -174,12 +184,28 @@ private void saveSettings() { isFormValid = updateErrorState(binding.apiKeyInputLayout, TextUtils.isEmpty(apiKey), R.string.error_text_input_field_blank) && isFormValid; String bqSecondsDelayText = ViewUtils.getTextTrimmed(binding.bqDelayTextInput); - isFormValid = updateErrorState(binding.bqDelayInputLayout, TextUtils.isEmpty(bqSecondsDelayText), R.string.error_text_input_field_blank) && isFormValid; Double bqSecondsDelay = StringUtils.parseDouble(bqSecondsDelayText, null); + boolean isBQSecondsDelayTextEmpty = TextUtils.isEmpty(bqSecondsDelayText); + if (isBQSecondsDelayTextEmpty) { + isFormValid = updateErrorState(binding.bqDelayInputLayout, true, R.string.error_text_input_field_blank) && isFormValid; + } else { + double minDelay = 1.0; + isFormValid = updateErrorState(binding.bqDelayInputLayout, + !isNumberValid(bqSecondsDelay, minDelay), + getString(R.string.error_number_input_field_small, String.valueOf(minDelay))) && isFormValid; + } String bqMinTasksText = ViewUtils.getTextTrimmed(binding.bqTasksTextInput); - isFormValid = updateErrorState(binding.bqTasksInputLayout, TextUtils.isEmpty(bqMinTasksText), R.string.error_text_input_field_blank) && isFormValid; Integer bqMinTasks = StringUtils.parseInteger(bqMinTasksText, null); + boolean isBQMinTasksTextEmpty = TextUtils.isEmpty(bqMinTasksText); + if (isBQMinTasksTextEmpty) { + isFormValid = updateErrorState(binding.bqTasksInputLayout, true, R.string.error_text_input_field_blank) && isFormValid; + } else { + int minTasks = 1; + isFormValid = updateErrorState(binding.bqTasksInputLayout, + !isNumberValid(bqMinTasks, minTasks), + getString(R.string.error_number_input_field_small, String.valueOf(minTasks))) && isFormValid; + } if (isFormValid) { binding.progressIndicator.show(); @@ -214,4 +240,12 @@ private boolean updateErrorState(TextInputLayout textInputLayout, ViewUtils.setError(textInputLayout, error); return !isErrorEnabled; } + + private boolean updateErrorState(TextInputLayout textInputLayout, + boolean isErrorEnabled, + String errorMessage) { + String error = isErrorEnabled ? errorMessage : null; + ViewUtils.setError(textInputLayout, error); + return !isErrorEnabled; + } } diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/AttributesTrackingFragment.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/AttributesTrackingFragment.java index e486cbe6e..fa68f30e1 100644 --- a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/AttributesTrackingFragment.java +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/AttributesTrackingFragment.java @@ -1,7 +1,6 @@ package io.customer.android.sample.java_layout.ui.tracking; import android.os.Bundle; -import android.text.TextUtils; import androidx.fragment.app.FragmentActivity; @@ -90,48 +89,31 @@ private void setupViews() { } binding.sendEventButton.setOnClickListener(view -> { - boolean isFormValid = true; String attributeName = ViewUtils.getText(binding.attributeNameTextInput); String attributeValue = ViewUtils.getText(binding.attributeValueTextInput); - if (TextUtils.isEmpty(attributeName)) { - ViewUtils.setError(binding.attributeNameInputLayout, getString(R.string.error_text_input_field_empty)); - isFormValid = false; - } else { - ViewUtils.setError(binding.attributeNameInputLayout, null); + Map attributes = new HashMap<>(); + attributes.put(attributeName, attributeValue); + + final String attributeType; + switch (mAttributeType) { + case ATTRIBUTE_TYPE_DEVICE: + attributeType = getString(R.string.device); + customerIORepository.setDeviceAttributes(attributes); + break; + case ATTRIBUTE_TYPE_PROFILE: + attributeType = getString(R.string.profile); + customerIORepository.setProfileAttributes(attributes); + break; + default: + return; } - if (TextUtils.isEmpty(attributeValue)) { - ViewUtils.setError(binding.attributeValueInputLayout, getString(R.string.error_text_input_field_empty)); - isFormValid = false; - } else { - ViewUtils.setError(binding.attributeValueInputLayout, null); - } - - if (isFormValid) { - Map attributes = new HashMap<>(); - attributes.put(attributeName, attributeValue); - - final String attributeType; - switch (mAttributeType) { - case ATTRIBUTE_TYPE_DEVICE: - attributeType = getString(R.string.device); - customerIORepository.setDeviceAttributes(attributes); - break; - case ATTRIBUTE_TYPE_PROFILE: - attributeType = getString(R.string.profile); - customerIORepository.setProfileAttributes(attributes); - break; - default: - return; - } - - FragmentActivity activity = getActivity(); - if (activity != null) { - Snackbar.make(binding.sendEventButton, - getString(R.string.attributes_tracked_msg_format, attributeType), - Snackbar.LENGTH_SHORT).show(); - } + FragmentActivity activity = getActivity(); + if (activity != null) { + Snackbar.make(binding.sendEventButton, + getString(R.string.attributes_tracked_msg_format, attributeType), + Snackbar.LENGTH_SHORT).show(); } }); } diff --git a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/CustomEventTrackingFragment.java b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/CustomEventTrackingFragment.java index 181f1381c..7fcf707d9 100644 --- a/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/CustomEventTrackingFragment.java +++ b/samples/java_layout/src/main/java/io/customer/android/sample/java_layout/ui/tracking/CustomEventTrackingFragment.java @@ -52,29 +52,21 @@ private void prepareViewsForAutomatedTests() { private void setupViews() { binding.sendEventButton.setOnClickListener(view -> { - boolean isFormValid = true; String eventName = ViewUtils.getText(binding.eventNameTextInput); String propertyName = ViewUtils.getText(binding.propertyNameTextInput); String propertyValue = ViewUtils.getText(binding.propertyValueTextInput); - if (TextUtils.isEmpty(eventName)) { - ViewUtils.setError(binding.eventNameInputLayout, getString(R.string.error_text_input_field_empty)); - isFormValid = false; - } else { - ViewUtils.setError(binding.eventNameInputLayout, null); + Map extras = new HashMap<>(); + if (!TextUtils.isEmpty(propertyName)) { + extras.put(propertyName, propertyValue); } + customerIORepository.trackEvent(eventName, extras); - if (isFormValid) { - Map extras = new HashMap<>(); - extras.put(propertyName, propertyValue); - customerIORepository.trackEvent(eventName, extras); - - FragmentActivity activity = getActivity(); - if (activity != null) { - Snackbar.make(binding.sendEventButton, - R.string.event_tracked_msg, - Snackbar.LENGTH_SHORT).show(); - } + FragmentActivity activity = getActivity(); + if (activity != null) { + Snackbar.make(binding.sendEventButton, + R.string.event_tracked_msg, + Snackbar.LENGTH_SHORT).show(); } }); } diff --git a/samples/java_layout/src/main/res/values/strings.xml b/samples/java_layout/src/main/res/values/strings.xml index 2213e724a..1b0c68c07 100644 --- a/samples/java_layout/src/main/res/values/strings.xml +++ b/samples/java_layout/src/main/res/values/strings.xml @@ -58,4 +58,5 @@ Java Sample Universal Scheme Links This field cannot be blank This field cannot be empty + The value must be greater than or equal to %1$s