Add partial support for Android scoped storage.

This is done by providing API access to app specific directories which don't have any limitations and allows us to bump the target sdk version to 30.
In addition, we're also bumping the min sdk version to 19 as version 18 is no longer supported by Google Play Services and only account of 0.3% of Android devices.

(cherry picked from commit c88d1608ab)
This commit is contained in:
ne0fhyk 2021-07-10 18:39:31 -07:00 committed by Rémi Verschelde
parent 7054f93e10
commit 2eb8875b77
No known key found for this signature in database
GPG key ID: C3336907360768E1
21 changed files with 125 additions and 269 deletions

View file

@ -1191,9 +1191,8 @@ bool _OS::is_keep_screen_on() const {
return OS::get_singleton()->is_keep_screen_on();
}
String _OS::get_system_dir(SystemDir p_dir) const {
return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir));
String _OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return OS::get_singleton()->get_system_dir(OS::SystemDir(p_dir), p_shared_storage);
}
String _OS::get_scancode_string(uint32_t p_code) const {
@ -1410,7 +1409,7 @@ void _OS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_dynamic_memory_usage"), &_OS::get_dynamic_memory_usage);
ClassDB::bind_method(D_METHOD("get_user_data_dir"), &_OS::get_user_data_dir);
ClassDB::bind_method(D_METHOD("get_system_dir", "dir"), &_OS::get_system_dir);
ClassDB::bind_method(D_METHOD("get_system_dir", "dir", "shared_storage"), &_OS::get_system_dir, DEFVAL(true));
ClassDB::bind_method(D_METHOD("get_unique_id"), &_OS::get_unique_id);
ClassDB::bind_method(D_METHOD("is_ok_left_and_cancel_right"), &_OS::is_ok_left_and_cancel_right);

View file

@ -347,7 +347,7 @@ public:
SCREEN_ORIENTATION_SENSOR,
};
String get_system_dir(SystemDir p_dir) const;
String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
String get_user_data_dir() const;

View file

@ -372,8 +372,7 @@ String OS::get_resource_dir() const {
}
// Access system-specific dirs like Documents, Downloads, etc.
String OS::get_system_dir(SystemDir p_dir) const {
String OS::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return ".";
}

View file

@ -453,7 +453,7 @@ public:
SYSTEM_DIR_RINGTONES,
};
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
virtual Error move_to_trash(const String &p_path) { return FAILED; }

View file

@ -379,9 +379,11 @@
<method name="get_system_dir" qualifiers="const">
<return type="String" />
<argument index="0" name="dir" type="int" enum="OS.SystemDir" />
<argument index="1" name="shared_storage" type="bool" default="true" />
<description>
Returns the actual path to commonly used folders across different platforms. Available locations are specified in [enum SystemDir].
[b]Note:[/b] This method is implemented on Android, Linux, macOS and Windows.
[b]Note:[/b] Shared storage is implemented on Android and allows to differentiate between app specific and shared directories. Shared directories have additional restrictions on Android.
</description>
</method>
<method name="get_system_time_msecs" qualifiers="const">

View file

@ -852,7 +852,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
Vector<String> perms;
_get_permissions(p_preset, p_give_internet, perms);
for (int i = 0; i < perms.size(); i++) {
manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
String permission = perms.get(i);
if (permission == "android.permission.WRITE_EXTERNAL_STORAGE" || permission == "android.permission.READ_EXTERNAL_STORAGE") {
manifest_text += vformat(" <uses-permission android:name=\"%s\" android:maxSdkVersion=\"29\" />\n", permission);
} else {
manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", permission);
}
}
manifest_text += _get_xr_features_tag(p_preset);
@ -1009,7 +1014,6 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
if (tname == "application" && attrname == "requestLegacyExternalStorage") {
encode_uint32(has_storage_permission ? 0xFFFFFFFF : 0, &p_manifest.write[iofs + 16]);
}

View file

@ -1,8 +1,8 @@
ext.versions = [
androidGradlePlugin: '4.2.1',
compileSdk : 29,
minSdk : 18,
targetSdk : 29,
androidGradlePlugin: '4.2.2',
compileSdk : 30,
minSdk : 19,
targetSdk : 30,
buildTools : '30.0.3',
supportCoreUtils : '1.0.0',
kotlinVersion : '1.5.10',

View file

@ -70,7 +70,6 @@ import android.os.Looper;
import android.os.Messenger;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.provider.Settings.Secure;
import android.view.Display;
import android.view.InputDevice;
import android.view.KeyEvent;
@ -603,7 +602,6 @@ public class Godot extends Fragment implements SensorEventListener, IDownloaderC
final Activity activity = getActivity();
io = new GodotIO(activity);
io.unique_id = Secure.getString(activity.getContentResolver(), Secure.ANDROID_ID);
GodotLib.io = io;
netUtils = new GodotNetUtils(activity);
mSensorManager = (SensorManager)activity.getSystemService(Context.SENSOR_SERVICE);

View file

@ -30,17 +30,19 @@
package org.godotengine.godot;
import org.godotengine.godot.input.*;
import org.godotengine.godot.input.GodotEditText;
import android.app.Activity;
import android.content.*;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.graphics.Point;
import android.media.*;
import android.net.Uri;
import android.os.*;
import android.os.Build;
import android.os.Environment;
import android.provider.Settings;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.SparseArray;
@ -49,15 +51,16 @@ import android.view.DisplayCutout;
import android.view.WindowInsets;
import java.io.IOException;
import java.io.InputStream;
import java.util.Locale;
// Wrapper for native library
public class GodotIO {
private static final String TAG = GodotIO.class.getSimpleName();
AssetManager am;
final Activity activity;
private final AssetManager am;
private final Activity activity;
private final String uniqueId;
GodotEditText edit;
MediaPlayer mediaPlayer;
@ -70,185 +73,6 @@ public class GodotIO {
final int SCREEN_SENSOR_PORTRAIT = 5;
final int SCREEN_SENSOR = 6;
/////////////////////////
/// FILES
/////////////////////////
public int last_file_id = 1;
class AssetData {
public boolean eof = false;
public String path;
public InputStream is;
public int len;
public int pos;
}
SparseArray<AssetData> streams;
public int file_open(String path, boolean write) {
//System.out.printf("file_open: Attempt to Open %s\n",path);
//Log.v("MyApp", "TRYING TO OPEN FILE: " + path);
if (write)
return -1;
AssetData ad = new AssetData();
try {
ad.is = am.open(path);
} catch (Exception e) {
//System.out.printf("Exception on file_open: %s\n",path);
return -1;
}
try {
ad.len = ad.is.available();
} catch (Exception e) {
System.out.printf("Exception availabling on file_open: %s\n", path);
return -1;
}
ad.path = path;
ad.pos = 0;
++last_file_id;
streams.put(last_file_id, ad);
return last_file_id;
}
public int file_get_size(int id) {
if (streams.get(id) == null) {
System.out.printf("file_get_size: Invalid file id: %d\n", id);
return -1;
}
return streams.get(id).len;
}
public void file_seek(int id, int bytes) {
if (streams.get(id) == null) {
System.out.printf("file_get_size: Invalid file id: %d\n", id);
return;
}
//seek sucks
AssetData ad = streams.get(id);
if (bytes > ad.len)
bytes = ad.len;
if (bytes < 0)
bytes = 0;
try {
if (bytes > (int)ad.pos) {
int todo = bytes - (int)ad.pos;
while (todo > 0) {
todo -= ad.is.skip(todo);
}
ad.pos = bytes;
} else if (bytes < (int)ad.pos) {
ad.is = am.open(ad.path);
ad.pos = bytes;
int todo = bytes;
while (todo > 0) {
todo -= ad.is.skip(todo);
}
}
ad.eof = false;
} catch (IOException e) {
System.out.printf("Exception on file_seek: %s\n", e);
return;
}
}
public int file_tell(int id) {
if (streams.get(id) == null) {
System.out.printf("file_read: Can't tell eof for invalid file id: %d\n", id);
return 0;
}
AssetData ad = streams.get(id);
return ad.pos;
}
public boolean file_eof(int id) {
if (streams.get(id) == null) {
System.out.printf("file_read: Can't check eof for invalid file id: %d\n", id);
return false;
}
AssetData ad = streams.get(id);
return ad.eof;
}
public byte[] file_read(int id, int bytes) {
if (streams.get(id) == null) {
System.out.printf("file_read: Can't read invalid file id: %d\n", id);
return new byte[0];
}
AssetData ad = streams.get(id);
if (ad.pos + bytes > ad.len) {
bytes = ad.len - ad.pos;
ad.eof = true;
}
if (bytes == 0) {
return new byte[0];
}
byte[] buf1 = new byte[bytes];
int r = 0;
try {
r = ad.is.read(buf1);
} catch (IOException e) {
System.out.printf("Exception on file_read: %s\n", e);
return new byte[bytes];
}
if (r == 0) {
return new byte[0];
}
ad.pos += r;
if (r < bytes) {
byte[] buf2 = new byte[r];
for (int i = 0; i < r; i++)
buf2[i] = buf1[i];
return buf2;
} else {
return buf1;
}
}
public void file_close(int id) {
if (streams.get(id) == null) {
System.out.printf("file_close: Can't close invalid file id: %d\n", id);
return;
}
streams.remove(id);
}
/////////////////////////
/// DIRECTORIES
/////////////////////////
@ -260,9 +84,9 @@ public class GodotIO {
public String path;
}
public int last_dir_id = 1;
private int last_dir_id = 1;
SparseArray<AssetDir> dirs;
private final SparseArray<AssetDir> dirs;
public int dir_open(String path) {
@ -283,7 +107,6 @@ public class GodotIO {
return -1;
}
//System.out.printf("Opened dir: %s\n",path);
++last_dir_id;
dirs.put(last_dir_id, ad);
@ -349,9 +172,15 @@ public class GodotIO {
am = p_activity.getAssets();
activity = p_activity;
//streams = new HashMap<Integer, AssetData>();
streams = new SparseArray<AssetData>();
dirs = new SparseArray<AssetDir>();
dirs = new SparseArray<>();
String androidId = Settings.Secure.getString(activity.getContentResolver(),
Settings.Secure.ANDROID_ID);
if (androidId == null) {
androidId = "";
}
uniqueId = androidId;
}
/////////////////////////
@ -451,7 +280,6 @@ public class GodotIO {
public int openURI(String p_uri) {
try {
Log.v("MyApp", "TRYING TO OPEN URI: " + p_uri);
String path = p_uri;
String type = "";
if (path.startsWith("/")) {
@ -479,8 +307,11 @@ public class GodotIO {
}
}
public String getDataDir() {
public String getCacheDir() {
return activity.getCacheDir().getAbsolutePath();
}
public String getDataDir() {
return activity.getFilesDir().getAbsolutePath();
}
@ -612,53 +443,54 @@ public class GodotIO {
public static final int SYSTEM_DIR_PICTURES = 6;
public static final int SYSTEM_DIR_RINGTONES = 7;
public String getSystemDir(int idx) {
String what = "";
public String getSystemDir(int idx, boolean shared_storage) {
String what;
switch (idx) {
case SYSTEM_DIR_DESKTOP: {
//what=Environment.DIRECTORY_DOCUMENTS;
what = Environment.DIRECTORY_DOWNLOADS;
case SYSTEM_DIR_DESKTOP:
default: {
what = null; // This leads to the app specific external root directory.
} break;
case SYSTEM_DIR_DCIM: {
what = Environment.DIRECTORY_DCIM;
} break;
} break;
case SYSTEM_DIR_DOCUMENTS: {
what = Environment.DIRECTORY_DOWNLOADS;
//what=Environment.DIRECTORY_DOCUMENTS;
what = Environment.DIRECTORY_DOCUMENTS;
} break;
case SYSTEM_DIR_DOWNLOADS: {
what = Environment.DIRECTORY_DOWNLOADS;
} break;
case SYSTEM_DIR_MOVIES: {
what = Environment.DIRECTORY_MOVIES;
} break;
case SYSTEM_DIR_MUSIC: {
what = Environment.DIRECTORY_MUSIC;
} break;
case SYSTEM_DIR_PICTURES: {
what = Environment.DIRECTORY_PICTURES;
} break;
case SYSTEM_DIR_RINGTONES: {
what = Environment.DIRECTORY_RINGTONES;
} break;
}
if (what.equals(""))
return "";
return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
if (shared_storage) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.w(TAG, "Shared storage access is limited on Android 10 and higher.");
}
return Environment.getExternalStoragePublicDirectory(what).getAbsolutePath();
} else {
return activity.getExternalFilesDir(what).getAbsolutePath();
}
}
protected static final String PREFS_FILE = "device_id.xml";
protected static final String PREFS_DEVICE_ID = "device_id";
public static String unique_id = "";
public String getUniqueID() {
return unique_id;
return uniqueId;
}
}

View file

@ -48,6 +48,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
}
_open_URI = p_env->GetMethodID(cls, "openURI", "(Ljava/lang/String;)I");
_get_cache_dir = p_env->GetMethodID(cls, "getCacheDir", "()Ljava/lang/String;");
_get_data_dir = p_env->GetMethodID(cls, "getDataDir", "()Ljava/lang/String;");
_get_locale = p_env->GetMethodID(cls, "getLocale", "()Ljava/lang/String;");
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
@ -58,7 +59,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(I)Ljava/lang/String;");
_get_system_dir = p_env->GetMethodID(cls, "getSystemDir", "(IZ)Ljava/lang/String;");
_play_video = p_env->GetMethodID(cls, "playVideo", "(Ljava/lang/String;)V");
_is_video_playing = p_env->GetMethodID(cls, "isVideoPlaying", "()Z");
_pause_video = p_env->GetMethodID(cls, "pauseVideo", "()V");
@ -85,6 +86,17 @@ Error GodotIOJavaWrapper::open_uri(const String &p_uri) {
}
}
String GodotIOJavaWrapper::get_cache_dir() {
if (_get_cache_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String());
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_cache_dir);
return jstring_to_string(s, env);
} else {
return String();
}
}
String GodotIOJavaWrapper::get_user_data_dir() {
if (_get_data_dir) {
JNIEnv *env = get_jni_env();
@ -192,11 +204,11 @@ int GodotIOJavaWrapper::get_screen_orientation() const {
}
}
String GodotIOJavaWrapper::get_system_dir(int p_dir) {
String GodotIOJavaWrapper::get_system_dir(int p_dir, bool p_shared_storage) {
if (_get_system_dir) {
JNIEnv *env = get_jni_env();
ERR_FAIL_COND_V(env == nullptr, String("."));
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir);
jstring s = (jstring)env->CallObjectMethod(godot_io_instance, _get_system_dir, p_dir, p_shared_storage);
return jstring_to_string(s, env);
} else {
return String(".");

View file

@ -46,6 +46,7 @@ private:
jclass cls;
jmethodID _open_URI = 0;
jmethodID _get_cache_dir = 0;
jmethodID _get_data_dir = 0;
jmethodID _get_locale = 0;
jmethodID _get_model = 0;
@ -69,6 +70,7 @@ public:
jobject get_instance();
Error open_uri(const String &p_uri);
String get_cache_dir();
String get_user_data_dir();
String get_locale();
String get_model();
@ -82,7 +84,7 @@ public:
void set_vk_height(int p_height);
void set_screen_orientation(int p_orient);
int get_screen_orientation() const;
String get_system_dir(int p_dir);
String get_system_dir(int p_dir, bool p_shared_storage);
void play_video(const String &p_path);
bool is_video_playing();
void pause_video();

View file

@ -51,6 +51,23 @@
#include "java_godot_io_wrapper.h"
#include "java_godot_wrapper.h"
String _remove_symlink(const String &dir) {
// Workaround for Android 6.0+ using a symlink.
// Save the current directory.
char current_dir_name[2048];
getcwd(current_dir_name, 2048);
// Change directory to the external data directory.
chdir(dir.utf8().get_data());
// Get the actual directory without the potential symlink.
char dir_name_wihout_symlink[2048];
getcwd(dir_name_wihout_symlink, 2048);
// Convert back to a String.
String dir_without_symlink(dir_name_wihout_symlink);
// Restore original current directory.
chdir(current_dir_name);
return dir_without_symlink;
}
class AndroidLogger : public Logger {
public:
virtual void logv(const char *p_format, va_list p_list, bool p_err) {
@ -802,34 +819,28 @@ int OS_Android::get_screen_dpi(int p_screen) const {
return godot_io_java->get_screen_dpi();
}
String OS_Android::get_user_data_dir() const {
String OS_Android::get_data_path() const {
return get_user_data_dir();
}
String OS_Android::get_user_data_dir() const {
if (data_dir_cache != String())
return data_dir_cache;
String data_dir = godot_io_java->get_user_data_dir();
if (data_dir != "") {
//store current dir
char real_current_dir_name[2048];
getcwd(real_current_dir_name, 2048);
//go to data dir
chdir(data_dir.utf8().get_data());
//get actual data dir, so we resolve potential symlink (Android 6.0+ seems to use symlink)
char data_current_dir_name[2048];
getcwd(data_current_dir_name, 2048);
//cache by parsing utf8
data_dir_cache.parse_utf8(data_current_dir_name);
//restore original dir so we don't mess things up
chdir(real_current_dir_name);
data_dir_cache = _remove_symlink(data_dir);
return data_dir_cache;
}
return ".";
}
String OS_Android::get_cache_path() const {
String cache_dir = godot_io_java->get_cache_dir();
if (cache_dir != "") {
cache_dir = _remove_symlink(cache_dir);
return cache_dir;
}
return ".";
}
@ -869,9 +880,8 @@ void OS_Android::native_video_pause() {
godot_io_java->pause_video();
}
String OS_Android::get_system_dir(SystemDir p_dir) const {
return godot_io_java->get_system_dir(p_dir);
String OS_Android::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
return godot_io_java->get_system_dir(p_dir, p_shared_storage);
}
void OS_Android::native_video_stop() {

View file

@ -188,6 +188,8 @@ public:
virtual Error shell_open(String p_uri);
virtual String get_user_data_dir() const;
virtual String get_data_path() const;
virtual String get_cache_path() const;
virtual String get_resource_dir() const;
virtual String get_locale() const;
virtual void set_clipboard(const String &p_text);
@ -197,7 +199,7 @@ public:
virtual String get_unique_id() const;
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
void process_accelerometer(const Vector3 &p_accelerometer);
void process_gravity(const Vector3 &p_gravity);

View file

@ -226,7 +226,7 @@ public:
virtual String get_bundle_resource_dir() const;
virtual String get_godot_dir_name() const;
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
virtual bool can_draw() const;

View file

@ -2297,8 +2297,7 @@ String OS_OSX::get_godot_dir_name() const {
return String(VERSION_SHORT_NAME).capitalize();
}
String OS_OSX::get_system_dir(SystemDir p_dir) const {
String OS_OSX::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
NSSearchPathDirectory id;
bool found = true;

View file

@ -258,8 +258,7 @@ String OS_Server::get_cache_path() const {
}
}
String OS_Server::get_system_dir(SystemDir p_dir) const {
String OS_Server::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
String xdgparam;
switch (p_dir) {

View file

@ -120,7 +120,7 @@ public:
virtual String get_data_path() const;
virtual String get_cache_path() const;
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
void disable_crash_handler();
bool is_disable_crash_handler() const;

View file

@ -3486,8 +3486,7 @@ String OS_Windows::get_godot_dir_name() const {
return String(VERSION_SHORT_NAME).capitalize();
}
String OS_Windows::get_system_dir(SystemDir p_dir) const {
String OS_Windows::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
KNOWNFOLDERID id;
switch (p_dir) {

View file

@ -535,7 +535,7 @@ public:
virtual String get_cache_path() const;
virtual String get_godot_dir_name() const;
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
virtual String get_user_data_dir() const;
virtual String get_unique_id() const;

View file

@ -3237,8 +3237,7 @@ String OS_X11::get_cache_path() const {
}
}
String OS_X11::get_system_dir(SystemDir p_dir) const {
String OS_X11::get_system_dir(SystemDir p_dir, bool p_shared_storage) const {
String xdgparam;
switch (p_dir) {

View file

@ -279,7 +279,7 @@ public:
virtual String get_data_path() const;
virtual String get_cache_path() const;
virtual String get_system_dir(SystemDir p_dir) const;
virtual String get_system_dir(SystemDir p_dir, bool p_shared_storage = true) const;
virtual Error shell_open(String p_uri);