Other Runtime permission "WRITE_EXTERNAL_STORAGE" for android:targetSdkVersion = 23+

fredo

Well-Known Member
Licensed User
Longtime User
I made this example to familiarize myself with the Permission Dialog sequence and came to a maybe suboptimal implementation.

So my question is: Is there a substantial better way to do implement the use case?

Edit: Erel has answered the question in #3. Als usual he knows a far more effective way...

Use Case:
a) The App needs to have android:targetSdkVersion = 23 Or above
b) The App shall use a public folder so that the user has access to the data content
c) The permission request must be granted as soon as possible since the visible layout depends on the contents of data in the Public folder​

28-09-_2016_17-27-17.jpg

B4X:
#Region  Project Attributes
    #ApplicationLabel: aaa exmpl permission request
    #VersionCode: 6
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' This is an example to familiarize yourself with the Permission Dialog sequence
' Here especially:  PERMISSION_WRITE_EXTERNAL_STORAGE
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
'
' Use Case: a) The App needs To have android:targetSdkVersion = 23 Or above
'           b) The App shall use a Public folder so that the user has access To the data content
'            c) The permission request must be granted As soon As possible since the visible layout
'              depends on the contents of data in the Public folder.
'
'   The problem to solve was that "...CheckAndRequest is not a blocking method. The program flow will continue
'    and only later Activity_PermissionsResult will be raised."
'   So it had to be assured that the permission was granted before we can perform App-essential startup actions
'    that need the permission.
' 
'   Public folder structure:
'            /storage/emulated/0/APPFOLDER
'                                           /LIVE
'                                                campusdat.db
'                                           /TRANS
'
' Remarks to the implementation:
'   It looks a bit overcomplicated with the flags but it was the only way to accomplish the task with handling the
'   Activity_Resume after the Permission dialog and avoiding "main._activity' on a null object" errors.
'       -->Thanks goto: https://www.b4x.com/android/forum/threads/solved-dialogs-crash-when-activity_pause.67109/
'
'   *** If there is are a substantial better way to do implement the use case
'   *** please provide a working example in Zip format.
'
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
' Before you start make sure you have read this: (press [ctl] on your keyboard and click on the link)
'
'        --> https://www.b4x.com/android/forum/threads/runtime-permissions-android-6-0-permissions.67689/#content
'                     Erel: "This is an optional feature.
'                             It is only relevant if android:targetSdkVersion is set to 23 or above.
'                             If the targetSdkVersion Is lower than 23 Then
'                             the standard permissions system will be used on all devices including Android 6+."
'
'       --> https://www.b4x.com/android/forum/threads/graphical-life-cycle-of-a-b4a-activity.40515/
'
' If you granted the permission in the dialog it is persistent until you uninstall the App.
' So if you want to see the dialog again just uninstall this app and install it again.
'
' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═


' ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║
' Additional Info:
'     If you upload your app to the Playstore with
'   android:targetSdkVersion="xy"
'   you later cannot upload the app with a lower targetSdkVersion
'   under the same packagename.
' ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║



#Region  Activity Attributes
    #FullScreen: False
    #IncludeTitle: True
#End Region

Sub Process_Globals
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.

    Dim rp As RuntimePermissions


End Sub

Sub Activity_Create(FirstTime As Boolean)
     Starter.intCntAC = Starter.intCntAC +1
    Log($"#-    starter.intCntAC=${Starter.intCntAC} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)

    If FirstTime Then
        Starter.bolPermission_SDWRITEX = False
        Starter.flg_PermissionDialogJustFinished = False
        Starter.flg_PermissionDialogManualCall = False
    End If

    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")
 
End Sub

Sub Activity_Resume
    Starter.intCntAR = Starter.intCntAR +1
    Log($"#-    starter.intCntAR=${Starter.intCntAR} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)
    Log($"#-    starter.flg_PermissionDialogJustFinished=${Starter.flg_PermissionDialogJustFinished}"$)

    If Starter.flg_PermissionDialogJustFinished Then
        ' This part is executed after the Permissionrequest is finished
        Log("#-       (do whatever needed in THIS Activity_Resume)")
     
    Else
        ' Asks for SD WRITE EXTERNAL permission (it's not blocking the sequence of code, causes PAUSE and RESUME)
        If Not(Starter.bolPermission_SDWRITEX) Then
            Log("#-    bolPermission_SDWRITEX=false")
            CallSubDelayed(Me, "CheckPermissions")
        Else
            Log("#-    bolPermission_SDWRITEX=TRUE")
        End If
    End If
 
    ' ...
 
End Sub

Sub Activity_Pause (UserClosed As Boolean)
    Starter.intCntAP = Starter.intCntAP +1
    Log($"#-    starter.intCntAP=${Starter.intCntAP} --. --. --. --. --. --. --. --. --. --. --. --. --. "$)
End Sub

Sub CheckPermissions
    Log("#-CheckPermissions .. .. .. .. .. .. ..")
 
    If rp.Check(rp.PERMISSION_WRITE_EXTERNAL_STORAGE) Then
        Log("#-  rp.Check(..)=TRUE -->permission already GRANTED")
        Starter.flg_PermissionDialogManualCall = True
        Activity_PermissionResult(rp.PERMISSION_WRITE_EXTERNAL_STORAGE, True)
    Else
        Log("#-  rp.Check(..)=false -->ask for permission")
        Starter.flg_PermissionDialogManualCall = False
        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
    End If
 
End Sub

Sub Activity_PermissionResult (Permission As String, Result As Boolean)
    Log($"#-Activity_PermissionResult, Permission=${Permission}, Result=${Result}.. .. .. .."$)
    Log($"#-  starter.flg_PermissionDialogManualCall=${Starter.flg_PermissionDialogManualCall}"$)
 
    If Starter.flg_PermissionDialogManualCall Then
        Starter.flg_PermissionDialogJustFinished = False
    Else
        Starter.flg_PermissionDialogJustFinished = True
    End If
 
    Select Permission
        Case rp.PERMISSION_WRITE_EXTERNAL_STORAGE
            If Result Then  
                Starter.bolPermission_SDWRITEX = True
             
                ' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
                ' Finaly the "permission-needy" procedures can be called
                ' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
                CallSubDelayed(Me, "PrepareAppsPermissionDependantParts")
                ' ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═ ═
             
            Else
                Dim intIlResult As Int=Msgbox2("This App needs to write data to a public folder. Try again?", "Access to public folder" ,"Yes","","No",Null)
                If intIlResult = DialogResponse.POSITIVE Then
                    Log("#-    try again...")
                    CallSubDelayed(Me, "CheckPermissions")
                Else
                    Starter.flg_PermissionDialogJustFinished = False
                    Log("#-    activity finish x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-x-")         
                    Activity.Finish 
                End If
            End If
    End Select 
End Sub

' ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬ ┬
Sub PrepareAppsPermissionDependantParts
    Log("#-PrepareAppsPermissionDependantParts.. .. .. .. .. .. .. .. .. .. ..")
    ' App procedures that depend on the permission

    ' Make sure public folders exist
    PrepWorkPaths
    CreateWorkDirs
 
    ' Init public database files etc.
    OpenDatabases
 
    ' ...
 
End Sub

Sub PrepWorkPaths
    Log("#-PrepWorkPaths.. .. .. .. .. .. .. .. .. .. ..")
 
    ' For test purposes this creates a Random Foldername. For a real App here goes the name of the public folder (e.g. "AppXy_Data")
    Starter.strWorkDir = $"_Test_${DateTime.GetMinute(DateTime.Now)}_${DateTime.GetSecond(DateTime.Now}"$ )

    ' Define folder startpoint
    If File.ExternalWritable Then
        Starter.strBasPath = File.DirRootExternal ' Public folder, easy to find for the user and other Apps
    Else
        Starter.strBasPath = File.DirInternal & "/" & Starter.strWorkDir
    End If
    Starter.strMyWorkPath = File.Combine(Starter.strBasPath, Starter.strWorkDir)

    If File.Exists(Starter.strMyWorkPath, "") Then
        Starter.bolWorkPathExists = True
    End If 
 
    Dim bolInfoOn As Boolean = False ' <<-- set this to TRUE to see more info in the Log()
    #region --- info ---
        If bolInfoOn Then
            ' 
            '
            ' In order to Log() the "android:TargetSdkVersion" you need Informatix's "PackageUtils"
            '            (read this: https://www.b4x.com/android/forum/threads/probundle-chargeable.58754/ )
            '           ...
            '            PackageUtils v2.0
            '                    This library replaces the PackageManager class of the Phone library.
            '                    It gives plenty of informations on packages (activities, features, permissions, receivers, services, etc.)
            '                    And can list the features available on the system (camera, gps, wifi, etc.).
            '                     It allows experts To change the enabled state of components.
            '                    An Application Is provided with the library To show what you can get with it.
            '           ...
            '
            ' If you have the PackageUtils library, then you can uncomment the next 4 lines
            '    Dim PU As PackageUtils
            '    Dim AppInfo As PkgApplicationInfo = PU.GetApplicationInfo(PU.GetMyPackageName)
            '    Log($"#-MyPackageName    = ${PU.GetMyPackageName}"$)
            '    Log($"#-TargetSdkVersion = ${AppInfo.TargetSdkVersion}"$)
            '
            Dim ph As Phone
            Log($"#-ph.SdkVersion    = ${ph.SdkVersion}"$)
            Log("#- --- --- --- --- --- --- ")
            Log($"#-File.ExternalWritable   = ${File.ExternalWritable}"$)
            Log($"#-File.DirInternal        = ${File.DirInternal}"$)
            Log($"#-File.DirRootExternal    = ${File.DirRootExternal}"$)
            Log($"#-file.DirDefaultExternal = ${File.DirDefaultExternal}"$)
            Log("#- --- --- --- --- --- --- ")
            Log($"#-GetSafeDirDefaultExternal('')= ${rp.GetSafeDirDefaultExternal("") }"$)
            Dim strSafeDirExternal() As String = rp.GetAllSafeDirsExternal("")
            For i=0 To strSafeDirExternal.Length -1
                Log($"#-        strSafeDirExternal(${i})= ${strSafeDirExternal(i)}"$)
            Next
            Log("#- --- --- --- --- --- --- ")
        End If
    #end region 

End Sub

Sub CreateWorkDirs
    Log("#-CreateWorkDirs .. .. .. .. .. .. ..") 
 
    ' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °
    ' Create new folders, if needed
    ' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °
    If Not(File.Exists(Starter.strMyWorkPath, "")) Then
        File.MakeDir(Starter.strMyWorkPath, "")
    End If
    '
    Starter.strDbPathLiveDats = File.Combine(Starter.strMyWorkPath, "LIVE")
    If Not(File.Exists(Starter.strDbPathLiveDats, "")) Then
        File.MakeDir(Starter.strDbPathLiveDats, "")
    End If
    '
    Starter.strDbPathTRS = File.Combine(Starter.strMyWorkPath, "TRANS")
    If Not(File.Exists(Starter.strDbPathTRS, "")) Then
        File.MakeDir(Starter.strDbPathTRS, "")
    End If
    ' ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° ° °

 
    ' Check for results
    Log($"#-   starter.strBasPath,     exists=${File.Exists(Starter.strBasPath, "")} --> ${Starter.strBasPath}"$)
    Log($"#-starter.strMyWorkPath,     exists=${File.Exists(Starter.strMyWorkPath, "")} --> ${Starter.strMyWorkPath}"$)
    Log($"#-starter.strDbPathLiveDats, exists=${File.Exists(Starter.strDbPathLiveDats, "")} --> ${Starter.strDbPathLiveDats}"$)
    Log($"#-starter.strDbPathTRS,      exists=${File.Exists(Starter.strDbPathTRS, "")} --> ${Starter.strDbPathTRS}"$)
 
End Sub

Sub OpenDatabases
    Log("#-OpenDatabases .. .. .. .. .. .. ..") 
    If File.Exists(Starter.strDbPathLiveDats, Starter.strDbName1) = False Then
        Try
            File.Copy(File.DirAssets, Starter.strDbName1, Starter.strDbPathLiveDats, Starter.strDbName1 )
        Catch
            Log("cambrdg-DBP-C1-Badpath: " & Starter.strDbPathLiveDats)
            Dim ph As Phone
            Msgbox($"Bad database path: ${CRLF}${CRLF} ${Starter.strDbPathLiveDats}"$, $"System fail - SDK ${ph.SdkVersion} "$)
            Activity.Finish
        End Try
    End If
 
    Log("#-    ...initialize SQL etc.")
 
 
End Sub
' ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴ ┴

B4X:
#Region  Service Attributes
    #StartAtBoot: False
    #ExcludeFromLibrary: True
#End Region

Sub Process_Globals
 
    'These global variables will be declared once when the application starts.
    'These variables can be accessed from all modules.
    Dim strWorkDir As String = "MyPublicFolder"
    Dim strBasPath As String = ""
    Dim strMyWorkPath As String = ""
    Dim bolWorkPathExists As Boolean = False
 
    Dim strDbPathLiveDats As String = ""
    Dim strDbPathTRS As String = ""
 
    Dim strDbName1 As String = "campusdat.db"

    Dim bolPermission_SDWRITEX As Boolean = False
 
    Dim flg_PermissionDialogJustFinished As Boolean = False
    Dim flg_PermissionDialogManualCall As Boolean = False
 

 
    Dim intCntAC As Int = 0 ' Just for test
    Dim intCntAR As Int = 0 ' Just for test
    Dim intCntAP As Int = 0 ' Just for test

End Sub

Sub Service_Create
    'This is the program entry point.
    'This is a good place to load resources that are not specific to a single activity.

End Sub

Sub Service_Start (StartingIntent As Intent)

End Sub

Sub Service_TaskRemoved
    'This event will be raised when the user removes the app from the recent apps list.
End Sub

'Return true to allow the OS default exceptions handler to handle the uncaught exception.
Sub Application_Error (Error As Exception, StackTrace As String) As Boolean
    Return True
End Sub

Sub Service_Destroy

End Sub
 

Attachments

  • Example_Permissions_WRITESD_6.zip
    12.7 KB · Views: 421
Last edited:

moster67

Expert
Licensed User
Longtime User
Interesting read. I still haven't target 23 so this will be useful...
 
Upvote 0

Erel

B4X founder
Staff member
Licensed User
Longtime User
I think that your example is a bit overcomplicated.

Complete code:
B4X:
'main
Sub Process_Globals
End Sub

Sub Globals

End Sub

Sub Activity_Create(FirstTime As Boolean)
   Starter.rp.CheckAndRequest(Starter.rp.PERMISSION_WRITE_EXTERNAL_STORAGE) 'will only show the dialog once on the first time the app is installed.
End Sub

Sub Activity_PermissionResult (Permission As String, Result As Boolean)
   If Permission = Starter.rp.PERMISSION_WRITE_EXTERNAL_STORAGE Then
     If Result = True Then
       'do whatever you need to do with the sd card
     Else
       'no permission. Close app
       Activity.Finish
     End If
     
   End If
End Sub
B4X:
'starter service
Sub Process_Globals
   Public rp As RuntimePermissions
End Sub
 
Upvote 0

fredo

Well-Known Member
Licensed User
Longtime User
Sub Activity_Create(FirstTime As Boolean)
Starter.rp.CheckAndRequest(Starter.rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
'will only show the dialog once on the first time the app is installed.
End Sub

Thank you very much Erel.

While trying to get a grip on the RuntimePermissions I misleaded myself too early with "CheckAndRequest is not a blocking method. The program flow will continue..." and didn't even consider that it "...will only show the dialog once on the first time the app is installed."
 
Last edited:
Upvote 0
Top