Android Question Runtime Permission -> Duplicate Request Warning

ilan

Expert
Licensed User
Longtime User
hi

i have updated some of my apps to target sdk 26 as google will force us to do that in few months.

the thing is that i am using only the WRITE_EXTERNAL_STORAGE permission so ifollowed the tutorials here and used .GetSafeDirDefaultExternal instead of asking the permission on runtime.

i also added the required permission for sdk =<18 as mentioned here: https://www.b4x.com/android/forum/threads/runtime-permissions-android-6-0-permissions.67689/#content

but after a while i understood that for some features in my app this wont be the right approch so for some features i am now asking the permission at runtime before the user want to use that feature like creating a backup of all data and save to the phone storage (RootExternal) since it makes no sense to save it to the GetSafeDirDefaultExternal because it will be deleted when he removes the app and the whole idea is to have a backup file that will stay on his phone.

the thing is that everything works fine BUT when i upload it to the store i get a warning that i use dublicate permission request and it wont allow me to upload that apk.

so is it not possible to use the GetSafeDirDefaultExternal + runtime permission? is it because i have the request for sdk =< 18 in mnifest and also use the runtime permission?

how can i use both together (GetSafeDirDefaultExternal for saving not so impotent files to the storage and runtime permission to create backupfiles)

thanx, ilan
 

ilan

Expert
Licensed User
Longtime User
Just remove the version limited permission as you are requesting it for all versions any way (open AndroidManifest.xml and you will see it).

thanx for your answer but it has confused me a little bit.

if i would use only the GetSafeDirDefaultExternal then i have to add the permission request to manifest correct?
so this is a way how to avoid using runtime permission but it will work only on sdk > 18 and for lower SDK i ask the permission from manifest.

now some features in my app use this approch, they save files at that GetSafeDirDefaultExternal BUT when i create a backup file i use runtime permission because i am saving it to RootExternal so if i remove the manifest permission request and a user with SDK < 18 will use my app it may crash or something like that but it should not work.

i need to mention that when you FIRST time install the app you will not be asked to accept the permission to write to external storage ONLY if you click on the BACKUP button you will be asked so until that all files are saved on GetSafeDirDefaultExternal so if a user will SDK < 18 will install the app what will happen if he want to save a file to GetSafeDirDefaultExternal without having the request in manifest??
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
Bottom line: remove the version limited permission and everything will work.

Explanation: GetSafeDirDefaultExternal doesn't require any permission on Android 5+. On older devices the storage permission is needed so the solution is to add the permission based on the Android version.

Now that you are using File.DirRootExternal in your code it means that the storage permission is added to all versions. Google Play correctly complains as your manifest includes the permission twice.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
To summarize (correct me if I'm wrong):
1. nothing to add in the Manifest
2. For older (pre-5) devices Android on installation will ask for the storage permission due to the presence of GetSafeDirDefaultExternal in code; for newer devices (5+) not need for a permission request
3. When the option for backup to external storage is needed, ask for the runtime permission and use RootExternal if granted
 
Upvote 0

ilan

Expert
Licensed User
Longtime User
Quoting myself...

this is my Manifest:

B4X:
'This code will be applied to the manifest file during compilation.
'You do not need to modify it in most cases.
'See this link for for more information: https://www.b4x.com/forum/showthread.php?p=78136
AddManifestText(
<uses-sdk android:minSdkVersion="9" android:targetSdkVersion="26"/>
<supports-screens android:largeScreens="true"
    android:normalScreens="true"
    android:smallScreens="true"
    android:anyDensity="true"/>)
SetApplicationAttribute(android:icon, "@drawable/icon")
SetApplicationAttribute(android:label, "$LABEL$")
'SetApplicationAttribute(android:theme,"@android:style/Theme.Holo.Light")
'SetApplicationAttribute(android:resizeableActivity, "true")
'AddApplicationText(<meta-data android:name="android.max_aspect" android:value="10"/>)
'End of default text.

SetApplicationAttribute(android:largeHeap,"true")

AddManifestText(<uses-permission GetSafeDirDefaultExternal on app start.
    android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="18" />
)

AddApplicationText(
  <provider
  android:name="android.support.v4.content.FileProvider"
  android:authorities="$PACKAGE$.provider"
  android:exported="false"
  android:grantUriPermissions="true">
  <meta-data
  android:name="android.support.FILE_PROVIDER_PATHS"
  android:resource="@xml/provider_paths"/>
  </provider>
)
CreateResource(xml, provider_paths,
   <external-files-path name="name" path="shared" />
)


SetApplicationAttribute(android:theme, "@style/LightTheme")
'CreateResource(values-v20, theme.xml,
'<resources>
'  <style
'     name="LightTheme" parent="@android:style/Theme.Material.Light">
'     <item name="android:windowFullscreen">true</item>
'  </style>
'</resources>
')
CreateResource(values-v14, theme.xml,
<resources>
  <style
     name="LightTheme" parent="@android:style/Theme.Holo.Light">
     <item name="android:windowFullscreen">true</item>
  </style>
</resources>
)
'
''dropbox
'SetApplicationAttribute(android:label, "@string/app_name") ' NEW in v3.0!!!
'AddApplicationText(
'<activity android:name="com.dropbox.sync.android.DbxAuthActivity" />
'<activity
'  android:name="com.dropbox.client2.android.AuthActivity"
'  android:launchMode="singleTask" >
'  <intent-filter>
'    <data android:scheme="db-sabj8u327rv607b" /> <!-- NEED TO UPDATE -->
'    <action android:name="android.intent.action.VIEW" />
'    <category android:name="android.intent.category.BROWSABLE" />
'    <category android:name="android.intent.category.DEFAULT" />
'  </intent-filter>
'</activity>
'<service
'  android:name="com.dropbox.sync.android.DbxSyncService"
'  android:enabled="true"
'  android:exported="false"
'  android:label="Dropbox Sync" />
'  )

'********* TabStrip ***********************
CreateResource(drawable, background_tab.xml,
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:exitFadeDuration="@android:integer/config_shortAnimTime">
    <item android:state_pressed="true" android:drawable="@color/background_tab_pressed" />
    <item android:state_focused="true" android:drawable="@color/background_tab_pressed"/>
    <item android:drawable="@android:color/transparent"/>
</selector>)
CreateResource(values, colors.xml,
<resources>
    <color name="background_tab_pressed">#6633B5E5</color>
</resources>)
'******************************************


'************ Firebase Ads ************
AddApplicationText(
  <activity
  android:name="com.google.android.gms.ads.AdActivity"
  android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|uiMode|screenSize|smallestScreenSize"
  android:theme="@android:style/Theme.Translucent" />
  <activity android:name="com.google.android.gms.ads.purchase.InAppPurchaseActivity"
  android:theme="@style/Theme.IAPTheme"/>
)
'************ Firebase Ads (end) ************

'************ Google Play Services Base ************
AddApplicationText(
   <activity android:name="com.google.android.gms.common.api.GoogleApiActivity"
  android:theme="@android:style/Theme.Translucent.NoTitleBar"
  android:exported="false"/>
    <meta-data
  android:name="com.google.android.gms.version"
  android:value="@integer/google_play_services_version" />
)
'************ Google Play Services Base (end) ************

'************ Firebase Base ************
CreateResourceFromFile("google-services", "google-services.json")
AddPermission(android.permission.ACCESS_NETWORK_STATE)
AddPermission(android.permission.INTERNET)
AddPermission(android.permission.WAKE_LOCK)
AddPermission(com.google.android.c2dm.permission.RECEIVE)
AddPermission(${applicationId}.permission.C2D_MESSAGE)
AddManifestText( <permission android:name="${applicationId}.permission.C2D_MESSAGE"
  android:protectionLevel="signature" />)
AddApplicationText(
<receiver
  android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
  android:enabled="true">
  <intent-filter>
  <action android:name="com.google.android.gms.measurement.UPLOAD"/>
  </intent-filter>
  </receiver>

  <service
  android:name="com.google.android.gms.measurement.AppMeasurementService"
  android:enabled="true"
  android:exported="false"/>
   <provider
  android:authorities="${applicationId}.firebaseinitprovider"
  android:name="com.google.firebase.provider.FirebaseInitProvider"
  android:exported="false"
  android:initOrder="100" />
    <receiver
  android:name="com.google.android.gms.measurement.AppMeasurementReceiver"
  android:enabled="true">
  <intent-filter>
  <action android:name="com.google.android.gms.measurement.UPLOAD"/>
  </intent-filter>
  </receiver>

  <service
  android:name="com.google.android.gms.measurement.AppMeasurementService"
  android:enabled="true"
  android:exported="false"/>
   <receiver
  android:name="com.google.firebase.iid.FirebaseInstanceIdReceiver"
  android:exported="true"
  android:permission="com.google.android.c2dm.permission.SEND" >
  <intent-filter>
  <action android:name="com.google.android.c2dm.intent.RECEIVE" />
  <action android:name="com.google.android.c2dm.intent.REGISTRATION" />
  <category android:name="${applicationId}" />
  </intent-filter>
  </receiver>
    <receiver
  android:name="com.google.firebase.iid.FirebaseInstanceIdInternalReceiver"
  android:exported="false" />


  <service
  android:name="com.google.firebase.iid.FirebaseInstanceIdService"
  android:exported="true">
  <intent-filter android:priority="-500">
  <action android:name="com.google.firebase.INSTANCE_ID_EVENT" />
  </intent-filter>
  </service>
)
'************ Firebase Base (end) ************

'************ Firebase Notifications ************
AddApplicationText(
    <service
  android:name="com.google.firebase.messaging.FirebaseMessagingService"
  android:exported="true">
  <intent-filter android:priority="-500">
  <action android:name="com.google.firebase.MESSAGING_EVENT" />
  </intent-filter>
  </service>
   <service android:name="anywheresoftware.b4a.objects.FirebaseNotificationsService">
     <intent-filter>
  <action android:name="com.google.firebase.MESSAGING_EVENT"/>
     </intent-filter>
   </service>
)
'************ Firebase Notifications (end)************

i have it only once inside, i really dont understand if you want me to remove the version related permission request how it will work on lower sdk phones if i DONT ask on app start for that permission BUT i use
 
Upvote 0

ilan

Expert
Licensed User
Longtime User
Ilan, you are still ignoring my replies. Open the generated AndroidManifest.xml file and you will see the two permissions.

As explained in the runtime permissions video tutorial you can see which permissions are added by the compiler by clicking on Logs - List Permissions.

erel i am not ignoring your replies i really try to understand them and i understand that you are a busy man. if you would write that the compiler adds new stuff to manifest while compiling i would understand your reply. this is something new to me. i did not knew that the compiler adds new stuff to manifest.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
2. Wrong. The compiler will add the storage permissions to all versions because you call File.DirRootExternal.
Thank you, Erel.
If I read you correctly, that means that the compiler decides what to put in the Manifest based on our code.
So if it finds only GetSafeDirDefaultExternal then on older devices it asks for permission while on newer ones it needs to do nothing.
If we use any reference to RootExternal (and similar), the permission is added anyway, but for Android6+ devices we have to manage the runtime permissions while for older ones its kind of "static" and asked on installation anyway.
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
So if it finds only GetSafeDirDefaultExternal then on older devices it asks for permission while on newer ones it needs to do nothing.
Not exactly. The compiler will not add anything for GetSafeDirDefaultExternal. You need to manually add it as explained in the tutorial.

If we use any reference to RootExternal (and similar), the permission is added anyway, but for Android6+ devices we have to manage the runtime permissions while for older ones its kind of "static" and asked on installation anyway.
That's true. Make sure to watch the video tutorial as you should treat all versions the same. Don't try to change the behavior based on the OS version, it will just make things more complicated.
 
Upvote 0

udg

Expert
Licensed User
Longtime User
Thanks, I already did my homeworks (i.e watching etp video) a few days ago when I asked you a similar question (and my app works fine from 4.4 to 7.1, not tested on 8+).
I posted in this thread in an attempt to make things clearer for me and anybody else who may will read it in the future. Yor answers clarify the whole matter, I think.
So, again, thank you for yor patience and invaluable support.
 
Upvote 0

Mahares

Expert
Licensed User
Longtime User
feature like creating a backup of all data and save to the phone storage (RootExternal) since it makes no sense to save it to the GetSafeDirDefaultExternal because it will be deleted when he removes the app and the whole idea is to have a backup file that will stay on his phone
I am like many of us still somewhat confused about Runtime Permission. What is the benefit of storing the app data in GetSafeDirDefaultExternal and make a backup in RootExternal. Anybody can copy and snatch the backup. Why not just save the app data and the backup in RootExternal to begin with.
 
Upvote 0
Top