fluttercommunity / plus_plugins

Flutter Community Plus Plugins
BSD 3-Clause "New" or "Revised" License
1.55k stars 927 forks source link

[Bug]: Android share and edit image from temp dir causes permission exception #1357

Open EnduringBeta opened 1 year ago

EnduringBeta commented 1 year ago

Platform

Android 12

Plugin

share_plus

Version

6.3.0

Flutter SDK

3.3.0

Steps to reproduce

Steps

  1. Run this repo and branch (screenshot) on an Android device.
  2. Touch the floating action button, which should use the screenshot package to create an image of a red/blue widget and bring up the Share bottom sheet.
  3. Ensure you are viewing the logs.
  4. Tap "Edit". Confirm you see the following exception...
E/DatabaseUtils( 2431): Writing exception to parcel
E/DatabaseUtils( 2431): java.lang.SecurityException: Permission Denial: writing dev.fluttercommunity.plus.share.ShareFileProvider uri content://com.example.flutterbug.flutter.share_provider/cache/testFile-0.png from pid=415, uid=10184 requires the provider be exported, or grantUriPermission()
E/DatabaseUtils( 2431):     at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:907)
E/DatabaseUtils( 2431):     at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:712)
E/DatabaseUtils( 2431):     at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:679)
E/DatabaseUtils( 2431):     at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:488)
E/DatabaseUtils( 2431):     at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:272)
E/DatabaseUtils( 2431):     at android.os.Binder.execTransactInternal(Binder.java:1179)
E/DatabaseUtils( 2431):     at android.os.Binder.execTransact(Binder.java:1143)

Thoughts

This app stores the screenshot PNG file in the temporary directory. There are SO posts and resources for this exception, but few focused on Flutter. The ones that are encourage adding a <provider> tag in the AndroidManifest.xml, which I was unsuccessful at doing properly.

When I looked at share_plus's AndroidManifest.xml, it has a <provider>, though for a sub directory. I'm skeptical I should be duplicating or near-duplicating this.

I'm new to this aspect, so I'm open to this not being a bug at all. If it's not, though, I believe my confusion might deserve a documentation update to guide future folks.

Code Sample

https://github.com/EnduringBeta/flutter-bug/tree/screenshot

Here's the core function

  // This bug happens without the Screenshot plugin, as well.
  // It applies on Android when sharing an image and tapping "Edit".
  // I haven't tested iOS.
  void _screenshotShareAndCount() async {
    Uint8List bytes = await screenshotController.captureFromWidget(
      Container(
          padding: const EdgeInsets.all(30.0),
          decoration: BoxDecoration(
            border: Border.all(color: Colors.blueAccent, width: 5.0),
            color: Colors.redAccent,
          ),
          child: const Text("This is an invisible widget")),
    );

    final String tempDir = (await getTemporaryDirectory()).path;
    final String filePath =
        "$tempDir${Platform.pathSeparator}testFile-$_counter.png";
    debugPrint(filePath);
    final File file = File(filePath);
    try {
      file.writeAsBytesSync(bytes);
    } catch (e) {
      debugPrint(e.toString());
      return null;
    }

    Share.shareXFiles([XFile(filePath)]);

    setState(() {
      _counter++;
    });
  }

Logs

[ +544 ms] √  Built build\app\outputs\flutter-apk\app-debug.apk.
[   +2 ms] executing: C:\Users\ross\AppData\Local\Android\sdk\build-tools\33.0.1\aapt dump xmltree C:\Users\ross\projects\flutter-bug\build\app\outputs\flutter-apk\app.apk AndroidManifest.xml
[  +22 ms] Exit code 0 from: C:\Users\ross\AppData\Local\Android\sdk\build-tools\33.0.1\aapt dump xmltree C:\Users\ross\projects\flutter-bug\build\app\outputs\flutter-apk\app.apk AndroidManifest.xml
[        ] N: android=http://schemas.android.com/apk/res/android
             E: manifest (line=2)
               A: android:versionCode(0x0101021b)=(type 0x10)0x1
               A: android:versionName(0x0101021c)="1.0.0" (Raw: "1.0.0")
               A: android:compileSdkVersion(0x01010572)=(type 0x10)0x21
               A: android:compileSdkVersionCodename(0x01010573)="13" (Raw: "13")
               A: package="com.example.flutterbug" (Raw: "com.example.flutterbug")
               A: platformBuildVersionCode=(type 0x10)0x21
               A: platformBuildVersionName=(type 0x10)0xd
               E: uses-sdk (line=7)
                 A: android:minSdkVersion(0x0101020c)=(type 0x10)0x10
                 A: android:targetSdkVersion(0x01010270)=(type 0x10)0x1f
               E: uses-permission (line=15)
                 A: android:name(0x01010003)="android.permission.INTERNET" (Raw: "android.permission.INTERNET")
               E: permission (line=17)
                 A: android:name(0x01010003)="com.example.flutterbug.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" (Raw: "com.example.flutterbug.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION")
                 A: android:protectionLevel(0x01010009)=(type 0x11)0x2
               E: uses-permission (line=21)
                 A: android:name(0x01010003)="com.example.flutterbug.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" (Raw: "com.example.flutterbug.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION")
               E: application (line=23)
                 A: android:label(0x01010001)="flutter-bug" (Raw: "flutter-bug")
                 A: android:icon(0x01010002)=@0x7f080000
                 A: android:name(0x01010003)="android.app.Application" (Raw: "android.app.Application")
                 A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff
                 A: android:appComponentFactory(0x0101057a)="androidx.core.app.CoreComponentFactory" (Raw: "androidx.core.app.CoreComponentFactory")
                 E: activity (line=29)
                   A: android:theme(0x01010000)=@0x7f0a0000
                   A: android:name(0x01010003)="com.example.flutterbug.MainActivity" (Raw: "com.example.flutterbug.MainActivity")
                   A: android:exported(0x01010010)=(type 0x12)0xffffffff
                   A: android:launchMode(0x0101001d)=(type 0x10)0x1
                   A: android:configChanges(0x0101001f)=(type 0x11)0x40003fb4
                   A: android:windowSoftInputMode(0x0101022b)=(type 0x11)0x10
                   A: android:hardwareAccelerated(0x010102d3)=(type 0x12)0xffffffff
                   E: meta-data (line=44)
                     A: android:name(0x01010003)="io.flutter.embedding.android.NormalTheme" (Raw: "io.flutter.embedding.android.NormalTheme")
                     A: android:resource(0x01010025)=@0x7f0a0001
                   E: intent-filter (line=48)
                     E: action (line=49)
                       A: android:name(0x01010003)="android.intent.action.MAIN" (Raw: "android.intent.action.MAIN")
                     E: category (line=51)
                       A: android:name(0x01010003)="android.intent.category.LAUNCHER" (Raw: "android.intent.category.LAUNCHER")
                 E: meta-data (line=66)
                   A: android:name(0x01010003)="flutterEmbedding" (Raw: "flutterEmbedding")
                   A: android:value(0x01010024)=(type 0x10)0x2
                 E: provider (line=73)
                   A: android:name(0x01010003)="dev.fluttercommunity.plus.share.ShareFileProvider" (Raw: "dev.fluttercommunity.plus.share.ShareFileProvider")
                   A: android:exported(0x01010010)=(type 0x12)0x0
                   A: android:authorities(0x01010018)="com.example.flutterbug.flutter.share_provider" (Raw: "com.example.flutterbug.flutter.share_provider")
                   A: android:grantUriPermissions(0x0101001b)=(type 0x12)0xffffffff
                   E: meta-data (line=78)
                     A: android:name(0x01010003)="android.support.FILE_PROVIDER_PATHS" (Raw: "android.support.FILE_PROVIDER_PATHS")
                     A: android:resource(0x01010025)=@0x7f0c0000
                 E: receiver (line=86)
                   A: android:name(0x01010003)="dev.fluttercommunity.plus.share.SharePlusPendingIntent" (Raw: "dev.fluttercommunity.plus.share.SharePlusPendingIntent")
                   A: android:exported(0x01010010)=(type 0x12)0xffffffff
                   E: intent-filter (line=89)
                     E: action (line=90)
                       A: android:name(0x01010003)="EXTRA_CHOSEN_COMPONENT" (Raw: "EXTRA_CHOSEN_COMPONENT")
                 E: uses-library (line=94)
                   A: android:name(0x01010003)="androidx.window.extensions" (Raw: "androidx.window.extensions")
                   A: android:required(0x0101028e)=(type 0x12)0x0
                 E: uses-library (line=97)
                   A: android:name(0x01010003)="androidx.window.sidecar" (Raw: "androidx.window.sidecar")
                   A: android:required(0x0101028e)=(type 0x12)0x0
[   +2 ms] Stopping app 'app.apk' on Pixel 3.
[        ] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ shell am force-stop com.example.flutterbug
[ +110 ms] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ shell pm list packages com.example.flutterbug
[  +87 ms] package:com.example.flutterbug
[        ] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ shell cat /data/local/tmp/sky.com.example.flutterbug.sha1
[  +93 ms] c86c251e0cc1a14309fdc94526415b3b34b660dc
[        ] Latest build already installed.
[        ] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ shell -x logcat -v time -t 1
[ +103 ms] --------- beginning of main
                    11-29 12:40:15.555 D/ProcessState( 3491): Binder ioctl to enable oneway spam detection failed: Invalid argument
[   +4 ms] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ shell am start -a android.intent.action.MAIN -c android.intent.category.LAUNCHER -f 0x20000000 --ez enable-dart-profiling true --ez
enable-checked-mode true --ez verify-entry-points true com.example.flutterbug/com.example.flutterbug.MainActivity
[  +98 ms] Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x20000000 cmp=com.example.flutterbug/.MainActivity (has extras) }
[        ] Waiting for observatory port to be available...
[ +707 ms] Observatory URL on device: http://127.0.0.1:39923/G3MHPHgrWYI=/
[        ] executing: C:\Users\ross\AppData\Local\Android\sdk\platform-tools\adb.exe -s 8C5X1JCQQ forward tcp:0 tcp:39923
[  +25 ms] 51045
[        ] Forwarded host port 51045 to device port 39923 for Observatory
[   +4 ms] Caching compiled dill
[  +28 ms] Connecting to service protocol: http://127.0.0.1:51045/G3MHPHgrWYI=/
[ +184 ms] Launching a Dart Developer Service (DDS) instance at http://127.0.0.1:0, connecting to VM service at http://127.0.0.1:51045/G3MHPHgrWYI=/.
[ +118 ms] DDS is listening at http://127.0.0.1:51048/Qppso3QMTUI=/.
[  +38 ms] Successfully connected to service protocol: http://127.0.0.1:51045/G3MHPHgrWYI=/
[  +56 ms] DevFS: Creating new filesystem on the device (null)
[  +30 ms] DevFS: Created new filesystem on the device (file:///data/user/0/com.example.flutterbug/code_cache/flutter-bugEJHAAX/flutter-bug/)
[   +1 ms] Updating assets
[  +69 ms] Syncing files to device Pixel 3...
[   +1 ms] Compiling dart to kernel with 0 updated files
[        ] Processing bundle.
[        ] <- recompile package:flutter_bug/main.dart 372ce75a-6508-4c42-aff4-7ac45e89e8ba
[        ] <- 372ce75a-6508-4c42-aff4-7ac45e89e8ba
[   +1 ms] Bundle processing done.
[ +107 ms] Updating files.
[        ] DevFS: Sync finished
[   +1 ms] Syncing files to device Pixel 3... (completed in 111ms)
[        ] Synced 0.0MB.
[   +1 ms] <- accept
[   +3 ms] Connected to _flutterView/0x77acbfc3e0.
[   +2 ms] Flutter run key commands.
[   +1 ms] r Hot reload. 
[        ] R Hot restart.
[        ] h List all available interactive commands.
[        ] d Detach (terminate "flutter run" but leave application running).
[        ] c Clear the screen
[        ] q Quit (terminate the application on the device).
[        ]  Running with sound null safety 
[        ] An Observatory debugger and profiler on Pixel 3 is available at: http://127.0.0.1:51048/Qppso3QMTUI=/
[ +526 ms] I/Gralloc4( 3508): mapper 4.x is not supported
[        ] W/Gralloc3( 3508): mapper 3.x is not supported
[        ] W/Gralloc4( 3508): allocator 4.x is not supported
[        ] W/Gralloc3( 3508): allocator 3.x is not supported
[   +6 ms] The Flutter DevTools debugger and profiler on Pixel 3 is available at: http://127.0.0.1:9101?uri=http://127.0.0.1:51048/Qppso3QMTUI=/
[+3979 ms] I/flutter ( 3508): /data/user/0/com.example.flutterbug/cache/testFile-0.png
[  +30 ms] D/CompatibilityChangeReporter( 3508): Compat change id reported: 160794467; UID 10342; state: ENABLED
[+1839 ms] E/DatabaseUtils( 3508): Writing exception to parcel
[        ] E/DatabaseUtils( 3508): java.lang.SecurityException: Permission Denial: writing dev.fluttercommunity.plus.share.ShareFileProvider uri content://com.example.flutterbug.flutter.share_provider/cache/testFile-0.png from pid=415, uid=10184   
requires the provider be exported, or grantUriPermission()
[        ] E/DatabaseUtils( 3508):      at android.content.ContentProvider.enforceWritePermissionInner(ContentProvider.java:907)
[        ] E/DatabaseUtils( 3508):      at android.content.ContentProvider$Transport.enforceWritePermission(ContentProvider.java:712)
[        ] E/DatabaseUtils( 3508):      at android.content.ContentProvider$Transport.enforceFilePermission(ContentProvider.java:679)
[        ] E/DatabaseUtils( 3508):      at android.content.ContentProvider$Transport.openAssetFile(ContentProvider.java:488)
[        ] E/DatabaseUtils( 3508):      at android.content.ContentProviderNative.onTransact(ContentProviderNative.java:272)
[        ] E/DatabaseUtils( 3508):      at android.os.Binder.execTransactInternal(Binder.java:1179)
[        ] E/DatabaseUtils( 3508):      at android.os.Binder.execTransact(Binder.java:1143)
[+3166 ms] I/OpenGLRenderer( 3508): Davey! duration=8867ms; Flags=1, FrameTimelineVsyncId=136919, IntendedVsync=75659661938017, Vsync=75659661938017, InputEventId=0, HandleInputStart=75659662307935, AnimationStart=75659662312570,
PerformTraversalsStart=75659662314393, DrawStart=75659662905643, FrameDeadline=75659682604683, FrameInterval=75659662296476, FrameStartTime=16666666, SyncQueued=75659663757466, SyncStart=75659663831893, IssueDrawCommandsStart=75659664178872,       
SwapBuffers=75659673759759, FrameCompleted=75668529412932, DequeueBufferDuration=1077083, QueueBufferDuration=500417, GpuCompleted=75668529412932, SwapBuffersCompleted=75659674799550, DisplayPresentTime=0,

Flutter Doctor

[√] Flutter (Channel stable, 3.3.0, on Microsoft Windows [Version 10.0.22621.819], locale en-US)
    • Flutter version 3.3.0 on channel stable at C:\Users\ross\projects\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision ffccd96b62 (3 months ago), 2022-08-29 17:28:57 -0700
    • Engine revision 5e9e0e0aa8
    • Dart version 2.18.0
    • DevTools version 2.15.0

[√] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    • Android SDK at C:\Users\ross\AppData\Local\Android\sdk
    • Platform android-33, build-tools 33.0.1
    • Java binary at: C:\Program Files\Android\Android Studio\jre\bin\java
    • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files (x86)\Google\Chrome\Application\chrome.exe

[√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.2.6)
    • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
    • Visual Studio Community 2022 version 17.2.32630.192
    • Windows 10 SDK version 10.0.19041.0

[√] Android Studio (version 2020.3)
    • Android Studio at C:\Program Files\Android\Android Studio
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.11+9-b60-7590822)

[√] Android Studio (version 2021.3)
    • Android Studio at C:\Program Files\Android\Android Studio1
    • Flutter plugin can be installed from:
       https://plugins.jetbrains.com/plugin/9212-flutter
    • Dart plugin can be installed from:
       https://plugins.jetbrains.com/plugin/6351-dart
    • Java version OpenJDK Runtime Environment (build 11.0.13+0-b1751.21-8125866)

[√] VS Code (version 1.66.2)
    • VS Code at C:\Users\ross\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension can be installed from:
       https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[√] Connected device (4 available)
    • Pixel 3 (mobile)  • 8C5X1JCQQ • android-arm64  • Android 12 (API 31)
    • Windows (desktop) • windows   • windows-x64    • Microsoft Windows [Version 10.0.22621.819]
    • Chrome (web)      • chrome    • web-javascript • Google Chrome 107.0.5304.108
    • Edge (web)        • edge      • web-javascript • Microsoft Edge 107.0.1418.42

[√] HTTP Host Availability
    • All required HTTP hosts are available

• No issues found!

Checklist before submitting a bug

github-actions[bot] commented 1 year ago

This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 15 days

christofkern commented 2 months ago

over a year later I am encountering the exact same issue, adding the contents of their AndroidManifest.xml didnt work for me, so did anyone find a solution to this?

miquelbeltran commented 2 months ago

Did you define a FileProvider in your AndroidManifest.xml for your app package folders? https://developer.android.com/training/secure-file-sharing/setup-sharing#DefineProvider

The share_plus package already provides one provider in the manifest, but it is only configured to handle the cached folder for share plus, i.e. share_plus/ inside the app temporal files, for when users share files stored temporally when using XFile.fromData. (this maybe needs better documentation)

I think you have to define a provider for your own paths as well.

christofkern commented 2 months ago

Yes I have defined such file provider like in the documentation. The issue I think to see is that in my Exception it says that the plus.share.ShareFileProvider does not have the permissions, so I dont think defining a FileProvider in my AndroidManifest wont help there?

<provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/filepaths"/> </provider>

`<?xml version="1.0" encoding="utf-8"?>

`

_E/DatabaseUtils( 9066): java.lang.SecurityException: Permission Denial: writing dev.fluttercommunity.plus.share.ShareFileProvider uri content://de.randomtick.orbit.flutter.share_provider/cache/sharedimage.png from pid=7308, uid=10120 requires the provider be exported, or grantUriPermission()

miquelbeltran commented 2 months ago

For what I can see, the package calls to grantUriPermission, which is what the error recommends: https://github.com/fluttercommunity/plus_plugins/blob/f1c3462a2b798ed4ea9356f71bde922a88a36da7/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt#L151

Is there a chance you can provide an example project where to reproduce this issue?

christofkern commented 2 months ago

https://github.com/christofkern/shareplus_issue

i just uploaded one, really dumbed down from what i want it to use, but error remains

miquelbeltran commented 2 months ago

I ran the example on my Pixel 5 and shared the picture with Telegram. Both the text and the image were shared successfully, and I didn't see any console errors. Are there any other conditions that cause the bug?

My logs

Syncing files to device Pixel 5...
I/flutter (11329): /data/user/0/com.example.shareplus_issue/cache/shared_image.png
D/CompatibilityChangeReporter(11329): Compat change id reported: 160794467; UID 10608; state: ENABLED
I/flutter (11329): /data/user/0/com.example.shareplus_issue/cache/shared_image.png
Application finished.
christofkern commented 2 months ago

yeah the error only comes up when pressing the edit button in the share popup, like originally described in this issue

miquelbeltran commented 2 months ago

Ok, I have completely missed the "Edit button" part. I don't see any Edit button either, is that an option on the share sheet? It does not appear on my Android version. Could you share a screenshot of that?

Nevertheless, I start to connect some dots. A 3rd app tries to edit the file shared from the app directly, which causes this to crash. As I understand, the permissions are set correctly to allow that, but other than that I am not sure what could be wrong.

christofkern commented 2 months ago

grafik

christofkern commented 2 months ago

if there is an option to get rid of that one, I would also not mind, as that feature isnt really necessary. But since it is there and leads to a crash I somehow have to adress it

miquelbeltran commented 2 months ago

What Android version do you have?

christofkern commented 2 months ago

The emulator is 33

christofkern commented 2 months ago

interestingly enough its not there on my own smartphone which is android 11 (30).

miquelbeltran commented 2 months ago

Yep, I can reproduce the issue now, I will reopen the ticket.

My first idea is to modify the plugin and also add Write permissions here to this line: https://github.com/fluttercommunity/plus_plugins/blob/f1c3462a2b798ed4ea9356f71bde922a88a36da7/packages/share_plus/share_plus/android/src/main/kotlin/dev/fluttercommunity/plus/share/Share.kt#L127

miquelbeltran commented 2 months ago

I am not able to figure it out.

But there are a couple of things that don't add up:

The file URI is content://com.example.shareplus_issue.flutter.share_provider/cache/shared_image.png.

The plugin setups the following filepaths:

<cache-path name="cache" path="share_plus/" />

The provider authority used by the plugin is <app id>.flutter.share_provider.

All of this seem to be correct, as the plugin always copies all files to the cache before sharing them.

Then, I am pretty sure that adding the following to the AndroidManifest in fact does nothing and can be removed:

      <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="${applicationId}.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data            
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/filepaths"
                />
        </provider>

I tried several things and none worked, but changing the above line to use write permissions did something:

        shareIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)

When I tap on edit nothing happens, but also the app doesn't crash (???) the other share options still work (e.g. share to Google search image), still not sure why is that making a difference. But if you want to test, I guess you can clone the repo and set the package path, e.g.

  share_plus:
    path: ../../fluttercommunity/plus_plugins/packages/share_plus/share_plus/

and change that line in the kt file.

Let me know if you try.

christofkern commented 1 month ago

Same behaviour for me. Does not crash anymore, even though the edit button does nothing. Thanks! This is very fine for me for now

miquelbeltran commented 1 month ago

It is still a very buggy behavior, and I even wonder if this is an issue on Android OS itself. At least it helped to investigate further.