diff --git a/ReadMe.txt b/ReadMe.txt
index f2a79dd..d791fdb 100644
--- a/ReadMe.txt
+++ b/ReadMe.txt
@@ -63,9 +63,10 @@ LINKS
CHANGES
-Version 2.2 (2020-12-08)
+Version 2.2 (2020-12-09)
-- The KML folder is now saved when changing the KML script for a custom one via the menu "Change KML Script...".
+- The KML folder is now well saved when changing the KML script for a custom one via the menu "Change KML Script...".
+- Fix an issue when the permission to read the KML folder has been lost.
Version 2.1 (2020-11-23)
diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt
index c16d28f..bdf856d 100644
--- a/app/CMakeLists.txt
+++ b/app/CMakeLists.txt
@@ -19,7 +19,6 @@ cmake_minimum_required(VERSION 3.4.1)
#add_compile_options(-DDEBUG_ANDROID_FILE)
#add_compile_options(-DNEW_WIN32_SOUND_ENGINE)
-#add_compile_options(-DWIN32_TIMER_THREAD) # old timer algorithm which stop when no more thread!
add_compile_options(-DEMUXX=48)
diff --git a/app/build.gradle b/app/build.gradle
index 6ce3c99..87c1b73 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -33,8 +33,8 @@ android {
applicationId "org.emulator.forty.eight"
minSdkVersion 19
targetSdkVersion 29
- versionCode 18
- versionName "2.1"
+ versionCode 20
+ versionName "2.2"
setProperty("archivesBaseName", "Emu48-v$versionName")
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
diff --git a/app/src/main/assets/ReadMe.txt b/app/src/main/assets/ReadMe.txt
index da67103..a76e644 100644
--- a/app/src/main/assets/ReadMe.txt
+++ b/app/src/main/assets/ReadMe.txt
@@ -63,9 +63,10 @@ LINKS
CHANGES
-Version 2.2 (2020-12-08)
+Version 2.2 (2020-12-09)
-- The KML folder is now saved when changing the KML script for a custom one via the menu "Change KML Script...".
+- The KML folder is now well saved when changing the KML script for a custom one via the menu "Change KML Script...".
+- Fix an issue when the permission to read the KML folder has been lost.
Version 2.1 (2020-11-23)
diff --git a/app/src/main/cpp/win32-layer.c b/app/src/main/cpp/win32-layer.c
index a795b96..f2e7beb 100644
--- a/app/src/main/cpp/win32-layer.c
+++ b/app/src/main/cpp/win32-layer.c
@@ -2498,128 +2498,6 @@ HANDLE WINAPI GetClipboardData(UINT uFormat) {
return szText;
}
-#if defined WIN32_TIMER_THREAD
-struct timerEvent {
- BOOL valid;
- int timerId;
- LPTIMECALLBACK fptc;
- DWORD_PTR dwUser;
- UINT fuEvent;
- timer_t timer;
-};
-
-#define MAX_TIMER 10
-struct timerEvent timerEvents[MAX_TIMER];
-pthread_mutex_t timerEventsLock;
-static void initTimer() {
- for (int i = 0; i < MAX_TIMER; ++i) {
- timerEvents[i].valid = FALSE;
- }
-}
-
-void deleteTimeEvent(UINT uTimerID) {
- pthread_mutex_lock(&timerEventsLock);
- timer_delete(timerEvents[uTimerID - 1].timer);
- timerEvents[uTimerID - 1].valid = FALSE;
- pthread_mutex_unlock(&timerEventsLock);
-}
-
-MMRESULT timeKillEvent(UINT uTimerID) {
- TIMER_LOGD("timeKillEvent(uTimerID: [%d])", uTimerID);
- deleteTimeEvent(uTimerID);
- return 0; //No error
-}
-
-void timerCallback(int timerId) {
- if(timerId >= 0 && timerId < MAX_TIMER && timerEvents[timerId].valid) {
- timerEvents[timerId].fptc((UINT) (timerId + 1), 0, (DWORD) timerEvents[timerId].dwUser, 0, 0);
-
- if(timerEvents[timerId].fuEvent == TIME_ONESHOT) {
- TIMER_LOGD("timerCallback remove timer uTimerID [%d]", timerId + 1);
- deleteTimeEvent((UINT) (timerId + 1));
- }
-
- jniDetachCurrentThread();
- }
-}
-MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_PTR dwUser, UINT fuEvent) {
- TIMER_LOGD("timeSetEvent(uDelay: %d, fuEvent: %d)", uDelay, fuEvent);
-
- pthread_mutex_lock(&timerEventsLock);
-
- // Find a timer id
- int timerId = -1;
- for (int i = 0; i < MAX_TIMER; ++i) {
- if(!timerEvents[i].valid) {
- timerId = i;
- break;
- }
- }
- if(timerId == -1) {
- LOGD("timeSetEvent() ERROR: No more timer available");
- pthread_mutex_unlock(&timerEventsLock);
- return NULL;
- }
- timerEvents[timerId].timerId = timerId;
- timerEvents[timerId].fptc = fptc;
- timerEvents[timerId].dwUser = dwUser;
- timerEvents[timerId].fuEvent = fuEvent;
-
-
- struct sigevent sev;
- sev.sigev_notify = SIGEV_THREAD;
- sev.sigev_notify_function = (void (*)(sigval_t)) timerCallback; //this function will be called when timer expires
- sev.sigev_value.sival_int = timerEvents[timerId].timerId; //this argument will be passed to cbf
- sev.sigev_notify_attributes = NULL;
- timer_t * timer = &(timerEvents[timerId].timer);
-
- // CLOCK_REALTIME 0 OK but X intervals only
- // CLOCK_MONOTONIC 1 OK but X intervals only
- // CLOCK_PROCESS_CPUTIME_ID 2 NOTOK, not working with PERIODIC!
- // CLOCK_THREAD_CPUTIME_ID 3 NOTOK
- // CLOCK_MONOTONIC_RAW 4 NOTOK
- // CLOCK_REALTIME_COARSE 5 NOTOK
- // CLOCK_MONOTONIC_COARSE 6 NOTOK
- // CLOCK_BOOTTIME 7 OK but X intervals only
- // CLOCK_REALTIME_ALARM 8 NOTOK EPERM
- // CLOCK_BOOTTIME_ALARM 9 NOTOK EPERM
- // CLOCK_SGI_CYCLE 10 NOTOK EINVAL
- // CLOCK_TAI 11
-
- if (timer_create(CLOCK_REALTIME, &sev, timer) == -1) {
- LOGD("timeSetEvent() ERROR in timer_create, errno: %d (EAGAIN 11 / EINVAL 22 / ENOMEM 12)", errno);
- // EAGAIN Temporary error during kernel allocation of timer structures.
- // EINVAL Clock ID, sigev_notify, sigev_signo, or sigev_notify_thread_id is invalid.
- // ENOMEM Could not allocate memory.
- pthread_mutex_unlock(&timerEventsLock);
- return NULL;
- }
-
- long freq_nanosecs = uDelay;
- struct itimerspec its;
- its.it_value.tv_sec = freq_nanosecs / 1000;
- its.it_value.tv_nsec = (freq_nanosecs % 1000) * 1000000;
- if(fuEvent == TIME_PERIODIC) {
- its.it_interval.tv_sec = its.it_value.tv_sec;
- its.it_interval.tv_nsec = its.it_value.tv_nsec;
- } else /*if(fuEvent == TIME_ONESHOT)*/ {
- its.it_interval.tv_sec = 0;
- its.it_interval.tv_nsec = 0;
- }
- if (timer_settime(timerEvents[timerId].timer, 0, &its, NULL) == -1) {
- LOGD("timeSetEvent() ERROR in timer_settime, errno: %d (EFAULT 14 / EINVAL 22)", errno);
- // EFAULT new_value, old_value, or curr_value is not a valid pointer.
- // EINVAL timerid is invalid. Or new_value.it_value is negative; or new_value.it_value.tv_nsec is negative or greater than 999,999,999.
- timer_delete(timerEvents[timerId].timer);
- pthread_mutex_unlock(&timerEventsLock);
- return NULL;
- }
- timerEvents[timerId].valid = TRUE;
- TIMER_LOGD("timeSetEvent() -> timerId+1: [%d]", timerId + 1);
- pthread_mutex_unlock(&timerEventsLock);
- return (MMRESULT) (timerId + 1); // No error
-}
-#else
struct timerEvent {
int timerId;
UINT uDelay;
@@ -2707,7 +2585,7 @@ static void insertTimer(struct timerEvent * newTimer) {
}
static void timerThreadStart(LPVOID lpThreadParameter) {
- LOGD("timerThreadStart() START");
+ TIMER_LOGD("timerThreadStart() START");
pthread_mutex_lock(&timerEventsLock);
while (!timerThreadToEnd) {
TIMER_LOGD("timerThreadStart() %ld", GetTickCount64());
@@ -2768,7 +2646,7 @@ static void timerThreadStart(LPVOID lpThreadParameter) {
timerThreadId = 0;
pthread_mutex_unlock(&timerEventsLock);
jniDetachCurrentThread();
- LOGD("timerThreadStart() END");
+ TIMER_LOGD("timerThreadStart() END");
}
MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_PTR dwUser, UINT fuEvent) {
@@ -2793,9 +2671,9 @@ MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_
if(!timerThreadId) {
// If not yet created, create the thread which will handle all the timers
- LOGD("timeSetEvent() pthread_create");
+ TIMER_LOGD("timeSetEvent() pthread_create");
if (pthread_create(&timerThreadId, NULL, (void *(*)(void *)) timerThreadStart, NULL) != 0) {
- LOGD("timeSetEvent() ERROR in pthread_create, errno: %d (EAGAIN 11 / EINVAL 22 / EPERM 1)", errno);
+ TIMER_LOGD("timeSetEvent() ERROR in pthread_create, errno: %d (EAGAIN 11 / EINVAL 22 / EPERM 1)", errno);
// EAGAIN Insufficient resources to create another thread.
// EINVAL Invalid settings in attr.
// ENOMEM No permission to set the scheduling policy and parameters specified in attr.
@@ -2808,7 +2686,6 @@ MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_
pthread_mutex_unlock(&timerEventsLock);
return (MMRESULT) newTimer->timerId; // No error
}
-#endif
MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc) {
if(ptc) {
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 cf8bb73..2574e4d 100644
--- a/app/src/main/java/org/emulator/forty/eight/MainActivity.java
+++ b/app/src/main/java/org/emulator/forty/eight/MainActivity.java
@@ -994,7 +994,6 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
// We only change the KML script here.
int result = NativeLib.onViewScript(scriptItem.filename, scriptItem.folder);
if(result > 0) {
- //TODO no need to call changeKMLFolder(scriptItem.folder);
settings.putString("settings_kml_folder_embedded", scriptItem.folder);
displayKMLTitle();
showKMLLog();
@@ -1515,8 +1514,20 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
// 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(kmlFolderDocumentFile != null) {
+ // Check the permission of the folder kmlScriptFolder
+ if (!kmlFolderDocumentFile.canRead()) {
+ // Permission denied, switch to the default asset folder
+ changeKMLFolder(null);
+ kmlScriptFolder = null;
+ showAlert(getString(R.string.message_open_kml_folder_permission_lost), true);
+ } else if (kmlFolderDocumentFile.exists())
+ changeKMLFolder(kmlScriptFolder);
+ } else {
+ // We are using the API < 21, so we
+ changeKMLFolder(null);
+ kmlScriptFolder = null;
+ }
}
if(settings.getBoolean("settings_port2en", false)) {
@@ -1527,13 +1538,14 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
DocumentFile port2DocumentFile = DocumentFile.fromSingleUri(this, port2Uri);
if (port2DocumentFile == null || !port2DocumentFile.exists()) {
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 = kmlScriptFolder;
+ kmlScriptFolderInIntentPort2Load = finalKmlScriptFolder;
Intent intentPort2 = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intentPort2.addCategory(Intent.CATEGORY_OPENABLE);
@@ -1544,7 +1556,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}).setNegativeButton(android.R.string.cancel, (dialog, which) -> {
// Deactivate the port2 because it is not reachable.
settings.putBoolean("settings_port2en", false);
- onFileOpenNative(url, kmlScriptFolder);
+ onFileOpenNative(url, finalKmlScriptFolder);
}).show();
return;
}
@@ -1936,7 +1948,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
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);
+ showAlert(String.format(Locale.US, getString(R.string.message_open_kml_not_found_alert_trying_other1), kmlScriptItem.filename), true);
NativeLib.setCurrentKml(kmlScriptItem.filename);
NativeLib.setEmuDirectory(kmlFolderURL);
return 1;
@@ -1953,13 +1965,13 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
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 embedded and compatible script: %s", kmlScriptItem.filename), true);
+ showAlert(String.format(Locale.US, getString(R.string.message_open_kml_not_found_alert_trying_other2), kmlScriptItem.filename), true);
NativeLib.setCurrentKml(kmlScriptItem.filename);
NativeLib.setEmuDirectory("assets/calculators/");
return 1;
}
}
- showAlert("Cannot find the KML template file, sorry.", true);
+ showAlert(getString(R.string.message_open_kml_not_found_alert), true);
return 0;
}
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 149f477..feddeb6 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -93,10 +93,14 @@
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.
+ Permission denied to access to the previously saved KML folder. Switch to the default embedded KML folder.
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.
+ Existing KML script not found. Trying the compatible script: %s
+ "Existing KML script not found. Trying the embedded and compatible script: %s"
+ "Cannot find the KML template file, sorry."
Port 2 File not Found
The port 2 file "%s" cannot be found. You should select it again, please.
State saved in %s