vendredi 6 février 2015

Android 5 Screen Pinning

One of the key feature of Android Lollipop “For Workplace And Education” is a new Screen mode called “Screen Pinning”. This new mode offers a true way to create Kiosk applications within the android Lollipop platform.

In the next chapters, we’ll see in more details how to use them in Android 5 (API 21).

Pin an app : the manual way

Pinning an application is not enabled by default. You’ll have to enable it by going to : Settings > Security > Advanced > Screen Pinning : On. These step only need to be done once.

You can pin an app by :

  • Launch your app
  • touch on the Overview Button (enter image description here)
  • select your app and touch the pin icon enter image description here at the bottom of the app.

A confirmation message will appear. You need to confirm this dialog message to start the pinning mode :

enter image description here

To exit the screen pinning mode :

  • Touch and hold Back (enter image description here) and Overview(enter image description here) buttons at the same time for 2-3 seconds.

7 Things to know about the manual pinning mode

  • Home (enter image description here) and Overview (enter image description here) buttons are disabled, but every time you touch one of them, you’ll get a message to help you exit the pinning mode.
  • If a Screen Lock (Pattern, Pin or Password) is activated, you can force the user to unlock the Screen before unpinning. The confirmation dialog shown when starting a app pinning will provide an extra option “Ask for unlock pattern before unpinning”.
  • If a Screen lock is activated and this option is set, you’ll go to this Screen Lock and need to unlock your screen to go Home. Once the Screen Lock is displayed, You have no way to go back your app.
  • No notifications will be shown. Even your own app’s notifications won’t show.
  • From a pinned app, you cannot start a secondary app, unless this one has the same shared user ID (which means that the sharedUserIdis set in the AndroidManifest.xml and that second application is packaged with the same certificate). Other apps’ Activities won’t be allowed to be started and doing so (by using Context.startActivity()) will simply be ignored.
  • the Status bar is invisible. No notification, time, battery charge or other status information is displayed.
  • It’s always possible to turn the device off, or to turn the volume.

This fact that the user can unpin the application and go back to the Screen Lock is annoying, we’ll see, in the next chapter, how we programmatically pin an to go even further in pinning apps.

Pin an app : the programmatic way

Screen pinning is referred as “Lock Task mode” in the documentation API.
This mode is mainly referred in 3 public API available in Activity through startLockTask()/startLockTask() and DevicePolicyManager through setLockTaskPackages() . We’ll see also see other API useful to subscribe to Lock Task’s events, and to get current’s activity state.

There are actually two Lock Task modes, depending on whether you are “authorized” by a Device Owner App or not. These two modes also have impacts on the behavior of the pinning.

Note :Authorized” is clearly not the best word of choice for this because its sense is not clear in this context. This is however used in the API’s documentation to refer to the different level of restriction a user will have when entering in the Screen Pinning mode. Both modes can indeed pin the app, but the authorized one will put the user in a more restricted experience as we’ll see later.

Before going further, some things to know about the API :

  • It’s only possible for an app to pin itself. There is no public API to tell another app to pin (except for system-apps, which we’ll discuss later).
  • Because of this last restriction, you can only pin in authorized mode an app that you own.
  • Rebooting the device will always bring you back to the original mode : no application pinned.
  • No special permission is required.

Un-authorized Task Lock mode

Any application’s Activity can programmatically be pinned by using Activity.startLockTask().

A use-case for this is to pin the screen when a user touch a button. You could go further and only call this method if the app is not already pinned. You can check its status by using ActivityManager.isInLockTaskMode(). For example :

// Main Activity
protected void onCreate(Bundle savedInstanceState) {
    // ...
    am = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    lockBtn = (Button) findViewById(R.id.btn_lock);
    lockBtn.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
            pin();
        }
    });
    // ...
}

void pin() {
    if(!am.isInLockTaskMode()) {
        startLockTask();
    }
}

Exiting the Pinning mode is reversely done by using Activity.stopLockTask() :

void unlockClicked() {
    stopLockTask();
} 

Because this mode you can be used freely by any developer, a confirmation message will appear to the end user to confirm entering in this mode. Additionnaly, this mode is not totally restricted compared to the “Authorized Task Lock mode”. Here are some points to know when using pinning in unauthorized mode :

  • Just after the startLockTask() is executed, the user will get the confirmation dialog “Use Screen Pinning ?”.
  • You can manually unlock the app at anytime : Touch and hold Back (enter image description here) and Overview(enter image description here) buttons at the same time for 2-3 seconds.
  • Unlock (manually or programmatically) will bring you back to the Screen lock if a Keyguard is set and the option “Ask for unlock pattern before unpinning” has been set.

Authorized Task Lock mode

Only a Device Owner app can specify which apps are authorized to pin. So this mode is typically useful for “Organizations” who want to restrict their user to only one app without no way to manually exit the app.
DevicePolicyManager.setLockTaskPackages() is used to specify which apps (though their package names) are pinnable. In other words, it specify which apps are authorized to be pinned in a more restrictive way.
This method has to be called from a Device Owner application (Setting an application as a Device Owner App is beyond the scope of this article, and is probably be an idea for a future article).

If you try to use from an application that is not set as a device or profile owner, you’ll get a SecurityException.

setLockTaskPackages() is used with a ComponentName referring to your DeviceAdminReceiver’s implementation. You also pass it an Array of authorized packages as a last parameter :

 DevicePolicyManager mDPM = (DevicePolicyManager) this.getSystemService(DEVICE_POLICY_SERVICE);
 ComponentName mDeviceAdminRcvr = new ComponentName(this, MyDeviceAdminReceiver.class);

  mDPM.setLockTaskPackages(mDeviceAdminRcvr, {"com.android.test.authorizedpinningapp"});

Once your application is authorized in your Device Admin App, you can then call the Activity.startLockTask() from the app you want to pin (as presented in earlier chapter).

6 Things to know about the Authorized Pinning mode :

  • Only the Back(enter image description here) button remains visible in the navigation bar. Home(enter image description here) and Overview(enter image description here) buttons are not visible
  • … and because of this, it’s impossible to unpin the application manually
  • You’ll have to provide a way to programmatically unpin your app. (otherwise, you’ll have no way to go back Home, unless you reboot the device)
  • You can enter the pin mode without user confirmation.
  • An app which is authorized though the use of setLockTaskPackages can still be pinned manually by going to Overview (enter image description here) and click on the Pin icon. In this case, the behavior is the same as a manual pinning (confirmation message, etc.).
  • If the Back button is going back to another activity, a Toast appears “Unpinning is not allowed by your Organization”

Task Lock events

DeviceAdminReceiver’s onLockTaskModeEntering and onLockTaskExiting methods can be used in your Device Owner App to subscribe to events relative to entering and exiting “Lock Task mode”.
All pinning events are raised the same way, no matter if they are authorized or not, manually or programmatically pinned.

Here’s a short example :

public class MyDeviceAdminReceiver extends DeviceAdminReceiver {

    public void onLockTaskModeEntering(Context context, Intent intent, String pkg) {
Toast.makeText(context, "Lock task mode entered", LENGTH_LONG).show();
      // ....
    }

    public void onLockTaskModeExiting(Context context, Intent intent) {
        Toast.makeText(context, "Lock task mode exited", LENGTH_LONG).show();
       // ...
    }
}

The second parameter of onLockTaskModeEntering and onLockTaskModeExiting is relative to the sender’s intent. You can retrieve two useful information from this intent :

  • The corresponding Action by calling intent.getAction(). Depending on which method your are, you’ll get DeviceAdminReceiver.ACTION_LOCK_TASK_ENTERING or DeviceAdminReceiver.ACTION_LOCK_TASK_EXITING value.
  • The corresponding App Package by calling … no, no it’s a trap ! getPackage() will return null here. To get the package of the app whose Lock Task has changed, you’ll have to use intent.getStringExtra(DeviceAdminReceiver.EXTRA_LOCK_TASK_PACKAGE).

Going further :

The source code of this article is available on my Github’s repository.
The announcement on Android’s developers site.
Android Source Code on Github.
Android API : DevicePolicyManager, Activity, DeviceAdminReceiver, ActivityManager

20 commentaires:

  1. I tried integrating the above code, but I am not getting the call backs for onLockTaskModeEntering, Exiting in DeviceAdmin receiver. Also can I make any package as a Authorised Admin App? It always trows the exception when I do setLockTaskPackages saying you not a owned profile. Sooner response is appreciated.

    Even I pulled your code from Git but failing with the same exception.

    RépondreSupprimer
    Réponses
    1. I know for sure that on some device, this doesn't work. I made my tests on a Google Nexus 9 and it worked fine. On some devices, depending on vendor's implementations, it may not work and provisioning may fail.

      Supprimer
    2. Its on Nexus 5 stock device on 5.0OS. Even tried on 6.0 as well same issue.

      Supprimer
  2. This is nice, but there is a way to handle a crash in the app, it will just close the pinned app, shouldnt it be restarted?

    RépondreSupprimer
    Réponses
    1. No idea. If you test it, don't hesitate to tell me what happened, I could update the article with this info. thx

      Supprimer
  3. Thanks

    If I have a simple DeviceOwnwer app that calls setLockTaskPackages for my other more complicated home app does the DeviceOwnwer app have to call setLockTaskPackages on every boot.
    Also how to I make sure DeviceOwnwer calls setLockTaskPackages before My pinned app starts ?

    Thanks

    RépondreSupprimer
    Réponses
    1. the setLockTaskPackages() has only to be called once. In your case, you should consider calling it inside your MyDeviceAdminReceiver.onEnabled() method.

      Basically, it's done during the provisioning of the device, so at this step, the device is not yet started completely and then no other app can be started.

      Supprimer
    2. Thanks. Seems to work fine.
      My only concern about this is how long will it take OEM to start abusing this and shipping with one of their apps as device owner. Am I write in thinking only root could get rid of it. I assume a device reset would go back to their initial setup.

      Thanks for the help.

      Supprimer
  4. Hello Florent,

    Thanks a lot for your work. The article is very clear and pedagogic compare to the other sites.

    But I have a problem to test the DeviceOwnerApp. You wrote "This app as to be set as a Device Admin App to work correctly." I am testing on a ZTE Blade L3, (android without any vendor covers).

    When I press "Set Authorized Apps", it crash.
    I have the following message in my logcat : java.lang.SecurityException: No active admin ComponentInfo{com.android.test.deviceownerapp/com.android.test.deviceownerapp.MyDeviceAdminReceiver}

    Do I need to modify or add something to your code to make the app a Device Admin App ?

    Thanks for your help,

    Olivier

    RépondreSupprimer
    Réponses
    1. I could fixed my problem thanks to your article :
      http://florent-dupont.blogspot.fr/2015/01/android-shell-command-dpm-device-policy.html

      Supprimer
  5. Hi Florent,

    You mentioned "There is no public API to tell another app to pin (except for system-apps, which we’ll discuss later)."

    Can you elaborate on how to pin a system app?

    Thanks a lot.

    RépondreSupprimer
    Réponses
    1. By that, I mean only system apps can pin other apps.
      If your app is not a system app, you can only app your own app, not another one.

      Supprimer
  6. Great article! But I'm stuck...What about the case that the device has hardware(physical) navigation bar(home, back, overview). I use startLockTask() programamtically and everything works fine, but the user is able to exit the kiosk mode by manual way(off in settings). Any thoughts?

    RépondreSupprimer
    Réponses
    1. I understand. Before planning to make a device available publicly (for instance, on public areas like bank agencies, train station halls, etc..), you should study what device fits the best for your need.
      Another approach is to consider which totem will be used to protect your devide (this site provides many types of totem..http://www.elockstore.com/73-borne-totem-tablette-maclocks-compulocks it's in french but you should find equivalent providers in many countries). Some of these totems may fits your need by hiding the physical buttons.

      Supprimer
  7. Hi Florent. Thank for your posts! I use authorized Screen pinning in my app and on LG G4s (Android 6.0) works perfect. I start first activity, call method startLockTask and navigate to second activity in my app. All activities always opens in my app. But on Alcatel Idol 3 (Android 5.0.1) I can't go to another activity after calling startLockTask. I don't understand what's wrong and where mistake.

    RépondreSupprimer
    Réponses
    1. I found solution)
      The problem was in launchMode of element in manifest.

      Supprimer
  8. Hello Florent,

    Is there a way to set status bar to visible when an app enters "lock task" mode?

    Regards,
    Novak

    RépondreSupprimer
  9. I am expecting more interesting topics from you. And this was nice content and definitely it will be useful for many people.
    ios App Development Company
    Mobile App Development Company
    Best Mobile App Development Company

    RépondreSupprimer