landfire13224
landfire13224

Reputation: 427

Restricted access to external storage, android < 10

I want to read and write files in user storage in android < 10 that applied Scoped storage and for some reason, I cannot use the privacy-friendly storage access for that therefore I need to use All files access permission. For getting the permission I'm using the Permission Handler package and calling the Permission.manageExternalStorage.request() like the following:

Future<void> _requestManageExternalStorage() async {
  if(!await Permission.manageExternalStorage.isGranted) {
    await Permission.manageExternalStorage.request();
  }
}

Alas, once I called that method this is what I got: No permissions found in manifest for: []22

As for the permissions list within Android Manifest.xml:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

note that I put it in the correct location, which is in /android/app/src/main/AndroidManifest.xml with that being said, the output given by the Permission Handler is pretty much non-sense. I tried to clean the projects with flutter clean and even create a new project only to test it but it still failed to retrieve the wanted permission.

I tried to check what actually is the current permission status for manageExternalStorage which I got: PermissionStatus.restricted

From the documentation PermissionStatus.restricted means:

The OS denied access to the requested feature. The user cannot change this app's status, possibly due to active restrictions such as parental controls being in place. Only supported on iOS.

From that, I can only assume that the OS is forcefully denied the All Files Access?

I'm using permission_handler: ^8.2.5 which at the time writing this is the latest version.

The full code I use to test:

import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool isGranted = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text(
              'Manage External Storage Test:',
            ),
            Text(
              'Is granted: $isGranted',
              style: Theme.of(context).textTheme.headline4,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _requestManageExternalStorage,
      ),
    );
  }

  Future<void> _requestManageExternalStorage() async {
    if(!await Permission.manageExternalStorage.isGranted) {
      final res = await Permission.manageExternalStorage.request();
      setState((){
        isGranted = res.isGranted;  
      });
    }
  }
}

Full AndroidManifest.xml:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.file_access_test">

    <!-- Permissions options for the `storage` group -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

    <!-- Permissions options for the `manage external storage` group -->
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

    <application
        android:name="io.flutter.app.FlutterApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="MAKE IT DAMN WORK"
        tools:ignore="AllowBackup,GoogleAppIndexingWarning">

        <activity android:name="io.flutter.embedding.android.FlutterActivity"
            android:launchMode="singleTop"
            android:theme="@android:style/Theme.Black.NoTitleBar"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>

Any insight or help is very appreciated! tested in android 10

Upvotes: 5

Views: 4130

Answers (2)

iocomxda
iocomxda

Reputation: 103

This is how I implemented it. I am basically checking which version of SDK is the android device using. If the device is below Android 11, we'll ask for basic permission. If the device is android 11 or above, then we'll ask for manage external storage, also. I am using a bool to know if the device got the right permissions. In my test, it works.

Future<bool> getPermissions() async {
    bool gotPermissions = false;

    var androidInfo = await DeviceInfoPlugin().androidInfo;
    var release = androidInfo.version.release; // Version number, example: Android 12
    var sdkInt = androidInfo.version.sdkInt; // SDK, example: 31
    var manufacturer = androidInfo.manufacturer;
    var model = androidInfo.model;

    print('Android $release (SDK $sdkInt), $manufacturer $model');

    if (Platform.isAndroid) {
        var storage = await Permission.storage.status;

        if (storage != PermissionStatus.granted) {
            await Permission.storage.request();
        }

        if (sdkInt >= 30) {
            var storage_external = await Permission.manageExternalStorage.status;

            if (storage_external != PermissionStatus.granted) {
                await Permission.manageExternalStorage.request();
            }

            storage_external = await Permission.manageExternalStorage.status;

            if (storage_external == PermissionStatus.granted 
                && storage == PermissionStatus.granted) {
                gotPermissions = true;
            }
        } else {
            // (SDK < 30)
            storage = await Permission.storage.status;

            if (storage == PermissionStatus.granted) {
                gotPermissions = true;
            }
        }
    }

    return gotPermissions;
}

Upvotes: 2

Maurits van Beusekom
Maurits van Beusekom

Reputation: 5989

The android.permission.MANAGE_EXTERNAL_STORAGE permission is introduced with Android 11 (API 30) and therefore not supported on Android 10 or lower (see official documentation).

The permission_handler plugin returns PermissionStatus.restricted because it doesn't know how to handle the permission on Android 10 and below. In case of Android 10 and lower simply restrict to requesting the android.permission.READ_EXTERNAL_STORAGE and android.permission.WRITE_EXTERNAL_STORAGE permissions.

The log message is printed because the android.permission.MANAGE_EXTERNAL_STORAGE permission is not returned as part of the array returned by the Context.getPackageInfo().requestedPermissions API. Therefore the permission_handler plugin thinks the permission is not listed in the AndroidManifest.xml file. In this case this is of course not entirely true, however my assumption is that Android filters out permissions that don't apply on the current Android version.

As maintainer of the permission_handler plugin I will make sure the log message gets updated so it takes this option also into account.

Upvotes: 4

Related Questions