> For the complete documentation index, see [llms.txt](https://docs.sentiance.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sentiance.com/important-topics/app-permissions.md).

# App Permissions

Learn how to request the permissions the Sentiance SDK needs at runtime, and what happens to detections when one is missing.

The Sentiance SDK requires a set of permissions to function and perform detections correctly. Some permissions are mandatory, while others are optional or automatically granted by the system.

Declaring a permission is not the same as holding it. The SDK does not request permissions on your behalf, so your app must ask the user for them at runtime. For the manifest and Info.plist declarations, and for the full list of permissions, check out the requirements page for [Android](/getting-started/sdk-integration/1.-requirements/android.md) and [iOS](/getting-started/sdk-integration/1.-requirements/ios.md).

***

## Overview

<table><thead><tr><th width="200">Permission</th><th width="120">Platform</th><th width="110">Required</th><th>Without it</th></tr></thead><tbody><tr><td><strong>Background location</strong><br>"Always" on iOS, "Allow all the time" on Android</td><td>iOS, Android</td><td>Mandatory</td><td>Detections will not run. The detection status stays "enabled but blocked".</td></tr><tr><td><strong>Precise location</strong></td><td>iOS, Android</td><td>Mandatory</td><td>Detections will not run. Approximate location is not sufficient.</td></tr><tr><td><strong>Motion &#x26; Fitness</strong> / <strong>Activity Recognition</strong></td><td>iOS, Android</td><td>Optional</td><td>Detections run, with reduced quality.</td></tr><tr><td><strong>Notifications</strong></td><td>Android 13+</td><td>Optional</td><td>Detections run, but the foreground service notification is not shown.</td></tr></tbody></table>

***

## Requesting Location Access

Both platforms grant background location in two stages. Your app must request foreground access first, and only then request background access.

{% hint style="danger" %}
**On Android, requesting both at once does not work**

If you request background location together with foreground location, Android silently drops the background part. The dialog appears, the user accepts, and your app is left with foreground access only, with no error to indicate it. Detections will not run.
{% endhint %}

{% tabs %}
{% tab title="iOS" %}
Call <mark style="color:$success;">**`requestWhenInUseAuthorization()`**</mark> first, and call <mark style="color:$success;">**`requestAlwaysAuthorization()`**</mark> only once the "When In Use" authorization has been granted. iOS then asks the user whether to allow location access always.

```swift
import CoreLocation

class PermissionHandler: NSObject, CLLocationManagerDelegate {
    private let locationManager = CLLocationManager()

    func requestLocationPermission() {
        locationManager.delegate = self
        locationManager.requestWhenInUseAuthorization()
    }

    func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) {
        switch manager.authorizationStatus {
        case .authorizedWhenInUse:
            // Ask to extend the authorization to the background
            manager.requestAlwaysAuthorization()
        case .authorizedAlways:
            // Precise Location must also be enabled
            if manager.accuracyAuthorization == .fullAccuracy {
                NSLog("Location permission granted")
            } else {
                NSLog("Precise Location is off, detections will not run")
            }
        default:
            NSLog("Location permission not granted")
        }
    }
}
```

Handle the outcome in <mark style="color:$success;">**`locationManagerDidChangeAuthorization(_:)`**</mark> rather than at the call site, since the user can change the authorization at any time from the Settings app.

{% hint style="warning" %}
**Do not request "Always" directly**

Calling `requestAlwaysAuthorization()` before "When In Use" has been granted gives your app a provisional "Always" authorization. When iOS turns it into a permanent one is unpredictable.
{% endhint %}
{% endtab %}

{% tab title="Android" %}
From Android 11 (API level 30), <mark style="color:$success;">**`ACCESS_BACKGROUND_LOCATION`**</mark> cannot be requested in the same call as foreground location. Request <mark style="color:$success;">**`ACCESS_FINE_LOCATION`**</mark> first, and request background access in a separate call once it has been granted.

On Android 11 and above, the system does not show a dialog for the second request. It opens your app's location settings screen instead, where the user selects **Allow all the time**. This is enforced by Android.

```kotlin
import android.Manifest
import android.os.Build
import androidx.activity.result.contract.ActivityResultContracts

private val foregroundLocation = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    if (isGranted && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        // Only now can background access be requested
        backgroundLocation.launch(Manifest.permission.ACCESS_BACKGROUND_LOCATION)
    } else {
        Log.e(TAG, "Location permission not granted")
    }
}

private val backgroundLocation = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    // On Android 11+, the user returns here from the settings screen
    if (isGranted) {
        Log.d(TAG, "Location permission granted")
    } else {
        Log.e(TAG, "Background location not granted, detections will not run")
    }
}

fun requestLocationPermission() {
    foregroundLocation.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
```

{% endtab %}

{% tab title="React Native" %}
Request the two Android permissions in sequence. On iOS, location permissions are requested through the native layer, so follow the iOS instructions above.

```typescript
import { PermissionsAndroid, Platform } from "react-native";

async function requestLocationPermission(): Promise<boolean> {
  const foreground = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION
  );

  if (foreground !== PermissionsAndroid.RESULTS.GRANTED) {
    console.log("Location permission not granted");
    return false;
  }

  if (Platform.Version >= 29) {
    // A separate request, and only after foreground access is granted
    const background = await PermissionsAndroid.request(
      PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION
    );
    return background === PermissionsAndroid.RESULTS.GRANTED;
  }

  return true;
}
```

{% endtab %}

{% tab title="Flutter" %}
Use a permission plugin such as [permission\_handler](https://pub.dev/packages/permission_handler), and request the two permissions in sequence.

```dart
import 'package:permission_handler/permission_handler.dart';

Future<bool> requestLocationPermission() async {
  final whenInUse = await Permission.locationWhenInUse.request();
  if (!whenInUse.isGranted) {
    print('Location permission not granted');
    return false;
  }

  // A separate request, and only after foreground access is granted
  final always = await Permission.locationAlways.request();
  return always.isGranted;
}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
**Prepare the user before the second request**

On both platforms the second request asks the user to extend access to the background, and on Android it takes the user out of your app. A short explanation of why the SDK needs location in the background, shown before the request, improves the chance that the user grants it.

Android also limits how often a permission dialog may be shown. After two denials, the system treats the permission as permanently denied and returns immediately without prompting, leaving your app's settings screen as the only route.
{% endhint %}

### Precise Location

A user can grant background access while leaving precise location off, in which case detections will not run.

On iOS, check that <mark style="color:$success;">**`accuracyAuthorization`**</mark> is <mark style="color:$success;">**`.fullAccuracy`**</mark>. A temporary grant through `requestTemporaryFullAccuracyAuthorization(withPurposeKey:)` is not sufficient for the SDK, since the SDK requires precise location in the background. The user must enable **Precise Location** for your app in the Settings app.

On Android, requesting `ACCESS_FINE_LOCATION` gives the user the choice between **Precise** and **Approximate** in the same dialog. If they choose approximate, request the permission again to prompt for an upgrade.

***

## Requesting Motion Access

This permission is optional. When granted, it helps improve the quality of the detections. Therefore, it is **highly recommended** to ask the user to grant this permission.

{% tabs %}
{% tab title="iOS" %}
The Motion & Fitness permission is requested by iOS the first time your app queries <mark style="color:$success;">**`CMMotionActivityManager`**</mark>. There is no separate request method.

Make sure <mark style="color:$success;">**`NSMotionUsageDescription`**</mark> is set in your **Info.plist**, otherwise your app will be terminated when the permission is requested. This is enforced by iOS.
{% endtab %}

{% tab title="Android" %}
On Android 10 (API level 29) and above, request the <mark style="color:$success;">**`ACTIVITY_RECOGNITION`**</mark> permission. This is a standard runtime request with a dialog.

```kotlin
private val activityRecognition = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    Log.d(TAG, "Activity recognition granted: $isGranted")
}

fun requestActivityRecognition() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
        activityRecognition.launch(Manifest.permission.ACTIVITY_RECOGNITION)
    }
}
```

{% endtab %}

{% tab title="React Native" %}

```typescript
import { PermissionsAndroid, Platform } from "react-native";

async function requestActivityRecognition(): Promise<boolean> {
  if (Platform.OS !== "android" || Platform.Version < 29) return true;

  const result = await PermissionsAndroid.request(
    PermissionsAndroid.PERMISSIONS.ACTIVITY_RECOGNITION
  );
  return result === PermissionsAndroid.RESULTS.GRANTED;
}
```

{% endtab %}

{% tab title="Flutter" %}

```dart
import 'package:permission_handler/permission_handler.dart';

Future<bool> requestActivityRecognition() async {
  final status = await Permission.activityRecognition.request();
  return status.isGranted;
}
```

{% endtab %}
{% endtabs %}

***

## Requesting Notification Access

On Android 13 (API level 33) and above, the <mark style="color:$success;">**`POST_NOTIFICATIONS`**</mark> permission decides whether the SDK's foreground service notification is visible to the user.

Detections are not affected by this permission. The foreground service still runs, but its notification is suppressed until the permission is granted. If you have customized this notification, this is the permission that determines whether anyone sees it.

```kotlin
private val postNotifications = registerForActivityResult(
    ActivityResultContracts.RequestPermission()
) { isGranted ->
    Log.d(TAG, "Notification permission granted: $isGranted")
}

fun requestNotificationPermission() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        postNotifications.launch(Manifest.permission.POST_NOTIFICATIONS)
    }
}
```

{% hint style="info" %}
For more about the foreground service and its notification, check out [this page](/sdk/appendix/android/foreground-service.md).
{% endhint %}

***

## Verifying the Result

Do not rely on the result of your permission requests alone. The user can revoke a permission at any time from the system settings, and a granted permission does not always mean the SDK can detect. Read the effective state back from the SDK instead.

<table><thead><tr><th width="330">SDK Status field</th><th>What to check</th></tr></thead><tbody><tr><td><code>locationPermission</code></td><td>Must be <code>ALWAYS</code>. <code>ONLY_WHILE_IN_USE</code> means the second request did not complete.</td></tr><tr><td><code>isPreciseLocationAuthorizationGranted</code></td><td>False when the user granted approximate location only.</td></tr><tr><td><code>isActivityRecognitionPermGranted</code></td><td>Motion &#x26; Fitness on iOS, Activity Recognition on Android.</td></tr><tr><td><code>detectionStatus</code></td><td><code>ENABLED_BUT_BLOCKED</code> while any mandatory permission is missing.</td></tr></tbody></table>

{% hint style="info" %}
For the full list of SDK status fields and what each one means, check out [this page](/sdk/appendix/sdk-status.md). To be notified when a permission changes while your app is running, subscribe for [SDK status updates](/getting-started/sdk-integration/6.-sdk-status-updates.md).
{% endhint %}

***

For the complete overview of platform-specific requirements, check out the following pages:

{% content-ref url="/pages/XBYgscThqpRuWPPxV5yY" %}
[iOS](/getting-started/sdk-integration/1.-requirements/ios.md)
{% endcontent-ref %}

{% content-ref url="/pages/3A1XvtDGeEUCu2y1q2aF" %}
[Android](/getting-started/sdk-integration/1.-requirements/android.md)
{% endcontent-ref %}
