Newer Post

Creating Loading Buttons with SVG and Segment

Older Post

How X-Teamers Unleashed Their Potential in 2015

Android Runtime Permissions

Android operating system is based on Linux. Applications run in their own sandboxes separated from each other, and are strictly controlled by the Operating System. When you tap on an application icon in order to open it (assuming it isn't running in the background already), operating system starts new process, loads the app into memory and then runs it. An application that can't reach outside its own sandbox would be useless, but having no control over its actions would be dangerous for the user. Android uses System Permissions for managing what any given application can and can't do.

Fine-grained permissions are used by Android to restrict access to sensitive resources (user and system information -- mobile phone number, email addresses...), data (contacts, calendar events...) and hardware features (camera, telephony...). This tutorial will walk you through Android Permissions and show you how to request and use permission to send SMS messages on user's behalf.

Prerequisites

Please refer to base tutorial How To Get Started With Android Programming for explanation of basic Android concepts. This tutorial directly expands on that base project -- you can download starting source code here. The code has been written using Android Studio 1.5.1 with Target SDK set to 23.

This tutorial assumes you're running device or emulator with Android 6.0 or later (API 23+). This is necessary at least for second part and testing Runtime Permissions.

1. Prepare user interface

In activity_main.xml layout file, edit the main action button and header label to have meaningful texts.

From Palette drag and drop two EditTexts and align them however you like -- don't forget to assign IDs that you're going to use later on. You should end up with XML similar to this (feel free to copy and paste it -- pay attention to any warnings that might appear):

<?xml version="1.0" encoding="utf-8"?>  
<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"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="io.github.adamjodlowski.playground.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Permissions test project"
        android:id="@+id/textView" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Send SMS"
        android:id="@+id/button"
        android:layout_below="@+id/editTextMessage"
        android:layout_centerHorizontal="true" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:inputType="phone"
        android:ems="10"
        android:id="@+id/editTextPhone"
        android:layout_below="@+id/textView"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:hint="Phone number"
        android:isScrollContainer="false" />

    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editTextMessage"
        android:layout_below="@+id/editTextPhone"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignRight="@+id/editTextPhone"
        android:layout_alignEnd="@+id/editTextPhone"
        android:hint="Message text" />

</RelativeLayout>  

Notice two interesting properties that EditText can define:

  • android:inputType="phone" tells the framework, that it should provide special virtual keyboard suited for entering phone numbers1 -- you'll see how it works when you run the app
  • android:hint="Phone number" is a placeholder for text

If you don't see inputType property in Properties section when the Design tab is active and you have EditText highlighted, click Show expert properties button right above View properties.

The entire layout should look like this:

2. Add code for sending SMS

Go to MainActivity.java and alter the code that gets invoked when user clicks the button. We'd like that to send SMS message to the number provided by user.

package io.github.adamjodlowski.playground;

import android.support.v7.app.AppCompatActivity;  
import android.os.Bundle;  
import android.telephony.SmsManager;  
import android.view.View;  
import android.widget.Button;  
import android.widget.EditText;  
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    EditText editTextPhone;
    EditText editTextMessage;

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

        textView = (TextView) findViewById(R.id.textView);
        editTextPhone = (EditText) findViewById(R.id.editTextPhone);
        editTextMessage = (EditText) findViewById(R.id.editTextMessage);

        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SmsManager smsManager = SmsManager.getDefault();
                smsManager.sendTextMessage(
                        editTextPhone.getText().toString(),
                        null,
                        editTextMessage.getText().toString(),
                        null,
                        null);

                textView.setText("SMS sent");
            }
        });
    }
}

As you can see, to send SMS messages we use SmsManager class, and it's simple as that.

If you don't see virtual keyboard popping up when you focus the EditText widget, it's because you enabled hardware keyboard on your emulator -- what you type on your computer's keyboard is passed to emulator.

Run the application, provide any number and text you like and hit Send SMS button.

Aw, snap!

What you see is the worst nightmare of every Android developer -- our users just got informed, that our application crashed. Go back to the Android Studio, look at the Android Monitor section and logcat tab at the bottom. It gives you the reason of crash:

It looks like java.lang.SecurityException has occurred. You can even click the blue link to MainActivity.java:[XX] and go straight to the line in your source code that caused the exception. The message says that our application doesn't have android.permission.SEND_SMS permission. This is Android security in action.

3. Request permission for sending SMS

Let's fix that by going to AndroidManifest.xml and adding necessary request:

<?xml version="1.0" encoding="utf-8"?>  
<manifest xmlns:android="http://schemas.android.com/apk/res/android"  
    package="io.github.adamjodlowski.playground">

    <uses-permission android:name="android.permission.SEND_SMS" />

    <application>...</application>
</manifest>  

<uses-permission> says that our application needs this particular permission, which is defined by the framework (hence the android prefix).

Additionally, go to the /app/build.gradle file and temporarily set targetSdkVersion to 22 (anything below 23 will do). This is to show very important change that recently happened to Android Permission system.

Sync project, run it and try sending SMS. You might get an error upon sending the application to the emulator/device -- accept the suggestion and let it reinstall. It should not crash anymore. Of course you're not going to send an actual message to provided number -- this is all emulated.

See if it really works by sending message to the emulator itself. Its phone number is equal to port on which it listens for connections -- it's displayed in the title of emulator window and by default its 5554. Go to the messaging app on the emulator and see if it has been received.

Traditional permission model

What you've seen up to this point is traditional Android System Permission model. It works like this:

  • required permissions are declared in Android Manifest and application can assume, that if it is installed, it has all of them granted
  • if you don't request permissions needed for certain actions, Security Exception is raised and if it's not handled -- the application crashes
  • application install requires all of the permissions to be accepted by the user
  • the only way to revoke them is to uninstall application (all or nothing approach)
  • new permissions prevent apps from auto-updating, they need additional review by the user2

Android 6.0 Marshmallow (SDK level 23) introduced the concept of Runtime Permissions, changing many aspects of managing them in Android. The change is aimed to greatly improve user experience and it seems the Android Team did just that. On the other hand, developers need to incorporate those changes into their apps, which might not look that easy at first glance. Although this requires additional effort, it's highly encouraged.

New runtime permission model

Now, every permission falls into one of three protection levels:

  • normal -- applications get them by default at the time of installation (for instance Internet connectivity)
  • dangerous -- must be granted explicitly by the user
  • signature -- custom permissions based on signing certificates that should match in order to request them, typically used by applications by the same developer in order to exchange some private, proprietary data

In addition to protection levels, new concept of permission groups has been introduced:

  • few separate permissions (like we are used to) might be grouped together into one group (for instance sending and receiving SMS)
  • user implicitly grants us all permissions from the group, that requested permission belongs to (ask for sending SMS, you get as well permission to receive them)
  • if we ask for more than one permission in a group, they are granted/denied in bulk, based on user's decision regarding the group

Don't assume that if you possess a permission from particular group, you also have all the other permissions from that group -- always ask for specific ones, because current behavior might change in the future.

What all of that means for your users, is they can just install any given app from the Play Store and start using it right away (no questions during installation asked). Permissions falling into normal protection level are granted by default, and those should be enough for typical app to function, at least at the beginning. The process of asking for permissions is pushed back to the last possible moment -- the moment that user wants to perform an action requiring dangerous permission, for example -- action of taking a photo would be briefly interrupted by a system dialog with permission request. Finally, user can grant or revoke3 any permission at any time from system settings, without uninstalling app.

This new approach requires us to take extra care and prepare for few scenarios, making sure that we:

  • declare required permissions in Android Manifest as usual
  • check on-the-fly, that we are actually granted permissions required to perform given actions
  • disable certain UI controls or indicate in other ways that application could perform those actions, if it had those permissions
  • are prepared for permission revocation at any given time4
  • make a clear distinction between permissions vital to your application and optional ones
  • show disclaimers and reasoning behind requests -- up-front or in context as they are needed

4. Asking for Runtime Permissions

1. Opt-in for Runtime Permissions

Revert the change we've made earlier -- Runtime Permissions that we're going to demonstrate are only available for applications built and run on platforms in version 23 and later. Make sure the following properties are set in /app/build.gradle:

compileSdkVersion 23  
targetSdkVersion 23  

2. Make sure to check if permissions have been granted and ask for them if necessary

Every time user opens our app, we need to make sure we have required permission to send SMS. To do that, you'll need to add a little bit more code to the MainActivity.java:

package io.github.adamjodlowski.playground;

import android.Manifest;  
import android.content.pm.PackageManager;  
import android.support.v4.app.ActivityCompat;  
import android.support.v7.app.AppCompatActivity;  
import android.os.Bundle;  
import android.telephony.SmsManager;  
import android.util.Log;  
import android.view.View;  
import android.widget.Button;  
import android.widget.EditText;  
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    TextView textView;
    EditText editTextPhone;
    EditText editTextMessage;
    Button button;

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

        textView = (TextView) findViewById(R.id.textView);
        editTextPhone = (EditText) findViewById(R.id.editTextPhone);
        editTextMessage = (EditText) findViewById(R.id.editTextMessage);

        button = (Button) findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                SmsManager smsManager = SmsManager.getDefault();
                smsManager.sendTextMessage(
                        editTextPhone.getText().toString(),
                        null,
                        editTextMessage.getText().toString(),
                        null,
                        null);

                textView.setText("SMS sent");
            }
        });

        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.SEND_SMS) != PackageManager.PERMISSION_GRANTED) {
            Log.d("PLAYGROUND", "Permission is not granted, requesting");
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.SEND_SMS}, 123);
            button.setEnabled(false);
        } else {
            Log.d("PLAYGROUND", "Permission is granted");
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        if (requestCode == 123) {
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Log.d("PLAYGROUND", "Permission has been granted");
                textView.setText("You can send SMS!");
                button.setEnabled(true);
            } else {
                Log.d("PLAYGROUND", "Permission has been denied or request cancelled");
                textView.setText("You can not send SMS!");
                button.setEnabled(false);
            }
        }
    }
}

As you can see, we've made few changes to the code, now:

  • Button for sending SMS and status TextView are updated dynamically, considering the permission in question
  • at the end of onCreate() method we find out if permission for sending SMS is granted -- if not, we request it using ActivityCompat's method (123 is just an ID for the request, no magic number)
  • result of the request is passed asynchronously to separate callback method onRequestPermissionsResult -- there is the logic for handling the response
  • you can request many permissions at once, passing an array to ActivityCompat#requestPermissions() method
  • you can check approval/denial on single permission level -- notice that response contains requested permissions and separate results for them

You are required to check permissions status every time you might need them, don't assume that you got permission once and it's available forever.

Log.d() method invokes system logger and is often used for simple debugging. You can find its output in the Android Monitor section, logcat tab in Android Studio.

3. Run the app and experiment with Runtime Permissions

First, uninstall application form your emulator/device, because Android OS might have saved information about permissions granted for previous version of the app. Then run it and look into the logcat to see what's happening.

To experiment with it, deny the request, change app permissions using global system settings, kill (longpress on last apps system button) and rerun it again to see different outcomes.

Additional considerations

You might have noticed that after first denial user is given the option not to bug them anymore with our request. This is virtually our last chance to explain them reasoning behind the request. In order to do that gracefully, API contains method that returns true, if we previously requested particular permission, but user denied the request. It's a good idea to add code for explaining why we really need that particular permission or warn user what she or he is going to miss if they deny the request. Here is the sample code:

if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.SEND_SMS)) {  
    // the request has been denied previously, show explanation why you need this, then request permission again
} else {
    // ask without explanations
}

Please note, this method returns false if user already checked Never ask again checkbox.

Summary

In this tutorial you learned classic and modern approach to managing Android Permissions. Few points to keep in mind:

  • apps compiled with Target SDK < 23 are treated as usual
  • many basic permissions are granted by default
  • you still need to declare them in the Manifest
  • you must make sure you have them every time you need them (Activity#onCreate() is sufficient)

Source code for this tutorial can be found on GitHub.

by Adam Jodłowski

  1. See what other kinds of input types are available -- highlight the inputType property and hit F1.

  2. That leads to developers requesting more permissions that they really need, in order to prevent already installed applications from bugging user again when new app features arrive. This is bad practice.

  3. This doesn't mean legacy apps are going to crash like crazy after you revoke permissions, throwing Exceptions all over the place. What really happens is, on Android 23+, API calls that had been restricted would return empty data sets or default values, as if there were no data available.

  4. When user revokes permissions, our application's process gets killed.

We'll help you unleash.

Join the 20,000 developers who subscribe to our newsletter.

Scale your
Development team

We help you execute projects by providing trusted developers who can join your team and immediately start delivering high-quality code.

Hire Developers
code, android