This tutorial describes how to write a robust script by handling SOTI MobiControl-specific errors. It also teaches how to terminate a script by throwing pre-defined or custom errors. Finally, it explains how the Android Agent JavaScript engine improves reporting programming mistakes.
Writing a Robust Script
A robust script should take into account that the Android Agent JavaScript APIs may not be supported or have limited functionality on selected devices and agents. For example, APN APIs are not supported on Android Enterprise agents running without the plugin on non-Samsung devices; File APIs will fail when accessing files out of the scoped storage, when running on Android 11; etc. Depending on the approach used to report such errors, the APIs can be split into several categories:
Nullable APIs
Nullable APIs return null
when error conditions occur.
These APIs are documented as nullable
in the API reference. For example, check
mobicontrol.app.installedApps or
mobicontrol.io.File.contentEquals.
If a property is nullable or a function returns nullable, a robust script is expected to have a null check:
var installedApps = mobicontrol.app.installedApps;
if (installedApps == null) {
mobicontrol.log.error("Cannot obtain the list of installed apps");
} else {
var installedAppPackageNames = installedApps
.map(app => app.packageName)
.join('|');
mobicontrol.log.info(installedAppPackageNames);
}
Note that if the null check is omitted, and mobicontrol.app.installedApps
returns null
,
the script will fail with an error similar to TypeError: Cannot read property "map" from null
.
It is not trivial to figure out the root cause of the failure from such an error.
Throwing APIs
Throwing APIs will throw an error when error conditions occur.
These APIs are documented as throwing an error in the API reference.
For example, mobicontrol.audio.setRingVolume
is documented to throw SetVolumeError.
In order to see all the possible error codes the API can throw, check the documentation
for the statusCode
property of the error. For setRingVolume
, this will be
SetVolumeStatusCode, which has two possible
values: UNKNOWN
and DO_NOT_DISTURB_MODE
.
If a function is documented to throw an error, like
mobicontrol.audio.setRingVolume, a script may want
to handle a specific error code.
For example, the following code tries to increase the audio volume.
If increasing the audio volume fails because the device is in do-not-disturb (DND) mode,
then the script will try to exit DND mode and retry setting the volume.
Exiting DND mode is implemented by injecting the volume up key several times, with a 200ms delay
between injections.
try {
adjustRingVolume();
} catch (err) {
if (err.statusCode == mobicontrol.audio.SetVolumeStatusCode.DO_NOT_DISTURB_MODE) {
exitDndModeAndAdjustRingVolume();
} else {
throw err;
}
}
function adjustRingVolume() {
mobicontrol.audio.setRingVolume(0.8);
mobicontrol.log.info('Ring volume adjusted')
}
function exitDndModeAndAdjustRingVolume() {
const KEYCODE_VOLUME_UP = 24;
var delay = 0;
for (var i = 0; i < 3; i++) {
setTimeout(injectVolumeUp, delay);
delay += 200;
}
setTimeout(adjustRingVolume, delay);
}
function injectVolumeUp() {
const KEYCODE_VOLUME_UP = 24;
mobicontrol.device.injectKey(KEYCODE_VOLUME_UP);
}
Note that if the error is not DO_NOT_DISTURB_MODE
, it is re-thrown.
It is usually recommended to re-throw errors which do not require special handling,
as this will produce the most useful feedback to the SOTI MobiControl console administrator.
Callback APIs
Callback APIs report error conditions by setting specific properties of the callback function's
result
parameter.
A general success status is reported by result.isSuccessful
and result.isFailure
,
while details can be obtained via result.statusCode
and result.isTimedOut
.
For example, mobicontrol.app.install accepts a callback parameter
of installCallback type.
If the callback fails due to timeout, result.isFailed
and result.isTimedOut
will be true
.
If the callback fails for any other reason, result.statusCode
will have the detailed status code
of the callback.
In order to see all the possible status codes, check the documentation of the statusCode
's type.
For installCallback
, this
will be InstallationStatusCode,
which has five possible values.
A robust script is expected to check for callback errors and report these to the SOTI MobiControl
console either by explicit logging or by throwing errors.
The following script demonstrates how to properly handle error conditions in an app installation
callback:
mobicontrol.app.install('/sdcard/example.apk', onFinish);
function onFinish(result) {
if (result.isSuccessful) {
if (result.statusCode == mobicontrol.app.InstallationStatusCode.ALREADY_INSTALLED) {
mobicontrol.log.info('Installation skipped, as the app is already installed.')
}
mobicontrol.app.start('net.soti.example');
} else {
if (result.isTimedOut) {
throw 'Installation timed out.'
} else {
throw result.statusCode
}
}
}
Note that unlike statusCode
of the throwing APIs,
callbacks might have successful status codes.
In the example above, this is illustrated by InstallationStatusCode.ALREADY_INSTALLED
.
Throwing Errors
A script can throw an error to terminate its execution, as shown in previous examples.
No matter where throw
is called from - a main body, a function or a callback - the
execution of the entire script will be terminated, and an error message will be logged to the
SOTI MobiControl console. The error message is calculated by calling toString
on the thrown
object, with debug information (file name and line number where the error was thrown) attached.
For example, if the app installation script would fail with
FILE_NOT_FOUND
, the following error message is expected: FILE_NOT_FOUND (UserScript#13)
.
Throwable APIs
The errors thrown in the app installation script are custom errors. The Android Agent JavaScript defines several APIs which are designed to be thrown by a script to indicate certain termination conditions. For example, if Termination.ABORTED is thrown from a package pre-installation script, the package installation is aborted and shown as "Failed" in the SOTI MobiControl console. Throwable APIs do not display error logs on the console when thrown, unlike custom errors.
The following script aborts package installation if it is running on a pre-Oreo device:
if (mobicontrol.os.apiLevel < 26) {
throw(mobicontrol.packages.Termination.ABORTED);
}
Programming Errors
Many Android Agent JavaScript APIs throw Error when a programming error occurs. This is when the corresponding API is used incorrectly. For example, when a function is called with fewer arguments than required, or when an argument is invalid. This is done to improve debugging of JavaScript code and detect programming mistakes sooner.
Programming errors are not designed to be caught, therefore, they are not documented in APIs that may throw them. It is still possible to catch programming errors, as demonstrated by the following script:
try {
// Call a function with missing argument
var dialog = mobicontrol.message.createInfoDialog();
}
catch(error) {
mobicontrol.log.warn(error.name + ' in ' + error.fileName + ' at line ' + error.lineNumber);
}
mobicontrol.log.info('Resuming the execution');
The script will log MobiControlError in UserScript at line 3
.
Note that the thrown error has the same properties as a Mozilla flavour of a
standard JavaScript Error
and its name property is equal to "MobiControlError".
Errors thrown by Throwing APIs
inherit from Error, adding a statusCode
property.