apache / cordova-plugin-camera

Apache Cordova Plugin camera
https://cordova.apache.org/
Apache License 2.0
961 stars 1.52k forks source link

Android 13 app crashes when trying to access the provider-path meta-data file #878

Open De47h69 opened 4 months ago

De47h69 commented 4 months ago

Bug Report

Problem

Android 13 App crashes when permission is granted for the first time. After requesting the permission once, the "Take Picture" button has no effect. Selecting an image from the gallery works. Note: This only happens in the Release version from the store

What is expected to happen?

Upon requesting the permission to take photos the camera intent should start and i should be able to take a photo with the camera

What does actually happen?

App is crashing when when granting the permission to take photos

https://github.com/apache/cordova-plugin-camera/assets/154965518/38d90cca-6dd0-4015-bcd5-b1a14634d4c1

Information

This is the stacktrace from logcat

FATAL EXCEPTION: main Process: com.newsolutions.checkware2, PID: 14859 java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=0, result=-1, data=Intent { act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }} to activity {com.newsolutions.checkware2/com.newsolutions.checkware2.MainActivity}: java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.newsolutions.checkware2.cordova.plugin.camera.provider at android.app.ActivityThread.deliverResults(ActivityThread.java:5994) at android.app.ActivityThread.handleSendResult(ActivityThread.java:6033) at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:67) at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574) at android.os.Handler.dispatchMessage(Handler.java:106) at android.os.Looper.loopOnce(Looper.java:226) at android.os.Looper.loop(Looper.java:313) at android.app.ActivityThread.main(ActivityThread.java:8757) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067) Caused by: java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.newsolutions.checkware2.cordova.plugin.camera.provider at androidx.core.content.FileProvider.getFileProviderPathsMetaData(FileProvider.java:664) at androidx.core.content.FileProvider.parsePathStrategy(FileProvider.java:695) at androidx.core.content.FileProvider.getPathStrategy(FileProvider.java:645) at androidx.core.content.FileProvider.getUriForFile(FileProvider.java:449) at org.apache.cordova.camera.CameraLauncher.takePicture(CameraLauncher.java:332) at org.apache.cordova.camera.CameraLauncher.onRequestPermissionResult(CameraLauncher.java:1394) at org.apache.cordova.CordovaInterfaceImpl.onRequestPermissionResult(CordovaInterfaceImpl.java:222) at org.apache.cordova.CordovaActivity.onRequestPermissionsResult(CordovaActivity.java:527) at android.app.Activity.dispatchRequestPermissionsResult(Activity.java:9124) at android.app.Activity.dispatchActivityResult(Activity.java:8955) at android.app.ActivityThread.deliverResults(ActivityThread.java:5987) at android.app.ActivityThread.handleSendResult(ActivityThread.java:6033)  at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:67)  at android.app.servertransaction.ActivityTransactionItem.execute(ActivityTransactionItem.java:45)  at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)  at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2574)  at android.os.Handler.dispatchMessage(Handler.java:106)  at android.os.Looper.loopOnce(Looper.java:226)  at android.os.Looper.loop(Looper.java:313)  at android.app.ActivityThread.main(ActivityThread.java:8757)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1067) 

Command or Code

Blank app with camera plugin

Environment, Platform, Device

Android 13 Samsung Galaxy A32 Android 13 Lenovo Tab M10 Plus 3rd Gen Android 13 Samsung Galaxy Tab S7 Android 14 Google Pixel Pro 7

Works fine on Android 12 and below and also on LineageOS Android 13

Version information

packages.json

`"dependencies": {
    "@angular/animations": "^15.2.9",
    "@angular/common": "^15.0.4",
    "@angular/core": "^15.0.4",
    "@angular/forms": "^15.0.4",
    "@angular/platform-browser": "^15.0.4",
    "@angular/platform-browser-dynamic": "^15.0.4",
    "@angular/router": "^15.0.4",
    "@awesome-cordova-plugins/barcode-scanner": "^6.6.0",
    "@awesome-cordova-plugins/camera": "^6.6.0",
    "@awesome-cordova-plugins/core": "^6.6.0",
    "@awesome-cordova-plugins/device": "^6.6.0",
    "@awesome-cordova-plugins/document-picker": "^6.6.0",
    "@awesome-cordova-plugins/file": "^6.6.0",
    "@awesome-cordova-plugins/file-chooser": "^6.4.0",
    "@awesome-cordova-plugins/file-path": "^6.6.0",
    "@awesome-cordova-plugins/geolocation": "^6.6.0",
    "@awesome-cordova-plugins/http": "^6.6.0",
    "@awesome-cordova-plugins/insomnia": "^6.6.0",
    "@awesome-cordova-plugins/keyboard": "^6.6.0",
    "@awesome-cordova-plugins/nfc": "^6.6.0",
    "@awesome-cordova-plugins/splash-screen": "^6.6.0",
    "@awesome-cordova-plugins/status-bar": "^6.6.0",
    "@ionic/angular": "^6.4.1",
    "@ionic/core": "^6.4.1",
    "@ionic/storage": "^4.0.0",
    "@ionic/storage-angular": "^3.0.6",
    "@iplab/ngx-color-picker": "^15.0.0",
    "@ngx-translate/core": "^13.0.0",
    "@ngx-translate/http-loader": "^6.0.0",
    "cordova": "^12.0.0",
    "font-awesome": "^4.7.0",
    "gulp-json-editor": "^2.5.6",
    "jquery": "^3.6.2",
    "js-base64": "^3.7.3",
    "keycloak-angular": "^13.0.0",
    "keycloak-js": "^20.0.2",
    "knockout": "^3.4.2",
    "localforage-cordovasqlitedriver": "^1.8.0",
    "lodash": "^4.17.21",
    "moment": "^2.29.4",
    "rxjs": "~6.6.0",
    "signature_pad": "^4.1.6",
    "sqlite3": "^5.1.4",
    "zone.js": "^0.11.8"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^15.0.4",
    "@angular/cli": "^15.0.4",
    "@angular/compiler": "^15.0.4",
    "@angular/compiler-cli": "^15.0.4",
    "@angular/language-service": "^15.0.4",
    "@ionic/angular-toolkit": "^7.0.0",
    "@ionic/cordova-builders": "^7.0.0",
    "@ionic/lab": "3.2.10",
    "@red-mobile/cordova-plugin-barcodescanner": "file:src/plugins/cordova-plugin-barcodescanner",
    "@share911/cordova-universal-links-plugin": "^1.2.5",
    "@types/jasmine": "~3.6.0",
    "@types/jasminewd2": "~2.0.3",
    "@types/knockout": "^3.4.72",
    "@types/node": "^12.20.55",
    "@typescript-eslint/eslint-plugin": "4.16.1",
    "@typescript-eslint/parser": "4.16.1",
    "compare-func": "^2.0.0",
    "cordova-android": "^12.0.1",
    "cordova-browser": "^7.0.0",
    "cordova-custom-config": "^5.1.1",
    "cordova-documentpicker": "^1.2.2",
    "cordova-electron": "^2.0.0",
    "cordova-ios": "^7.0.1",
    "cordova-plugin-advanced-http": "^3.3.1",
    "cordova-plugin-browsertab": "file:src/plugins/cordova-plugin-browsertab",
    "cordova-plugin-camera": "^7.0.0",
    "cordova-plugin-compat": "^1.2.0",
    "cordova-plugin-device": "^2.1.0",
    "cordova-plugin-file": "^8.0.1",
    "cordova-plugin-filechooser": "^1.2.0",
    "cordova-plugin-filepath": "^1.6.0",
    "cordova-plugin-geolocation": "^5.0.0",
    "cordova-plugin-inappbrowser": "^6.0.0",
    "cordova-plugin-insomnia": "^4.3.0",
    "cordova-plugin-ionic-keyboard": "^2.2.0",
    "cordova-plugin-ionic-webview": "file:src/plugins/cordova-plugin-ionic-webview",
    "cordova-plugin-statusbar": "^4.0.0",
    "cordova-sqlite-storage": "^6.1.0",
    "cordova-universal-links-plugin": "file:src/plugins/cordova-universal-links-plugin",
    "electron-builder": "22.10.5",
    "eslint": "^7.6.0",
    "eslint-plugin-import": "2.22.1",
    "eslint-plugin-jsdoc": "30.7.6",
    "eslint-plugin-prefer-arrow": "1.2.2",
    "gulp": "^4.0.2",
    "gulp-cheerio": "^1.0.0",
    "jasmine-core": "~3.8.0",
    "jasmine-spec-reporter": "~5.0.0",
    "karma": "^6.4.1",
    "karma-chrome-launcher": "^3.1.1",
    "karma-coverage": "~2.0.3",
    "karma-coverage-istanbul-reporter": "~3.0.2",
    "karma-jasmine": "^4.0.2",
    "karma-jasmine-html-reporter": "^1.7.0",
    "phonegap-nfc": "file:src/plugins/phonegap-nfc",
    "protractor": "~7.0.0",
    "ts-node": "~8.3.0",
    "tslib": "^2.6.2",
    "typescript": "~4.8.4"
  },
  "description": "Checkware 2",
  "cordova": {
    "plugins": {
      "cordova-plugin-filechooser": {},
      "cordova-plugin-filepath": {},
      "cordova-plugin-geolocation": {
        "GPS_REQUIRED": "true",
        "GEOLOCATION_USAGE_DESCRIPTION": "in checklist"
      },
      "cordova-plugin-insomnia": {},
      "cordova-plugin-ionic-keyboard": {},
      "cordova-sqlite-storage": {},
      "cordova-plugin-inappbrowser": {},
      "cordova-universal-links-plugin": {},
      "cordova-plugin-camera": {
        "ANDROIDX_CORE_VERSION": "1.6.+"
      },
      "cordova-plugin-browsertab": {
        "CUSTOM_TAB_COLOR_RGB": "#ffffff"
      },
      "cordova-plugin-compat": {},
      "cordova-plugin-device": {},
      "cordova-plugin-file": {
        "ANDROIDX_WEBKIT_VERSION": "1.4.0"
      },
      "cordova-plugin-ionic-webview": {},
      "cordova-plugin-statusbar": {},
      "phonegap-nfc": {
        "NFC_USAGE_DESCRIPTION": "Read NFC Tags"
      },
      "cordova-plugin-advanced-http": {
        "ANDROIDBLACKLISTSECURESOCKETPROTOCOLS": "SSLv3,TLSv1"
      },
      "@red-mobile/cordova-plugin-barcodescanner": {
        "ANDROIDX_LEGACY_SUPPORT_V4_VERSION": "1.0.0"
      }
    },`

Generated AndroidManifest.xml

`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<manifest android:hardwareAccelerated="true" android:versionCode="5050001" android:versionName="5.5.1" xmlns:android="http://schemas.android.com/apk/res/android">
  <supports-screens android:anyDensity="true" android:largeScreens="true" android:normalScreens="true" android:resizeable="true" android:smallScreens="true" android:xlargeScreens="true"/>
  <uses-permission android:name="android.permission.INTERNET"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
  <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
  <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
  <uses-permission android:name="android.permission.NFC"/>
  <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
  <uses-permission android:name="android.permission.CAMERA"/>
  <application android:hardwareAccelerated="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:usesCleartextTraffic="true">
    <activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode" android:exported="true" android:label="@string/activity_name" android:launchMode="singleTask" android:name="MainActivity" android:theme="@style/Theme.App.SplashScreen" android:windowSoftInputMode="adjustResize">
      <intent-filter android:label="@string/launcher_name">
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="mycheckware.com" android:scheme="https"/>
      </intent-filter>
      <intent-filter android:autoVerify="true">
        <action android:name="android.intent.action.VIEW"/>
        <category android:name="android.intent.category.DEFAULT"/>
        <category android:name="android.intent.category.BROWSABLE"/>
        <data android:host="main" android:scheme="checkware"/>
      </intent-filter>
    </activity>
    <activity android:clearTaskOnLaunch="true" android:configChanges="orientation|keyboardHidden|screenSize" android:exported="false" android:name="com.google.zxing.client.android.CaptureActivity" android:theme="@android:style/Theme.NoTitleBar.Fullscreen" android:windowSoftInputMode="stateAlwaysHidden"/>
    <activity android:label="Share" android:name="com.google.zxing.client.android.encode.EncodeActivity"/>
    <provider android:authorities="com.newsolutions.checkware2.cordova.plugin.camera.provider" android:exported="false" android:grantUriPermissions="true" android:name="org.apache.cordova.camera.FileProvider">
      <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/camera_provider_paths"/>
    </provider>
  </application>
  <uses-feature android:name="android.hardware.location.gps" android:required="true"/>
  <uses-feature android:name="android.hardware.nfc" android:required="false"/>
  <uses-feature android:name="android.hardware.camera" android:required="true"/>
  <uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
  <queries>
    <intent>
      <action android:name="android.media.action.IMAGE_CAPTURE"/>
    </intent>
    <intent>
      <action android:name="android.intent.action.GET_CONTENT"/>
    </intent>
    <intent>
      <action android:name="android.intent.action.PICK"/>
    </intent>
    <intent>
      <action android:name="com.android.camera.action.CROP"/>
      <data android:mimeType="image/*" android:scheme="content"/>
    </intent>
  </queries>
</manifest>`

Checklist

jcesarmobile commented 4 months ago

can you provide a sample app that reproduces the issue?

De47h69 commented 4 months ago

It will take some time to set up a working test project. In the meantime you can test my release version. Apparently Github doesn't accept .zip files even though it says so... The file should be available for the public

https://download.germanedge.com/index.php/s/WJ9RxoF2L8DntKG

Just follow the workflow i provided in the gif. You can extract the logcat stuff when you're connected to your device with adb shell and then run the following command

logcat | grep com.newsolutions.checkware2

jcesarmobile commented 4 months ago

Thanks for the app, but we don’t request sample apps because we don’t believe you, we ask for sample apps for being able to reproduce the issue in an app we can debug and use that app to verify a possible fix, so a release version is not useful.

De47h69 commented 3 months ago

My intention was so that you can see it for yourself in Logcat, since I can't reproduce this issue when I debug the app. Only happens when I install it from the Play Store.

Currently setting up a sample

De47h69 commented 3 months ago

Ok, I've done some extensive digging on this issue. It seems that the meta-data file get stripped from the apk on installation.

There was a bugfix for that in the Android.core.content.FileProvider stuff as seen here https://android-review.googlesource.com/c/platform/frameworks/support/+/1978527

Most important here is

Some OEMs strip meta-data from the manifest. This is problematic as FileProvider was depending on meta-data to specify a resource containing the paths that should be shared.

Because of that I set the ANDROIDX_CORE_VERSION to 1.9.0 which includes the fix, but that sadly didn't fixed it

From what I could gather from the FileProvider implementation in the core package it calls the getUriForFile function from the FileProvider which internally calls getPathStrategy -> parsePathStrategy -> getFileProviderPathsMetaData and then ends on line 664 in the FileProvider.java file with the exception that's displayed in Logcat if (info == null) { throw new IllegalArgumentException( "Couldn't find meta-data for provider with authority " + authority); }

Another suggestions that was made was to call super of the FileProvider with the ResourceId of the meta-data file so i adjusted the org.apache.cordova.camera.FileProvider to this:

public class FileProvider extends androidx.core.content.FileProvider {
  private static final String LOG_TAG = "FileProvider";
  public FileProvider() {
    super(R.xml.camera_provider_paths);
    LOG.d(LOG_TAG, "ResourceId" + R.xml.camera_provider_paths);
  }
}

When i debug the application it returns the Id for the meta-data file which is 2131951616 and writes the log into Logcat

FileProvider com.newsolutions.checkware2 D ResourceID: 2131951616

However when I install the apk via the PlayStore this Log entry won't be written which is weird

I added some logging into the CameraLauchner.java file and this is the output

` CameraLauncher        com.newsolutions.checkware2          D  photo: /data/user/0/com.newsolutions.checkware2/cache/.Pic.jpg
 CameraLauncher          com.newsolutions.checkware2          D  imageFilePath: /data/user/0/com.newsolutions.checkware2/cache/.Pic.jpg
 CameraLauncher          com.newsolutions.checkware2          D  activity: com.newsolutions.checkware2.MainActivity@a19591
 CameraLauncher          com.newsolutions.checkware2          D  authority: com.newsolutions.checkware2.cordova.plugin.camera.provider
CameraLauncher          com.newsolutions.checkware2          D  -------------------`

So the file for the picture gets created as intended, the cache folder exists and the authority is also correct.

There seems to be some kind of issue when the apk gets installed on the device. When I inspect the release apk from the PlayStore page the xml file exists

grafik

I tried to recreate this issue in a sample project. However I can't recreate it locally in debug or if I create a Signed APK or AAB via Android Studio. It only happens after I download the apk from the PlayStore. You can't reproduce this issue when debugging.

Is there some flag or something I can set in my release workflow so I can debug the Release version?

breautek commented 3 months ago

s there some flag or something I can set in my release workflow so I can debug the Release version?

In your AndroidManifest.xml, try setting android:debuggable="true" on your <application> tag.

https://developer.android.com/guide/topics/manifest/application-element#debug

By default its true for debug builds and false for release builds. When true it should allow the native debugger to be attached. Note that a release build with debuggable enabled will be rejected in the play store. So if the issue truly only occurs on a build variant from the play store, then this won't help.

If you have any plugins that manipulates the AndroidManifest it may get overwritten if you use the cordova command line, in which case you can try setting:

android {
    buildTypes {
       release {
           debuggable true
       }
    }
}

in the app's build.gradle file.

In other thoughts, if you use proguard I'd try disabling it (or enable it for your debug build to see if it breaks then)

De47h69 commented 3 months ago

Note that a release build with debuggable enabled will be rejected in the play store. Will it be rejected even if I only push it to interal testing?

Not sure about ProGuard. I'm gonna investigate that one

breautek commented 3 months ago

Will it be rejected even if I only push it to interal testing?

Good question... not 100% sure, but I feel like I recall Google producing an error on upload complaining its a debuggable artefact... so I think so, but not really sure.

De47h69 commented 3 months ago

Thanks for your answers. I'm gonna test your suggestions and see what comes from it

De47h69 commented 3 months ago

Ok. So compiling and signing it with ProGuard enabled doesn't change the fact that the generated APK runs fine. So this is really some issue when it's uploaded to the PlayStore.

Interestingly enough now the Store APK also crashes on Devices running Android 12 and lower. But that could also be a problem with my changes. Gonna revert that and test it again.

EDIT: Well, even my older releases aren't working anymore with Android 12 and lower 😕 I have zero idea what's going on

Oh and yeah, the PlayStore refuses the upload if android:debuggable is set to true 🙄

I guess I'm gonna contact google now. Maybe they can tell me what the heck is going on

De47h69 commented 3 months ago

Actually there's one more thing that confuses me Given this line from logcat java.lang.RuntimeException: Failure delivering result ResultInfo{who=@android:requestPermissions:, request=0, result=-1, data=Intent why is the request amount 0? it should request more than zero permissions right?

breautek commented 3 months ago

So compiling and signing it with ProGuard enabled doesn't change the fact that the generated APK runs fine.

Just wanted to clarify that ProGuard should be disabled unless you know what you're doing. ProGuard adds a layer of protection to your app against reverse engineering. It does so by obfuscating your compiled code, mangling symbol names, etc... which can break stuff, especially in Cordova where it relies on dynamic references by string names. In this case, I was worried it was breaking the manifest mapping.

why is the request amount 0? it should request more than zero permissions right?

I believe that's just simply a side effect of the full error:

FATAL EXCEPTION: main Process: com.newsolutions.checkware2, PID: 14859
java.lang.RuntimeException: Failure delivering result
ResultInfo{who=@android:requestPermissions:, request=0, result=-1, data=Intent {
 act=android.content.pm.action.REQUEST_PERMISSIONS (has extras) }}
 to activity {com.newsolutions.checkware2/com.newsolutions.checkware2.MainActivity}: 

java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.newsolutions.checkware2.cordova.plugin.camera.provider 

The main error is java.lang.IllegalArgumentException: Couldn't find meta-data for provider with authority com.newsolutions.checkware2.cordova.plugin.camera.provider

De47h69 commented 3 months ago

Just wanted to clarify that ProGuard should be disabled unless you know what you're doing. ProGuard adds a layer of protection to your app against reverse engineering. It does so by obfuscating your compiled code, mangling symbol names, etc... which can break stuff, especially in Cordova where it relies on dynamic references by string names. In this case, I was worried it was breaking the manifest mapping.

Yeah, I know what it's doing. Opening the release APK from the store reveals that the meta-data file is still present. At least in the Universal APK that you can download from the internal pages. Adding obfuscation doesn't change the fact that the file is still present

grafik

It just changes the filename to something stupid