In Android development, handling permissions is crucial for accessing sensitive user data or system features. While modern Android development often utilizes Kotlin and Jetpack Compose, many existing and legacy projects are still based on XML. Understanding how to effectively manage permissions in XML-based Android projects remains essential.
What are Android Permissions?
Android permissions are a security mechanism that controls access to sensitive user data and system features. Apps must request permission from the user before accessing certain resources, such as the camera, microphone, location, or contacts. This ensures user privacy and protects the integrity of the system.
Why are Permissions Important?
- User Privacy: Protects user data and prevents unauthorized access.
- Security: Ensures that apps only access resources they are authorized to use.
- User Trust: Transparency in permission requests builds trust with users.
Declaring Permissions in AndroidManifest.xml
In XML-based Android projects, permissions are declared in the AndroidManifest.xml
file. The <uses-permission>
tag is used to request specific permissions.
Step 1: Open AndroidManifest.xml
Locate the AndroidManifest.xml
file in your project’s app/manifests/
directory.
Step 2: Declare Permissions
Add the <uses-permission>
tag for each permission your app needs. For example, to request camera and location permissions, add the following:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.MyApp">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
Requesting Permissions at Runtime
Declaring permissions in the manifest is not enough. On Android 6.0 (API level 23) and higher, you must also request dangerous permissions at runtime.
Step 1: Check for Permission
Use ContextCompat.checkSelfPermission()
to check if the app already has the permission.
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
public class MainActivity extends AppCompatActivity {
private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;
private Button cameraButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cameraButton = findViewById(R.id.cameraButton);
cameraButton.setOnClickListener(v -> checkCameraPermission());
}
private void checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
requestCameraPermission();
} else {
// Permission is already granted
openCamera();
}
}
private void openCamera() {
Toast.makeText(this, "Opening Camera", Toast.LENGTH_SHORT).show();
// Implement camera opening logic here
}
}
Step 2: Request the Permission
If the permission is not granted, use ActivityCompat.requestPermissions()
to request it.
import android.Manifest;
import android.content.pm.PackageManager;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
public class MainActivity extends AppCompatActivity {
private static final int CAMERA_PERMISSION_REQUEST_CODE = 100;
private Button cameraButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
cameraButton = findViewById(R.id.cameraButton);
cameraButton.setOnClickListener(v -> checkCameraPermission());
}
private void checkCameraPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
requestCameraPermission();
} else {
// Permission is already granted
openCamera();
}
}
private void requestCameraPermission() {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
CAMERA_PERMISSION_REQUEST_CODE);
}
private void openCamera() {
Toast.makeText(this, "Opening Camera", Toast.LENGTH_SHORT).show();
// Implement camera opening logic here
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == CAMERA_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission was granted
openCamera();
} else {
// Permission was denied
Toast.makeText(this, "Camera permission denied", Toast.LENGTH_SHORT).show();
}
}
}
}
Step 3: Handle the Permission Result
Override onRequestPermissionsResult()
to handle the result of the permission request.
Best Practices for Handling Permissions
- Request Permissions Just-In-Time: Only request permissions when they are actually needed.
- Explain Why Permissions Are Needed: Provide a clear explanation of why your app requires specific permissions.
- Handle Permission Denials Gracefully: If the user denies a permission, explain the consequences and provide alternative ways to use the app without that permission.
- Test Permission Scenarios: Thoroughly test your app’s behavior under different permission scenarios, including granted, denied, and revoked permissions.
Example: Accessing Location
Here’s a complete example of how to access the device’s location using XML and Java, including declaring the necessary permissions, requesting them at runtime, and handling the results.
AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapp">
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
activity_main.xml:
<?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:padding="16dp">
<TextView
android:id="@+id/locationTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Location: Not Available"/>
<Button
android:id="@+id/getLocationButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/locationTextView"
android:text="Get Location"/>
</RelativeLayout>
MainActivity.java:
import android.Manifest;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends AppCompatActivity {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 200;
private TextView locationTextView;
private Button getLocationButton;
private LocationManager locationManager;
private LocationListener locationListener;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
locationTextView = findViewById(R.id.locationTextView);
getLocationButton = findViewById(R.id.getLocationButton);
locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
locationListener = new LocationListener() {
@Override
public void onLocationChanged(Location location) {
locationTextView.setText("Location: " + location.getLatitude() + ", " + location.getLongitude());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
Toast.makeText(MainActivity.this, "Please enable location service", Toast.LENGTH_SHORT).show();
}
};
getLocationButton.setOnClickListener(v -> checkLocationPermission());
}
private void checkLocationPermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
LOCATION_PERMISSION_REQUEST_CODE);
} else {
getLocation();
}
}
private void getLocation() {
try {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, locationListener);
} catch (SecurityException e) {
e.printStackTrace();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == LOCATION_PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
getLocation();
} else {
Toast.makeText(this, "Location permission denied", Toast.LENGTH_SHORT).show();
}
}
}
@Override
protected void onPause() {
super.onPause();
locationManager.removeUpdates(locationListener);
}
}
This example includes:
- Declaring the
ACCESS_FINE_LOCATION
permission inAndroidManifest.xml
. - Checking for the location permission at runtime using
ContextCompat.checkSelfPermission()
. - Requesting the permission using
ActivityCompat.requestPermissions()
. - Handling the permission result in
onRequestPermissionsResult()
. - Accessing the location using
LocationManager
andLocationListener
, but only after the permission has been granted. - Handling the case where the user denies the permission by displaying a toast message.
- Properly unregistering the location listener in
onPause()
to prevent memory leaks and unnecessary battery consumption.
Conclusion
Effectively managing permissions is crucial in Android development to ensure user privacy and security. By declaring permissions in AndroidManifest.xml
and requesting dangerous permissions at runtime, you can build robust and user-friendly applications. Always follow best practices to provide clear explanations to users and handle permission denials gracefully.