In this codelab, you'll learn how build applications for corporate-owned, single-use (COSU) devices for use cases such as digital signage, point of sale, and inventory management, by using task lock and device management APIs available in Android.

What you'll learn

What you'll need

How will you use this tutorial?

Read it through only Read it and complete the exercises

How would rate your experience with building Android apps?

Novice Intermediate Proficient

You can either download all the sample code to your computer,

Download Zip

or clone the GitHub repository from the command line:

$ git clone https://github.com/googlecodelabs/cosu.git

In the sample below, you'll build an app that takes a picture and locks it to the screen of an Android device. We will start by running the finished sample app. First, you will need to open the completed sample app in Android Studio.

  1. Launch Android Studio
  2. Choose the "open an existing Android Studio Project" option and select the cosu_codelab_complete directory from the sample code folder
  3. Enable USB debugging on your Android device.
  4. Plug in your Android device and click the Run button. You should see the COSU App appear after a few seconds.
  5. Open a command line terminal on your computer and enter this Android Debug Bridge command:
adb shell dpm set-device-owner com.google.codelabs.cosu/.DeviceAdminReceiver
  1. Tap the "Take Picture" Button and take a picture. Feel free to take a silly selfie. Once you have a picture, tap the "Start Lock Task Mode" button. Verify that the Home and Recents buttons disappear, and that you cannot leave the COSU app. If you have time, verify that the COSU app starts immediately when you reboot the device using the power button. Wait around 10 seconds after starting lock task mode but before rebooting the device.

Frequently Asked Questions

Now that you've seen COSU Mode in action, it's time to start building our own COSU app.

  1. Select the cosu_codelab_init directory from your sample code download (File > Open... >cosu_codelab_init).
  2. Click the Run button.

You should see the COSU Codelab app appear after a few seconds. Take a picture to see it appear on the screen in the app. The steps below will allow you to lock this app to the screen, displaying the picture you've taken.

We will now add task lock capabilities to our app. If you already know how to do this, and you would like to learn how to build an end-to-end solution, jump ahead to "Make the COSU app a Device Owner"

We will start by adding a button to MainActivity. This button will send an Intent to LockedActivity, where we will start lock task mode.

Add Lock Button to Layout

Start by adding a button to the activity_main.xml layout in the main_button_layout as below. Note that the new button is disabled by default. It will be enabled once the user has taken a picture:

activity_main.xml

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom"
        android:layout_marginRight="16dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:id="@+id/main_button_layout">

        <Button
            android:id="@+id/pic_button"
            android:text="@string/pic_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/start_lock_button"
            android:text="@string/lock_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:enabled="false"/>

    </LinearLayout>

Remember to add the lock_button string to strings.xml

strings.xml

<string name="lock_button">Start lock task mode</string>

Set Lock Button to start Lock Activity

Once you have added the button to the layout, you should update MainActivity.java to launch LockedActivity when this button is pressed.

  1. Add the button, and a DevicePolicyManager to MainActivity.java's member variables, and the String EXTRA_FILEPATH as a static member variable. The DevicePolicyManager will be used to verify whether your app is whitelisted to start lock task mode.

MainActivity.java

public class MainActivity extends Activity {

    private Button takePicButton;
    private Button lockTaskButton;
    private ImageView imageView;
    private String mCurrentPhotoPath;
    private int permissionCheck;
    public DevicePolicyManager mDevicePolicyManager;

    public static final String EXTRA_FILEPATH = 
            "com.google.codelabs.cosu.EXTRA_FILEPATH";

  1. Add the following code in the onCreate(...) method of MainActivity.java, just below takePicButton.setOnClickListener(...) This code initializes the device policy manager, and implements the button which will start lock task mode. When the button is tapped, as long as the app is whitelisted correctly, it will start the lock task mode activity.

MainActivity.java

mDevicePolicyManager = (DevicePolicyManager)
    getSystemService(Context.DEVICE_POLICY_SERVICE);

lockTaskButton = (Button) findViewById(R.id.start_lock_button);
lockTaskButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if ( mDevicePolicyManager.isLockTaskPermitted(
                getApplicationContext().getPackageName())) {
            Intent lockIntent = new Intent(getApplicationContext(),
                    LockedActivity.class);
            lockIntent.putExtra(EXTRA_FILEPATH, mCurrentPhotoPath);
            startActivity(lockIntent);
            finish();
        } else {
            Toast.makeText(getApplicationContext(),
                    R.string.not_lock_whitelisted,Toast.LENGTH_SHORT)
                    .show();
        }
    }
});
  1. Add the following in the setImageToView() method, just below imageView.setImageBitmap(...). This will enable the start lock task mode button once the user has taken a picture.

MainActivity.java

lockTaskButton.setEnabled(true);

Now we will build the Activity that will start lock task mode. Once it receives the intent from MainActivity, it will display the image the user has taken and then start lock task mode. It also saves the image in SharedPreferences so that if it is started after reboot, it can also display the image. This will be important in a later section.

Create Activity Layout and add locking functionality

  1. Replace the text in activity_locked.xml with the following:

activity_locked.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.google.codelabs.cosu.LockedActivity">

    <LinearLayout
        android:orientation="vertical"
        android:id="@+id/linearLayout"
        android:layout_above="@+id/lock_button_layout"
        android:layout_alignParentEnd="true"
        android:layout_alignParentTop="true"
        android:layout_alignStart="@+id/lock_button_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/lock_imageView"
            android:contentDescription="@string/lock_image_description"/>
    </LinearLayout>

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_gravity="bottom"
        android:layout_marginRight="16dp"
        android:layout_alignParentBottom="true"
        android:layout_alignParentStart="true"
        android:id="@+id/lock_button_layout">

        <Button
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/stop_lock_button"
            android:id="@+id/stop_lock_button"/>

    </LinearLayout>

</RelativeLayout>
  1. Add the following member variables to LockedActivity.java

LockedActivity.java

public class LockedActivity extends Activity {

    private ImageView imageView;
    private Button stopLockButton;
    private String mCurrentPhotoPath;
    private DevicePolicyManager mDevicePolicyManager;

    private static final String PREFS_FILE_NAME = "MyPrefsFile";
    private static final String PHOTO_PATH = "Photo Path";
  1. Replace the onCreate(...) method for LockedActivity.java with the code below:

LockedActivity.java

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_locked);

    mDevicePolicyManager = (DevicePolicyManager)
            getSystemService(Context.DEVICE_POLICY_SERVICE);

    // Setup stop lock task button
    stopLockButton = (Button) findViewById(R.id.stop_lock_button);
    stopLockButton.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            ActivityManager am = (ActivityManager) getSystemService(
                    Context.ACTIVITY_SERVICE);
            if (am.getLockTaskModeState() ==
                    ActivityManager.LOCK_TASK_MODE_LOCKED) {
                stopLockTask();
            }
            Intent intent = new Intent(
                    getApplicationContext(), MainActivity.class);
            startActivity(intent);
            finish();
        }
    });

    // Set image to View
    setImageToView();
}
  1. You also need to implement the LockedActivity.setImageToView() method used in onCreate(...). It is similar to the MainActivity.setImageToView() method, with the exception that if there is an image saved in SharedPreferences, it will load that image. This will be important later in the codelab. Add the code below to LockedActivity.

LockedActivity.java

private void setImageToView(){

    // Restore preferences
    SharedPreferences settings = getSharedPreferences(PREFS_FILE_NAME, 0);
    String savedPhotoPath = settings.getString(PHOTO_PATH, null);

    //Initialize the image view and display the picture if one exists
    imageView = (ImageView) findViewById(R.id.lock_imageView);
    Intent intent = getIntent();

    String passedPhotoPath = intent.getStringExtra(
            MainActivity.EXTRA_FILEPATH);
    if (passedPhotoPath != null) {
        mCurrentPhotoPath = passedPhotoPath;
    } else {
        mCurrentPhotoPath = savedPhotoPath;
    }

    if (mCurrentPhotoPath != null) {
        int targetH = imageView.getMaxHeight();
        int targetW = imageView.getMaxWidth();

        // Get the dimensions of the bitmap
        BitmapFactory.Options bmOptions = new BitmapFactory.Options();
        bmOptions.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(mCurrentPhotoPath, bmOptions);
        int photoH = bmOptions.outHeight;
        int photoW = bmOptions.outWidth;

        // Determine how much to scale down image
        int scaleFactor = Math.min(photoW/targetW, photoH/targetH);


        // Decode the image file into a Bitmap sized to fill the View
        bmOptions.inJustDecodeBounds = false;
        bmOptions.inSampleSize = scaleFactor;

        Bitmap imageBitmap = BitmapFactory.decodeFile(mCurrentPhotoPath,
                bmOptions);
        imageView.setImageBitmap(imageBitmap);
    }
}
  1. Add the following onStart() method to LockedActivity.java, which will lock LockedActivity to the user's screen.

LockedActivity.java

@Override
protected void onStart() {
    super.onStart();

    // Start lock task mode if its not already active
    if(mDevicePolicyManager.isLockTaskPermitted(this.getPackageName())){
        ActivityManager am = (ActivityManager) getSystemService(
                Context.ACTIVITY_SERVICE);
        if (am.getLockTaskModeState() ==
                ActivityManager.LOCK_TASK_MODE_NONE) {
            startLockTask();
        }
    }
}
  1. Lastly, save the photo being used in SharedPreferences by adding the following onStop() method to LockedActivity.java

LockedActivity.java

@Override
protected void onStop(){
    super.onStop();

    // Get editor object and make preference changes to save photo filepath
    SharedPreferences settings = getSharedPreferences(PREFS_FILE_NAME, 0);
    SharedPreferences.Editor editor = settings.edit();
    editor.putString(PHOTO_PATH, mCurrentPhotoPath);
    editor.commit();
}

Test your application. You'll need to use a sample Device Owner application to whitelist your app for lock task mode before testing.

  1. Download the latest available version of TestDPC
  2. Factory reset your Android device, and then navigate through the Setup Wizard.
  1. Enable USB debugging on your Android device.
  2. Install TestDPC on the device and set it as device owner using the following adb commands:
adb install path/to/TestDPC.apk
adb shell dpm set-device-owner com.afwsamples.testdpc/.DeviceAdminReceiver
  1. Plug in your Android device and click the Run button. You should see the COSU App home screen appear after a few seconds.
  2. Leave the COSU app and Launch TestDPC. Scroll down to "Manage lock task list" and tap it. Enable "CosuCodelab" and tap "OK"
  3. Navigate back to COSUCodelab. Tap the "Take Picture" Button and take a picture. Feel free to take a silly selfie. Once you have a picture, tap the "Start Lock Task Mode" button. Verify that the Home and Recents buttons disappear, and that you cannot leave the COSU app, until you tap the "Stop Lock Task Mode" button.

So far in this exercise, you've been building our COSU app with the assumption that it will be installed on a device that is managed by another Device Owner application. Follow the steps below to make your app a device owner application itself so that there is no need for another Device Owner to set the app into lock task mode.

Build the managed COSU app

  1. Select the cosu_codelab_managed directory from your sample code download (File > Open... >cosu_codelab_managed).
  2. Click the Run button.

Add a DeviceAdminReceiver

Start by adding a DeviceAdminReceiver. This will make your application eligible to become a device owner. DeviceAdminReceiver also allows your application to capture many events related to device management and respond accordingly.

  1. Add a receiver to the application element of the AndroidManifest.xml as follows:

AndroidManifest.xml

<receiver
    android:name=".DeviceAdminReceiver"
    android:description="@string/app_name"
    android:label="@string/app_name"
    android:permission="android.permission.BIND_DEVICE_ADMIN">
    <meta-data
        android:name="android.app.device_admin"
        android:resource="@xml/device_admin_receiver" />
    <intent-filter>
        <action android:name="android.intent.action.DEVICE_ADMIN_ENABLED"/>
        <action android:name="android.intent.action.PROFILE_PROVISIONING_COMPLETE"/>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
    </intent-filter>
</receiver>
  1. Add the xml file referenced in "android:resource="@xml/device_admin_receiver". Right click on the res folder in the Project Pane in Android Studio. Choose New > Android Resource Directory.

  1. Name the directory "xml" and set it to type "xml".

  1. Inside of the new folder create a new xml file. Right click on the xml folder in the Project Pane and choose New > XML Resource File.
  2. Name the file "device_admin_receiver.xml"
  3. Replace the text in device_admin_receiver.xml with the following:

device_admin_receiver.xml

<?xml version="1.0" encoding="utf-8"?>

<device-admin>
    <uses-policies>
        <disable-keyguard-features/>
    </uses-policies>
</device-admin>
  1. Add the DeviceAdminReceiver class to your application, under app > java > com.google.codelabs.cosu. You can do so by right clicking on the com.google.codelabs.cosu folder in the Project Pane and choosing New > Java Class.
  2. Replace the auto-generated code with the following:

DeviceAdminReceiver.java

package com.google.codelabs.cosu;

import android.content.ComponentName;
import android.content.Context;

// Handles events related to managed profiles and devices
public class DeviceAdminReceiver extends android.app.admin.DeviceAdminReceiver {
    private static final String TAG = "DeviceAdminReceiver";

    public static ComponentName getComponentName(Context context) {
        return new ComponentName(context.getApplicationContext(),  DeviceAdminReceiver.class);
    }
}

Updating LockedActivity.java

Next, update LockedActivity.java to take advantage of the new device management tools available.

Start by modifying the AndroidManifest.xml to make LockedActivity.java a launcher activity, and to set it as disabled by default. Later we will be making LockedActivity.java the default launcher once Lock Task Mode is started. This will ensure that if the device is rebooted, LockedActivity.java is launched immediately, preventing the user from leaving lock task mode by rebooting the device.

  1. Replace the LockedActivity object in the AndroidManifest.xml with the following:

AndroidManifest.xml

<activity
    android:name=".LockedActivity"
    android:label="@string/title_activity_locked"
    android:enabled="false">
    <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.HOME"/>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>
  1. Add a member variable to LockedActivity.java to hold the Activity's ComponentName

LockedActivity.java

public class LockedActivity extends Activity {

    private ComponentName mAdminComponentName;  
  1. Add the three methods below to LockedActivity. These methods take advantage of the new device management APIs, and allow you to:

The methods are also built so they are able to reverse this process when leaving lock task mode. Add the following code to the LockedActivity class:

LockedActivity.java

private void setDefaultCosuPolicies(boolean active){

    // Set user restrictions
    setUserRestriction(UserManager.DISALLOW_SAFE_BOOT, active);
    setUserRestriction(UserManager.DISALLOW_FACTORY_RESET, active);
    setUserRestriction(UserManager.DISALLOW_ADD_USER, active);
    setUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA, active);
    setUserRestriction(UserManager.DISALLOW_ADJUST_VOLUME, active);

    // Disable keyguard and status bar
    mDevicePolicyManager.setKeyguardDisabled(mAdminComponentName, active);
    mDevicePolicyManager.setStatusBarDisabled(mAdminComponentName, active);

    // Enable STAY_ON_WHILE_PLUGGED_IN
    enableStayOnWhilePluggedIn(active);

    // Set system update policy
    if (active){
        mDevicePolicyManager.setSystemUpdatePolicy(mAdminComponentName,
                SystemUpdatePolicy.createWindowedInstallPolicy(60, 120));
    } else {
        mDevicePolicyManager.setSystemUpdatePolicy(mAdminComponentName,
                null);
    }

    // set this Activity as a lock task package
    mDevicePolicyManager.setLockTaskPackages(mAdminComponentName,
            active ? new String[]{getPackageName()} : new String[]{});

    IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
    intentFilter.addCategory(Intent.CATEGORY_HOME);
    intentFilter.addCategory(Intent.CATEGORY_DEFAULT);

    if (active) {
        // set Cosu activity as home intent receiver so that it is started
        // on reboot
        mDevicePolicyManager.addPersistentPreferredActivity(
                mAdminComponentName, intentFilter, new ComponentName(
                       getPackageName(), LockedActivity.class.getName()));
    } else {
        mDevicePolicyManager.clearPackagePersistentPreferredActivities(
                mAdminComponentName, getPackageName());
    }
}

private void setUserRestriction(String restriction, boolean disallow){
    if (disallow) {
        mDevicePolicyManager.addUserRestriction(mAdminComponentName,
                restriction);
    } else {
        mDevicePolicyManager.clearUserRestriction(mAdminComponentName,
                restriction);
    }
}

private void enableStayOnWhilePluggedIn(boolean enabled){
    if (enabled) {
        mDevicePolicyManager.setGlobalSetting(
                mAdminComponentName,
                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
                Integer.toString(BatteryManager.BATTERY_PLUGGED_AC
                    | BatteryManager.BATTERY_PLUGGED_USB
                    | BatteryManager.BATTERY_PLUGGED_WIRELESS));
    } else {
        mDevicePolicyManager.setGlobalSetting(
                mAdminComponentName,
                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
                "0"
                );
    }
}
  1. Use these new methods to set device policy. Add the following in onCreate(..), under setImageToView():

LockedActivity.java

// Set Default COSU policy
mAdminComponentName = DeviceAdminReceiver.getComponentName(this);
mDevicePolicyManager = (DevicePolicyManager) getSystemService(
        Context.DEVICE_POLICY_SERVICE);
if(mDevicePolicyManager.isDeviceOwnerApp(getPackageName())){
    setDefaultCosuPolicies(true);
}
else {
    Toast.makeText(getApplicationContext(),
            R.string.not_device_owner,Toast.LENGTH_SHORT)
            .show();
}
  1. Replace the OnClickListener for our stopLockButton with the following. This ensures that when you stop lock task mode to return to the main activity, you reverse the policies you just set.

LockedActivity.java

stopLockButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        ActivityManager am = (ActivityManager) getSystemService(
                Context.ACTIVITY_SERVICE);

        if (am.getLockTaskModeState() ==
                    ActivityManager.LOCK_TASK_MODE_LOCKED) {
            stopLockTask();
        }
        setDefaultCosuPolicies(false);
        Intent intent = new Intent(
                getApplicationContext(), MainActivity.class);

        intent.putExtra(LOCK_ACTIVITY_KEY, FROM_LOCK_ACTIVITY);
        startActivity(intent);
        finish();
    }
});
  1. Finally, add the following static member variables under "private static final String PHOTO_PATH = "Photo Path"".

LockedActivity.java

public static final String LOCK_ACTIVITY_KEY = "lock_activity";
public static final int FROM_LOCK_ACTIVITY = 1;

You are now ready to take the final step: Modify MainActivity.java to enable and disable LockedActivity.java as needed.

Updating MainActivity.java

Start by adding two new member variables to our MainActivity under "private DevicePolicyManager mDevicePolicyManager;" as follows:

MainActivity.java

private PackageManager mPackageManager;
private ComponentName mAdminComponentName;
  1. Add the following under "mDevicePolicyManager = (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);" in onCreate(...). This retrieves the ComponentName of your DeviceAdminReceiver and a PackageManager so that you can enable and disable LockedActivity.

MainActivity.java

// Retrieve DeviceAdminReceiver ComponentName so we can make
// device management api calls later
mAdminComponentName = DeviceAdminReceiver.getComponentName(this);

// Retrieve Package Manager so that we can enable and
// disable LockedActivity
mPackageManager = this.getPackageManager();
  1. Replace the lockTaskButton OnClickListener with the following. This will enable LockedActivity before an intent is sent to it.

MainActivity.java

lockTaskButton.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if ( mDevicePolicyManager.isDeviceOwnerApp(
                getApplicationContext().getPackageName())) {
            Intent lockIntent = new Intent(getApplicationContext(),
                    LockedActivity.class);
            lockIntent.putExtra(EXTRA_FILEPATH,mCurrentPhotoPath);

            mPackageManager.setComponentEnabledSetting(
                    new ComponentName(getApplicationContext(),
                            LockedActivity.class),
                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                    PackageManager.DONT_KILL_APP);
            startActivity(lockIntent);
            finish();
        } else {
            Toast.makeText(getApplicationContext(),
                    R.string.not_lock_whitelisted,Toast.LENGTH_SHORT)
                    .show();
        }
    }
});
  1. Finally, add the following to the bottom of OnCreate(...), to ensure you disable LockedActivity as needed.

MainActivity.java

// Check to see if started by LockActivity and disable LockActivity if so
Intent intent = getIntent();

if(intent.getIntExtra(LockedActivity.LOCK_ACTIVITY_KEY,0) ==
        LockedActivity.FROM_LOCK_ACTIVITY){
    mDevicePolicyManager.clearPackagePersistentPreferredActivities(
            mAdminComponentName,getPackageName());
    mPackageManager.setComponentEnabledSetting(
            new ComponentName(getApplicationContext(), LockedActivity.class),
            PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
            PackageManager.DONT_KILL_APP);
}

Your app is now a Device owner all on its own and can set itself into lock task mode! You can verify this by Factory resetting your Android device, navigating through the Setup Wizard and taking the same steps you took in "Run the sample app".

Note: Do not add a Google Account when prompted. Skip that step

What we've covered

Next Steps

Learn More