- Add an optional menu button in the top left corner.

- Rewrite the timers engine (Hoping this fixes the issue with the stuck "busy" annunciator.)
- Prevent to load/save object and copy/paste with HP39/40.
This commit is contained in:
dgis 2019-06-05 00:52:30 +02:00
parent 3e345c61fd
commit f4a3ec6476
7 changed files with 294 additions and 66 deletions

View file

@ -4,7 +4,7 @@ WARNING: WITH VERSION 1.3, THE STATUS FILE HAS BEEN MODIFIED AND IS NOW FULLY CO
HOWEVER, BEFORE THE UPDATE, BACK UP YOUR DATA BECAUSE YOU COULD LOSE THEM.
This project ports the Windows application Emu48 written in C to Android.
It uses the Android NDK. The former Emu48 source code remains untouched because of a thin win32 emulation layer above Linux/NDK!
It uses the Android NDK. The former Emu48 source code (written by Sébastien Carlier and Christoph Giesselink) remains untouched because of a thin win32 emulation layer above Linux/NDK!
This win32 layer will allow to easily update from the original Emu48 source code.
It can open or save the exact same state files (state.e48/e49) than the original Windows application!
@ -20,12 +20,13 @@ QUICK START
1. From the left side, slide your finger to open the menu.
2. Touch the "New..." menu item.
3. Select a predefined faceplate (or select a custom KML script folder).
3. Select a predefined faceplate (or select a custom KML script folder with Android >= 5.0).
4. And the calculator should now be opened.
NOTES
- The Help menu displays Emu48's original help HTML page and may not accurately reflect the behavior of this Android version.
- When using a custom KML script by selecting a folder, you must take care of the case sensitivity of its dependency files.
- Starting with the version 1.4, a RAM card generator for the port 2 of the HP48SX and HP48GX has been added.
Like with the MKSHARED.EXE on Windows, you can generate the card in a file (i.e.: SHARED.BIN).
@ -57,13 +58,16 @@ NOT WORKING YET
CHANGES
Version 1.4 (2019-05-xx)
Version 1.4 (2019-06-xx)
- Add an optional menu button in the top left corner.
- Add a RAM card generator for the port 2 of the HP48SX and HP48GX.
- Add the possibility to hide the status and/or the navigation bars.
- Rewrite the timers engine (Hoping this fixes the issue with the stuck "busy" annunciator.)
- Update the Win32 layer from Emu42 dev.
- Fix the authentic speed issue at the first start.
- Fix the non working Restore/Delete backup.
- Prevent to load/save object and copy/paste with HP39/40.
Version 1.3 (2019-04-04)
@ -131,17 +135,18 @@ The Eric's Real scripts ("real*.kml" and "real*.bmp") are embedded in this appli
TODO
- Warn the user about the KML folder selection if this is Android < 5.0
- Disable the Restore after loading a new state file.
- Improve the swipe gesture.
- The clock seems unsynchronized sometimes
- Sometimes the "busy" annunciator gets stuck
- Add KML script loading dependencies fallback to the inner ROM (and may be KML include?)
- Add a separation between the pixels (Suggestion from Jaime Meza)
- Improve the access to the menu
- Change the logo following the template
BUILD
Emu48 for Android is built with Android Studio 3.3 (2019).
Emu48 for Android is built with Android Studio 3.4 (2019).
And to generate an installable APK file with a real Android device, it MUST be signed.
Either use Android Studio:

View file

@ -19,6 +19,7 @@ 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)

View file

@ -32,25 +32,13 @@ DWORD GetLastError(VOID) {
}
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];
static void initTimer();
#define MAX_FILE_MAPPING_HANDLE 10
static HANDLE fileMappingHandles[MAX_FILE_MAPPING_HANDLE];
void win32Init() {
for (int i = 0; i < MAX_TIMER; ++i) {
timerEvents[i].valid = FALSE;
}
initTimer();
for (int i = 0; i < MAX_FILE_MAPPING_HANDLE; ++i) {
fileMappingHandles[i] = NULL;
}
@ -850,9 +838,9 @@ LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
}
return NULL;
}
BOOL PostMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
BOOL PostMessage(HWND handleWindow, UINT Msg, WPARAM wParam, LPARAM lParam) {
//TODO
if(hWnd == 0 && Msg == WM_COMMAND) {
if(Msg == WM_COMMAND && hWnd == handleWindow) {
int menuCommand = (int) ((wParam & 0xffff) - 40000);
LOGD("Menu Item %d", menuCommand);
sendMenuItemCommand(menuCommand);
@ -1143,7 +1131,7 @@ MMRESULT waveOutOpen(LPHWAVEOUT phwo, UINT uDeviceID, LPCWAVEFORMATEX pwfx, DWOR
sev.sigev_notify_attributes = NULL;
timer_t * timer = &(handle->playerDoneTimer);
if (timer_create(CLOCK_MONOTONIC, &sev, timer) == -1) {
TIMER_LOGD("timeSetEvent() ERROR in timer_create");
WAVE_OUT_LOGD("waveOutOpen() ERROR in timer_create");
return NULL;
}
@ -1282,7 +1270,7 @@ MMRESULT waveOutWrite(HWAVEOUT hwo, LPWAVEHDR pwh, UINT cbwh) {
}
if (timer_settime(hwo->playerDoneTimer, 0, &(hwo->playerDoneTimerSetTimes), NULL) == -1) {
TIMER_LOGD("timeSetEvent() ERROR in timer_settime (playerDoneTimerSetTimes)");
WAVE_OUT_LOGD("waveOutWrite() ERROR in timer_settime (playerDoneTimerSetTimes)");
}
#endif
@ -1611,7 +1599,7 @@ BOOL PatBlt(HDC hdcDest, int x, int y, int w, int h, DWORD rop) {
destinationStride = androidBitmapInfo.stride;
if ((ret = AndroidBitmap_lockPixels(jniEnv, bitmapMainScreen, &pixelsDestination)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
LOGD("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return FALSE;
}
} else {
@ -1738,7 +1726,7 @@ BOOL StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, int hDest, HDC hdc
destinationStride = androidBitmapInfo.stride;
if ((ret = AndroidBitmap_lockPixels(jniEnv, bitmapMainScreen, &pixelsDestination)) < 0) {
LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret);
LOGD("AndroidBitmap_lockPixels() failed ! error=%d", ret);
return FALSE;
}
} else {
@ -1938,7 +1926,7 @@ BOOL StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, int hDest, HDC hdc
}
if(jniEnv && (ret = AndroidBitmap_unlockPixels(jniEnv, bitmapMainScreen)) < 0) {
LOGE("AndroidBitmap_unlockPixels() failed ! error=%d", ret);
LOGD("AndroidBitmap_unlockPixels() failed ! error=%d", ret);
return FALSE;
}
@ -2224,10 +2212,38 @@ 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);
@ -2243,6 +2259,8 @@ void timerCallback(int timerId) {
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) {
@ -2252,7 +2270,8 @@ MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_
}
}
if(timerId == -1) {
TIMER_LOGD("timeSetEvent() ERROR: No more timer available");
LOGD("timeSetEvent() ERROR: No more timer available");
pthread_mutex_unlock(&timerEventsLock);
return NULL;
}
timerEvents[timerId].timerId = timerId;
@ -2267,8 +2286,26 @@ MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_
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);
if (timer_create(CLOCK_MONOTONIC, &sev, timer) == -1) {
TIMER_LOGD("timeSetEvent() ERROR in timer_create");
// 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;
}
@ -2284,20 +2321,198 @@ MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_
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);
TIMER_LOGD("timeSetEvent() ERROR in timer_settime");
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;
UINT uResolution;
LPTIMECALLBACK fptc;
DWORD_PTR dwUser;
UINT fuEvent;
ULONGLONG triggerTime;
struct timerEvent * next;
struct timerEvent * prev;
};
static struct timerEvent timersList;
static int lastTimerId;
static pthread_mutex_t timerEventsLock;
static pthread_t timerThreadId;
static BOOL timerThreadToEnd;
static void initTimer() {
timersList.next = &timersList;
timersList.prev = &timersList;
timerThreadId = 0;
timerThreadToEnd = TRUE;
lastTimerId = 0;
}
MMRESULT timeKillEvent(UINT uTimerID) {
TIMER_LOGD("timeKillEvent(uTimerID: [%d])", uTimerID);
deleteTimeEvent(uTimerID);
pthread_mutex_lock(&timerEventsLock);
struct timerEvent * nextTimer, * timerToFree = NULL;
// Search the timer by id
for (nextTimer = timersList.next; nextTimer != &timersList; nextTimer = nextTimer->next) {
if(uTimerID == nextTimer->timerId) {
// Remove the timer
nextTimer->next->prev = nextTimer->prev;
nextTimer->prev->next = nextTimer->next;
timerToFree = nextTimer;
break;
}
}
if(timersList.next == &timersList) {
// The list is empty
// Leave the thread?
timerThreadToEnd = TRUE;
}
pthread_mutex_unlock(&timerEventsLock);
if(timerToFree)
free(timerToFree);
return 0; //No error
}
static void insertTimer(struct timerEvent * newTimer) {
struct timerEvent * nextTimer;
// Search where to insert this new timer
for (nextTimer = timersList.next; nextTimer != &timersList; nextTimer = nextTimer->next) {
if(newTimer->triggerTime < nextTimer->triggerTime)
break;
}
// Insert this new timer
newTimer->next = nextTimer;
newTimer->prev = nextTimer->prev;
nextTimer->prev->next = newTimer;
nextTimer->prev = newTimer;
}
static void dumpTimers() {
TIMER_LOGD("dumpTimers()");
for (struct timerEvent * nextTimer = timersList.next; nextTimer != &timersList; nextTimer = nextTimer->next) {
TIMER_LOGD("\ttimerId: %d (%x), uDelay: %d, dwUser: %d, fuEvent: %d, triggerTime: %ld)",
nextTimer->timerId, nextTimer, nextTimer->uDelay, nextTimer->dwUser, nextTimer->fuEvent, nextTimer->triggerTime);
}
}
static void timerThreadStart(LPVOID lpThreadParameter) {
pthread_mutex_lock(&timerEventsLock);
while (!timerThreadToEnd) {
TIMER_LOGD("timerThreadStart() %ld", GetTickCount64());
int sleep_time;
for (;;) {
struct timerEvent *timer = (timersList.next == &timersList ? NULL : timersList.next);
if (!timer) {
sleep_time = -1;
break;
}
sleep_time = timer->triggerTime - GetTickCount64();
if (sleep_time > 0)
break;
// Remove this timer from the linked list
timer->next->prev = timer->prev;
timer->prev->next = timer->next;
struct timerEvent *timerToFree;
if(timer->fuEvent == TIME_PERIODIC) {
timer->triggerTime += timer->uDelay;
/* Re-insert the timer */
insertTimer(timer);
timerToFree = NULL;
} else
timerToFree = timer;
// Copy the timer data because we unlock the mutex!
int timerId = timer->timerId;
LPTIMECALLBACK fptc = timer->fptc;
DWORD_PTR dwUser = timer->dwUser;
pthread_mutex_unlock(&timerEventsLock);
// Call the timer callback now!
fptc((UINT)timerId, 0, (DWORD) dwUser, 0, 0);
pthread_mutex_lock(&timerEventsLock);
if(timerToFree)
free(timerToFree);
}
#if defined(TIMER_LOGD)
dumpTimers();
#endif
if (sleep_time < 0)
break;
if (sleep_time == 0)
continue;
pthread_mutex_unlock(&timerEventsLock);
Sleep(sleep_time);
pthread_mutex_lock(&timerEventsLock);
}
timerThreadId = 0;
pthread_mutex_unlock(&timerEventsLock);
jniDetachCurrentThread();
}
MMRESULT timeSetEvent(UINT uDelay, UINT uResolution, LPTIMECALLBACK fptc, DWORD_PTR dwUser, UINT fuEvent) {
TIMER_LOGD("timeSetEvent(uDelay: %d, fuEvent: %d)", uDelay, fuEvent);
struct timerEvent * newTimer = (struct timerEvent *)malloc(sizeof(struct timerEvent));
pthread_mutex_lock(&timerEventsLock);
newTimer->timerId = ++lastTimerId;
newTimer->uDelay = uDelay; // In ms
newTimer->uResolution = uResolution;
newTimer->fptc = fptc;
newTimer->dwUser = dwUser;
newTimer->fuEvent = fuEvent; // TIME_ONESHOT / TIME_PERIODIC
newTimer->triggerTime = GetTickCount64() + (ULONGLONG)uDelay;
insertTimer(newTimer);
timerThreadToEnd = FALSE;
if(!timerThreadId) {
// If not yet created, create the thread which will handle all the timers
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);
// 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.
pthread_mutex_unlock(&timerEventsLock);
return NULL;
}
}
TIMER_LOGD("timeSetEvent() -> timerId: [%d]", newTimer->timerId);
pthread_mutex_unlock(&timerEventsLock);
return (MMRESULT) newTimer->timerId; // No error
}
#endif
MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc) {
if(ptc) {
ptc->wPeriodMin = 1; // ms
@ -2320,21 +2535,25 @@ VOID GetLocalTime(LPSYSTEMTIME lpSystemTime) {
clock_gettime(CLOCK_REALTIME, &ts);
time_t tim = ts.tv_sec;
localtime_r(&tim, &tm);
lpSystemTime->wYear = 1900 + tm.tm_year;
lpSystemTime->wMonth = 1 + tm.tm_mon;
lpSystemTime->wDayOfWeek = tm.tm_wday;
lpSystemTime->wDay = tm.tm_mday;
lpSystemTime->wHour = tm.tm_hour;
lpSystemTime->wMinute = tm.tm_min;
lpSystemTime->wSecond = tm.tm_sec;
lpSystemTime->wMilliseconds = ts.tv_nsec / 1e6;
lpSystemTime->wYear = (WORD) (1900 + tm.tm_year);
lpSystemTime->wMonth = (WORD) (1 + tm.tm_mon);
lpSystemTime->wDayOfWeek = (WORD) (tm.tm_wday);
lpSystemTime->wDay = (WORD) (tm.tm_mday);
lpSystemTime->wHour = (WORD) (tm.tm_hour);
lpSystemTime->wMinute = (WORD) (tm.tm_min);
lpSystemTime->wSecond = (WORD) (tm.tm_sec);
lpSystemTime->wMilliseconds = (WORD) (ts.tv_nsec / 1e6);
}
}
WORD GetTickCount(VOID) {
//TODO
ULONGLONG GetTickCount64(VOID) {
struct timespec now;
if (clock_gettime(CLOCK_MONOTONIC, &now))
return 0;
return (ULONGLONG) ((ULONGLONG)now.tv_sec * 1000UL + (ULONGLONG)now.tv_nsec / 1000000UL);
}
DWORD GetTickCount(VOID) {
return (DWORD)GetTickCount64();
}
BOOL EnableWindow(HWND hWnd, BOOL bEnable) {
//TODO

View file

@ -1034,7 +1034,8 @@ extern MMRESULT timeGetDevCaps(LPTIMECAPS ptc, UINT cbtc);
extern MMRESULT timeBeginPeriod(UINT uPeriod);
extern MMRESULT timeEndPeriod(UINT uPeriod);
extern VOID GetLocalTime(LPSYSTEMTIME lpSystemTime);
extern WORD GetTickCount(VOID);
extern ULONGLONG GetTickCount64(VOID);
extern DWORD GetTickCount(VOID);
extern BOOL EnableWindow(HWND hWnd, BOOL bEnable);
extern HWND GetDlgItem(HWND hDlg, int nIDDlgItem);

View file

@ -361,22 +361,19 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
Menu menu = navigationView.getMenu();
boolean isDocumentAvailable = NativeLib.isDocumentAvailable();
boolean isBackup = NativeLib.isBackup();
// disable stack loading items on HP38G, HP39/40G, HP39G+
// BOOL bStackEnable = cCurrentRomType!='6' && cCurrentRomType!='A' && cCurrentRomType!='E' && cCurrentRomType!='P'; // CdB for HP: add apples
// BOOL bRun = nState == SM_RUN || nState == SM_SLEEP;
// UINT uStackEnable = (bRun && bStackEnable) ? MF_ENABLED : MF_GRAYED;
// UINT uRun = bRun ? MF_ENABLED : MF_GRAYED;
// UINT uBackup = bBackup ? MF_ENABLED : MF_GRAYED;
int cCurrentRomType = NativeLib.getCurrentModel();
//int nState = NativeLib.getState();
//boolean bRun = (nState == 0 /* SM_RUN */ || nState == 3 /* SM_SLEEP */);
boolean bStackEnable = cCurrentRomType!='6' && cCurrentRomType!='A' && cCurrentRomType!='E' && cCurrentRomType!='P'; // CdB for HP: add apples
menu.findItem(R.id.nav_save).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_save_as).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_close).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_load_object).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_save_object).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_load_object).setEnabled(isDocumentAvailable && bStackEnable);
menu.findItem(R.id.nav_save_object).setEnabled(isDocumentAvailable && bStackEnable);
menu.findItem(R.id.nav_copy_screen).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_copy_stack).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_paste_stack).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_copy_stack).setEnabled(isDocumentAvailable && bStackEnable);
menu.findItem(R.id.nav_paste_stack).setEnabled(isDocumentAvailable && bStackEnable);
menu.findItem(R.id.nav_reset_calculator).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_backup_save).setEnabled(isDocumentAvailable);
menu.findItem(R.id.nav_backup_restore).setEnabled(isDocumentAvailable && isBackup);
@ -633,29 +630,35 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
int model = NativeLib.getCurrentModel();
String extension = "e48";
String filename = "emu48-state.e48";
switch (model) {
case 'S': //HP48SX
filename = "emu48-state-48sx.e48";
break;
case 'G': //HP48GX
extension = "e48";
filename = "emu48-state-48gx.e48";
break;
case '6': //HP38G 64K RAM
case 'A': //HP38G 32K RAM
extension = "e38";
filename = "emu48-state.e38";
break;
case 'E': // HP39G/(HP39G+/HP39GS)/HP40G/HP40GS
extension = "e39";
filename = "emu48-state.e39";
break;
case 'P': // HP39G+/HP39GS
extension = "e39";
filename = "emu48-state.e39";
break;
case '2': // HP48GII
filename = "emu48-state-48gii.e49";
break;
case 'Q': // HP49G+/HP50G
filename = "emu48-state-50g.e49";
break;
case 'X': // HP49G
extension = "e49";
filename = "emu48-state-49g.e49";
break;
}
intent.putExtra(Intent.EXTRA_TITLE, "emu48-state." + extension);
intent.putExtra(Intent.EXTRA_TITLE, filename);
startActivityForResult(intent, INTENT_GETSAVEFILENAME);
}
private void OnFileClose() {

View file

@ -6,7 +6,6 @@ import android.content.pm.ActivityInfo;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.DisplayMetrics;
import android.util.Log;
@ -251,7 +250,7 @@ public class MainScreenView extends PanAndScaleView {
if(imageRatio > 1.0f)
((Activity)getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
else
((Activity)getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
((Activity)getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE);
} else if(autoZoom) {
((Activity)getContext()).setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
if (imageRatio < 1.0f != viewRatio < 1.0f) {

View file

@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.0'
classpath 'com.android.tools.build:gradle:3.4.1'
// NOTE: Do not place your application dependencies here; they belong