Interacting with Other Apps Using Intents

This tutorial describes how to launch other apps using Android intents and how to share information between other apps and the agent. The following topics are covered:

  1. Constructing intents with extras
  2. Starting activities and services and sending broadcasts
  3. Starting activities for result
  4. Sending information to and receiving information from custom apps
  5. File sharing

Starting Activities with Intents

Let's take a look at a typical start activity example, taken from Android Common Intents. The script launches the dialer or phone app on the device, with the phone number 212-555-1212 entered:

const ACTION_DIAL = "android.intent.action.DIAL";
const URI_DIAL_NUMBER = "tel:2125551212";

var intent = mobicontrol.android.createIntent()
    .withAction(ACTION_DIAL)
    .withData(URI_DIAL_NUMBER);

mobicontrol.android.startActivity(intent);

The code above can be split into the following three steps:

  1. Create an empty intent with mobicontrol.android.createIntent
  2. Augment the intent with desired properties using various intent builder functions like mobicontrol.android.Intent.withAction and mobicontrol.android.Intent.withComponent.
  3. Launch activity with mobicontrol.android.startActivity.

Intent Extras

When extra properties need to be specified, use mobicontrol.android.Bundle. Like Intent, Bundle provides builder functions which allow construction of an empty Bundle and populating it with specific properties.

The following script demonstrates how to open the Calendar app, adding a "John Doe Vacation" all-day event on October 20, 2025:

const startTime = new Date("2025-10-20T11:00:00").getTime();
var extras = mobicontrol.android.createBundle().
    withString("title", "John Doe Vacation").
    withBoolean("allDay", true);
var intent = mobicontrol.android.createIntent().
    withData("content://com.android.calendar/events").
    withAction("android.intent.action.INSERT").
    withExtras(extras);
mobicontrol.android.startActivity(intent);

Sending Broadcasts and Starting Services with Intents

Intents can also be used to send broadcasts with mobicontrol.android.sendBroadcast and to start services with mobicontrol.android.startService.

For example, OEMs often use broadcasts to implement device management features. The following script reboots a Philips device:

var intent = mobicontrol.android.createIntent()
    .withAction("php.intent.action.REBOOT");
mobicontrol.android.sendBroadcast(intent);

Starting an Activity for a Result

In some cases launched activities can send back a result, and our script might want to wait until such an activity is finished, so it can act on the result. This functionality can be achieved with mobicontrol.android.startActivityForResult. For example, the following script opens a file picker and lets the user select any image file. When the selection is done, the callback function is triggered with result.intent.data holding information about the picked file.

var intent = mobicontrol.android.createIntent()
    .withAction("android.intent.action.GET_CONTENT")
    .withType("image/*");
mobicontrol.android.startActivityForResult(intent, onFilePicked);

function onFilePicked(result) {
    if (result.isSuccessful) {
        mobicontrol.log.info("The file '" + result.intent.data + "' is picked!");
    }
}

One limitation of mobicontrol.android.startActivityForResult is that we can only wait for the result of one activity at a time. Any new call to startActivityForResult will cancel a previous call, even if the latter was done from a different script. This will cause the previous callback to fail with NEW_ACTIVITY_STARTED error code.

To guarantee against concurrency failures in the same script, time the calls to startActivityForResult so they don't overlap. The example below demonstrates this technique: the contact picker intent is launched only after the file picker intent is finished. Note that we set a timeout for the file picker app, limiting the wait time to 5 minutes.

var filePickerIntent = mobicontrol.android.createIntent()
    .withAction("android.intent.action.GET_CONTENT")
    .withType("image/*");
mobicontrol.android.startActivityForResult(filePickerIntent, onFilePicked, 5 * 60000);

function onFilePicked(result) {
    if (result.isSuccessful) {
        mobicontrol.log.info("The file is picked!");
    }
    selectContact()
}

function selectContact() {
    var intent = mobicontrol.android.createIntent()
        .withAction("android.intent.action.PICK")
        .withType("vnd.android.cursor.dir/contact");
    mobicontrol.android.startActivityForResult(intent, onContactPicked);
}

function onContactPicked(result) {
    if (result.isSuccessful) {
        mobicontrol.log.info("The contact is picked!");
    }
}

Sharing Information using Intents

Intents provide a secure mechanism for information sharing (including file sharing) between the agent and other apps, as the administrator controls every aspect of the shared information:

  1. The app to run
  2. The information to send
  3. The information to receive

The following paragraphs provide detailed examples on how to write a custom app which can communicate with the agent.

Sending Data to a Custom App

The following script sends the MobiControl device ID to a custom com.example.app app:

var extras = mobicontrol.android.createBundle()
    .withString("id", mobicontrol.device.id);
var intent = mobicontrol.android.createIntent()
    .withComponent("com.example.app/.MainActivity")
    .withExtras(extras);
mobicontrol.android.startActivity(intent);

In order to handle this intent, the custom app's MainActivity needs to implement the onCreate method:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val deviceId = getIntent().getStringExtra("id")
        // Do something with deviceId
    }
}

Multiple properties can be provided to a custom app either by multiple intent extras, or by a single extra containing a serialized JSON object:

var deviceInfo = {
    id:           mobicontrol.device.id,
    agentVersion: mobicontrol.agent.version,
    freeStorage:  mobicontrol.storage.internal.availableSpace
};
var extras = mobicontrol.android.createBundle()
    .withString("deviceInfo", JSON.stringify(deviceInfo));
var intent = mobicontrol.android.createIntent()
    .withComponent("com.example.app/.MainActivity")
    .withExtras(extras);
mobicontrol.android.startActivity(intent);

You can then use the Android JSON parser in the custom app's MainActivity to extract each property:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val jObject = JSONObject(getIntent().getStringExtra("deviceInfo"))
        val deviceId = jObject.getString("id")
        // Do something with deviceId
    }
}

The following example demonstrates how to send custom attributes to a custom app. The custom attributes can be used later to configure the app:

const APP_PREFIX = "EXAMPLE_";
var exampleConfiguration = {};
var customAttributeKeys = [ "location", "manager", "phone_number" ];
customAttributeKeys.forEach(attribute =>
    exampleConfiguration[attribute] = mobicontrol.agent.getCustomAttribute(APP_PREFIX + attribute));
var extras = mobicontrol.android.createBundle()
    .withString("configuration", JSON.stringify(exampleConfiguration));
var intent = mobicontrol.android.createIntent()
    .withComponent("com.example.app/.MainActivity")
    .withExtras(extras);
mobicontrol.android.startActivity(intent);

Here is how the custom app's MainActivity can read the value of the custom attribute corresponding to the "EXAMPLE_location" key:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val jObject = JSONObject(getIntent().getStringExtra("configuration"))
        val location = jObject.getString("location")
        // Configure the app with location
    }
}

Receiving Data from a Custom App

In order to receive information from a custom app, the script needs to call mobicontrol.android.startActivityForResult. The callback function can then process the data from the app. The following example shows how to populate custom data with itemsInStock and lastStockUpdateDate values from a custom app:

var intent = mobicontrol.android.createIntent()
    .withComponent("com.example.app/.MainActivity")
    .withAction("com.example.app.GET_STATUS");
mobicontrol.android.startActivityForResult(intent, receiveStatus);

function receiveStatus(result) {
    if (result.isSuccessful) {
        var jsonFile = new mobicontrol.io.File(mobicontrol.storage.internal.dataDirectory + '/status.json');
        var status = JSON.parse(jsonFile.readText());
        status.itemsInStock = result.intent.extras.getInteger("itemsInStock");
        status.lastStockUpdateDate = result.intent.extras.getString("lastStockUpdateDate");
        jsonFile.writeText(JSON.stringify(status));
    }
}

The corresponding custom app implementation will look like the following:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        if (getIntent().action == "com.example.app.GET_STATUS") {
            val result = Intent().apply {
                putExtra("itemsInStock", getItemsInStock())
                putExtra("lastStockUpdateDate", getLastStockUpdateDate())
            }
            setResult(RESULT_OK, result)
        }
        finish()
    }
}

Note that the example above assumes the custom data is configured with JSON build type and two custom definitions (itemsInStock and lastStockUpdateDate) of corresponding types.

File Sharing

Files can be securely shared between the agent and other apps using content: URIs. To get a content: URI from a file, use mobicontrol.android.createFileContentUri. The snippet below shows how to pass a file into the camera app. The app will store a captured image in that file:

var file = new mobicontrol.io.File(mobicontrol.storage.internal.dataDirectory + "/target.jpg");
var fileUri = mobicontrol.android.createFileContentUri(file)
var extras = mobicontrol.android.createBundle().withUri("output", fileUri);
var intent = mobicontrol.android.createIntent().
    withAction("android.media.action.IMAGE_CAPTURE").
    withExtras(extras);
mobicontrol.android.startActivity(intent);

Reading from a File Content URI

mobicontrol.io.File.writeContent can be used to read from a content: URI into a file. The following code copies an image file picked by the user into the agent's sandbox. Note that result.intent.data returned by the file picker is a content: URI, which is guaranteed by CATEGORY_OPENABLE. See Common intents documentation.

var intent = mobicontrol.android.createIntent()
    .withAction("android.intent.action.GET_CONTENT")
    .withCategories([ "android.intent.category.OPENABLE" ])
    .withType("image/*");
mobicontrol.android.startActivityForResult(intent, onFilePicked);

function onFilePicked(result) {
    if (result.isSuccessful) {
        var newFile = new mobicontrol.io.File(mobicontrol.storage.internal.dataDirectory + "/picked_image.jpg");
        newFile.writeContent(result.intent.data);
    }
}

Handling File Content URIs in a Custom App

Here is how a custom app can read from a content: URI and write its content into the file FILE_PATH:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        getIntent().data?.let { uri ->
            contentResolver.openInputStream(uri).use { input ->
                File(FILE_PATH).outputStream().use { output ->
                    input?.copyTo(output)
                }
            }
        }
    }
}

If your custom app needs to create a content: URI from a file FILE_PATH, and pass it to the agent, use the following technique:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        if (getIntent().action == "com.example.app.GET_FILE") {
            val uri = FileProvider.getUriForFile(this, packageName, File(FILE_PATH))
            val result = Intent().apply {
                data = uri
            }
            setResult(RESULT_OK, result)
        }
        finish()
    }
}

Note that in order for FILE_PATH to be sharable, it needs to reside in a sharable folder configured in the app's AndroidManifest.xml, as clarified here.