Help Users Resolve Permission and Device Setting Errors

This section enables you to help users resolve errors that might come up in permission and device settings. This section includes the following topics:

Help Users Resolve Permission and Device Setting Errors

Help Users Resolve Permission and Device Setting Errors

Help Users Resolve Permission and Device Setting Errors

Help Users Resolve Permission and Device Setting Errors

Help Users Resolve Permission and Device Setting Errors

1.0. Listen To Zendrive SDK Callbacks For Permission and Setting Changes

This section describes how to listen to Zendrive SDK callbacks to enable permission and setting changes.

1.1. Pass the ZendriveBroadcastReceiver Via Zendrive SDK Setup Call

In order to receive permission error callbacks, the application needs to subclass the ZendriveBroadcastReceiver class and implement the abstract handlers. The subclass should then be registered with Zendrive SDK via the SDK setup call, as outlined in the following code snippet:

// ZendriveReceiver.kt

class ZendriveReceiver : ZendriveBroadcastReceiver() {






  override fun onZendriveSettingsConfigChanged(
       context: Context?,
       errorsFound: Boolean,
       warningsFound: Boolean
   ) {
       if (errorsFound || warningsFound) {
           // Notify user to resolve the errors or Warnings Found
           ZWLCore.zendriveManager()
               .onZendriveSettingsConfigChanged(context, errorsFound, warningsFound)
       } else {
           // Dismiss the existing notification if all errors are resolved
           NotificationProvider.dismissPermissionErrorNotifications()
       }
   }



}

1.2. Listen To the onSettingsChanged Zendrive SDK Callback

Use the following code snippet to listen to onSettingsChanged Zendrive SDK callback and pull the user's latest permissions status.

class ZendriveManager(
    database: IQLDatabase,
    private val sdkKey: String,
    private val loginRepository: LoginRepository,
    private val tripRepository: TripRepository
) {
    ...
    ...
    fun onZendriveSettingsConfigChanged(
        context: Context?,
        errorsFound: Boolean,
        warningsFound: Boolean
    ) {

        // If the App is in the background, send notification to resolve the permission related errors found
        Timber.d("onZendriveSettingsConfigChanged: errorsFound: $errorsFound warningsFound: $warningsFound")
        context?.let {
            Zendrive.getZendriveSettings(context) { zendriveSettings ->
                zendriveSettings?.let {
                    if (ZWLCore.isAppInBackground()) {
                        it.errors.forEach { err ->
                            Timber.e("Received settings error - ${err.type}")
                            val intent = Intent(context, ZWLCore.getHomeActivity())
                            intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP)
                            NotificationProvider.showNotification(
                                ZWLCore.context(),
                                PERMISSION_ERROR_NOTIFICATION_ID,
                                context.getString(R.string.iql_reference_app_not_working),
                                context.getString(R.string.please_fix_the_issues),
                                intent
                            )
                        }

                        it.warnings.forEach { warn ->
                            Timber.e("Received settings warning - ${warn.type}")
                        }
                    }
                }
            }
        }
    }
    ...
    ...
}

1.3. Pull the Latest Permission Status Using Zendrive SDK

Use the following code snippet to pull the current state of settings affecting the Zendrive SDK's normal operation. To do this, pull the current settings when the application move from inactive to active state, or when the application resumes. Process the received errors and trigger the observer to handle them in their respective screens.

1 class AutoFragment : Fragment(R.layout.fragment_auto) { 
2 … 
3 … 
4 … 
5 override fun onResume() { 
6 super.onResume() 
7 getZenDriveSettings() 
8 viewModel.shouldShowOfferCard { 
9 isOfferAvailable -> handleViewsVisibility(isOfferAvailable) 10 } 
11 } 
12 … 
13 … 
14 fun getZenDriveSettings() { 
15 if (viewModel.shouldShowPermissionErrorBand()) { 16 Zendrive.getZendriveSettings(ZWLCore.context()) { 17 // Handle the received error 
18 permissionViewModel.handleZenDriveSettings(it) 19 } 
20 } else { 
21 hideErrorBand() 
22 } 
23 } 
24 … 
25 } 
26 
27 
28 // class PermissionViewmodel 
29 // Handle the received error 
30 fun handleZenDriveSettings(settings: ZendriveSettings?) { 31 var filteredWarnings: List<SettingWarningZendrive> = emptyList() 32 var filteredErrors: List<SettingErrorZendrive> = emptyList() 33 
34 viewModelScope.launch(Dispatchers.IO) { 
35 val allZenDriveIssueTypes = mutableSetOf<ErrorOrWarning>() 
val appErrors = 
36 
listOf(ZendriveAppError(ZendriveAppIssueType.INTERNET_DISABLED)).map { 
37 SettingZendriveAppError(appError = it) 
38 } 
39 val notificationAppError = 
40 
listOf(ZendriveAppError(ZendriveAppIssueType.NOTIFICATION_DISABLED)).map { 41 SettingZendriveAppError(appError = it) 
42 } 
if (getInternetWarning() && 
43 
ErrorBandState.isInternetErrorDismissed.not()) { 
44 allZenDriveIssueTypes.addAll(appErrors) 
45 } else { 
46 allZenDriveIssueTypes.removeAll(appErrors.toSet()) 47 } 
48 if (areNotificationsEnabled().not()) { 
49 allZenDriveIssueTypes.addAll(notificationAppError) 50 } else { 
51 allZenDriveIssueTypes.removeAll(notificationAppError.toSet()) 52 } 
53 
54 settings?.let { 
55 it.errors.forEach { error -> 
56 Timber.d("SDK Error Callback for ${error.type.name} ") 57 } 
58 it.warnings.forEach { warning -> 
59 Timber.d("SDK Warning Callback for ${warning.type.name} ") 60 } 
61 
62 if (it.warnings.isNotEmpty()) { 
63 val warnings = it.warnings.toMutableList() 64 warnings.sortByDescending { it.type.ordinal } 65 filteredWarnings = warnings.filter { 
66 return@filter (isZenDriveIssueTypeWarning(it.type)) 
67 }.map { 
68 SettingWarningZendrive(warning = it) 69 } 
70 } 
71 if (it.errors.isNotEmpty()) { 
72 val errors = it.errors.toMutableList() 
73 errors.forEach { 
74 when (it.type) { 
75 ZendriveIssueType.AIRPLANE_MODE_ENABLED -> { 76 if (ErrorBandState.isAirplaneErrorDismissed) { 77 errors.remove(it) 
78 } 
79 } 
80 ZendriveIssueType.GOOGLE_PLAY_SETTINGS_ERROR -> { 
if 
81 
(ErrorBandState.isGooglePlaySettingsErrorDismissed) { 
82 errors.remove(it) 
83 } 
84 } 
85 ZendriveIssueType.GOOGLE_PLAY_CONNECTION_ERROR -> { 
if 
86 
(ErrorBandState.isGooglePlayConnectionErrorDismissed) { 
87 errors.remove(it) 
88 } 
89 } 
90 ZendriveIssueType.POWER_SAVER_MODE_ENABLED -> { 91 if (ErrorBandState.isPowerSaverErrorDismissed) { 92 errors.remove(it) 
93 } 
94 }
95 else -> {} 
96 } 
97 } 
98 
99 filteredErrors = errors.filter { 
100 return@filter (isZenDriveIssueTypeError(it.type)) 101 }.map { 
102 SettingErrorZendrive(error = it) 
103 } 
104 } 
105 
106 if (filteredErrors.isNotEmpty()) { 
107 allZenDriveIssueTypes.addAll(filteredErrors) 
108 } 
109 
110 if (filteredWarnings.isNotEmpty()) { 
111 allZenDriveIssueTypes.addAll(filteredWarnings) 
112 } 
113 } 
114 // Post the processed errors to the observers to be handled 115 zendriveIssueType.postValue(allZenDriveIssueTypes) 
116 } 
117 …. 
118 …. 
119 …. 
120 } 

2.0. Refresh User's Permission and Device Setting Status

This section describes how you can periodically refresh user's permission and device setting status.

2.1.Create a Worker To Refresh Permissions Status

class ZendriveSettingErrorNotificationWorker(private val context: Context, workerParams: WorkerParameters) : 
CoroutineWorker(context, workerParams) { 
override suspend fun doWork(): Result { 
Timber.d("Notification Worker Triggered")

// Pull permission errors or warnings from ZendriveSDK to notify the user to resolve them 
Zendrive.getZendriveSettings(context) { zenDriveSettings -> zenDriveSettings?.let { 
it.errors.forEach { err -> 
Timber.e("Received settings error - ${err.type}") 
 val intent = 
Intent(context, 
 
ZWLCore.config().onBoardingActivityIntentClass) 
intent.flags = 
(Intent.FLAG_ACTIVITY_CLEAR_TOP or 

Intent.FLAG_ACTIVITY_SINGLE_TOP) 


// Notify users to fix the permission errors with 
appropriate message 
 NotificationProvider.showNotification( 
  context, PERMISSION_ERROR_NOTIFICATION_ID,  
context.getString(R.string.iql_reference_app_not_working),  
context.getString(R.string.please_fix_the_permission_issues_to_book), 
intent 
 ) 
} 
 
 it.warnings.forEach { warn -> 
 Timber.e("Received settings warning - ${warn.type}") 
 } 
} 
 } 
 return Result.success() 
 } 
 } 

2.2. Schedule the Worker At Application Launch

object ZWLWorkManager 
{ … 

// Function that schedules a periodic worker to process current permission errors 
 fun enqueuePermissionErrorNotificationWorker() { 
 WorkManager.getInstance(ZWLCore.context()) 
 .enqueueUniquePeriodicWork( 
 ZEN_DRIVE_SETTING_ERROR_NOTIFICATION_WORKER, 
 ExistingPeriodicWorkPolicy.KEEP, 
 PeriodicWorkRequestBuilder<ZendriveSettingErrorNotificationWorker>(  PERMISSION_ERROR_CHECK_DURATION, 
 TimeUnit.HOURS 
 ) 
 .addTag(ZEN_DRIVE_SETTING_ERROR_NOTIFICATION_WORKER)  
 .setConstraints( 
 
Constraints.Builder().setRequiresBatteryNotLow(true).build() 
 ) 
 .build() 
 ) 
 } 


 } 
class AppLifecycleObserver : DefaultLifecycleObserver {

   override fun onStart(owner: LifecycleOwner) {


   }

   override fun onStop(owner: LifecycleOwner) {
       super.onStop(owner)
       Timber.d("App is in background")
       appInBackground()
   }

   private fun appInBackground() {
       ZWLWorkManager.enqueueSetupZendriveSDKWorker()
       val emailId = ZWLCore.loginRepository().retrieveLoginRequestEmail(ZWLCore.context())
       if (emailId.isNullOrEmpty().not()) {
           // Schedules periodic permission check while app goes in background
           ZWLWorkManager.enqueuePermissionErrorNotificationWorker()
       } else {
           Timber.d("No user, no enqueueing for any pending work")
       }
   }



}

2.3. Pull the Latest Permission Status On Application Launch

class AutoFragment : Fragment(R.layout.fragment_auto) { 
... 
... 
override fun onResume() { 
super.onResume() 
getZenDriveSettings() 
viewModel.shouldShowOfferCard { isOfferAvailable -> 
handleViewsVisibility(isOfferAvailable) 
} 
 } 
 
 private fun getZenDriveSettings() { 
 if (viewModel.shouldShowPermissionErrorBand()) { 
 Zendrive.getZendriveSettings(ZWLCore.context()) { 
 permissionViewModel.handleZenDriveSettings(it) 
 } 
 } else { 
 hideErrorBand() 
 } 
 } 
 ... 
 ... 
 } 

3.0. Handle Permission and Setting Issues

This sections helps you to handle the following permission and setting issues:

  • Handle Location permission issues

  • Handle Physical activity issues

  • Handle other device setting issues (airplane mode, no internet access, power saver mode)

  • Handle device battery setting issues

Use the following code snippets to present an in-app error notification to handle the permission or setting issues that are detected. Show PermissionButtomSheet to handle the error by navigating the user to the app’s settings page on the action button.

Handle Location Permission Issues

Handle Physical Activity Issues

Handle Other Device Setting Issues

Handle Internet Access Issues

Handle Power Saver Issues

Handle Battery Setting Issues

Handle Google Location Services Issues

Handle Notification Permission Issues

4.0. IQL Reference Application UX Design

Here is the overall design implementation for the IQL Reference Application:

5.0. Publisher Integration Testing Checklist

Use the following testing checklist to test the permission and device setting error resolution in your application:

  • Permission Errors: Ensure that the application throws an error whenever a required permission is missing. Additionally, a notification should be displayed asking the user to fix the issue.