9

I have a problem with permissions on Android 6.0 and higher.

The game I'm working on was using the WRITE_EXTERNAL_STORAGE permission. On Android 6.0 and higher the alert which asks a user to allow it had to be displayed.

I've received a request to get rid of this alert, which means, the game cannot use WRITE_EXTERNAL_STORAGE anymore.

Technically the game is not writing and reading the external storage anywhere right now. All saves and cached files are stored in the application-specific directory (/storage/emulated/0/Android/data/com.company.productname) But, the game has to read (and sometimes download) the OBB file, which is stored in /storage/emulated/0/Android/obb/com.company.productname directory.

The game has an access to this directory, even without WRITE_EXTERNAL_STORAGE or READ_EXTERNAL_STORAGE on devices:

  • Motorola Nexus 6
  • LG Nexus 5

But it seems it doesn't have na access to this directory on:

  • Samsung Galaxy S5, S6 and S7
  • Nvidia Shield tablet

I've checked it even using the Alpha Tests in order to simulate downloading the game from the store.

When the permission to the Storage is given (via App Settings) then everything runs fine.

How the OBB is mounted?

The Unreal Engine 4, which I use, is using the C open() function to handle the OBB file:

int32 Handle = open(LocalPath, O_RDONLY);

Where LocalPath is a full path to the obb file: /storage/emulated/0/Android/obb/com.company.productname/main.10003.com.company.productname.obb

The questions are:

  1. Does the game need a READ/WRITE _EXTERNAL_STORAGE in order to read and download OBB file?
  2. If yes, then how can I possibly get rid of the alert which asks the user for permissions.
  3. If no, then why Samsung Galaxy S series make such troubles?
zompi
  • 268
  • 1
  • 2
  • 10
  • `received a request to get rid of this alert`. Silly request. Tell them! Let them go to Settings of your app to swich the permission on. – greenapps Jul 13 '16 at 15:45
  • How are you mounting to OBB and what are you passing to UE4 as `LocalPath`? – Larry Schiefer Jul 13 '16 at 16:17
  • Also, what version(s) of Android are running on these different devices? – Larry Schiefer Jul 13 '16 at 16:52
  • @LarrySchiefer All of those devices runs on Android 6.0.1. The LocalPath is a const char* pointing to /storage/emulated/0/Android/obb/com.company.productname/main.10003.com.company.productname.obb Exactly it is a combination of the getExternalStorageDirectory() + "/Android/obb/" + PackageName If You have access to UE4 github You can check the source code https://github.com/EpicGames/UnrealEngine/blob/release/Engine/Source/Runtime/Core/Private/Android/AndroidFile.cpp (MountOBB function) – zompi Jul 14 '16 at 08:13

4 Answers4

10

Ok, it seems the issue was listed somewhere else, but I couldn't find because I didn't know it was the exact issue...

https://code.google.com/p/android/issues/detail?id=197287

The API23 for some reason set's the user of the OBB to root, not the proper user and because of it the app has no access to it.

After rebooting the device everything works fine.

At this point there is no clear workaround for this.

Thanks @Larry for providing all necessary and helpful information :)

zompi
  • 268
  • 1
  • 2
  • 10
2

You are kindly invited to check if your application from store has writting permission.

Ciprian Jijie
  • 479
  • 5
  • 20
1

Thanks to @Larry for pointing to right solution.

For those, who is wondering, why AOSP APK Expansion Support libray is not working without permission, here is the modified APKExpansionSupport.java

package com.android.vending.expansion.zipfile;
/*
 * Copyright (C) 2012 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import java.io.File;
import java.io.IOException;
import java.util.Vector;

import android.content.Context;

public class APKExpansionSupport {
    public static String[] getAPKExpansionFiles(Context ctx, int mainVersion, int patchVersion) {
        String packageName = ctx.getPackageName();
        Vector<String> ret = new Vector<>(2);
        File expPath = ctx.getObbDir();
        // Check that expansion file path exists
        if (expPath.exists()) {
            if ( mainVersion > 0 ) {
                String strMainPath = expPath + File.separator + "main." + mainVersion + "." + packageName + ".obb";
                File main = new File(strMainPath);
                if ( main.isFile() ) {
                    ret.add(strMainPath);
                }
            }
            if ( patchVersion > 0 ) {
                String strPatchPath = expPath + File.separator + "patch." + mainVersion + "." + packageName + ".obb";
                File main = new File(strPatchPath);
                if ( main.isFile() ) {
                    ret.add(strPatchPath);
                }
            }
        }
        String[] retArray = new String[ret.size()];
        ret.toArray(retArray);
        return retArray;
    }

    public static ZipResourceFile getResourceZipFile(String[] expansionFiles) throws IOException {
        ZipResourceFile apkExpansionFile = null;
        for (String expansionFilePath : expansionFiles) {
            if ( null == apkExpansionFile ) {
                apkExpansionFile = new ZipResourceFile(expansionFilePath);
            } else {
                apkExpansionFile.addPatchFile(expansionFilePath);
            }
        }
        return apkExpansionFile;
    }

    public static ZipResourceFile getAPKExpansionZipFile(Context ctx, int mainVersion, int patchVersion) throws IOException{
        String[] expansionFiles = getAPKExpansionFiles(ctx, mainVersion, patchVersion);
        return getResourceZipFile(expansionFiles);
    }
}
Dmytro Rostopira
  • 10,588
  • 4
  • 64
  • 86
0

Providing an answer here based on the above comment thread. The problem is the way that LocalPath is being provided. In Android, the filesystem paths are not consistent between different manufacturers and you should make no assumptions about storage paths.

So rather than have this (just example code):

public class MyActivity extends Activity {
    static final String LocalPath = "/storage/emulated/0/Android/obb/com.company.productname/main.10003.com.company.p‌​roductname.obb ";
    ...
}

You need to do something like this:

public class MyActivity extends Activity {
    File mObb;
    ...

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate();
        ...

        mObb = new File(getObbDir(), getPackageName() + ".obb");

        //  Get the path for passing to UE4 later
        String LocalPath = mObb.getAbsolutePath();
    }

This will ensure that your code gets the correct OBB location for your app on primary external storage for the given device. You can then save the OBB there and later on when your app gets UE4 started it can point right to the file.

An app's location on primary external storage for OBB (and other files) does not require permissions to read and write for that app. If the path on external storage is not one generated by the above methods then the WRITE_EXTERNAL_STORAGE permission is needed in order to write data.

Larry Schiefer
  • 15,687
  • 2
  • 27
  • 33
  • 1
    Thanks for a tip, but still... I took the address using getObbDir() and getAbsolutePath() and it returned... /storage/emulated/0/Android/obb/com.company.productname ;) I also checked the errno from open method and it's `EACCES 13 /* Permission denied */` There is also one thing... I install the game by putting apk and obb to the created directory in Android/obb and install APK. When I put Obb in root of storage and then copy it via inside Android File Manager to the proper directory and then install... then it works and has access... – zompi Jul 14 '16 at 12:49
  • Is the UE4 engine running within your app's process, or is it a separate component / package? – Larry Schiefer Jul 14 '16 at 12:52
  • OK, it sounds very odd what you're doing to install the app and obb file. The app installation should be done via `adb` or the play store, so no special storage location needed. Typically your obb file(s) are downloaded by the app and placed into the obb directory *or* you can host them on Google Play and pull them down in that way. The framework needs to be the thing that creates the obb directory, so the APK must be installed first so permissions get setup correctly. If you are manually creating the directory (adb shell?) then trying to use it, that will cause problems. – Larry Schiefer Jul 14 '16 at 12:56
  • UE4 engine is running within the app (it means the engine and the game is the same entity). What's more mystarious, I've checked the file with **getExternalStorageState** and received **MEDIA_MOUNTED** as result. So... it should be accessible, but not via C open? :/ I was also installing the app via Alpha Tests and the same problem occurs. – zompi Jul 14 '16 at 13:15
  • Ok, so I've installed the apk via adb and then simply put the obb to the created by application directory in Android/obb. And now it works... But why it didn't work when the game was installed via Alpha Test from Google Store? :( – zompi Jul 14 '16 at 13:24
  • It should be OK if created by install from the play store or adb and you're using the `getObbDir()` method. If using the constant string it will fail on some devices as the mount points differ. – Larry Schiefer Jul 14 '16 at 13:50
  • 1
    After quite a long search I found the issue here https://code.google.com/p/android/issues/detail?id=197287 and here http://comments.gmane.org/gmane.comp.handhelds.android.devel/244716 and I can confirm that the user of the installed app i root not the user. – zompi Jul 14 '16 at 15:43