Android Code Snippet AddToGallery - add images and videos to the Gallery (SDK 20 to 30) - an update of Erel's AddBitmapToGallery

In this code snippet:

https://www.b4x.com/android/forum/threads/add-image-to-gallery-android-5-10.121992/

Erel shows how to add an image to an Android gallery for all SDKs 20? to 30.

Only shortcomings are that it is image/jpeg specific (i.e. doesn't handle video/mp4) at SDK >= 29 and it doesn't handle user specified album names - both being functionality I need.

After a lot of mucking around and googling I have managed to upgrade it to do these:
B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#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.
'    Private xui As XUI
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
End Sub

Sub Activity_Create(FirstTime As Boolean)

    AddToGallery(File.OpenInput(File.DirAssets, "junk.jpg"), "XYZalbum", "junk.jpg", "image/jpeg")
    Wait For AddToGallery_Complete_jpeg
    AddToGallery(File.OpenInput(File.DirAssets, "junk.mp4"), "XYZalbum", "junk.mp4", "video/mp4")
    Wait For AddToGallery_Complete_mp4
End Sub

Sub AddToGallery (In As InputStream, AlbumName As String, TargetName As String, MimeType As String)
    Dim p As Phone
    Log(p.SdkVersion)
    Dim ctxt As JavaObject
    ctxt.InitializeContext
    If p.SdkVersion >= 29 Then
        Dim cr As ContentResolver
        cr.Initialize("cr")
        Dim values As ContentValues
        values.Initialize
        values.PutString("relative_path", "Pictures/" & AlbumName)
        values.PutString("_display_name", TargetName)
        values.PutString("mime_type", MimeType)
        Dim MediaStore As JavaObject
        If MimeType = "image/jpeg" Then
            MediaStore.InitializeStatic("android.provider.MediaStore.Images$Media")
        Else
            MediaStore.InitializeStatic("android.provider.MediaStore.Video$Media")
        End If
        Dim EXTERNAL_CONTENT_URI As Uri = MediaStore.GetField("EXTERNAL_CONTENT_URI")
        cr.Delete(EXTERNAL_CONTENT_URI, "_display_name = ?", Array As String(TargetName))
        Dim imageuri As JavaObject = cr.Insert(EXTERNAL_CONTENT_URI, values)
        Dim out As OutputStream = ctxt.RunMethodJO("getContentResolver", Null).RunMethod("openOutputStream", Array(imageuri))
        File.Copy2(In, out)
        out.Close
        Log("finito SDK >=29")
    Else
        Dim rp As RuntimePermissions
        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult (Permission As String, Result As Boolean) 'change to Activity if not using B4XPages
        If Result Then
            File.MakeDir(File.DirRootExternal, AlbumName)
            Dim out As OutputStream = File.OpenOutput(File.DirRootExternal, AlbumName & "/" & TargetName, False)
            File.Copy2(In, out)
            out.Close
            Dim FilePath As String = File.Combine(File.DirRootExternal, AlbumName & "/" & TargetName)
            Dim MediaScannerConnection As JavaObject
            MediaScannerConnection.InitializeStatic("android.media.MediaScannerConnection")
            Dim interface As Object = MediaScannerConnection.CreateEventFromUI("android.media.MediaScannerConnection.OnScanCompletedListener", "ScanCompleted", _
                   Null)
            MediaScannerConnection.RunMethod("scanFile", Array(ctxt, Array As String(FilePath), Array As String(MimeType), interface))
            Wait For ScanCompleted_Event (MethodName As String, Args() As Object)
            Log(Args(0))
            Log(Args(1))
            Log("finito SDK < 29")
        End If
    End If
    If MimeType = "image/jpeg" Then
        CallSubDelayed(Me, "AddToGallery_Complete_jpeg")
    Else
        CallSubDelayed(Me, "AddToGallery_Complete_mp4")
    End If
End Sub

Attached is the project as a zip - because of the size limitations imposed on uploaded zips you need to add a video called junk.mp4 to the Files folder and Add it in the Files tab.

I have tested it on a Samsung S5 (SDK 23) and a Pixel 3 (SDK 30)

I would really appreciate it if forum members with devices other than SDK 23 or 30 would test it and report back - thanks in anticipation.

Happy coding...
 

Attachments

  • AddToGallery.zip
    24.7 KB · Views: 120
Last edited:

tuhatinhvn

Active Member
Licensed User
What SDK are you on?
i tested on ANdroid 7 and android 11

you can view more here
MediaStore.Audio.Media
MediaStore.Files
MediaStore.Downloads


and you should fix this code to add to gallery with android <=18
B4X:
    If Phone.SdkVersion <= 18 Then           ' min - 4.3.1
                Dim i As Intent
                i.Initialize("android.intent.action.MEDIA_SCANNER_SCAN_FILE", "file://" & FilePath)
                Phone.SendBroadcastIntent(i)
            Else
                Dim ctxt As JavaObject
                ctxt.InitializeContext
                Dim MediaScannerConnection As JavaObject
                MediaScannerConnection.InitializeStatic("android.media.MediaScannerConnection")
                Dim interface As Object = MediaScannerConnection.CreateEventFromUI("android.media.MediaScannerConnection.OnScanCompletedListener", "ScanCompleted", _
           Null)
                MediaScannerConnection.RunMethod("scanFile", Array(ctxt, Array As String(FilePath), Array As String(MimeType), interface))
            End If
            Wait For ScanCompleted_Event (MethodName As String, Args() As Object)
            Log(Args(0))
            Log(Args(1))

and this code use for video is not good:
B4X:
values.PutString("relative_path", "Pictures/" & AlbumName)

and shouldn't set to Pictures or Movies,.... because if user phone's language is not English, this is not correct
you can use this code to make it better
B4X:
        'Environment.DIRECTORY_PICTURES + File.pathSeparator + SUB_DIRECTORY_NAME
        'Environment.DIRECTORY_MOVIES + File.separator + SUB_DIRECTORY_NAME

I will update it and release better version!
 

tuhatinhvn

Active Member
Licensed User
Here is my code support images/videos/documents...... work well Android >=5

B4X:
#Region  Project Attributes
    #ApplicationLabel: B4A Example
    #VersionCode: 1
    #VersionName:
    'SupportedOrientations possible values: unspecified, landscape or portrait.
    #SupportedOrientations: unspecified
    #CanInstallToExternalStorage: False
#End Region

#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.
'    Private xui As XUI
End Sub

Sub Globals
    'These global variables will be redeclared each time the activity is created.
End Sub

Sub Activity_Create(FirstTime As Boolean)
'    AddToGallery(File.OpenInput(File.DirAssets, "junk.jpg"), "XYZalbum", "junk.jpg", "image/jpeg")
'    Wait For AddToGallery_Complete_jpeg
'    AddToGallery(File.OpenInput(File.DirAssets, "1.mp4"), "XYZalbum", "1.mp4", "video/mp4")
'    Wait For AddToGallery_Complete_mp4
DownloadAndSave("https://qppl.hatinh.gov.vn/vbpq_hatinh.nsf/47794e937861e28f472579100054f798/81C51CD599E8B089472586F6003616BC/$file/QD2336.signed.pdf")
'    Log(GetEnvironment("separator"))
End Sub
Sub DownloadAndSave (Url As String) As ResumableSub
    Dim j As HttpJob
    j.Initialize("", Me)
    j.Download(Url)
    Wait For (j) JobDone(j As HttpJob)
    If j.Success Then
        'File.Copy2(j.GetInputStream, out)
        AddToGallery(j.GetInputStream,"AppName","filename.pdf","application/pdf")
        Wait For AddToGallery_Complete
        Log("Finish download and add to gallery")
    End If
    j.Release
    Return j.Success
End Sub

Sub AddToGallery (In As InputStream, AlbumName As String, TargetName As String, MimeType As String)
    Dim p As Phone
    Log(p.SdkVersion)
    Dim ctxt As JavaObject
    ctxt.InitializeContext
    If p.SdkVersion >= 29 Then
        Dim cr As ContentResolver
        cr.Initialize("cr")
        Dim values As ContentValues
        values.Initialize
        values.PutString("_display_name", TargetName)
        values.PutString("mime_type", MimeType)
        Dim MediaStore As JavaObject
        If MimeType.ToLowerCase.Contains("image/") Then
            MediaStore.InitializeStatic("android.provider.MediaStore.Images$Media")
            values.PutString("relative_path", GetEnvironment("DIRECTORY_PICTURES") & GetEnvironment("separator") & AlbumName)
            
        Else if MimeType.ToLowerCase.Contains( "video/") Then
            MediaStore.InitializeStatic("android.provider.MediaStore.Video$Media")
            values.PutString("relative_path", GetEnvironment("DIRECTORY_MOVIES") & GetEnvironment("separator") & AlbumName)
            Else 'pdf, document,....
            MediaStore.InitializeStatic("android.provider.MediaStore.Downloads")
            values.PutString("relative_path", GetEnvironment("DIRECTORY_DOWNLOADS") & GetEnvironment("separator") & AlbumName)
        End If
        Dim EXTERNAL_CONTENT_URI As Uri = MediaStore.GetField("EXTERNAL_CONTENT_URI")
        cr.Delete(EXTERNAL_CONTENT_URI, "_display_name = ?", Array As String(TargetName))
        Dim imageuri As JavaObject = cr.Insert(EXTERNAL_CONTENT_URI, values)
        Dim out As OutputStream = ctxt.RunMethodJO("getContentResolver", Null).RunMethod("openOutputStream", Array(imageuri))
        File.Copy2(In, out)
        out.Close
        Log("finito SDK >=29")
    Else
        Dim rp As RuntimePermissions
        rp.CheckAndRequest(rp.PERMISSION_WRITE_EXTERNAL_STORAGE)
        Wait For Activity_PermissionResult (Permission As String, Result As Boolean) 'change to Activity if not using B4XPages
        If Result Then
            File.MakeDir(File.DirRootExternal, AlbumName)
            Dim out As OutputStream = File.OpenOutput(File.DirRootExternal, AlbumName & "/" & TargetName, False)
            File.Copy2(In, out)
            out.Close
            Dim FilePath As String = File.Combine(File.DirRootExternal, AlbumName & "/" & TargetName)
            Dim Phone As Phone
            If Phone.SdkVersion <= 18 Then           ' min - 4.3.1
                Dim i As Intent
                i.Initialize("android.intent.action.MEDIA_SCANNER_SCAN_FILE", "file://" & FilePath)
                Phone.SendBroadcastIntent(i)
            Else
                Dim ctxt As JavaObject
                ctxt.InitializeContext
                Dim MediaScannerConnection As JavaObject
                MediaScannerConnection.InitializeStatic("android.media.MediaScannerConnection")
                Dim interface As Object = MediaScannerConnection.CreateEventFromUI("android.media.MediaScannerConnection.OnScanCompletedListener", "ScanCompleted", _
           Null)
                MediaScannerConnection.RunMethod("scanFile", Array(ctxt, Array As String(FilePath), Array As String(MimeType), interface))
            End If
            Wait For ScanCompleted_Event (MethodName As String, Args() As Object)
            Log(Args(0))
            Log(Args(1))
            Log("finito SDK < 29")
        End If
    End If
    'If MimeType = "image/jpeg" Then
        CallSubDelayed(Me, "AddToGallery_Complete")
    'Else
        'CallSubDelayed(Me, "AddToGallery_Complete_mp4")
    'End If
End Sub

Sub GetEnvironment(noidung As String) As String
    Dim nt As JavaObject
    nt.InitializeContext
    Return nt.GetField(noidung)
End Sub
#IF JAVA
import android.os.Environment;
import java.io.File;
        public static final String DIRECTORY_ALARMS =     Environment.DIRECTORY_ALARMS;
        public static final String DIRECTORY_DCIM =     Environment.DIRECTORY_DCIM;
        public static final String DIRECTORY_DOCUMENTS =     Environment.DIRECTORY_DOCUMENTS;
        public static final String DIRECTORY_DOWNLOADS =     Environment.DIRECTORY_DOWNLOADS;
        public static final String DIRECTORY_MOVIES =     Environment.DIRECTORY_MOVIES;
        public static final String DIRECTORY_MUSIC =     Environment.DIRECTORY_MUSIC;
        public static final String DIRECTORY_NOTIFICATIONS =     Environment.DIRECTORY_NOTIFICATIONS;
        public static final String DIRECTORY_PICTURES =     Environment.DIRECTORY_PICTURES;
        public static final String DIRECTORY_PODCASTS =     Environment.DIRECTORY_PODCASTS;
        public static final String DIRECTORY_RINGTONES =     Environment.DIRECTORY_RINGTONES;
        public static final String separator= File.separator;
#End If
 

JackKirk

Well-Known Member
Licensed User
Thanks, I've learnt something.

Interestingly I can store photos and videos in Environment.DIRECTORY_PICTURES (translates to Pictures on my SDK 30 phone)

-BUT-

I can only store videos in Environment.DIRECTORY_MOVIES (translates to Movies on my SDK 30 phone).
 
Last edited:

tuhatinhvn

Active Member
Licensed User
QUESTION - is this ever anything other than / on Android?
Different operating systems use different characters as file and path separators. When our application has to run on multiple platforms, we need to handle these correctly.

Java helps us to pick an appropriate separator and provides functions to help us create paths that work on the host's operating system.

The file separator is the character used to separate the directory names that make up the path to a specific location.

The output will depend on the host operating system. The file separator is \ on Windows and / on macOS and Unix-based operating systems.

On Android, you can use "/" without problems
 

CaptKronos

Active Member
Licensed User
One problem with the SDK<29 branch is that the Google Play Store no longer allows PERMISSION_WRITE_EXTERNAL_STORAGE. Of course, if you don't need to target the Play Store, that's not a problem.
 
Top