2023-03-06 14:23:56 +01:00
# include <inttypes.h>
2021-06-05 19:29:23 +02:00
# include "Common/File/AndroidStorage.h"
# include "Common/StringUtils.h"
# include "Common/Log.h"
# include "Common/TimeUtil.h"
# include "android/jni/app-android.h"
# if PPSSPP_PLATFORM(ANDROID) && !defined(__LIBRETRO__)
static jmethodID openContentUri ;
static jmethodID listContentUriDir ;
static jmethodID contentUriCreateFile ;
static jmethodID contentUriCreateDirectory ;
2021-07-20 12:32:08 +02:00
static jmethodID contentUriCopyFile ;
2021-07-25 15:32:15 +02:00
static jmethodID contentUriMoveFile ;
2021-06-05 19:29:23 +02:00
static jmethodID contentUriRemoveFile ;
2021-07-18 22:28:30 +02:00
static jmethodID contentUriRenameFileTo ;
2021-06-05 19:29:23 +02:00
static jmethodID contentUriGetFileInfo ;
2021-07-18 23:06:14 +02:00
static jmethodID contentUriFileExists ;
2021-06-05 19:29:23 +02:00
static jmethodID contentUriGetFreeStorageSpace ;
static jmethodID filePathGetFreeStorageSpace ;
2021-07-20 12:55:33 +02:00
static jmethodID isExternalStoragePreservedLegacy ;
2021-09-11 16:36:35 +02:00
static jmethodID computeRecursiveDirectorySize ;
2021-06-05 19:29:23 +02:00
static jobject g_nativeActivity ;
void Android_StorageSetNativeActivity ( jobject nativeActivity ) {
g_nativeActivity = nativeActivity ;
}
void Android_RegisterStorageCallbacks ( JNIEnv * env , jobject obj ) {
openContentUri = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " openContentUri " , " (Ljava/lang/String;Ljava/lang/String;)I " ) ;
_dbg_assert_ ( openContentUri ) ;
listContentUriDir = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " listContentUriDir " , " (Ljava/lang/String;)[Ljava/lang/String; " ) ;
_dbg_assert_ ( listContentUriDir ) ;
2021-07-25 14:52:52 +02:00
contentUriCreateDirectory = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriCreateDirectory " , " (Ljava/lang/String;Ljava/lang/String;)I " ) ;
2021-06-05 19:29:23 +02:00
_dbg_assert_ ( contentUriCreateDirectory ) ;
2021-07-25 14:52:52 +02:00
contentUriCreateFile = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriCreateFile " , " (Ljava/lang/String;Ljava/lang/String;)I " ) ;
2021-06-05 19:29:23 +02:00
_dbg_assert_ ( contentUriCreateFile ) ;
2021-07-25 14:46:29 +02:00
contentUriCopyFile = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriCopyFile " , " (Ljava/lang/String;Ljava/lang/String;)I " ) ;
2021-07-20 12:32:08 +02:00
_dbg_assert_ ( contentUriCopyFile ) ;
2021-07-25 14:46:29 +02:00
contentUriRemoveFile = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriRemoveFile " , " (Ljava/lang/String;)I " ) ;
2021-06-05 19:29:23 +02:00
_dbg_assert_ ( contentUriRemoveFile ) ;
2021-07-25 14:46:29 +02:00
contentUriMoveFile = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriMoveFile " , " (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)I " ) ;
2021-07-25 15:32:15 +02:00
_dbg_assert_ ( contentUriMoveFile ) ;
2021-07-25 14:12:57 +02:00
contentUriRenameFileTo = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriRenameFileTo " , " (Ljava/lang/String;Ljava/lang/String;)I " ) ;
2021-07-18 22:28:30 +02:00
_dbg_assert_ ( contentUriRenameFileTo ) ;
2021-06-05 19:29:23 +02:00
contentUriGetFileInfo = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriGetFileInfo " , " (Ljava/lang/String;)Ljava/lang/String; " ) ;
_dbg_assert_ ( contentUriGetFileInfo ) ;
2021-07-18 23:06:14 +02:00
contentUriFileExists = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriFileExists " , " (Ljava/lang/String;)Z " ) ;
_dbg_assert_ ( contentUriFileExists ) ;
2021-06-05 19:29:23 +02:00
contentUriGetFreeStorageSpace = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " contentUriGetFreeStorageSpace " , " (Ljava/lang/String;)J " ) ;
_dbg_assert_ ( contentUriGetFreeStorageSpace ) ;
filePathGetFreeStorageSpace = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " filePathGetFreeStorageSpace " , " (Ljava/lang/String;)J " ) ;
_dbg_assert_ ( filePathGetFreeStorageSpace ) ;
2021-07-20 12:55:33 +02:00
isExternalStoragePreservedLegacy = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " isExternalStoragePreservedLegacy " , " ()Z " ) ;
_dbg_assert_ ( isExternalStoragePreservedLegacy ) ;
2021-09-11 16:36:35 +02:00
computeRecursiveDirectorySize = env - > GetMethodID ( env - > GetObjectClass ( obj ) , " computeRecursiveDirectorySize " , " (Ljava/lang/String;)J " ) ;
_dbg_assert_ ( computeRecursiveDirectorySize ) ;
2021-06-05 19:29:23 +02:00
}
2023-11-13 23:24:42 +01:00
bool Android_IsContentUri ( std : : string_view filename ) {
2021-06-05 19:29:23 +02:00
return startsWith ( filename , " content:// " ) ;
}
2023-11-13 23:24:42 +01:00
int Android_OpenContentUriFd ( std : : string_view filename , Android_OpenContentUriMode mode ) {
2021-06-05 19:29:23 +02:00
if ( ! g_nativeActivity ) {
return - 1 ;
}
2023-11-13 23:24:42 +01:00
std : : string fname ( filename ) ;
2021-06-05 19:29:23 +02:00
// PPSSPP adds an ending slash to directories before looking them up.
// TODO: Fix that in the caller (or don't call this for directories).
if ( fname . back ( ) = = ' / ' )
fname . pop_back ( ) ;
auto env = getEnv ( ) ;
const char * modeStr = " " ;
switch ( mode ) {
case Android_OpenContentUriMode : : READ : modeStr = " r " ; break ;
case Android_OpenContentUriMode : : READ_WRITE : modeStr = " rw " ; break ;
case Android_OpenContentUriMode : : READ_WRITE_TRUNCATE : modeStr = " rwt " ; break ;
}
jstring j_filename = env - > NewStringUTF ( fname . c_str ( ) ) ;
jstring j_mode = env - > NewStringUTF ( modeStr ) ;
int fd = env - > CallIntMethod ( g_nativeActivity , openContentUri , j_filename , j_mode ) ;
return fd ;
}
2021-07-25 14:52:52 +02:00
StorageError Android_CreateDirectory ( const std : : string & rootTreeUri , const std : : string & dirName ) {
2021-06-05 19:29:23 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:52:52 +02:00
return StorageError : : UNKNOWN ;
2021-06-05 19:29:23 +02:00
}
auto env = getEnv ( ) ;
jstring paramRoot = env - > NewStringUTF ( rootTreeUri . c_str ( ) ) ;
jstring paramDirName = env - > NewStringUTF ( dirName . c_str ( ) ) ;
2021-07-25 14:52:52 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriCreateDirectory , paramRoot , paramDirName ) ) ;
2021-06-05 19:29:23 +02:00
}
2021-07-25 14:52:52 +02:00
StorageError Android_CreateFile ( const std : : string & parentTreeUri , const std : : string & fileName ) {
2021-06-05 19:29:23 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:52:52 +02:00
return StorageError : : UNKNOWN ;
2021-06-05 19:29:23 +02:00
}
auto env = getEnv ( ) ;
jstring paramRoot = env - > NewStringUTF ( parentTreeUri . c_str ( ) ) ;
jstring paramFileName = env - > NewStringUTF ( fileName . c_str ( ) ) ;
2021-07-25 14:52:52 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriCreateFile , paramRoot , paramFileName ) ) ;
2021-06-05 19:29:23 +02:00
}
2021-07-25 14:46:29 +02:00
StorageError Android_CopyFile ( const std : : string & fileUri , const std : : string & destParentUri ) {
2021-07-20 12:32:08 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:46:29 +02:00
return StorageError : : UNKNOWN ;
2021-07-20 12:32:08 +02:00
}
auto env = getEnv ( ) ;
jstring paramFileName = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
jstring paramDestParentUri = env - > NewStringUTF ( destParentUri . c_str ( ) ) ;
2021-07-25 14:46:29 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriCopyFile , paramFileName , paramDestParentUri ) ) ;
2021-07-20 12:32:08 +02:00
}
2021-07-25 14:46:29 +02:00
StorageError Android_MoveFile ( const std : : string & fileUri , const std : : string & srcParentUri , const std : : string & destParentUri ) {
2021-07-25 15:32:15 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:46:29 +02:00
return StorageError : : UNKNOWN ;
2021-07-25 15:32:15 +02:00
}
auto env = getEnv ( ) ;
jstring paramFileName = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
jstring paramSrcParentUri = env - > NewStringUTF ( srcParentUri . c_str ( ) ) ;
jstring paramDestParentUri = env - > NewStringUTF ( destParentUri . c_str ( ) ) ;
2021-07-25 14:46:29 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriMoveFile , paramFileName , paramSrcParentUri , paramDestParentUri ) ) ;
2021-07-25 15:32:15 +02:00
}
2021-07-25 14:46:29 +02:00
StorageError Android_RemoveFile ( const std : : string & fileUri ) {
2021-06-05 19:29:23 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:46:29 +02:00
return StorageError : : UNKNOWN ;
2021-06-05 19:29:23 +02:00
}
auto env = getEnv ( ) ;
jstring paramFileName = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
2021-07-25 14:46:29 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriRemoveFile , paramFileName ) ) ;
2021-06-05 19:29:23 +02:00
}
2021-07-25 14:12:57 +02:00
StorageError Android_RenameFileTo ( const std : : string & fileUri , const std : : string & newName ) {
2021-07-18 22:28:30 +02:00
if ( ! g_nativeActivity ) {
2021-07-25 14:12:57 +02:00
return StorageError : : UNKNOWN ;
2021-07-18 22:28:30 +02:00
}
auto env = getEnv ( ) ;
jstring paramFileUri = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
jstring paramNewName = env - > NewStringUTF ( newName . c_str ( ) ) ;
2021-07-25 14:12:57 +02:00
return StorageErrorFromInt ( env - > CallIntMethod ( g_nativeActivity , contentUriRenameFileTo , paramFileUri , paramNewName ) ) ;
2021-07-18 22:28:30 +02:00
}
2021-07-20 00:33:40 +02:00
// NOTE: Does not set fullName - you're supposed to already know it.
2021-06-05 19:29:23 +02:00
static bool ParseFileInfo ( const std : : string & line , File : : FileInfo * fileInfo ) {
std : : vector < std : : string > parts ;
SplitString ( line , ' | ' , parts ) ;
2021-07-20 00:33:40 +02:00
if ( parts . size ( ) ! = 4 ) {
2024-07-14 14:42:59 +02:00
ERROR_LOG ( Log : : FileSystem , " Bad format: %s " , line . c_str ( ) ) ;
2021-06-05 19:29:23 +02:00
return false ;
}
fileInfo - > name = std : : string ( parts [ 2 ] ) ;
fileInfo - > isDirectory = parts [ 0 ] [ 0 ] = = ' D ' ;
fileInfo - > exists = true ;
2021-06-06 10:50:58 +02:00
sscanf ( parts [ 1 ] . c_str ( ) , " % " PRIu64 , & fileInfo - > size ) ;
fileInfo - > isWritable = true ; // TODO: Should be passed as part of the string.
2022-07-13 23:40:38 -07:00
// TODO: For read-only mappings, reflect that here, similarly as with isWritable.
// Directories are normally executable (0111) which means they're traversable.
fileInfo - > access = fileInfo - > isDirectory ? 0777 : 0666 ;
2021-07-18 22:44:54 +02:00
uint64_t lastModifiedMs = 0 ;
2021-07-20 12:32:08 +02:00
sscanf ( parts [ 3 ] . c_str ( ) , " % " PRIu64 , & lastModifiedMs ) ;
2021-07-18 22:44:54 +02:00
// Convert from milliseconds
uint32_t lastModified = lastModifiedMs / 1000 ;
// We don't have better information, so let's just spam lastModified into all the date/time fields.
fileInfo - > mtime = lastModified ;
fileInfo - > ctime = lastModified ;
fileInfo - > atime = lastModified ;
2021-06-05 19:29:23 +02:00
return true ;
}
bool Android_GetFileInfo ( const std : : string & fileUri , File : : FileInfo * fileInfo ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
jstring paramFileUri = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
jstring str = ( jstring ) env - > CallObjectMethod ( g_nativeActivity , contentUriGetFileInfo , paramFileUri ) ;
if ( ! str ) {
return false ;
}
const char * charArray = env - > GetStringUTFChars ( str , 0 ) ;
bool retval = ParseFileInfo ( std : : string ( charArray ) , fileInfo ) ;
2021-07-20 00:33:40 +02:00
fileInfo - > fullName = Path ( fileUri ) ;
2021-06-05 19:29:23 +02:00
env - > DeleteLocalRef ( str ) ;
return retval & & fileInfo - > exists ;
}
2021-07-18 23:06:14 +02:00
bool Android_FileExists ( const std : : string & fileUri ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
jstring paramFileUri = env - > NewStringUTF ( fileUri . c_str ( ) ) ;
bool exists = env - > CallBooleanMethod ( g_nativeActivity , contentUriFileExists , paramFileUri ) ;
return exists ;
}
2022-10-09 11:37:19 -07:00
std : : vector < File : : FileInfo > Android_ListContentUri ( const std : : string & path , bool * exists ) {
2021-06-05 19:29:23 +02:00
if ( ! g_nativeActivity ) {
2022-10-09 11:37:19 -07:00
* exists = false ;
2021-06-05 19:29:23 +02:00
return std : : vector < File : : FileInfo > ( ) ;
}
auto env = getEnv ( ) ;
2022-10-09 11:37:19 -07:00
* exists = true ;
2021-06-05 19:29:23 +02:00
double start = time_now_d ( ) ;
jstring param = env - > NewStringUTF ( path . c_str ( ) ) ;
jobject retval = env - > CallObjectMethod ( g_nativeActivity , listContentUriDir , param ) ;
jobjectArray fileList = ( jobjectArray ) retval ;
std : : vector < File : : FileInfo > items ;
int size = env - > GetArrayLength ( fileList ) ;
for ( int i = 0 ; i < size ; i + + ) {
jstring str = ( jstring ) env - > GetObjectArrayElement ( fileList , i ) ;
const char * charArray = env - > GetStringUTFChars ( str , 0 ) ;
if ( charArray ) { // paranoia
2022-10-09 11:37:19 -07:00
std : : string line = charArray ;
2021-06-05 19:29:23 +02:00
File : : FileInfo info ;
2022-10-09 11:37:19 -07:00
if ( line = = " X " ) {
// Indicates an exception thrown, path doesn't exist.
* exists = false ;
} else if ( ParseFileInfo ( line , & info ) ) {
2021-07-20 00:33:40 +02:00
// We can just reconstruct the URI.
info . fullName = Path ( path ) / info . name ;
2021-06-05 19:29:23 +02:00
items . push_back ( info ) ;
}
}
env - > ReleaseStringUTFChars ( str , charArray ) ;
env - > DeleteLocalRef ( str ) ;
}
env - > DeleteLocalRef ( fileList ) ;
double elapsed = time_now_d ( ) - start ;
2023-12-08 14:05:12 +01:00
double threshold = 0.1 ;
if ( elapsed > = threshold ) {
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : FileSystem , " Listing directory on content URI '%s' took %0.3f s (%d files, log threshold = %0.3f) " , path . c_str ( ) , elapsed , ( int ) items . size ( ) , threshold ) ;
2021-06-06 10:52:51 +02:00
}
2021-06-05 19:29:23 +02:00
return items ;
}
int64_t Android_GetFreeSpaceByContentUri ( const std : : string & uri ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
jstring param = env - > NewStringUTF ( uri . c_str ( ) ) ;
return env - > CallLongMethod ( g_nativeActivity , contentUriGetFreeStorageSpace , param ) ;
}
int64_t Android_GetFreeSpaceByFilePath ( const std : : string & filePath ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
jstring param = env - > NewStringUTF ( filePath . c_str ( ) ) ;
return env - > CallLongMethod ( g_nativeActivity , filePathGetFreeStorageSpace , param ) ;
}
2021-09-11 16:36:35 +02:00
int64_t Android_ComputeRecursiveDirectorySize ( const std : : string & uri ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
jstring param = env - > NewStringUTF ( uri . c_str ( ) ) ;
2021-09-11 18:17:14 +02:00
double start = time_now_d ( ) ;
int64_t size = env - > CallLongMethod ( g_nativeActivity , computeRecursiveDirectorySize , param ) ;
double elapsed = time_now_d ( ) - start ;
2024-07-14 14:42:59 +02:00
INFO_LOG ( Log : : IO , " ComputeRecursiveDirectorySize(%s) in %0.3f s " , uri . c_str ( ) , elapsed ) ;
2021-09-11 18:17:14 +02:00
return size ;
2021-09-11 16:36:35 +02:00
}
2021-07-20 12:55:33 +02:00
bool Android_IsExternalStoragePreservedLegacy ( ) {
if ( ! g_nativeActivity ) {
return false ;
}
auto env = getEnv ( ) ;
return env - > CallBooleanMethod ( g_nativeActivity , isExternalStoragePreservedLegacy ) ;
}
2021-07-25 14:12:57 +02:00
const char * Android_ErrorToString ( StorageError error ) {
2021-07-20 12:55:33 +02:00
switch ( error ) {
2021-07-25 14:12:57 +02:00
case StorageError : : SUCCESS : return " SUCCESS " ;
case StorageError : : UNKNOWN : return " UNKNOWN " ;
case StorageError : : NOT_FOUND : return " NOT_FOUND " ;
case StorageError : : DISK_FULL : return " DISK_FULL " ;
case StorageError : : ALREADY_EXISTS : return " ALREADY_EXISTS " ;
2021-07-20 12:55:33 +02:00
default : return " (UNKNOWN) " ;
}
}
2021-07-25 15:32:15 +02:00
# else
2021-08-04 23:21:28 +02:00
// These strings should never appear except on Android.
// Very hacky.
2021-07-25 15:32:15 +02:00
std : : string g_extFilesDir = " (IF YOU SEE THIS THERE'S A BUG) " ;
2021-08-04 23:21:28 +02:00
std : : string g_externalDir = " (IF YOU SEE THIS THERE'S A BUG (2)) " ;
2023-12-12 19:26:22 +03:00
std : : string g_nativeLibDir = " (IF YOU SEE THIS THERE'S A BUG (3)) " ;
2021-07-25 15:32:15 +02:00
2021-06-05 19:29:23 +02:00
# endif