From 85d8a6bb3e2320ac86b59ff7574ade61bd0782b1 Mon Sep 17 00:00:00 2001 From: dgis Date: Sun, 30 Aug 2020 23:29:45 +0200 Subject: [PATCH] Improve the error scenarios when loading the KML files and its dependencies. - If the KML folder does not exist (like the first time), prompt the user to choose a new KML folder. - Move the KML folder in the JSON settings embedded in the state file because Windows cannot open the state file with KML url longer than 256 byte. - Prevent to auto save before launching the "Open...", "Save As...", "Load Object...", "Save Object...", etc... --- ReadMe.txt | 17 +- app/src/main/cpp/emu-jni.c | 137 ++-- app/src/main/cpp/win32-layer.c | 60 +- app/src/main/cpp/win32-layer.h | 6 +- .../org/emulator/calculator/NativeLib.java | 6 +- .../java/org/emulator/calculator/Utils.java | 6 +- .../emulator/forty/eight/MainActivity.java | 697 +++++++++++------- app/src/main/res/values/strings.xml | 10 +- 8 files changed, 551 insertions(+), 388 deletions(-) diff --git a/ReadMe.txt b/ReadMe.txt index 7b44d93..1f88950 100644 --- a/ReadMe.txt +++ b/ReadMe.txt @@ -63,6 +63,14 @@ LINKS CHANGES +Version 1.9 (2020-09-XX) + +- If the KML folder does not exist (like the first time), prompt the user to choose a new KML folder. +- If the memory card file for the port 2 cannot be found, prompt the user to choose a new memory card file. +- Move the KML folder in the JSON settings embedded in the state file because Windows cannot open the state file with KML url longer than 256 byte. +- Prevent to auto save before launching the "Open...", "Save As...", "Load Object...", "Save Object...", etc... + + Version 1.8 (2020-05-24) - Intercept the ESC keyboard key to allow the use of the BACK soft key. @@ -197,15 +205,6 @@ The Eric's Real scripts ("real*.kml" and "real*.bmp/png") are embedded in this a TODO - Android 11 new storage issues :-( -- Move the KML folder in the JSON settings embedded in the state file because Windows cannot open the state file with KML url longer than 256 byte. - * Need to set szEmuDirectory (and may be szRomDirectory for Emu48 only) in onFileNew() before NewDocument(). - * If the JSON settings contains the KML folder, we need to set szEmuDirectory (and may be szRomDirectory for Emu48 only) in onFileOpen() before OpenDocument(). - Else if NO JSON settings contains the KML folder, we can extract the variable szCurrentKml after OpenDocument(). - If szCurrentKml is using the old format, we remove the KML folder part in the variable szCurrentKml and set this KML folder in the JSON setting. - Else if szCurrentKml does not contain the KML folder part, we should prompt the user to select the KML folder (It should solve the next issue). - * Need to change the variable szCurrentKml before saving (in onFileSave()/onFileSaveAs() before SaveDocument()). - * onViewScript should be change too! -- If the KML folder does not exist (like the first time), prompt the user to choose a new KML folder. - ANR in NativeLib.buttonUp(), should make Win32::InvalidateRect() asynchronous (may be the cause of the lag and freeze). - Add the name of the file in the toast "State saved". - Bug: In Xiaomi mi A3 under Android10, the haptic feedback does not work (add an intensity setting). diff --git a/app/src/main/cpp/emu-jni.c b/app/src/main/cpp/emu-jni.c index 4a5e581..11955c4 100644 --- a/app/src/main/cpp/emu-jni.c +++ b/app/src/main/cpp/emu-jni.c @@ -35,6 +35,7 @@ TCHAR szKmlLog[10240]; TCHAR szKmlLogBackup[10240]; TCHAR szKmlTitle[10240]; BOOL securityExceptionOccured; +BOOL kmlFileNotFound = FALSE; BOOL settingsPort2en; BOOL settingsPort2wr; BOOL soundAvailable = FALSE; @@ -192,32 +193,21 @@ void sendMenuItemCommand(int menuItem) { TCHAR lastKMLFilename[MAX_PATH]; -BOOL getFirstKMLFilenameForType(BYTE chipsetType, TCHAR * firstKMLFilename, size_t firstKMLFilenameSize) { - if(firstKMLFilename) { - JNIEnv *jniEnv = getJNIEnvironment(); - if(jniEnv) { - jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity); - if(mainActivityClass) { - jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "getFirstKMLFilenameForType", "(C)Ljava/lang/String;"); - jobject resultString = (*jniEnv)->CallObjectMethod(jniEnv, mainActivity, midStr, (char)chipsetType); - (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass); - if (resultString) { - const char *strReturn = (*jniEnv)->GetStringUTFChars(jniEnv, resultString, 0); - if(_tcscmp(lastKMLFilename, strReturn) == 0) { - (*jniEnv)->ReleaseStringUTFChars(jniEnv, resultString, strReturn); - return FALSE; - } - _tcscpy(lastKMLFilename, strReturn); - _tcsncpy(firstKMLFilename, strReturn, firstKMLFilenameSize); - (*jniEnv)->ReleaseStringUTFChars(jniEnv, resultString, strReturn); - return TRUE; - } - } - } - } - return FALSE; +BOOL getFirstKMLFilenameForType(BYTE chipsetType) { + JNIEnv *jniEnv = getJNIEnvironment(); + if(jniEnv) { + jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity); + if(mainActivityClass) { + jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "getFirstKMLFilenameForType", "(C)I"); + int result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, (char)chipsetType); + (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass); + return result ? TRUE : FALSE; + } + } + return FALSE; } + void clipboardCopyText(const TCHAR * text) { JNIEnv *jniEnv = getJNIEnvironment(); if(jniEnv) { @@ -468,6 +458,29 @@ JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getKMLTitle(JNI return result; } +JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getCurrentKml(JNIEnv *env, jobject thisz) { + jstring result = (*env)->NewStringUTF(env, szCurrentKml); + return result; +} + +JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setCurrentKml(JNIEnv *env, jobject thisz, jstring currentKml) { + const char *currentKmlUTF8 = (*env)->GetStringUTFChars(env, currentKml, NULL); + _tcscpy(szCurrentKml, currentKmlUTF8); + (*env)->ReleaseStringUTFChars(env, currentKml, currentKmlUTF8); +} + +JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getEmuDirectory(JNIEnv *env, jobject thisz) { + jstring result = (*env)->NewStringUTF(env, szEmuDirectory); + return result; +} + +JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setEmuDirectory(JNIEnv *env, jobject thisz, jstring emuDirectory) { + const char *emuDirectoryUTF8 = (*env)->GetStringUTFChars(env, emuDirectory, NULL); + _tcscpy(szEmuDirectory, emuDirectoryUTF8); + _tcscpy(szRomDirectory, emuDirectoryUTF8); + (*env)->ReleaseStringUTFChars(env, emuDirectory, emuDirectoryUTF8); +} + JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_getPort1Plugged(JNIEnv *env, jobject thisz) { return (jboolean) ((Chipset.cards_status & PORT1_PRESENT) != 0); } @@ -502,24 +515,21 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileNew(JNIEnv * _tcscpy(szChosenCurrentKml, filenameUTF8); (*env)->ReleaseStringUTFChars(env, kmlFilename, filenameUTF8); - TCHAR * documentScheme = _T("document:"); - TCHAR * urlSchemeFound = _tcsstr(szChosenCurrentKml, documentScheme); - if(urlSchemeFound) { - if(kmlFolder) { - const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL); - // The folder URL is separated from the script filename and comes from the JSON settings in the state file. - _tcscpy(szEmuDirectory, kmlFolderUTF8); - _tcscpy(szRomDirectory, kmlFolderUTF8); - (*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8); - } else { - // Keep the compatibility by allowing to put the KML folder combined with the KML script filename with a document: scheme. - _tcscpy(szEmuDirectory, szChosenCurrentKml + _tcslen(documentScheme) * sizeof(TCHAR)); - TCHAR * filename = _tcschr(szEmuDirectory, _T('|')); - if(filename) { - *filename = _T('\0'); - } - _tcscpy(szRomDirectory, szEmuDirectory); - } + TCHAR * documentScheme = _T("document:"); + TCHAR * documentSchemeFound = _tcsstr(szChosenCurrentKml, documentScheme); + if(kmlFolder) { + const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL); + // The folder URL is separated from the script filename and comes from the JSON settings in the state file. + _tcscpy(szEmuDirectory, kmlFolderUTF8); + _tcscpy(szRomDirectory, kmlFolderUTF8); + (*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8); + } else if(documentSchemeFound) { + // Keep the compatibility by allowing to put the KML folder combined with the KML script filename with a document: scheme. + _tcscpy(szEmuDirectory, szChosenCurrentKml + _tcslen(documentScheme) * sizeof(TCHAR)); + TCHAR * filename = _tcschr(szEmuDirectory, _T('|')); + if(filename) + *filename = _T('\0'); + _tcscpy(szRomDirectory, szEmuDirectory); } else { _tcscpy(szEmuDirectory, "assets/calculators/"); _tcscpy(szRomDirectory, "assets/calculators/"); @@ -554,15 +564,23 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileOpen(JNIEnv _tcscpy(szBufferFilename, stateFilenameUTF8); (*env)->ReleaseStringUTFChars(env, stateFilename, stateFilenameUTF8); + chooseCurrentKmlMode = ChooseKmlMode_FILE_OPEN; if(kmlFolder) { const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL); // The folder URL is separated from the script filename (not in the document: URL) and comes from the JSON settings in the state file. _tcscpy(szEmuDirectory, kmlFolderUTF8); _tcscpy(szRomDirectory, kmlFolderUTF8); (*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8); + chooseCurrentKmlMode = ChooseKmlMode_FILE_OPEN_WITH_FOLDER; + } else { + // We are loading a KML script from the embedded asset folder inside the Android App. + // We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder + // which contain the script and its dependencies like the includes, the images and the ROMs. + _tcscpy(szEmuDirectory, "assets/calculators/"); + _tcscpy(szRomDirectory, "assets/calculators/"); } - chooseCurrentKmlMode = ChooseKmlMode_FILE_OPEN; + kmlFileNotFound = FALSE; lastKMLFilename[0] = '\0'; BOOL result = OpenDocument(szBufferFilename); if (result) { @@ -580,6 +598,10 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileOpen(JNIEnv securityExceptionOccured = FALSE; result = -2; } + if(kmlFileNotFound) { + kmlFileNotFound = FALSE; + result = -3; + } return result; } JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onFileSave(JNIEnv *env, jobject thisz) { @@ -707,16 +729,6 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onObjectLoad(JNIEn return TRUE; } -JNIEXPORT jstring JNICALL Java_org_emulator_calculator_NativeLib_getCurrentKml(JNIEnv *env, jobject thisz) { - jstring result = (*env)->NewStringUTF(env, szCurrentKml); - return result; -} -JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_setCurrentKml(JNIEnv *env, jobject thisz, jstring currentKml) { - const char *currentKmlUTF8 = (*env)->GetStringUTFChars(env, currentKml, NULL); - _tcscpy(szCurrentKml, currentKmlUTF8); - (*env)->ReleaseStringUTFChars(env, currentKml, currentKmlUTF8); -} - JNIEXPORT jobjectArray JNICALL Java_org_emulator_calculator_NativeLib_getObjectsToSave(JNIEnv *env, jobject thisz) { return 0; } @@ -724,7 +736,6 @@ JNIEXPORT jobjectArray JNICALL Java_org_emulator_calculator_NativeLib_getObjects JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onObjectSave(JNIEnv *env, jobject thisz, jstring filename, jbooleanArray objectsToSaveItemChecked) { const char *filenameUTF8 = (*env)->GetStringUTFChars(env, filename , NULL) ; - //OnObjectSave(); if (nState != SM_RUN) { @@ -880,21 +891,27 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onViewScript(JNIEn // make a copy of the current KML script file name lstrcpyn(szKmlFile,szCurrentKml,ARRAYSIZEOF(szKmlFile)); - const char *filenameUTF8 = (*env)->GetStringUTFChars(env, kmlFilename , NULL) ; + const char * filenameUTF8 = (*env)->GetStringUTFChars(env, kmlFilename , NULL) ; _tcscpy(szCurrentKml, filenameUTF8); (*env)->ReleaseStringUTFChars(env, kmlFilename, filenameUTF8); - const char *kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL); - if(kmlFolderUTF8) { + if(kmlFolder) { + const char * kmlFolderUTF8 = (*env)->GetStringUTFChars(env, kmlFolder, NULL); // The folder URL is separated from the script filename and comes from the JSON settings in the state file. _tcscpy(szEmuDirectory, kmlFolderUTF8); _tcscpy(szRomDirectory, kmlFolderUTF8); (*env)->ReleaseStringUTFChars(env, kmlFolder, kmlFolderUTF8); + } else { + // We are loading a KML script from the embedded asset folder inside the Android App. + // We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder + // which contain the script and its dependencies like the includes, the images and the ROMs. + _tcscpy(szEmuDirectory, "assets/calculators/"); + _tcscpy(szRomDirectory, "assets/calculators/"); } chooseCurrentKmlMode = ChooseKmlMode_CHANGE_KML; - BOOL bSucc = InitKML(szCurrentKml,FALSE); + BOOL bSucc = InitKML(szCurrentKml, FALSE); if(!bSucc) { // restore KML script file name @@ -903,13 +920,13 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_onViewScript(JNIEn _tcsncpy(szKmlLogBackup, szKmlLog, sizeof(szKmlLog) / sizeof(TCHAR)); // try to restore old KML script - bSucc = InitKML(szCurrentKml,FALSE); + bSucc = InitKML(szCurrentKml, FALSE); _tcsncpy(szKmlLog, szKmlLogBackup, sizeof(szKmlLog) / sizeof(TCHAR)); } chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN; - if (bSucc) { + if(bSucc) { if(hLcdDC && hLcdDC->selectedBitmap) { hLcdDC->selectedBitmap->bitmapInfoHeader->biHeight = -abs(hLcdDC->selectedBitmap->bitmapInfoHeader->biHeight); } diff --git a/app/src/main/cpp/win32-layer.c b/app/src/main/cpp/win32-layer.c index be72622..b0c308b 100644 --- a/app/src/main/cpp/win32-layer.c +++ b/app/src/main/cpp/win32-layer.c @@ -41,6 +41,7 @@ size_t assetsPrefixLength; const TCHAR * contentScheme = _T("content://"); size_t contentSchemeLength; const TCHAR * documentScheme = _T("document:"); +size_t documentSchemeLength; TCHAR szFilePathTmp[MAX_PATH]; @@ -64,6 +65,7 @@ void win32Init() { assetsPrefixLength = _tcslen(assetsPrefix); contentSchemeLength = _tcslen(contentScheme); + documentSchemeLength = _tcslen(documentScheme); } int abs (int i) { @@ -120,12 +122,13 @@ HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, } #endif - TCHAR * foundDocumentScheme = _tcsstr(lpFileName, documentScheme); - TCHAR * urlContentSchemeFound = _tcsstr(lpFileName, contentScheme); + BOOL foundDocumentScheme = _tcsncmp(lpFileName, documentScheme, documentSchemeLength) == 0; + BOOL urlContentSchemeFound = _tcsncmp(lpFileName, contentScheme, contentSchemeLength) == 0; - if(chooseCurrentKmlMode == ChooseKmlMode_FILE_OPEN || chooseCurrentKmlMode == ChooseKmlMode_CHANGE_KML) { + if(chooseCurrentKmlMode == ChooseKmlMode_FILE_OPEN /*|| chooseCurrentKmlMode == ChooseKmlMode_CHANGE_KML*/) { // A E48 state file can contain a path to the KML script. if(foundDocumentScheme) { + // Keep for compatibility: // When the state file is created or saved with this Android version, // an URL like: document:content://|content:// // is created and saved in the state file. @@ -147,44 +150,13 @@ HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, #endif SetCurrentDirectory(szFilePathTmp); } - } else { - TCHAR * fileExtension = _tcsrchr(lpFileName, _T('.')); - if (fileExtension && - ((fileExtension[1] == 'K' && fileExtension[2] == 'M' && fileExtension[3] == 'L') || - (fileExtension[1] == 'k' && fileExtension[2] == 'm' && fileExtension[3] == 'l') - )) { - if(lpFileName[0] == '/') { - // We are loading a standard KML script from the folder inside the filesystem. - // We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder - // which contain the script and its dependencies like the includes, the images and the ROMs. - // Deprecated, not supported by Android >= 10. - _tcscpy(szEmuDirectory, lpFileName); - TCHAR * filename = _tcsrchr(szEmuDirectory, _T('/')); - if(filename) { - *filename = _T('\0'); - } -#if EMUXX == 48 - _tcscpy(szRomDirectory, szEmuDirectory); -#endif - SetCurrentDirectory(szEmuDirectory); - } else { - // We are loading a KML script from the embedded asset folder inside the Android App. - // We directly set the variable "szEmuDirectory"/"szRomDirectory" and "szCurrentAssetDirectory" with the KML folder - // which contain the script and its dependencies like the includes, the images and the ROMs. - _tcscpy(szEmuDirectory, "assets/calculators/"); -#if EMUXX == 48 - _tcscpy(szRomDirectory, "assets/calculators/"); -#endif - SetCurrentDirectory(szEmuDirectory); - } - } } } if(!forceNormalFile && (szCurrentAssetDirectory || _tcsncmp(lpFileName, assetsPrefix, assetsPrefixLength) == 0) - && foundDocumentScheme == NULL - && urlContentSchemeFound == NULL) { + && !foundDocumentScheme + && !urlContentSchemeFound) { // Loading a file from the Android asset folders (embedded in the app) TCHAR szFileName[MAX_PATH]; AAsset * asset = NULL; @@ -236,10 +208,9 @@ HANDLE CreateFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, // Case of an absolute file with the scheme "content://". fd = openFileFromContentResolver(lpFileName, dwDesiredAccess); useOpenFileFromContentResolver = TRUE; - if(fd < 0) { + if(fd == -2) { FILE_LOGD("CreateFile() openFileFromContentResolver() %d", errno); - if(fd == -2) - securityExceptionOccured = TRUE; + securityExceptionOccured = TRUE; } } else if(szCurrentContentDirectory) { // Case of a relative file to a folder with the scheme "content://". @@ -2920,21 +2891,17 @@ PIDLIST_ABSOLUTE SHBrowseForFolderA(LPBROWSEINFOA lpbi) { #define IDD_USERCODE 121 #endif INT_PTR DialogBoxParam(HINSTANCE hInstance, LPCTSTR lpTemplateName, HWND hWndParent, DLGPROC lpDialogFunc, LPARAM dwInitParam) { - //TODO if(lpTemplateName == MAKEINTRESOURCE(IDD_CHOOSEKML)) { if(chooseCurrentKmlMode == ChooseKmlMode_UNKNOWN) { } else if(chooseCurrentKmlMode == ChooseKmlMode_FILE_NEW) { lstrcpy(szCurrentKml, szChosenCurrentKml); - } else if(chooseCurrentKmlMode == ChooseKmlMode_FILE_OPEN) { + } else if(chooseCurrentKmlMode == ChooseKmlMode_FILE_OPEN || chooseCurrentKmlMode == ChooseKmlMode_FILE_OPEN_WITH_FOLDER) { // We are here because we open a state file and the embedded KML path is not reachable. // So, we try to find a correct KML file in the current Custom KML scripts folder. - if(!getFirstKMLFilenameForType(Chipset.type, szCurrentKml, sizeof(szCurrentKml) / sizeof(szCurrentKml[0]))) { - showAlert(_T("Cannot find the KML template file, sorry."), 0); + if(!getFirstKMLFilenameForType(Chipset.type)) { + kmlFileNotFound = TRUE; return -1; } -// else { -// showAlert(_T("Cannot find the KML template file, so, try another one."), 0); //TODO is it right? -// } } } else if(lpTemplateName == MAKEINTRESOURCE(IDD_KMLLOG)) { lpDialogFunc(NULL, WM_INITDIALOG, 0, 0); @@ -3149,3 +3116,4 @@ int win32_select(int __fd_count, fd_set* __read_fds, fd_set* __write_fds, fd_set } return select(__fd_count, __read_fds, __write_fds, __exception_fds, __timeout); } + \ No newline at end of file diff --git a/app/src/main/cpp/win32-layer.h b/app/src/main/cpp/win32-layer.h index 28a4fef..7f50ca4 100644 --- a/app/src/main/cpp/win32-layer.h +++ b/app/src/main/cpp/win32-layer.h @@ -1222,7 +1222,8 @@ enum ChooseKmlMode { ChooseKmlMode_UNKNOWN, ChooseKmlMode_FILE_NEW, ChooseKmlMode_FILE_OPEN, - ChooseKmlMode_CHANGE_KML + ChooseKmlMode_FILE_OPEN_WITH_FOLDER, + ChooseKmlMode_CHANGE_KML //TODO To remove }; extern enum ChooseKmlMode chooseCurrentKmlMode; enum DialogBoxMode { @@ -1236,13 +1237,14 @@ enum DialogBoxMode { }; extern enum DialogBoxMode currentDialogBoxMode; extern BOOL securityExceptionOccured; +extern BOOL kmlFileNotFound; #define MAX_LABEL_SIZE 5000 extern TCHAR labels[MAX_LABEL_SIZE]; #define MAX_ITEMDATA 100 extern int selItemDataIndex[MAX_ITEMDATA]; extern int selItemDataCount; extern TCHAR getSaveObjectFilenameResult[MAX_PATH]; -BOOL getFirstKMLFilenameForType(BYTE chipsetType, TCHAR * firstKMLFilename, size_t firstKMLFilenameSize); +BOOL getFirstKMLFilenameForType(BYTE chipsetType); void clipboardCopyText(const TCHAR * text); const TCHAR * clipboardPasteText(); void performHapticFeedback(); diff --git a/app/src/main/java/org/emulator/calculator/NativeLib.java b/app/src/main/java/org/emulator/calculator/NativeLib.java index 93c5007..7e22a95 100644 --- a/app/src/main/java/org/emulator/calculator/NativeLib.java +++ b/app/src/main/java/org/emulator/calculator/NativeLib.java @@ -46,6 +46,10 @@ public class NativeLib { public static native boolean isBackup(); public static native String getKMLLog(); public static native String getKMLTitle(); + public static native String getCurrentKml(); + public static native void setCurrentKml(String currentKml); + public static native String getEmuDirectory(); + public static native void setEmuDirectory(String emuDirectory); public static native boolean getPort1Plugged(); public static native boolean getPort1Writable(); public static native boolean getSoundEnabled(); @@ -58,8 +62,6 @@ public class NativeLib { public static native int onFileSaveAs(String newFilename); public static native int onFileClose(); public static native int onObjectLoad(String filename); - public static native String getCurrentKml(); - public static native void setCurrentKml(String currentKml); public static native String[] getObjectsToSave(); public static native int onObjectSave(String filename, boolean[] objectsToSaveItemChecked); diff --git a/app/src/main/java/org/emulator/calculator/Utils.java b/app/src/main/java/org/emulator/calculator/Utils.java index a0ff653..2f7e860 100644 --- a/app/src/main/java/org/emulator/calculator/Utils.java +++ b/app/src/main/java/org/emulator/calculator/Utils.java @@ -44,7 +44,11 @@ import javax.microedition.khronos.egl.EGLDisplay; public class Utils { public static void showAlert(Context context, String text) { - Toast toast = Toast.makeText(context, text, Toast.LENGTH_SHORT); + showAlert(context, text, false); + } + + public static void showAlert(Context context, String text, boolean lengthLong) { + Toast toast = Toast.makeText(context, text, lengthLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); //View view = toast.getView(); //view.setBackgroundColor(0x80000000); toast.show(); diff --git a/app/src/main/java/org/emulator/forty/eight/MainActivity.java b/app/src/main/java/org/emulator/forty/eight/MainActivity.java index f87ba56..a8fd06b 100644 --- a/app/src/main/java/org/emulator/forty/eight/MainActivity.java +++ b/app/src/main/java/org/emulator/forty/eight/MainActivity.java @@ -51,6 +51,7 @@ import androidx.core.content.FileProvider; import androidx.core.view.GravityCompat; import androidx.documentfile.provider.DocumentFile; import androidx.drawerlayout.widget.DrawerLayout; +import androidx.fragment.app.Fragment; import com.google.android.material.navigation.NavigationView; @@ -107,15 +108,23 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On public static final int INTENT_PORT2LOAD = 6; public static final int INTENT_PICK_KML_FOLDER_FOR_NEW_FILE = 7; public static final int INTENT_PICK_KML_FOLDER_FOR_CHANGING = 8; - public static final int INTENT_PICK_KML_FOLDER_FOR_SECURITY = 10; - public static final int INTENT_CREATE_RAM_CARD = 11; - public static final int INTENT_MACRO_LOAD = 12; - public static final int INTENT_MACRO_SAVE = 13; + public static final int INTENT_PICK_KML_FOLDER_FOR_SECURITY = 10; + public static final int INTENT_PICK_KML_FOLDER_FOR_KML_NOT_FOUND = 11; + public static final int INTENT_CREATE_RAM_CARD = 12; + public static final int INTENT_MACRO_LOAD = 13; + public static final int INTENT_MACRO_SAVE = 14; - private String kmlMimeType = "application/vnd.google-earth.kml+xml"; + public static String intentPickKmlFolderForUrlToOpen; + public String urlToOpenInIntentPort2Load; + public String kmlScriptFolderInIntentPort2Load; + + + private String kmlMimeType = "application/vnd.google-earth.kml+xml"; private boolean kmlFolderUseDefault = true; private String kmlFolderURL = ""; - private boolean kmlFolderChange = true; + private boolean kmlFolderChange = true; + + private boolean saveWhenLaunchingActivity = true; private int selectedRAMSize = -1; private boolean[] objectsToSaveItemChecked = null; @@ -149,7 +158,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On settings.setIsDefaultSettings(true); - ViewGroup mainScreenContainer = findViewById(R.id.main_screen_container); + ViewGroup mainScreenContainer = findViewById(R.id.main_screen_container); mainScreenView = new MainScreenView(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mainScreenView.setStatusBarColor(getWindow().getStatusBarColor()); @@ -207,9 +216,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On documentToOpenUri = intent.getData(); if (documentToOpenUri != null) { String scheme = documentToOpenUri.getScheme(); - if(scheme != null && scheme.compareTo("file") == 0) { + if(scheme != null && scheme.compareTo("file") == 0) documentToOpenUrl = null; - } else + else documentToOpenUrl = documentToOpenUri.toString(); } } else if (action.equals(Intent.ACTION_SEND)) { @@ -223,11 +232,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if(documentToOpenUrl != null && documentToOpenUrl.length() > 0) try { - if(onFileOpen(documentToOpenUrl) != 0) { - saveLastDocument(documentToOpenUrl); - if(intent != null && documentToOpenUri != null) - Utils.makeUriPersistable(this, intent, documentToOpenUri); - } + // FileOpen auto-open. + onFileOpen(documentToOpenUrl, intent, null); } catch (Exception e) { if(debug) Log.e(TAG, e.getMessage()); } @@ -272,26 +278,26 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @Override protected void onStop() { - //TODO We cannot make the difference between going to the settings or loading/saving a file and a real app stop/kill! - // -> Maybe by settings some flags when loading/saving - settings.putStringSet("MRU", mruLinkedHashMap.keySet()); - if(lcdOverlappingView != null) - lcdOverlappingView.saveViewLayout(); + if(saveWhenLaunchingActivity) { + settings.putStringSet("MRU", mruLinkedHashMap.keySet()); + if (lcdOverlappingView != null) + lcdOverlappingView.saveViewLayout(); - if(NativeLib.isDocumentAvailable() && settings.getBoolean("settings_autosave", true)) { - String currentFilename = NativeLib.getCurrentFilename(); - if (currentFilename != null && currentFilename.length() > 0) - onFileSave(); + if (NativeLib.isDocumentAvailable() && settings.getBoolean("settings_autosave", true)) { + String currentFilename = NativeLib.getCurrentFilename(); + if (currentFilename != null && currentFilename.length() > 0) + onFileSave(); + } + + settings.saveApplicationSettings(); + + clearFolderCache(); } - settings.saveApplicationSettings(); - - clearFolderCache(); - super.onStop(); } - @Override + @Override protected void onDestroy() { //onDestroy is never called! NativeLib.stop(); @@ -393,13 +399,8 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mruLinkedHashMap.get(url); ensureDocumentSaved(() -> { - if(onFileOpen(url) != 0) { - saveLastDocument(url); - } else { - // We should remove this file from the MRU list because it might be deleted or the permissions does not allow to reach it anymore. - mruLinkedHashMap.remove(url); - navigationView.post(this::updateMRU); - } + // FileOpen from MRU. + onFileOpen(url, null, null); }); } @@ -408,6 +409,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On return true; } + private void removeMRU(String url) { + // We should remove this file from the MRU list because it might be deleted or the permissions does not allow to reach it anymore. + if(mruLinkedHashMap.containsKey(url)) { + mruLinkedHashMap.remove(url); + navigationView.post(this::updateMRU); + } + } + private void updateNavigationDrawerItems() { Menu menu = navigationView.getMenu(); boolean isBackup = NativeLib.isBackup(); @@ -437,8 +446,35 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On menu.findItem(R.id.nav_macro_stop).setEnabled(uRun && nMacroState != 0 /* MACRO_OFF */); } - class KMLScriptItem { - public String filename; + private void loadKMLFolder() { + kmlFolderUseDefault = settings.getBoolean("settings_kml_default", true); + if (!kmlFolderUseDefault) { + kmlFolderURL = settings.getString("settings_kml_folder", ""); + // https://github.com/googlesamples/android-DirectorySelection + // https://stackoverflow.com/questions/44185477/intent-action-open-document-tree-doesnt-seem-to-return-a-real-path-to-drive/44185706 + // https://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-android-5-0-lollipop + } + kmlFolderChange = true; + } + + private void changeKMLFolder(String url) { + if(url == null || url.startsWith("assets/")) { + if(!kmlFolderUseDefault) + kmlFolderChange = true; + kmlFolderUseDefault = true; + settings.putBoolean("settings_kml_default", true); + } else { + if(kmlFolderUseDefault || !url.equals(kmlFolderURL)) + kmlFolderChange = true; + kmlFolderUseDefault = false; + kmlFolderURL = url; + settings.putBoolean("settings_kml_default", false); + settings.putString("settings_kml_folder", kmlFolderURL); + } + } + + static class KMLScriptItem { + public String filename; public String folder; public String title; public String model; @@ -485,7 +521,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if (inGlobal) { if (mLine.indexOf("End") == 0) { KMLScriptItem newKMLScriptItem = new KMLScriptItem(); - newKMLScriptItem.filename = calculatorFilename; + newKMLScriptItem.filename = calculatorFilename; newKMLScriptItem.folder = null; newKMLScriptItem.title = title; newKMLScriptItem.model = model; @@ -520,7 +556,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } else { // We use the custom KML scripts and ROMs from a chosen folder. - Uri kmlFolderUri = Uri.parse(kmlFolderURL); + Uri kmlFolderUri = Uri.parse(kmlFolderURL); List calculatorsAssetFilenames = new LinkedList<>(); DocumentFile kmlFolderDocumentFile = DocumentFile.fromTreeUri(this, kmlFolderUri); @@ -565,8 +601,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if (inGlobal) { if (mLine.indexOf("End") == 0) { KMLScriptItem newKMLScriptItem = new KMLScriptItem(); - //newKMLScriptItem.filename = kmlFolderUseDefault ? calculatorFilename : "document:" + kmlFolderURL + "|" + calculatorFilename; - newKMLScriptItem.filename = "document:|" + calculatorFilename; + newKMLScriptItem.filename = documentFile.getName(); newKMLScriptItem.folder = kmlFolderURL; newKMLScriptItem.title = title; newKMLScriptItem.model = model; @@ -621,7 +656,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if (which == DialogInterface.BUTTON_POSITIVE) { if (hasFilename) { onFileSave(); - if (continueCallback != null) + if (continueCallback != null) continueCallback.run(); } else { fileSaveAsCallback = continueCallback; @@ -669,13 +704,15 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On // Create a new genuine state file int result = NativeLib.onFileNew(kmlScriptFilename, kmlScriptFolder); if(result > 0) { - showCalculatorView(true); + showCalculatorView(true); displayFilename(""); showKMLLog(); - // Note: kmlScriptFolder should be equal to kmlFolderURL! - // We keep the global settings_kml_folder distinct from embedded settings_kml_folder_embedded. - settings.putString("settings_kml_folder_embedded", kmlScriptFolder); - suggestToSaveNewFile(); + if(kmlScriptFolder != null) { + // Note: kmlScriptFolder should be equal to kmlFolderURL! + // We keep the global settings_kml_folder distinct from embedded settings_kml_folder_embedded. + settings.putString("settings_kml_folder_embedded", kmlScriptFolder); + } + suggestToSaveNewFile(); } else showKMLLogForce(); updateNavigationDrawerItems(); @@ -695,6 +732,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.filename) + "-state.e48"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_GETOPENFILENAME); }); } @@ -734,6 +772,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On break; } intent.putExtra(Intent.EXTRA_TITLE, filename); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_GETSAVEFILENAME); } private void OnFileClose() { @@ -741,7 +780,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On NativeLib.onFileClose(); settings.setIsDefaultSettings(true); settings.clearEmbeddedStateSettings(); - showCalculatorView(false); + showCalculatorView(false); saveLastDocument(""); updateNavigationDrawerItems(); displayFilename(""); @@ -785,6 +824,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.filename) + "-object.hp"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_OBJECT_LOAD); } @@ -808,6 +848,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.filename) + "-object.hp"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_OBJECT_SAVE); } private void OnViewCopyFullscreen() { @@ -932,6 +973,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On .setTitle(getResources().getString(R.string.pick_calculator)) .setItems(kmlScriptTitles, (dialog, which) -> { if(which == lastIndex) { + // [Select a Custom KML script folder...] if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { // < API 21 new AlertDialog.Builder(MainActivity.this) .setTitle(getString(R.string.message_kml_folder_selection_need_api_lollipop)) @@ -940,17 +982,18 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On }).show(); } else { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + saveWhenLaunchingActivity = false; startActivityForResult(intent, changeKML ? INTENT_PICK_KML_FOLDER_FOR_CHANGING : INTENT_PICK_KML_FOLDER_FOR_NEW_FILE); } } else if(which == lastIndex + 1) { - // Reset to default KML folder - settings.putBoolean("settings_kml_default", true); - updateFromPreferences("settings_kml", true); + // [Default KML script folder] + changeKMLFolder(null); if(changeKML) OnViewScript(); else OnFileNew(); } else { + // We choose a calculator name from the list. KMLScriptItem scriptItem = kmlScriptsForCurrentModel.get(which); if(changeKML) { // We only change the KML script here. @@ -1009,6 +1052,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On break; } intent.putExtra(Intent.EXTRA_TITLE, "shared-" + sizeTitle + ".bin"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_CREATE_RAM_CARD); }).show(); } @@ -1018,6 +1062,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.filename) + "-macro.mac"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_MACRO_SAVE); } @@ -1026,6 +1071,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, getString(R.string.filename) + "-macro.mac"); + saveWhenLaunchingActivity = false; startActivityForResult(intent, INTENT_MACRO_LOAD); } @@ -1053,27 +1099,15 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if (url != null) { switch (requestCode) { case INTENT_GETOPENFILENAME: { - if(debug) Log.d(TAG, "onActivityResult INTENT_GETOPENFILENAME " + url); - int openResult = onFileOpen(url); - if (openResult > 0) { - saveLastDocument(url); - Utils.makeUriPersistable(this, data, uri); - } else if(openResult == -2 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // >= API 21 - // For security reason, you must select the folder where are the KML and ROM files and then, reopen this file! - new AlertDialog.Builder(this) - .setTitle(getString(R.string.message_open_security)) - .setMessage(getString(R.string.message_open_security_description)) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(intent, INTENT_PICK_KML_FOLDER_FOR_SECURITY); - }).show(); - } + if(debug) Log.d(TAG, "onActivityResult INTENT_GETOPENFILENAME " + url); + // FileOpen from the "Open..." menu. + onFileOpen(url, data, null); break; } case INTENT_GETSAVEFILENAME: { - if(debug) Log.d(TAG, "onActivityResult INTENT_GETSAVEFILENAME " + url); + if(debug) Log.d(TAG, "onActivityResult INTENT_GETSAVEFILENAME " + url); if (NativeLib.onFileSaveAs(url) != 0) { - settings.saveInStateFile(this, url); + settings.saveInStateFile(this, url); saveLastDocument(url); Utils.makeUriPersistable(this, data, uri); displayFilename(url); @@ -1084,22 +1118,35 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On break; } case INTENT_OBJECT_LOAD: { - if(debug) Log.d(TAG, "onActivityResult INTENT_OBJECT_LOAD " + url); + if(debug) Log.d(TAG, "onActivityResult INTENT_OBJECT_LOAD " + url); NativeLib.onObjectLoad(url); break; } - case INTENT_OBJECT_SAVE: { - if(debug) Log.d(TAG, "onActivityResult INTENT_OBJECT_SAVE " + url); - NativeLib.onObjectSave(url, null); - break; - } + case INTENT_OBJECT_SAVE: { + if(debug) Log.d(TAG, "onActivityResult INTENT_OBJECT_SAVE " + url); + NativeLib.onObjectSave(url, null); + break; + } + case INTENT_PORT2LOAD: { + if(debug) Log.d(TAG, "onActivityResult INTENT_PORT2LOAD " + url); + //TODO Duplicate code in SettingsFragment! + settings.putString("settings_port2load", url); + Utils.makeUriPersistable(this, data, uri); + + if(urlToOpenInIntentPort2Load != null) { + onFileOpenNative(urlToOpenInIntentPort2Load, kmlScriptFolderInIntentPort2Load); + urlToOpenInIntentPort2Load = null; + kmlScriptFolderInIntentPort2Load = null; + } + break; + } case INTENT_PICK_KML_FOLDER_FOR_NEW_FILE: case INTENT_PICK_KML_FOLDER_FOR_CHANGING: - case INTENT_PICK_KML_FOLDER_FOR_SECURITY: { - if(debug) Log.d(TAG, "onActivityResult INTENT_PICK_KML_FOLDER " + url); - settings.putBoolean("settings_kml_default", false); - settings.putString("settings_kml_folder", url); - updateFromPreferences("settings_kml", true); + case INTENT_PICK_KML_FOLDER_FOR_SECURITY: + case INTENT_PICK_KML_FOLDER_FOR_KML_NOT_FOUND: + { + if(debug) Log.d(TAG, "onActivityResult INTENT_PICK_KML_FOLDER_X " + url); + changeKMLFolder(url); Utils.makeUriPersistableReadOnly(this, data, uri); switch (requestCode) { @@ -1109,18 +1156,22 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case INTENT_PICK_KML_FOLDER_FOR_CHANGING: OnViewScript(); break; - case INTENT_PICK_KML_FOLDER_FOR_SECURITY: - new AlertDialog.Builder(this) - .setTitle(getString(R.string.message_open_security_retry)) - .setMessage(getString(R.string.message_open_security_retry_description)) - .setPositiveButton(android.R.string.ok, (dialog, which) -> { - }).show(); - break; + case INTENT_PICK_KML_FOLDER_FOR_SECURITY: + case INTENT_PICK_KML_FOLDER_FOR_KML_NOT_FOUND: + if(intentPickKmlFolderForUrlToOpen != null) + onFileOpen(intentPickKmlFolderForUrlToOpen, null, url); + else + new AlertDialog.Builder(this) + .setTitle(getString(R.string.message_open_security_retry)) + .setMessage(getString(R.string.message_open_security_retry_description)) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + }).show(); + break; } break; } case INTENT_CREATE_RAM_CARD: { - if(debug) Log.d(TAG, "onActivityResult INTENT_CREATE_RAM_CARD " + url); + if(debug) Log.d(TAG, "onActivityResult INTENT_CREATE_RAM_CARD " + url); if(selectedRAMSize > 0) { int size = 2 * selectedRAMSize; FileOutputStream fileOutputStream; @@ -1144,13 +1195,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On break; } case INTENT_MACRO_LOAD: { - if(debug) Log.d(TAG, "onActivityResult INTENT_MACRO_LOAD " + url); + if(debug) Log.d(TAG, "onActivityResult INTENT_MACRO_LOAD " + url); NativeLib.onToolMacroPlay(url); updateNavigationDrawerItems(); break; } case INTENT_MACRO_SAVE: { - if(debug) Log.d(TAG, "onActivityResult INTENT_MACRO_SAVE " + url); + if(debug) Log.d(TAG, "onActivityResult INTENT_MACRO_SAVE " + url); NativeLib.onToolMacroNew(url); updateNavigationDrawerItems(); break; @@ -1159,9 +1210,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On break; } } + } else { + // resultCode == Activity.RESULT_CANCELED + urlToOpenInIntentPort2Load = null; + kmlScriptFolderInIntentPort2Load = null; } + saveWhenLaunchingActivity = true; fileSaveAsCallback = null; - super.onActivityResult(requestCode, resultCode, data); + super.onActivityResult(requestCode, resultCode, data); } private void saveLastDocument(String url) { @@ -1186,61 +1242,161 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } - private int onFileOpen(String url) { + private void onFileOpen(String url, Intent intent, String openWithKMLScriptFolder) { // Eventually, close the previous state file NativeLib.onFileClose(); showCalculatorView(false); displayFilename(""); + intentPickKmlFolderForUrlToOpen = null; // Pre-Load the embedded settings from the end of the classic state file settings.setIsDefaultSettings(false); settings.clearEmbeddedStateSettings(); settings.loadFromStateFile(this, url); - String kmlScriptFolder = settings.getString("settings_kml_folder_embedded", null); + if(intent != null) + // Make this file persistable to allow a next access to this same file. + Utils.makeUriPersistable(this, intent, Uri.parse(url)); - // Update the Emu VM with the new settings - updateFromPreferences(null, false); + String kmlScriptFolder = null; + String embeddedKMLScriptFolder = settings.getString("settings_kml_folder_embedded", null); - // Load the genuine state file - int result = NativeLib.onFileOpen(url, kmlScriptFolder); - if(result > 0) { - setPort1Settings(NativeLib.getPort1Plugged(), NativeLib.getPort1Writable()); //TODO is it in the true state file or in settings? - if(kmlScriptFolder == null) { - // The KML folder is not in the JSON settings embedded in the state file, - // so, we need to extract it and change the variable szCurrentKml - String currentKml = NativeLib.getCurrentKml(); - Pattern patternKMLDocumentURL = Pattern.compile("document:([^|]*)\\|(.+)"); - Matcher m = patternKMLDocumentURL.matcher(currentKml); - if (m.find() && m.groupCount() == 2) { - kmlScriptFolder = m.group(1); - String kmlScriptFilename = m.group(2); - if(kmlScriptFolder != null && kmlScriptFolder.length() > 0) { - settings.putString("settings_kml_folder_embedded", kmlScriptFolder); - NativeLib.setCurrentKml("document:|" + kmlScriptFilename); - } else { - //TODO We should prompt the user to choose the KML folder? - // It should not happen here with custom KML folder because result should be 0! - } - } + if(openWithKMLScriptFolder != null) + kmlScriptFolder = openWithKMLScriptFolder; + else + kmlScriptFolder = embeddedKMLScriptFolder; -// String kmlScriptFolder = NativeLib.getEmuDirectory(); -// settings.putString("settings_kml_folder_embedded", kmlScriptFolder); - } - showCalculatorView(true); - displayFilename(url); - showKMLLog(); - } else { - // Because it failed to load the state file, we switch to the default settings - settings.setIsDefaultSettings(true); - settings.clearEmbeddedStateSettings(); - showKMLLogForce(); - } - updateNavigationDrawerItems(); - return result; + if(kmlScriptFolder != null && !kmlScriptFolder.startsWith("assets/")) { + // Change the default KMLFolder to allow getFirstKMLFilenameForType() to find in the specific folder! + Uri kmlFolderUri = Uri.parse(kmlScriptFolder); + DocumentFile kmlFolderDocumentFile = DocumentFile.fromTreeUri(this, kmlFolderUri); + if(kmlFolderDocumentFile != null && kmlFolderDocumentFile.exists()) + changeKMLFolder(kmlScriptFolder); + } + + if(settings.getBoolean("settings_port2en", false)) { + // Check if the access to the port2 shared file is still possible. + String port2Url = settings.getString("settings_port2load", ""); + if(port2Url != null && port2Url.length() > 0) { + Uri port2Uri = Uri.parse(port2Url); + DocumentFile port2DocumentFile = DocumentFile.fromSingleUri(this, port2Uri); + if (port2DocumentFile == null || !port2DocumentFile.exists()) { + //showAlert("Cannot access to the port 2 file!"); + String port2Filename = getFilenameFromURL(port2Url); + String finalKmlScriptFolder = kmlScriptFolder; + new AlertDialog.Builder(this) + .setTitle(getString(R.string.message_open_port2_file_not_found)) + .setMessage(String.format(Locale.US, getString(R.string.message_open_port2_file_not_found_description), port2Filename)) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + + urlToOpenInIntentPort2Load = url; + kmlScriptFolderInIntentPort2Load = finalKmlScriptFolder; + + Intent intentPort2 = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intentPort2.addCategory(Intent.CATEGORY_OPENABLE); + intentPort2.setType("*/*"); + intentPort2.putExtra(Intent.EXTRA_TITLE, "shared.bin"); + saveWhenLaunchingActivity = false; + startActivityForResult(intentPort2, MainActivity.INTENT_PORT2LOAD); + }).setNegativeButton(android.R.string.cancel, (dialog, which) -> { + // Deactivate the port2 because it is not reachable. + settings.putBoolean("settings_port2en", false); + onFileOpenNative(url, finalKmlScriptFolder); + }).show(); + return; + } + } + } + + onFileOpenNative(url, kmlScriptFolder); } + private void onFileOpenNative(String url, String kmlScriptFolder) { + // Update the Emu VM with the new settings. + updateFromPreferences(null, false); + + // Load the genuine state file + int result = NativeLib.onFileOpen(url, kmlScriptFolder); + if(result > 0) { + // Copy the port1 settings from the state file to the settings (for the UI). + setPort1Settings(NativeLib.getPort1Plugged(), NativeLib.getPort1Writable()); + // State file successfully opened. + if(kmlScriptFolder == null) { + // Needed for compatibility: + // The KML folder is not in the JSON settings embedded in the state file, + // so, we need to extract it and change the variable szCurrentKml. + String currentKml = NativeLib.getCurrentKml(); + Pattern patternKMLDocumentURL = Pattern.compile("document:([^|]+)\\|(.+)"); + Matcher m = patternKMLDocumentURL.matcher(currentKml); + if (m.find() && m.groupCount() == 2) { + String kmlScriptFolderInDocument = m.group(1); + String kmlScriptFilenameInDocument = m.group(2); + kmlScriptFolder = kmlScriptFolderInDocument; + NativeLib.setCurrentKml(kmlScriptFilenameInDocument); + } else { + // Should not happen... But in case. + kmlScriptFolder = NativeLib.getEmuDirectory(); + } + } + settings.putString("settings_kml_folder_embedded", kmlScriptFolder); + changeKMLFolder(kmlScriptFolder); + showCalculatorView(true); + displayFilename(url); + saveLastDocument(url); + showKMLLog(); + } else { + // Because the state file failed to load, we switch to the default settings. + settings.setIsDefaultSettings(true); + settings.clearEmbeddedStateSettings(); + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP // >= API 21 + && (result < 0 || kmlScriptFolder == null)) { + // If the KML file takes an access denied (-2) or the KML file has not been found (-3) or kmlScriptFolder is null, + // we must prompt the use to choose one (after onFileOpen to know the model)! + if(result == -2) + // For security reason, you must select the folder where are the KML and ROM files and then, reopen this file! + new AlertDialog.Builder(this) + .setTitle(getString(R.string.message_open_security)) + .setMessage(getString(R.string.message_open_security_description)) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + intentPickKmlFolderForUrlToOpen = url; + saveWhenLaunchingActivity = false; + startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), INTENT_PICK_KML_FOLDER_FOR_SECURITY); + }).setNegativeButton(android.R.string.cancel, (dialog, which) -> { + removeMRU(url); + }).show(); + else if(result == -3 || kmlScriptFolder == null) { + // KML file (or a compatible KML file) has not been found, you must select the folder where are the KML and ROM files and then, reopen this file! + String currentKml = NativeLib.getCurrentKml(); + String description; + if(currentKml != null) { + if(kmlScriptFolder != null) + description = String.format(Locale.US, getString(R.string.message_open_kml_not_found_description3), currentKml, kmlScriptFolder); + else + description = String.format(Locale.US, getString(R.string.message_open_kml_not_found_description2), currentKml); + } else + description = getString(R.string.message_open_kml_not_found_description); + new AlertDialog.Builder(this) + .setTitle(getString(R.string.message_open_kml_not_found)) + .setMessage(description) + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + intentPickKmlFolderForUrlToOpen = url; + saveWhenLaunchingActivity = false; + startActivityForResult(new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE), INTENT_PICK_KML_FOLDER_FOR_KML_NOT_FOUND); + }).setNegativeButton(android.R.string.cancel, (dialog, which) -> { + removeMRU(url); + }).show(); + } else + removeMRU(url); + } else { + // Show the KML error + showKMLLogForce(); + removeMRU(url); + } + } + updateNavigationDrawerItems(); + } + private void onFileSave() { if (NativeLib.onFileSave() == 1) { String currentFilenameUrl = NativeLib.getCurrentFilename(); @@ -1250,7 +1406,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } } - private void displayFilename(String url) { + private void displayFilename(String url) { String displayName = getFilenameFromURL(url); View headerView = displayKMLTitle(); if(headerView != null) { @@ -1364,18 +1520,22 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On @SuppressWarnings("unused") public int openFileInFolderFromContentResolver(String filename, String folderURL, int writeAccess) { - if(folderURLCached == null || !folderURLCached.equals(folderURL)) { - folderURLCached = folderURL; - folderCache.clear(); - Uri folderURI = Uri.parse(folderURL); - DocumentFile folderDocumentFile = DocumentFile.fromTreeUri(this, folderURI); - if (folderDocumentFile != null) - for (DocumentFile file : folderDocumentFile.listFiles()) - folderCache.put(file.getName(), file.getUri().toString()); - } - String filenameUrl = folderCache.get(filename); - if(filenameUrl != null) - return openFileFromContentResolver(filenameUrl, writeAccess); + if(filename != null) { + if(filename.startsWith("content://")) + return openFileFromContentResolver(filename, writeAccess); + if (folderURLCached == null || !folderURLCached.equals(folderURL)) { + folderURLCached = folderURL; + folderCache.clear(); + Uri folderURI = Uri.parse(folderURL); + DocumentFile folderDocumentFile = DocumentFile.fromTreeUri(this, folderURI); + if (folderDocumentFile != null) + for (DocumentFile file : folderDocumentFile.listFiles()) + folderCache.put(file.getName(), file.getUri().toString()); + } + String filenameUrl = folderCache.get(filename); + if (filenameUrl != null) + return openFileFromContentResolver(filenameUrl, writeAccess); + } return -1; } @@ -1396,8 +1556,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On return -1; } - public void showAlert(String text) { - Utils.showAlert(this, text); + public void showAlert(String text) { + showAlert(text, false); + } + + public void showAlert(String text, boolean lengthLong) { + Utils.showAlert(this, text, lengthLong); } @SuppressWarnings("unused") @@ -1480,30 +1644,38 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } @SuppressWarnings("unused") - public String getFirstKMLFilenameForType(char chipsetType) { - extractKMLScripts(); + public int getFirstKMLFilenameForType(char chipsetType) { + if(!kmlFolderUseDefault) { + extractKMLScripts(); - for (int i = 0; i < kmlScripts.size(); i++) { - KMLScriptItem kmlScriptItem = kmlScripts.get(i); - if (kmlScriptItem.model.charAt(0) == chipsetType) { - return kmlScriptItem.filename; - } - } + for (int i = 0; i < kmlScripts.size(); i++) { + KMLScriptItem kmlScriptItem = kmlScripts.get(i); + if (kmlScriptItem.model.charAt(0) == chipsetType) { + showAlert(String.format(Locale.US, "Existing KML script not found. Trying the compatible script: %s", kmlScriptItem.filename), true); + NativeLib.setCurrentKml(kmlScriptItem.filename); + NativeLib.setEmuDirectory(kmlFolderURL); + return 1; + } + } - // Not found, so we search in the default KML asset folder - kmlFolderUseDefault = true; - kmlFolderChange = true; + // Not found, so we search in the default KML asset folder + kmlFolderUseDefault = true; + kmlFolderChange = true; + } extractKMLScripts(); for (int i = 0; i < kmlScripts.size(); i++) { KMLScriptItem kmlScriptItem = kmlScripts.get(i); if (kmlScriptItem.model.charAt(0) == chipsetType) { - return kmlScriptItem.filename; + showAlert(String.format(Locale.US, "Existing KML script not found. Trying the embedded and compatible script: %s", kmlScriptItem.filename), true); + NativeLib.setCurrentKml(kmlScriptItem.filename); + NativeLib.setEmuDirectory("assets/calculators/"); + return 1; } } - - return null; + showAlert("Cannot find the KML template file, sorry.", true); + return 0; } @SuppressWarnings("unused") @@ -1552,11 +1724,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On bitmapIcon.copyPixelsFromBuffer(buffer); } catch (Exception ex) { // Cannot load the icon -// bitmapIcon.recycle(); bitmapIcon = null; } } else if(bitmapIcon != null) { -// bitmapIcon.recycle(); bitmapIcon = null; } if(bitmapIcon == null) @@ -1580,145 +1750,140 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } private void setPort1Settings(boolean port1Plugged, boolean port1Writable) { - settings.putBoolean("settings_port1en", port1Plugged); - settings.putBoolean("settings_port1wr", port1Writable); - updateFromPreferences("settings_port1", true); + if(this.getClass().getPackage().getName().equals("org.emulator.forty.eight")) { + settings.putBoolean("settings_port1en", port1Plugged); + settings.putBoolean("settings_port1wr", port1Writable); + updateFromPreferences("settings_port1", true); + } } - private void updateFromPreferences(String key, boolean isDynamic) { + private void updateFromPreferences(String key, boolean isDynamic) { int isDynamicValue = isDynamic ? 1 : 0; - if(key == null) { - String[] settingKeys = { - "settings_realspeed", "settings_grayscale", "settings_rotation", "settings_auto_layout", "settings_allow_pinch_zoom", "settings_lcd_overlapping_mode", "settings_lcd_pixel_borders", - "settings_hide_bar", "settings_hide_button_menu", "settings_sound_volume", "settings_haptic_feedback", - "settings_background_kml_color", "settings_background_fallback_color", + if(key == null) { + String[] settingKeys = { + "settings_realspeed", "settings_grayscale", "settings_rotation", "settings_auto_layout", "settings_allow_pinch_zoom", "settings_lcd_overlapping_mode", "settings_lcd_pixel_borders", + "settings_hide_bar", "settings_hide_button_menu", "settings_sound_volume", "settings_haptic_feedback", + "settings_background_kml_color", "settings_background_fallback_color", "settings_printer_model", "settings_macro", - "settings_kml", "settings_port1", "settings_port2" }; + "settings_kml", "settings_port1", "settings_port2" }; for (String settingKey : settingKeys) - updateFromPreferences(settingKey, false); - } else { - switch (key) { - case "settings_realspeed": + updateFromPreferences(settingKey, false); + } else { + switch (key) { + case "settings_realspeed": NativeLib.setConfiguration(key, isDynamicValue, settings.getBoolean(key, false) ? 1 : 0, 0, null); - break; - case "settings_grayscale": + break; + case "settings_grayscale": NativeLib.setConfiguration(key, isDynamicValue, settings.getBoolean(key, false) ? 1 : 0, 0, null); - break; + break; - case "settings_rotation": - int rotationMode = 0; - try { + case "settings_rotation": + int rotationMode = 0; + try { rotationMode = Integer.parseInt(settings.getString("settings_rotation", "0")); - } catch (NumberFormatException ex) { - // Catch bad number format - } - mainScreenView.setRotationMode(rotationMode, isDynamic); - break; - case "settings_auto_layout": + } catch (NumberFormatException ex) { + // Catch bad number format + } + mainScreenView.setRotationMode(rotationMode, isDynamic); + break; + case "settings_auto_layout": int autoLayoutMode = 1; - try { + try { autoLayoutMode = Integer.parseInt(settings.getString("settings_auto_layout", "1")); - } catch (NumberFormatException ex) { - // Catch bad number format - } - mainScreenView.setAutoLayout(autoLayoutMode, isDynamic); - break; - case "settings_allow_pinch_zoom": + } catch (NumberFormatException ex) { + // Catch bad number format + } + mainScreenView.setAutoLayout(autoLayoutMode, isDynamic); + break; + case "settings_allow_pinch_zoom": mainScreenView.setAllowPinchZoom(settings.getBoolean("settings_allow_pinch_zoom", true)); - break; - case "settings_lcd_overlapping_mode": - int overlappingLCDMode = LCDOverlappingView.OVERLAPPING_LCD_MODE_NONE; - try { + break; + case "settings_lcd_overlapping_mode": + int overlappingLCDMode = LCDOverlappingView.OVERLAPPING_LCD_MODE_NONE; + try { overlappingLCDMode = Integer.parseInt(settings.getString("settings_lcd_overlapping_mode", "0")); - } catch (NumberFormatException ex) { - // Catch bad number format - } - lcdOverlappingView.setOverlappingLCDMode(overlappingLCDMode); - break; - case "settings_lcd_pixel_borders": + } catch (NumberFormatException ex) { + // Catch bad number format + } + lcdOverlappingView.setOverlappingLCDMode(overlappingLCDMode); + break; + case "settings_lcd_pixel_borders": boolean usePixelBorders = settings.getBoolean("settings_lcd_pixel_borders", false); - mainScreenView.setUsePixelBorders(usePixelBorders); - lcdOverlappingView.setUsePixelBorders(usePixelBorders); - break; - case "settings_hide_bar": - case "settings_hide_bar_status": - case "settings_hide_bar_nav": + mainScreenView.setUsePixelBorders(usePixelBorders); + lcdOverlappingView.setUsePixelBorders(usePixelBorders); + break; + case "settings_hide_bar": + case "settings_hide_bar_status": + case "settings_hide_bar_nav": if (settings.getBoolean("settings_hide_bar_status", false) || settings.getBoolean("settings_hide_bar_nav", false)) - hideSystemUI(); - else - showSystemUI(); - break; - case "settings_hide_button_menu": + hideSystemUI(); + else + showSystemUI(); + break; + case "settings_hide_button_menu": imageButtonMenu.setVisibility(settings.getBoolean("settings_hide_button_menu", false) ? View.GONE : View.VISIBLE); - break; + break; - case "settings_sound_volume": { + case "settings_sound_volume": { int volumeOption = settings.getInt("settings_sound_volume", 64); - NativeLib.setConfiguration("settings_sound_volume", isDynamicValue, volumeOption, 0, null); - break; - } - case "settings_haptic_feedback": - // Nothing to do - break; + NativeLib.setConfiguration("settings_sound_volume", isDynamicValue, volumeOption, 0, null); + break; + } + case "settings_haptic_feedback": + // Nothing to do + break; - case "settings_background_kml_color": + case "settings_background_kml_color": mainScreenView.setBackgroundKmlColor(settings.getBoolean("settings_background_kml_color", false)); - break; - case "settings_background_fallback_color": - try { + break; + case "settings_background_fallback_color": + try { mainScreenView.setBackgroundFallbackColor(Integer.parseInt(settings.getString("settings_background_fallback_color", "0"))); - } catch (NumberFormatException ex) { - // Catch bad number format - } - break; - case "settings_printer_model": - try { + } catch (NumberFormatException ex) { + // Catch bad number format + } + break; + case "settings_printer_model": + try { printerSimulator.setPrinterModel82240A(Integer.parseInt(settings.getString("settings_printer_model", "1")) == 0); - } catch (NumberFormatException ex) { - // Catch bad number format - } - break; + } catch (NumberFormatException ex) { + // Catch bad number format + } + break; - case "settings_kml": - case "settings_kml_default": - case "settings_kml_folder": - kmlFolderUseDefault = settings.getBoolean("settings_kml_default", true); - if (!kmlFolderUseDefault) { - kmlFolderURL = settings.getString("settings_kml_folder", ""); - // https://github.com/googlesamples/android-DirectorySelection - // https://stackoverflow.com/questions/44185477/intent-action-open-document-tree-doesnt-seem-to-return-a-real-path-to-drive/44185706 - // https://stackoverflow.com/questions/26744842/how-to-use-the-new-sd-card-access-api-presented-for-android-5-0-lollipop - } - kmlFolderChange = true; - break; + case "settings_kml": + case "settings_kml_default": + case "settings_kml_folder": + loadKMLFolder(); + break; - case "settings_macro": - case "settings_macro_real_speed": - case "settings_macro_manual_speed": + case "settings_macro": + case "settings_macro_real_speed": + case "settings_macro_manual_speed": boolean macroRealSpeed = settings.getBoolean("settings_macro_real_speed", true); int macroManualSpeed = settings.getInt("settings_macro_manual_speed", 500); - NativeLib.setConfiguration("settings_macro", isDynamicValue, macroRealSpeed ? 1 : 0, macroManualSpeed, null); - break; + NativeLib.setConfiguration("settings_macro", isDynamicValue, macroRealSpeed ? 1 : 0, macroManualSpeed, null); + break; - case "settings_port1": - case "settings_port1en": - case "settings_port1wr": - NativeLib.setConfiguration("settings_port1", isDynamicValue, + case "settings_port1": + case "settings_port1en": + case "settings_port1wr": + NativeLib.setConfiguration("settings_port1", isDynamicValue, settings.getBoolean("settings_port1en", false) ? 1 : 0, settings.getBoolean("settings_port1wr", false) ? 1 : 0, - null); - break; - case "settings_port2": - case "settings_port2en": - case "settings_port2wr": - case "settings_port2load": - NativeLib.setConfiguration("settings_port2", isDynamicValue, + null); + break; + case "settings_port2": + case "settings_port2en": + case "settings_port2wr": + case "settings_port2load": + NativeLib.setConfiguration("settings_port2", isDynamicValue, settings.getBoolean("settings_port2en", false) ? 1 : 0, settings.getBoolean("settings_port2wr", false) ? 1 : 0, settings.getString("settings_port2load", "")); - break; - } + break; + } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index a5b5fa0..6606343 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -77,10 +77,16 @@ 2mb (16 ports: 2 through 17) 4mb (32 ports: 2 through 33) Permission Denied - For security reason, you must select the folder where are the KML and ROM files and then, reopen this file! + For security reason, you must select the folder where are the KML and ROM files, please. Please, open again I hope now you could open again the state file. - State saved in "%s". + KML Script not Found + You must select the folder where are the KML and ROM files, please. + The KML script "%s" cannot be found. You must select the folder where are the KML and ROM files, please. + The KML script "%s" cannot be found in the folder "%s". You must select the folder where are the KML and ROM files, please. + Port 2 File not Found + The port 2 file "%s" cannot be found. You should select it again, please. + State saved in %s Do you want to save changes?\n(BACK to cancel) Do you want to save this new state file?\n(To avoid losing the state of the machine) Yes