Skip to content

Overview

There are two types of settings: predefined policies and custom settings. These settings are handled by the ClientPolicyService and CustomSettingsService services.

Predefined Client Policies

The following model classes are defined for predefined client policies in the policies package:

  • PasscodePolicy
  • UsagePolicy
  • LogPolicy
  • NetworkPolicy
  • FeatureRestrictionPolicy
  • BlockWipingPolicy
  • ClientPolicies to hold references to all above policies.

Loading Client Policies

As of Android SDK 3.2, the following APIs are defined to load all client policies:

suspend fun retrieveClientPolicies(
        target: SettingsTarget = SettingsTarget.DEVICE
    ): ServiceResult<ClientPolicies> = withContext(IO) { ... }

fun retrieveClientPolicies(
        target: SettingsTarget = SettingsTarget.DEVICE,
        listener: ServiceListener<ClientPolicies>
    ) { ... }

The first API can be used in Kotlin client code. It runs on the IO thread and returns the ClientPolicies in ServiceResult<ClientPolicies>. The second function can be used in the Java client. The actual API call also runs on the IO thread but the result will be notified with ServiceListener<ClientPolicies> on the UI thread.

findViewById(R.id.button).setOnClickListener(v -> new ClientPolicyService()
    .retrieveClientPolicies(SettingsTarget.DEVICE, serviceResult -> {
        if (serviceResult instanceof ServiceResult.SUCCESS) {
            ClientPolicies polices =
                (((ServiceResult.SUCCESS<ClientPolicies>) serviceResult).getData());
            ...
        } else {
            Log.e(
                "TEST",
                ((ServiceResult.FAILURE<ClientPolicies>) serviceResult).getMessage()
            );
        }
    }));
when (ClientPolicyService().retrieveClientPolicies(SettingsTarget.USER)) {
    is ServiceResult.SUCCESS -> {
        assertNotNull(result.data)
        val passcodePolicy = result.data?.passcodePolicy
        assertNotNull(passcodePolicy)
        assertEquals(5, passcodePolicy!!.minLength)
    }
    is ServiceResult.FAILURE -> {
        assertTrue(false)
    }
}

Client policies may be updated on the server side. ClientPolicyService can be configured to load the latest updates when the user is authenticated after the application restarts or is brought to the foreground after a passcode timeout period. To do this, the target and autoRetrieveListener parameters in the constructor should be provided, and then start the service with SDKInitializer.

List<MobileService> services = new ArrayList<>();
ClientPolicyService clientPolicyService = new ClientPolicyService(
                SettingsTarget.USER, serviceResult -> {

        }
    );
services.add(clientPolicyService);
SDKInitializer.INSTANCE.start(this, services.toArray(new MobileService[0]), null);
val services = mutableListOf<MobileService>()
val clientPolicyService =
        ClientPolicyService(SettingsTarget.USER, object : ServiceListener<ClientPolicies> {
            override fun onServiceDone(result: ServiceResult<ClientPolicies>) {
                when (result) {
                    is ServiceResult.SUCCESS -> Log.d(
                        "TEST",
                        "Client policies: ${result.data?.toString()}"
                    )
                    is ServiceResult.FAILURE -> Log.e("TEST", "Error: ${result.message}")
                }
            }

        })
services.add(clientPolicyService)
SDKInitializer.start(this, * services.toTypedArray())

Storing Client Policies

To store settings, call the updatePolicy method. You can use the store method to create new values or update existing values. The APIs are defined as:

suspend fun <T : AbstractSettingsEntity> updatePolicy(
        policy: T,
        target: SettingsTarget = SettingsTarget.DEVICE
    ): ServiceResult<T> = withContext(IO) { ... }
fun <T : AbstractSettingsEntity> updatePolicy(
    policy: T,
    target: SettingsTarget = SettingsTarget.DEVICE,
    listener: ServiceListener<T>
) { ... }

For Kotlin client code, the first API can be used. It runs on the IO thread and returns the result with ServiceResult. The Java client can call the second function and the result will be notified with ServiceListener on the UI thread.

As of Android SDK 3.2, updatePolicy does not require the keyPath any more because each policy is internally aware of the value.

PasscodePolicy passcodePolicy = ...;
new ClientPolicyService().updatePolicy(passcodePolicy, SettingsTarget.DEVICE, serviceResult -> {
    if( serviceResult instanceof ServiceResult.SUCCESS) {
        ...
    } else {
        ...
    }
});
val passcodePolicy = ...
val passcodePolicyToUpdate = passcodePolicy.copy(minLength = 8)
ClientPolicyService().updatePolicy(
    passcodePolicyToUpdate,
    SettingsTarget.USER,
    object : ServiceListener<PasscodePolicy> {
        override fun onServiceDone(result: ServiceResult<PasscodePolicy>) {
            assertNotNull(result)
            assertTrue(result is ServiceResult.SUCCESS)
            assertTrue((result as ServiceResult.SUCCESS).data!!.minLength == 8)
        }
    })

Deleting Client Policies

As of Android SDK 3.2, ClientPolicyService does not expose deletion APIs. CustomSettingsService will provide APIs to delete custom settings.

Custom Settings

As of Android SDK 3.2, CustomSettingsService is provided to handle custom settings. To handle custom settings, CustomSettingsEntity should be extended:

abstract class CustomSettingsEntity(private val configurationName: String) {
    abstract fun toJsonString(): String
}

configurationName will be used as the keyPath as before.

Loading Custom Settings

suspend fun <T : CustomSettingsEntity> loadCustomSettings(
        target: SettingsTarget = SettingsTarget.DEVICE,
        keyPath: String,
        parse: (String) -> T
    ): ServiceResult<T> = withContext(IO) { ... }

fun <T : CustomSettingsEntity> loadCustomSettings(
        target: SettingsTarget = SettingsTarget.DEVICE,
        keyPath: String,
        parser: (String) -> T,
        listener: ServiceListener<T>
    ) { ... }

Like ClientPolicyService, there are also two APIs to load custom settings. If client code holds an instance of CustomSettingsEntity, getKeyPath() will return the keyPath, otherwise, configurationName can be provided as the keyPath for this API. parser converts the JSON string returned from the mobile server to an instance of CustomSettingsEntity.

Like predefined client policies, custom settings can also be loaded automatically when a user is authenticated or the mobile app is brought to the foreground. To do this, a little more information will be required when creating the instance of CustomSettingsService.

class CustomSettingsService<out R : CustomSettingsEntity>(
    private val target: SettingsTarget = SettingsTarget.USER,
    private val keyPath: String? = null,
    private val parse: ((String) -> R?)? = null,
    private val autoCustomSettingsLoadListener: ServiceListener<R>? = null
) : MobileService() { ... }

From the constructor above, keyPath, parse, and autoCustomSettingsLoadListener will all be required. After creating the instance, start it with SDKInitializer.

List<MobileService> services = new ArrayList<>();
CustomSettingsService customSettingsService = new CustomSettingsService(
            SettingsTarget.USER,
            "key_path_to_custom_setting",
            jsonString -> new CustomUserEntity("key_path_to_custom_setting"),
            serviceResult -> {
            }
    );
services.add(customSettingsService);
SDKInitializer.INSTANCE.start(this, services.toArray(new MobileService[0]), null);
val customSetting = CustomSettingExample()
    val customSettingService = CustomSettingsService(
        target = SettingsTarget.USER,
        keyPath = customSetting.getKeyPath(),
        parse = { jsonString -> CustomSettingExample.createFromJsonString(jsonString) },
        autoCustomSettingsLoadListener = object : ServiceListener<CustomSettingExample> {
            override fun onServiceDone(result: ServiceResult<CustomSettingExample>) {
                when (result) {
                    is ServiceResult.SUCCESS -> {
                        Log.d("TEST", result.data?.toString()?: "")
                    }
                    is ServiceResult.FAILURE -> {
                        Log.e("TEST", "Error: ${result.message}")
                    }
                }
            }
        }
    )
    services.add(customSettingService)
    SDKInitializer.start(this, * services.toTypedArray())

Note

Since SDKInitializer can only initialize one instance of CustomSettingsService, design the CustomSettingsEntity carefully when using this feature.

Storing Custom Settings

suspend fun <T : CustomSettingsEntity> storeCustomPolicy(
        entity: T,
        target: SettingsTarget = SettingsTarget.DEVICE
    ): ServiceResult<T> { ... }

fun <T : CustomSettingsEntity> storeCustomPolicy(
        entity: T,
        target: SettingsTarget = SettingsTarget.DEVICE,
        listener: ServiceListener<T>
    ) { ... }

For 'update', only the CustomSettingsEntity instance is required. Both keyPath and the JSON string can be obtained from the instance. The suspend function will run on the IO thread. The other function will notify the result with ServiceListener on the UI thread.

Deleting Custom Settings

suspend fun deleteCustomPolicy(
        keyPath: String,
        target: SettingsTarget = SettingsTarget.DEVICE
    ): ServiceResult<Nothing> { ... }

fun deleteCustomPolicy(
        keyPath: String,
        target: SettingsTarget = SettingsTarget.DEVICE,
        listener: ServiceListener<Nothing>
    ) { ... }

The suspend function will run on the IO thread. The other function will notify the result with ServiceListener on the UI thread.

Permissions

Note that the access to settings (read/write/delete) is controlled by the user role permissions set by your mobile services administrator. See Storage Service API for more information.


Last update: April 18, 2022