The 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 is not running in the background already), the operating system starts a new process, loads the app into memory and then runs it. An application that cannot 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 cannot 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 the user's behalf.
Please refer to the base tutorial How To Get Started With Android Programming for an explanation of basic Android concepts. This tutorial directly expands on that base project — you can download the 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 are running device or emulator with Android 6.0 or later (API 23+). This is necessary at least for the second part and testing Runtime Permissions.
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 — do not forget to assign IDs that you are 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 numbers[1] — you will see how it works when you run the appandroid:hint="Phone number"
is a placeholder for textIf you do not see the
inputType
property in the Properties section when the Design tab is active and you have EditText highlighted, click the Show expert properties button right above View properties.
The entire layout should look like this:
Go to MainActivity.java
and alter the code that gets invoked when a user clicks the button. We would like that to send SMS message to the number provided by the 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 the SmsManager
class, and it is simple as that.
If you do not see the virtual keyboard popping up when you focus the EditText widget, it is because you enabled the hardware keyboard on your emulator — what you type on your computer's keyboard is passed to the emulator.
Run the application, provide any number and text you like, and hit the 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 the logcat tab at the bottom. It gives you the reason for the 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 does not have the android.permission.SEND_SMS
permission. This is Android security in action.
Let us fix that by going to AndroidManifest.xml
and adding the 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 settargetSdkVersion
to22
(anything below 23 will do). This is to show a very important change that recently happened to the Android Permission system.
Sync the project, run it and try sending an SMS. You might get an error upon sending in the application on the emulator/device — accept the suggestion and let it reinstall. It should not crash anymore. Of course, you are not going to send an actual message to provided number — this is all emulated.
See if it really works by sending a message to the emulator itself. Its phone number is equal to the port on which it listens for connections — it is displayed in the title of emulator window, and by default, it is 5554. Go to the messaging app on the emulator and see if it has been received.
What you have seen up to this point is a traditional Android System Permission model. It works like this:
Android 6.0 Marshmallow (SDK level 23) introduced the concept of Runtime Permissions, changing many aspects of managing them in Android. The move aims to improve user experience considerably, 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 is highly encouraged.
Now, every permission falls into one of three protection levels:
In addition to protection levels, the new concept of permission groups has been introduced:
Don't assume that if you possess a permission from a 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 under the normal protection level are granted by default, and those should be enough for the typical app to function, at least at the beginning. The process of asking for permissions is pushed back to the last possible moment — the time when the user wants to perform an action requiring the dangerous permission, for example — the action of taking a photo would be briefly interrupted by a system dialog with a permission request. Finally, the user can grant or revoke[3] 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:
Revert the change we have made earlier — the Runtime Permissions that we are 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
Every time the user opens our app, we need to make sure we have the permission required to send SMS. To do that, you will 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 have made few changes to the code, now:
onCreate()
method we find out if permission for sending an SMS is granted — if not, we request it using ActivityCompat
's method (123
is just an ID for the request, no magic number) onRequestPermissionsResult
-- there is the logic for handling the responseActivityCompat#requestPermissions()
methodYou are required to check permissions status every time you might need them, do not assume that you got permission once and it is available forever.
Log.d()
method invokes the system logger and is often used for simple debugging. You can find its output in the Android Monitor section, logcat tab in Android Studio.
First, uninstall the application from your emulator/device, because Android OS might have saved information about permissions granted for the 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 to see different outcomes.
You might have noticed that after the first denial, the user is given the option not to bug them anymore with our request. This is virtually our last chance to explain to them the reasoning behind the request. In order to do that gracefully, the API contains a method that returns true
, if we previously requested a particular permission, but the user denied the request. It is a good idea to add code for explaining why we really need that particular permission or warn the 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 the user has already checked the Never ask again checkbox.
In this tutorial, you learned about the classic and modern approaches to managing Android Permissions. A few points to keep in mind:
Activity#onCreate()
is sufficient)The source code for this tutorial can be found on GitHub.
See what other kinds of input types are available — highlight the inputType property and hit F1. ↩︎
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. ↩︎
This does not 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. ↩︎
When a user revokes permissions, our application's process gets killed. ↩︎