Titanium JIRA Archive
Titanium SDK/CLI (TIMOB)

[TIMOB-26370] Android: "Failed to create external storage directory" when download image and save to camera roll

GitHub Issuen/a
TypeBug
PriorityCritical
StatusClosed
ResolutionFixed
Resolution Date2018-11-19T23:17:29.000+0000
Affected Version/sn/a
Fix Version/sRelease 8.0.0
Componentsn/a
Labelsandroid, api, camera, roll, save
ReporterKen Rucker
AssigneeYordan Banev
Created2018-09-05T20:22:16.000+0000
Updated2018-11-26T20:10:27.000+0000

Description

When downloading any image, saving to temp folder and then attempting to save the image to the Android Camera Roll, results in error (saving to Camera Roll) "TiMedia: (main) [526999,526999] Failed to create external storage directory". Works as expected on Android API levels 25 and below. Fails on API levels 26, 27, 28. Tested on emulators API 25-28 and devices API 21 and API 28. *Sample Test Code:*
var checkCameraPermissions = function(callback) {
    if (Ti.Media.hasCameraPermissions()) {
        Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'Already had permissions. Good to go.'}).show();
        callback(true);
    } else { 
        Ti.Media.requestCameraPermissions(function(e) {
             if (e.success === true) {
                 Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'User granted permissions. Good to go.'}).show();
                 callback(true);
             } else {
                 Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permissions', message:'User did not granted permissions. Bad day.'}).show();
                 callback(false);
             }
        });
    }
};
  
var win = Ti.UI.createWindow({
    exitOnClose: true,
    fullscreen: false,
    title: 'Test'
});

var button = Titanium.UI.createButton({
    title: 'Test DL Img & Save to Camera Roll'
});   
button.addEventListener('click', function(e) {
    checkCameraPermissions(function(returnedData) {            
        if (returnedData == true) {
            var xhrImageGet = Ti.Network.createHTTPClient({
                onerror : function(xhrErr) {
                    Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error', message:'Unable to download the remote image. ' + xhrErr.error}).show();
                },
                onload : function() {
                    try {
                        var image_to_save = this.responseData;                    
                        var picture = Titanium.Filesystem.getFile(Titanium.Filesystem.tempDirectory, 'icon-arrowFeature.png');
                        picture.write(image_to_save);                                                        
                        Ti.Media.saveToPhotoGallery(picture.read(), {
                            success:function(){
                                Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Image Saved', message:'icon-arrowFeature.png has been saved to your photo gallery.'}).show();
                            },
                            error:function(err){
                                Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error Saving to Gallery', message:err.error}).show();
// FAILS RIGHT HERE WITH NO ERROR MESSAGE RETURNED. HOWEVER CONSOLE SHOWS THE ERROR TiMedia: (main) [526999,526999] Failed to create external storage directory.
                            }
                        });                        
                        image_to_save = null;
                    }
                    catch(saveErr) {
                        Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Error', message:'Unable to save the file to your device. ' + saveErr.error}).show();
                    }
                }
            });                                        
            xhrImageGet.open("GET", 'https://s3.amazonaws.com/www.appcelerator.com.images/icon-arrowFeature.png');
            xhrImageGet.send();                                            
        } else {
            Ti.UI.createAlertDialog({ cancel: 0, buttonNames: ['OK'], title:'Permission Not Granted', message:'You have not granted the app the needed permissions to access your camera and/or device storage.'}).show();
        }        
    });                                            
});

win.add(button);        
win.open();
*Test App tiapp.xml:*
<?xml version="1.0" encoding="UTF-8"?>
<ti:app xmlns:ti="http://ti.appcelerator.org">
    <id>com.walkthelot.testsavetogallery</id>
    <name>TestSaveToGallery</name>
    <version>1.0</version>
    <publisher>Ken</publisher>
    <url/>
    <description>undefined</description>
    <copyright>2018 by Ken</copyright>
    <icon>appicon.png</icon>
    <fullscreen>false</fullscreen>
    <navbar-hidden>false</navbar-hidden>
    <analytics>true</analytics>
    <guid>a17321c0-c4e2-4542-9222-e3f5731f8fb5</guid>
    <property name="ti.ui.defaultunit" type="string">dp</property>
    <property name="run-on-main-thread" type="bool">true</property>
    <android xmlns:android="http://schemas.android.com/apk/res/android">
        <manifest android:installLocation="auto">
            <uses-permission android:name="android.permission.WAKE_LOCK"/>
            <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
            <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
            <uses-permission android:name="android.permission.INTERNET"/>
            <uses-permission android:name="android.permission.VIBRATE"/>
            <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE"/>
            <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
            <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
            <uses-permission android:name="android.permission.CAMERA"/>
            <uses-sdk android:minSdkVersion="16" android:targetSdkVersion="26"/>
        </manifest>
    </android>
    <modules/>
    <deployment-targets>
        <target device="android">true</target>
        <target device="ipad">false</target>
        <target device="iphone">false</target>
    </deployment-targets>
    <sdk-version>7.3.1.GA</sdk-version>
</ti:app>
I've dug and dug and can not find anything that I may be missing.

Attachments

FileDateSize
Screenshot_20180906-165427.jpg2018-09-06T10:55:12.000+000079182

Comments

  1. Rakhi Mitro 2018-09-06

    Hello, Tested this issue and able to reproduce on SDK 7.3.0.GA, android 8(Huawei y9 2018) device. *Test Environemnt:*
       
       Appcelerator Command-Line Interface, version 7.0.5
       Operating System
         Name                        = Mac OS X
         Version                     = 10.13.6
         Architecture                = 64bit
         # CPUs                      = 4
         Memory                      = 8589934592
       Node.js
         Node.js Version             = 8.9.1
         npm Version                 = 5.5.1
       Titanium CLI
         CLI Version                 = 5.1.1
       Titanium SDK
         SDK Version                 = 7.3.0.GA
       
       Device: Android 8(Huawei y9 2018) device, Android 5 device
         
    *Test code:* Sample code provided above *Test Steps:* 1. Create a classic project . 2. Paste the sample code and run. 3. Click on *Test DL Img & Save to Camera Roll* button. Checking the permissions. And receive error “*Error Saving to Gallery*" by the app. Attached the screenshot. *Console logs:*
       [WARN] :   libEGL: EGLNativeWindowType 0x7330b73010 disconnect failed
       [ERROR] :  TiMedia: (main) [14437,14437] Failed to create external storage directory.
       [INFO] :   zygote64: Do partial code cache collection, code=30KB, data=29KB
       [INFO] :   zygote64: After code cache collection, code=30KB, data=29KB
       [INFO] :   zygote64: Increasing code cache capacity to 128KB
       [INFO] :   PressGestureDetector: HiTouch restricted: AboardArea.
       [INFO] :   APSAnalyticsRunnable: Analytics Started
       [INFO] :   APSAnalyticsRunnable: Stopping Service
       
  2. Pietro Granati 2018-10-04

    Same problem here! Tested on LG and Huawei with android 8 and not working, on LG with android 6 no problem
  3. Pietro Granati 2018-10-05

    Any news? This bug is HUGE!
  4. Yordan Banev 2018-10-05

    I think this may be due to not providing WRITE_EXTERNAL_STORAGE permission at runtime. I will give the code a go and get back here when I have some results.
  5. Michael Gangolf 2018-10-05

    [~ybanev] you are right. Requesting the runtime permission:
       var permissions = ['android.permission.WRITE_EXTERNAL_STORAGE'...];
       Ti.Android.requestPermissions(permissions, function(e) {...})
       
    works on Android 8. Without that I'll get the error described above
  6. Pietro Granati 2018-10-05

    Yeah while searching I was trying and seems to work
  7. Yordan Banev 2018-10-05

    Here is the reason why this problem occurred: https://developer.android.com/about/versions/oreo/android-8.0-changes#rmp What happens is that on Android API levels below 26 the WRITE_EXTERNAL_STORAGE was automatically granted if the user grants the READ_EXTERNAL_STORAGE permission since they are in the same permission group. The latter is requested by calling the Ti.Media.requestCameraPermissions method. They fixed that behavior in Android Oreo (see the link above). The workaround provided by [~michael] is the best way to go for now.
  8. Michael Gangolf 2018-10-11

    I can't update the Wiki (permission denied), but the examples there should be updated to the latest permissions too: https://docs.appcelerator.com/platform/latest/#!/guide/Camera_and_Photo_Gallery_APIs
  9. John Dalsgaard 2018-10-11

    Agree with Michael The documentation should mention that for iOS you need to add these permissions in tiapp.xml:
                       <key>NSCameraUsageDescription</key>
                       <string>We need access to take a photo</string>
                       <key>NSPhotoLibraryUsageDescription</key>
                       <string>We need access to save a photo</string>
                       <key>NSPhotoLibraryAddUsageDescription</key>
                       <string>We need access to save a photo</string>
       
    and the showCamera() method could be adapted to something like this:
           // check if we already have permissions to capture media
           var permissionsToRequest = ["android.permission.CAMERA","android.permission.WRITE_EXTERNAL_STORAGE"];
           if ((OS_ANDROID && !Ti.Android.hasPermission(permissionsToRequest)) || (OS_IOS && !Ti.Media.hasCameraPermissions())) {
               // request permissions to capture media
               if(OS_ANDROID) {
       	        Ti.Android.requestPermissions(permissionsToRequest,function (e) {
       	            // success! display the camera
       	            if (e.success) {
       	                camera();
       	            } else {
       	                callback(new Error('could not obtain camera permissions!'), null);
       	            }
       	        });
               } else {
       	        Ti.Media.requestCameraPermissions(function (e) {
       	            // success! display the camera
       	            if (e.success) {
       	                camera();
       	            } else {
       	                callback(new Error('could not obtain camera permissions!'), null);
       	            }
       	        });
               }
           } else {
               camera();
           }
       
    ... or some cleaner code ;-)
  10. Yordan Banev 2018-10-17

    [~michael], [~jda] Thank you for the contribution! Scheduling to have the proposed changes/improvements included in 8.0.0.
  11. Yordan Banev 2018-11-08

    PR: https://github.com/appcelerator/titanium_mobile/pull/10443
  12. Keerthi Mahalingam 2018-11-19

    FR Passed. PR merged
  13. Keerthi Mahalingam 2018-11-26

    Verified the fix on SDK 8.0.0.v20181126083929 .Image get saved.Closing Test Environment: Name = Mac OS X Version = 10.13.6 Architecture = 64bit Memory = 17179869184 Node.js Node.js Version = 8.12.0 npm Version = 6.4.1 Titanium CLI CLI Version = 5.1.1 Titanium SDK SDK Version = 8.0.0.v20181120090229 Device =Pixel android 9,samsung s5 android 6 Emulator =nexsus 6p android 8

JSON Source