Implement runtime permission request for Android 6.0 and later

Issue #175 resolved
Tae Hanazono created an issue

This is the code that I used and it seems work for my case.

This code needs to be added to LOVE GameActivity.java.

    protected final int[] externalStorageRequestRes = new int[1];
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
    {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);

        if (requestCode == 1)
        {
            synchronized (externalStorageRequestRes)
            {
                externalStorageRequestRes[0] = grantResults[0];
                externalStorageRequestRes.notify();
            }
        }
    }

    protected Activity self;
    @Keep
    public boolean hasExternalStorage() {
        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED)
        {
            if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.WRITE_EXTERNAL_STORAGE))
            {
                self = this;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run()
                    {
                        ActivityCompat.requestPermissions(self, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
                    }
                });

                synchronized (externalStorageRequestRes)
                {
                    try
                    {
                        externalStorageRequestRes.wait();
                    }
                    catch (InterruptedException ex) {
                        ex.printStackTrace();
                        return false;
                    }

                }
                return ActivityCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
            }
        } else
            return true;
        return false;
    }

Then this one in common/android.cpp (you already know how to create the function definition for the header right, based on the code below)

bool hasExternalStoragePermission()
{
    JNIEnv *env = (JNIEnv*) SDL_AndroidGetJNIEnv();

    jobject activity = (jobject) SDL_AndroidGetActivity();

    jclass clazz(env->GetObjectClass(activity));
    jmethodID method_id = env->GetMethodID(clazz, "hasExternalStorage", "()Z");

    jboolean a = env->CallBooleanMethod(activity, method_id);

    env->DeleteLocalRef(activity);
    env->DeleteLocalRef(clazz);
    return a;
}

modules/filesystem/Filesystem.cpp (replace existing definition)

void Filesystem::setAndroidSaveExternal(bool useExternal)
{
#ifdef LOVE_ANDROID
    if (useExternal && !love::android::hasExternalStoragePermission())
        throw love::Exception("External storage permission denied!");
#endif
    this->useExternal = useExternal;
}

modules/filesystem/wrap_Filesystem.cpp (replace existing definition)

int w_setAndroidSaveExternal(lua_State *L)
{
    bool useExternal = luax_optboolean(L, 1, false);
    luax_catchexcept(L, [&]() {
        instance()->setAndroidSaveExternal(useExternal);
    });
    return 0;
}

Note that it's actually not necessary to request external storage permission with t.externalstorage=true. Starting from KitKat, application is also granted permission to read and write in /sdcard/Android/data/package.name/files without WRITE_EXTERNAL_STORAGE support, but it's needed to look game in /sdcard/lovegame (I removed this in my custom modified LOVE, so it doesn't run arbitrary game)

PS: I used "critical" priority because Google requires all apps submitted in Play Store to target Android 8.0 SDK (API 26) and this month is last month to do it (I don't remember exactly if it's for already existing apps or for new apps).

Comments (1)

  1. Log in to comment