OpenCPN Partial API docs
Loading...
Searching...
No Matches
androidUTIL.cpp
1/***************************************************************************
2 *
3 * Project: OpenCPN
4 * Purpose: OpenCPN Android support utilities
5 * Author: David Register
6 *
7 ***************************************************************************
8 * Copyright (C) 2015 by David S. Register *
9 * *
10 * This program is free software; you can redistribute it and/or modify *
11 * it under the terms of the GNU General Public License as published by *
12 * the Free Software Foundation; either version 2 of the License, or *
13 * (at your option) any later version. *
14 * *
15 * This program is distributed in the hope that it will be useful, *
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
18 * GNU General Public License for more details. *
19 * *
20 * You should have received a copy of the GNU General Public License *
21 * along with this program; if not, write to the *
22 * Free Software Foundation, Inc., *
23 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
24 **************************************************************************/
25#include "wx/wxprec.h"
26
27#ifndef WX_PRECOMP
28#include "wx/wx.h"
29#endif // precompiled headers
30
31#include <sstream>
32
33#include <wx/tokenzr.h>
34#include <wx/aui/aui.h>
35#include <wx/config.h>
36#include <wx/fontpicker.h>
37#include <wx/filepicker.h>
38#include <wx/zipstrm.h>
39#include <wx/textwrapper.h>
40
41#include <QtAndroidExtras/QAndroidJniObject>
42
43#include "about.h"
44#include "AISTargetAlertDialog.h"
45#include "AISTargetListDialog.h"
46#include "AISTargetQueryDialog.h"
47#include "AndroidSound.h"
48#include "androidUTIL.h"
49#include "CanvasOptions.h"
50#include "chartdb.h"
51#include "chartdbs.h"
52#include "chcanv.h"
53#include "config.h"
54#include "config_vars.h"
55#include "dychart.h"
56#include "glChartCanvas.h"
57#include "gui_lib.h"
58#include "idents.h"
59#include "iENCToolbar.h"
60#include "logger.h"
61#include "MarkInfo.h"
62#include "MUIBar.h"
63#include "multiplexer.h"
64#include "nav_object_database.h"
65#include "navutil.h"
66#include "nmea0183.h"
67#include "OCPNPlatform.h"
68#include "ocpn_plugin.h"
69#include "options.h"
70#include "own_ship.h"
71#include "plugin_loader.h"
72#include "routemanagerdialog.h"
73#include "routeman.h"
74#include "RoutePropDlgImpl.h"
75#include "s52plib.h"
76#include "s52s57.h"
77#include "s52utils.h"
78#include "S57QueryDialog.h"
79#include "select.h"
80#include "TCWin.h"
81#include "toolbar.h"
82#include "toolbar.h"
83#include "TrackPropDlg.h"
84
85#ifdef HAVE_DIRENT_H
86#include "dirent.h"
87#endif
88
89const wxString AndroidSuppLicense = wxT(
90 "<br><br>The software included in this product contains copyrighted "
91 "software that is licensed under the GPL.")
92 wxT("A copy of that license is shown above.") wxT(
93 "You may obtain the complete Corresponding Source code from us for ")
94 wxT("a period of three years after our last shipment of this product, ")
95 wxT("by sending a money order or check for $5 to:<br><br>")
96 wxT("GPL Compliance Division<br>") wxT("Dyad Inc.<br>")
97 wxT("31 Ocean Reef Dr<br>") wxT("# C101-449<br>")
98 wxT("Key Largo, FL 33037-5282<br>")
99 wxT("United States<br><br>")
100 wxT("Please write “source for OpenCPN Version "
101 "{insert version here} in the memo line of "
102 "your payment.<br><br>");
103
104#if !defined(NAN)
105static const long long lNaN = 0xfff8000000000000;
106#define NAN (*(double *)&lNaN)
107#endif
108
109class androidUtilHandler;
110class PlugInManager;
111
112extern MyFrame *gFrame;
113extern const wxEventType wxEVT_OCPN_DATASTREAM;
114// extern const wxEventType wxEVT_DOWNLOAD_EVENT;
115
116wxEvtHandler *s_pAndroidNMEAMessageConsumer;
117wxEvtHandler *s_pAndroidBTNMEAMessageConsumer;
118
119extern AISTargetAlertDialog *g_pais_alert_dialog_active;
120extern AISTargetQueryDialog *g_pais_query_dialog_active;
121extern AISTargetListDialog *g_pAISTargetList;
122// extern MarkInfoImpl *pMarkPropDialog;
123extern RoutePropDlgImpl *pRoutePropDialog;
124extern TrackPropDlg *pTrackPropDialog;
125extern S57QueryDialog *g_pObjectQueryDialog;
126extern options *g_options;
127extern bool g_bSleep;
128androidUtilHandler *g_androidUtilHandler;
129extern wxDateTime g_start_time;
130extern RouteManagerDialog *pRouteManagerDialog;
131extern about *g_pAboutDlgLegacy;
132extern bool g_bFullscreen;
133extern OCPNPlatform *g_Platform;
134
135// Static globals
136extern ChartDB *ChartData;
137extern MyConfig *pConfig;
138
139// Preferences globals
140extern bool g_bShowOutlines;
141extern bool g_bShowChartBar;
142extern bool g_bShowDepthUnits;
143extern bool g_bskew_comp;
144extern bool g_bopengl;
145extern bool g_bsmoothpanzoom;
146extern bool g_bShowMag;
147extern int g_chart_zoom_modifier_raster;
148extern int g_NMEAAPBPrecision;
149
150extern wxString *pInit_Chart_Dir;
151extern bool g_bfilter_cogsog;
152extern int g_COGFilterSec;
153extern int g_SOGFilterSec;
154
155extern bool g_bDisplayGrid;
156
157// AIS Global configuration
158extern bool g_bCPAMax;
159extern double g_CPAMax_NM;
160extern bool g_bCPAWarn;
161extern double g_CPAWarn_NM;
162extern bool g_bTCPA_Max;
163extern double g_TCPA_Max;
164extern bool g_bMarkLost;
165extern double g_MarkLost_Mins;
166extern bool g_bRemoveLost;
167extern double g_RemoveLost_Mins;
168extern bool g_bShowCOG;
169extern double g_ShowCOG_Mins;
170extern bool g_bAISShowTracks;
171extern double g_AISShowTracks_Mins;
172extern bool g_bHideMoored;
173extern double g_ShowMoored_Kts;
174extern bool g_bAIS_CPA_Alert;
175extern bool g_bAIS_CPA_Alert_Audio;
176extern wxString g_sAIS_Alert_Sound_File;
177extern bool g_bAIS_CPA_Alert_Suppress_Moored;
178extern bool g_bShowAreaNotices;
179extern bool g_bDrawAISSize;
180extern bool g_bShowAISName;
181extern int g_Show_Target_Name_Scale;
182extern bool g_bWplIsAprsPosition;
183
184extern int g_iNavAidRadarRingsNumberVisible;
185extern float g_fNavAidRadarRingsStep;
186extern int g_pNavAidRadarRingsStepUnits;
187extern int g_iWaypointRangeRingsNumber;
188extern float g_fWaypointRangeRingsStep;
189extern int g_iWaypointRangeRingsStepUnits;
190extern wxColour g_colourWaypointRangeRingsColour;
191extern bool g_bWayPointPreventDragging;
192
193extern bool g_bPreserveScaleOnX;
194extern bool g_bPlayShipsBells;
195extern int g_iSoundDeviceIndex;
196extern bool g_bFullscreenToolbar;
197
198extern int g_OwnShipIconType;
199extern double g_n_ownship_length_meters;
200extern double g_n_ownship_beam_meters;
201extern double g_n_gps_antenna_offset_y;
202extern double g_n_gps_antenna_offset_x;
203extern int g_n_ownship_min_mm;
204extern double g_n_arrival_circle_radius;
205
206extern bool g_bEnableZoomToCursor;
207extern bool g_bTrackDaily;
208extern bool g_bHighliteTracks;
209extern double g_TrackIntervalSeconds;
210extern double g_TrackDeltaDistance;
211extern double g_TrackDeltaDistance;
212extern int g_nTrackPrecision;
213
214extern int g_iSDMMFormat;
215extern int g_iDistanceFormat;
216extern int g_iSpeedFormat;
217
218extern bool g_bAdvanceRouteWaypointOnArrivalOnly;
219
220extern int g_cm93_zoom_factor;
221
222extern int g_COGAvgSec;
223
224extern bool g_bCourseUp;
225extern bool g_bLookAhead;
226
227extern double g_ownship_predictor_minutes;
228extern double g_ownship_HDTpredictor_miles;
229
230extern bool g_bAISRolloverShowClass;
231extern bool g_bAISRolloverShowCOG;
232extern bool g_bAISRolloverShowCPA;
233
234extern bool g_bAIS_ACK_Timeout;
235extern double g_AckTimeout_Mins;
236
237extern bool g_bQuiltEnable;
238extern bool g_bFullScreenQuilt;
239extern bool g_bConfirmObjectDelete;
240
241#if wxUSE_XLOCALE || !wxCHECK_VERSION(3, 0, 0)
242extern wxLocale *plocale_def_lang;
243#endif
244
245// extern OCPN_Sound g_anchorwatch_sound;
246extern bool g_bMagneticAPB;
247
248extern bool g_fog_overzoom;
249extern double g_overzoom_emphasis_base;
250extern bool g_oz_vector_scale;
251extern bool g_bShowStatusBar;
252
253extern ocpnGLOptions g_GLOptions;
254
255extern s52plib *ps52plib;
256
257extern wxString g_locale;
258extern bool g_bportable;
259extern bool g_bdisable_opengl;
260
261extern ChartGroupArray *g_pGroupArray;
262
263extern bool g_bUIexpert;
264// Some constants
265#define ID_CHOICE_NMEA wxID_HIGHEST + 1
266
267// extern wxArrayString *EnumerateSerialPorts(void); // in chart1.cpp
268
269extern wxArrayString TideCurrentDataSet;
270extern wxString g_TCData_Dir;
271
272extern AisDecoder *g_pAIS;
273
274extern options *g_pOptions;
275
276extern bool g_btouch;
277extern bool g_bresponsive;
278extern bool g_bAutoHideToolbar;
279extern int g_nAutoHideToolbar;
280extern int g_GUIScaleFactor;
281extern int g_ChartScaleFactor;
282
283extern double g_config_display_size_mm;
284extern float g_ChartScaleFactorExp;
285extern bool g_config_display_size_manual;
286
287extern Multiplexer *g_pMUX;
288extern bool b_inCloseWindow;
289extern bool g_config_display_size_manual;
290extern MarkInfoDlg *g_pMarkInfoDialog;
291extern PlugInManager *g_pi_manager;
292extern iENCToolbar *g_iENCToolbar;
293extern int g_iENCToolbarPosX;
294extern int g_iENCToolbarPosY;
295extern ocpnFloatingToolbarDialog *g_MainToolbar;
296extern int g_maintoolbar_x;
297extern int g_maintoolbar_y;
298extern long g_maintoolbar_orient;
299extern int g_restore_stackindex;
300extern int g_restore_dbindex;
301extern ChartStack *pCurrentStack;
302extern Select *pSelect;
303extern WayPointman *pWayPointMan;
304extern bool g_bCruising;
305extern RoutePoint *pAnchorWatchPoint1;
306extern RoutePoint *pAnchorWatchPoint2;
307extern bool g_bAutoAnchorMark;
308extern wxAuiManager *g_pauimgr;
309extern wxString g_AisTargetList_perspective;
310
311extern ocpnFloatingToolbarDialog *g_MainToolbar;
312
313WX_DEFINE_ARRAY_PTR(ChartCanvas *, arrayofCanvasPtr);
314extern arrayofCanvasPtr g_canvasArray;
315
316wxString callActivityMethod_vs(const char *method);
317wxString callActivityMethod_is(const char *method, int parm);
318
319// Globals, accessible only to this module
320
321JavaVM *java_vm;
322JNIEnv *global_jenv;
323bool b_androidBusyShown;
324double g_androidDPmm;
325double g_androidDensity;
326
327bool g_bExternalApp;
328
329wxString g_androidFilesDir;
330wxString g_androidCacheDir;
331wxString g_androidExtFilesDir;
332wxString g_androidExtCacheDir;
333wxString g_androidExtStorageDir;
334wxString g_androidGetFilesDirs0;
335wxString g_androidGetFilesDirs1;
336wxString g_androidDownloadDirectory;
337
338
339int g_mask;
340int g_sel;
341int g_ActionBarHeight;
342int g_follow_state;
343bool g_track_active;
344bool bGPSEnabled;
345
346wxSize config_size;
347
348bool s_bdownloading;
349wxString s_requested_url;
350wxEvtHandler *s_download_evHandler;
351wxString s_download_destination;
352
353bool g_running;
354bool g_bstress1;
355extern int g_GUIScaleFactor;
356
357wxString g_deviceInfo;
358
359int s_androidMemTotal;
360int s_androidMemUsed;
361bool g_backEnabled;
362bool g_bFullscreenSave;
363bool s_optionsActive;
364
365extern int ShowNavWarning();
366extern bool g_btrackContinuous;
367extern wxString ChartListFileName;
368
369int doAndroidPersistState();
370
371bool bInConfigChange;
372AudioDoneCallback s_soundCallBack;
373void *s_soundData;
374
375bool g_detect_smt590;
376int g_orientation;
377int g_Android_SDK_Version;
378MigrateAssistantDialog *g_migrateDialog;
379
380// Some dummy devices to ensure plugins have static access to these classes
381// not used elsewhere
382wxFontPickerEvent g_dummy_wxfpe;
383
384#define ANDROID_EVENT_TIMER 4389
385#define ANDROID_STRESS_TIMER 4388
386#define ANDROID_RESIZE_TIMER 4387
387
388#define ACTION_NONE -1
389#define ACTION_RESIZE_PERSISTENTS 1
390#define ACTION_FILECHOOSER_END 3
391#define ACTION_COLORDIALOG_END 4
392#define ACTION_POSTASYNC_END 5
393#define ACTION_SAF_PERMISSION_END 6
394
395#define SCHEDULED_EVENT_CLEAN_EXIT 5498
396
397class androidUtilHandler : public wxEvtHandler {
398public:
399 androidUtilHandler();
400 ~androidUtilHandler() {}
401
402 void onTimerEvent(wxTimerEvent &event);
403 void onStressTimer(wxTimerEvent &event);
404 void OnResizeTimer(wxTimerEvent &event);
405 void OnScheduledEvent(wxCommandEvent &event);
406
407 wxString GetStringResult() { return m_stringResult; }
408 void LoadAuxClasses();
409
410 wxTimer m_eventTimer;
411 int m_action;
412 bool m_done;
413 wxString m_stringResult;
414 wxTimer m_stressTimer;
415 wxTimer m_resizeTimer;
416 int timer_sequence;
417 int m_bskipConfirm;
418 bool m_migratePermissionSetDone;
419
420 DECLARE_EVENT_TABLE()
421};
422
423const char wxMessageBoxCaptionStr[] = "Message";
424
425BEGIN_EVENT_TABLE(androidUtilHandler, wxEvtHandler)
426EVT_TIMER(ANDROID_EVENT_TIMER, androidUtilHandler::onTimerEvent)
427EVT_TIMER(ANDROID_RESIZE_TIMER, androidUtilHandler::OnResizeTimer)
428EVT_COMMAND(wxID_ANY, wxEVT_COMMAND_MENU_SELECTED,
429 androidUtilHandler::OnScheduledEvent)
430
431END_EVENT_TABLE()
432
433androidUtilHandler::androidUtilHandler() {
434 m_eventTimer.SetOwner(this, ANDROID_EVENT_TIMER);
435 m_stressTimer.SetOwner(this, ANDROID_STRESS_TIMER);
436 m_resizeTimer.SetOwner(this, ANDROID_RESIZE_TIMER);
437
438 m_bskipConfirm = false;
439
440 LoadAuxClasses();
441}
442
443void androidUtilHandler::LoadAuxClasses()
444{
445 // We do a few little dummy class accesses here, to cause the static link to
446 // wxWidgets to bring in some class members required by some plugins, that
447 // would be missing otherwise.
448
449 wxRegion a(0, 0, 1, 1);
450 wxRegion b(0, 0, 2, 2);
451 bool c = a.IsEqual(b);
452
453 wxFilePickerCtrl *pfpc = new wxFilePickerCtrl();
454
455 wxZipEntry *entry = new wxZipEntry();
456
457 wxSplitterWindow *swin = new wxSplitterWindow();
458
459}
460
461void androidUtilHandler::onTimerEvent(wxTimerEvent &event) {
462 qDebug() << "onTimerEvent" << m_action;
463
464 switch (m_action) {
465 case ACTION_RESIZE_PERSISTENTS: // Handle rotation/resizing of persistent
466 // dialogs
467
468 // AIS Target Query
469 if (g_pais_query_dialog_active) {
470 qDebug() << "AISB";
471
472 bool bshown = g_pais_query_dialog_active->IsShown();
473 g_pais_query_dialog_active->Hide();
474 g_pais_query_dialog_active->RecalculateSize();
475 if (bshown) {
476 qDebug() << "AISC";
477 g_pais_query_dialog_active->Show();
478 g_pais_query_dialog_active->Raise();
479 }
480 }
481
482 // Route Props
483 if (RoutePropDlgImpl::getInstanceFlag()) {
484 bool bshown = pRoutePropDialog->IsShown();
485 if (bshown) {
486 pRoutePropDialog->Hide();
487 pRoutePropDialog->RecalculateSize();
488 pRoutePropDialog->Show();
489 } else {
490 pRoutePropDialog->Destroy();
491 pRoutePropDialog = NULL;
492 }
493 }
494
495 // Track Props
496 if (TrackPropDlg::getInstanceFlag()) {
497 bool bshown = pTrackPropDialog->IsShown();
498 if (bshown) {
499 pTrackPropDialog->Hide();
500 pTrackPropDialog->RecalculateSize();
501 pTrackPropDialog->Show();
502 } else {
503 pTrackPropDialog->Destroy();
504 pTrackPropDialog = NULL;
505 }
506 }
507
508 // Mark Props
509
510 if (g_pMarkInfoDialog) {
511 bool bshown = g_pMarkInfoDialog->IsShown();
512 g_pMarkInfoDialog->Hide();
513 g_pMarkInfoDialog->RecalculateSize();
514 if (bshown) {
515 if (g_pMarkInfoDialog->m_SaveDefaultDlg) {
516 g_pMarkInfoDialog->m_SaveDefaultDlg->Destroy();
517 g_pMarkInfoDialog->m_SaveDefaultDlg = NULL;
518 }
519 g_pMarkInfoDialog->Show();
520 }
521 }
522
523 // ENC Object Query
524 if (g_pObjectQueryDialog) {
525 bool bshown = g_pObjectQueryDialog->IsShown();
526 g_pObjectQueryDialog->Hide();
527 g_pObjectQueryDialog->RecalculateSize();
528 if (bshown) {
529 g_pObjectQueryDialog->Show();
530 }
531 }
532
533 // AIS Target List dialog
534 if (g_pAISTargetList) {
535 qDebug() << "ATLA";
536 bool bshown = g_pAISTargetList->IsShown();
537 g_pAISTargetList->Hide();
538 g_pAISTargetList->RecalculateSize();
539 if (bshown) {
540 qDebug() << "ATLB";
541 g_pAISTargetList->Show();
542 g_pAISTargetList->Raise();
543 }
544 }
545
546 // Tide/Current window
547 if (gFrame->GetPrimaryCanvas()->getTCWin()) {
548 bool bshown = gFrame->GetPrimaryCanvas()->getTCWin()->IsShown();
549 gFrame->GetPrimaryCanvas()->getTCWin()->Hide();
550 gFrame->GetPrimaryCanvas()->getTCWin()->RecalculateSize();
551 if (bshown) {
552 gFrame->GetPrimaryCanvas()->getTCWin()->Show();
553 gFrame->GetPrimaryCanvas()->getTCWin()->Refresh();
554 }
555 }
556
557 // Route Manager dialog
558 if (RouteManagerDialog::getInstanceFlag()) {
559 bool bshown = pRouteManagerDialog->IsShown();
560 if (bshown) {
561 pRouteManagerDialog->Hide();
562 pRouteManagerDialog->RecalculateSize();
563 pRouteManagerDialog->Show();
564 } else {
565 pRouteManagerDialog->Destroy();
566 pRouteManagerDialog = NULL;
567 }
568 }
569
570 // About dialog
571 if (g_pAboutDlgLegacy) {
572 bool bshown = g_pAboutDlgLegacy->IsShown();
573 if (bshown) {
574 g_pAboutDlgLegacy->Hide();
575 g_pAboutDlgLegacy->RecalculateSize();
576 g_pAboutDlgLegacy->Show();
577 }
578 }
579
580 if (g_options) {
581 g_options->RecalculateSize();
582 }
583
584 bInConfigChange = false;
585
586 break;
587
588 case ACTION_FILECHOOSER_END: // Handle polling of android Dialog
589 {
590 // qDebug() << "chooser poll";
591 // Get a reference to the running FileChooser
592 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
593 "org/qtproject/qt5/android/QtNative", "activity",
594 "()Landroid/app/Activity;");
595
596 if (!activity.isValid()) {
597 // qDebug() << "onTimerEvent : Activity is not valid";
598 return;
599 }
600
601 // Call the method which tracks the completion of the Intent.
602 QAndroidJniObject data = activity.callObjectMethod(
603 "isFileChooserFinished", "()Ljava/lang/String;");
604
605 jstring s = data.object<jstring>();
606
607 JNIEnv *jenv;
608
609 // Need a Java environment to decode the resulting string
610 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
611 // qDebug() << "GetEnv failed.";
612 } else {
613 // The string coming back will be one of:
614 // "no" ......Intent not done yet.
615 // "cancel:" .. user cancelled intent.
616 // "file:{file_name}" .. user selected this file, fully qualified.
617 if (!s) {
618 // qDebug() << "isFileChooserFinished returned null";
619 } else if ((jenv)->GetStringLength(s)) {
620 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
621 // qDebug() << "isFileChooserFinished returned
622 // " << ret_string;
623 if (!strncmp(ret_string, "cancel:", 7)) {
624 m_done = true;
625 m_stringResult = _T("cancel:");
626 } else if (!strncmp(ret_string, "file:", 5)) {
627 m_done = true;
628 m_stringResult = wxString(ret_string, wxConvUTF8);
629 }
630 }
631 }
632
633 break;
634 }
635
636 case ACTION_COLORDIALOG_END: // Handle polling of android Dialog
637 {
638 // qDebug() << "colorpicker poll";
639 // Get a reference to the running FileChooser
640 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
641 "org/qtproject/qt5/android/QtNative", "activity",
642 "()Landroid/app/Activity;");
643
644 if (!activity.isValid()) {
645 // qDebug() << "onTimerEvent : Activity is not valid";
646 return;
647 }
648
649 // Call the method which tracks the completion of the Intent.
650 QAndroidJniObject data = activity.callObjectMethod(
651 "isColorPickerDialogFinished", "()Ljava/lang/String;");
652
653 jstring s = data.object<jstring>();
654
655 JNIEnv *jenv;
656
657 // Need a Java environment to decode the resulting string
658 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
659 // qDebug() << "GetEnv failed.";
660 } else {
661 // The string coming back will be one of:
662 // "no" ......Dialog not done yet.
663 // "cancel:" .. user cancelled Dialog.
664 // "color: ".
665 if (!s) {
666 qDebug() << "isColorPickerDialogFinished returned null";
667 } else if ((jenv)->GetStringLength(s)) {
668 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
669 // qDebug() << "isColorPickerDialogFinished returned " << ret_string;
670 if (!strncmp(ret_string, "cancel:", 7)) {
671 m_done = true;
672 m_stringResult = _T("cancel:");
673 } else if (!strncmp(ret_string, "color:", 6)) {
674 m_done = true;
675 m_stringResult = wxString(ret_string, wxConvUTF8);
676 }
677 }
678 }
679
680 break;
681 }
682
683 case ACTION_POSTASYNC_END: // Handle polling of android async POST task
684 // end
685 {
686 // qDebug() << "colorpicker poll";
687 // Get a reference to the running FileChooser
688 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
689 "org/qtproject/qt5/android/QtNative", "activity",
690 "()Landroid/app/Activity;");
691
692 if (!activity.isValid()) {
693 // qDebug() << "onTimerEvent : Activity is not valid";
694 return;
695 }
696
697 // Call the method which tracks the completion of the POST async task.
698 QAndroidJniObject data =
699 activity.callObjectMethod("checkPostAsync", "()Ljava/lang/String;");
700
701 jstring s = data.object<jstring>();
702
703 JNIEnv *jenv;
704
705 // Need a Java environment to decode the resulting string
706 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
707 // qDebug() << "GetEnv failed.";
708 } else {
709 // The string coming back will be either:
710 // "ACTIVE" ......Post command not done yet.
711 // A valid XML response body.
712 if (!s) {
713 qDebug() << "checkPostAsync returned null";
714 } else if ((jenv)->GetStringLength(s)) {
715 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
716 qDebug() << "checkPostAsync returned " << ret_string;
717 if (strncmp(ret_string, "ACTIVE", 6)) { // Must be done....
718 m_done = true;
719 m_stringResult = wxString(ret_string, wxConvUTF8);
720 }
721 }
722 }
723
724
725 break;
726 }
727
728 case ACTION_SAF_PERMISSION_END: // Handle android SAF Dialog
729 {
730 qDebug() << "SAF permission chooser poll";
731
732 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
733 "org/qtproject/qt5/android/QtNative", "activity",
734 "()Landroid/app/Activity;");
735
736 if (!activity.isValid()) {
737 // qDebug() << "onTimerEvent : Activity is not valid";
738 return;
739 }
740
741 // Call the method which tracks the completion of the activity.
742 QAndroidJniObject data = activity.callObjectMethod(
743 "isSAFChooserFinished", "()Ljava/lang/String;");
744
745 jstring s = data.object<jstring>();
746
747 JNIEnv *jenv;
748
749 // Need a Java environment to decode the resulting string
750 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
751 // qDebug() << "GetEnv failed.";
752 } else {
753 // The string coming back will be one of:
754 // "no" ......Intent not done yet.
755 // "cancel:" .. user cancelled intent.
756 // "file:{file_name}" .. user selected this file, fully qualified.
757 if (!s) {
758 // qDebug() << "isFileChooserFinished returned null";
759 } else if ((jenv)->GetStringLength(s)) {
760 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
761 //qDebug() << "isFileChooserFinished returned" << ret_string;
762 if (!strncmp(ret_string, "cancel:", 7)) {
763 m_migratePermissionSetDone = true;
764 m_stringResult = _T("cancel:");
765 } else if (!strncmp(ret_string, "file:", 5)) {
766 m_migratePermissionSetDone = true;
767 m_stringResult = wxString(ret_string, wxConvUTF8);
768 }
769 }
770
771 if(m_migratePermissionSetDone){
772 g_androidUtilHandler->m_action = ACTION_NONE;
773 g_androidUtilHandler->m_eventTimer.Stop();
774
775 if(g_migrateDialog)
776 g_migrateDialog->onPermissionGranted(m_stringResult);
777 }
778 }
779
780 break;
781 }
782
783
784
785 default:
786 break;
787 }
788}
789
790void androidUtilHandler::OnResizeTimer(wxTimerEvent &event) {
791 if (timer_sequence == 0) {
792 // On QT, we need to clear the status bar item texts to prevent the status
793 // bar from growing the parent frame due to unexpected width changes.
794 // if( m_pStatusBar != NULL ){
795 // int widths[] = { 2,2,2,2,2 };
796 // m_pStatusBar->SetStatusWidths( m_StatusBarFieldCount, widths
797 // );
798 //
799 // for(int i=0 ; i < m_pStatusBar->GetFieldsCount() ; i++){
800 // m_pStatusBar->SetStatusText(_T(""), i);
801 // }
802 // }
803 qDebug() << "sequence 0";
804
805 timer_sequence++;
806 // This timer step needs to be long enough to allow Java induced size
807 // change to take effect in another thread. The results will be checked in
808 // sequence 1.
809 m_resizeTimer.Start(1000, wxTIMER_ONE_SHOT);
810 return;
811 }
812
813 if (timer_sequence == 1) {
814 qDebug() << "sequence 1";
815
816 qDebug() << "****config_size: " << config_size.x << config_size.y;
817
818 wxSize szt = gFrame->GetSize();
819 qDebug() << "****Frame Size: " << szt.x << szt.y;
820
821 // Some Android devices do not correctly process the config change, and
822 // properly resize the app. A slower forced config change is then necessary,
823 // with lots of steps.
824
825 // However, if we can detect the ones that do properly resize the app Frame,
826 // we can skip all this.
827
828 wxSize new_size = getAndroidDisplayDimensions();
829 qDebug() << "****NewSize: " << new_size.x << new_size.y;
830
831 if ((g_orientation == 1) || (g_orientation == 3)) { // Portrait
832 if (szt.x < szt.y) // OK
833 return;
834 } else if ((g_orientation == 2) || (g_orientation == 4)) { // Landscape
835 if (szt.x > szt.y) // OK
836 return;
837 }
838
839 qDebug() << "****Force config change";
840 gFrame->SetSize(config_size);
841 timer_sequence++;
842 if (!m_bskipConfirm) m_resizeTimer.Start(10, wxTIMER_ONE_SHOT);
843 m_bskipConfirm = false;
844 return;
845 }
846
847 if (timer_sequence == 2) {
848 qDebug() << "sequence 2";
849 timer_sequence++;
850 m_resizeTimer.Start(10, wxTIMER_ONE_SHOT);
851 return;
852 }
853
854 if (timer_sequence == 3) {
855 qDebug() << "sequence 3";
856 androidConfirmSizeCorrection();
857
858 timer_sequence++;
859 m_resizeTimer.Start(10, wxTIMER_ONE_SHOT);
860 return;
861 }
862
863 if (timer_sequence == 4) {
864 qDebug() << "sequence 4";
865
866 // Raise the resized options dialog.
867 // This has no effect if the dialog is not already shown.
868 if (g_options) g_options->Raise();
869
870 resizeAndroidPersistents();
871 return;
872 }
873}
874
875int stime;
876
877void androidUtilHandler::onStressTimer(wxTimerEvent &event) {
878 g_GUIScaleFactor = -5;
879 g_ChartScaleFactor = -5;
880 gFrame->SetGPSCompassScale();
881
882 s_androidMemUsed = 80;
883
884 g_GLOptions.m_bTextureCompression = 0;
885 g_GLOptions.m_bTextureCompressionCaching = 0;
886
887 if (600 == stime++) androidTerminate();
888}
889
890void androidUtilHandler::OnScheduledEvent(wxCommandEvent &event) {
891 switch (event.GetId()) {
892 case SCHEDULED_EVENT_CLEAN_EXIT:
893 // gFrame->FrameTimer1.Stop();
894 // gFrame->FrameCOGTimer.Stop();
895 //
896 // doAndroidPersistState();
897 // androidTerminate();
898 break;
899
900 case ID_CMD_TRIGGER_RESIZE:
901 qDebug() << "Trigger Resize";
902 timer_sequence = 0;
903 m_resizeTimer.Start(10, wxTIMER_ONE_SHOT);
904 bInConfigChange = true;
905 break;
906
907 case ID_CMD_SOUND_FINISHED:
908 // qDebug() << "Trigger SoundFinished";
909 if (s_soundCallBack) {
910 s_soundCallBack(s_soundData); // Wirh user data
911 s_soundCallBack = 0;
912 }
913 break;
914
915 /*
916 case ID_CMD_STOP_RESIZE:
917 // Stop any underway timer chain
918 qDebug() << "Stop Resize";
919 m_resizeTimer.Stop();
920 m_eventTimer.Stop();
921 timer_sequence = 0;
922 bInConfigChange = false;
923 break;
924 */
925
926 default:
927 break;
928 }
929}
930
931bool androidUtilInit(void) {
932 qDebug() << "androidUtilInit()";
933
934 g_androidUtilHandler = new androidUtilHandler();
935
936 // Initialize some globals
937
938 s_androidMemTotal = 100;
939 s_androidMemUsed = 50;
940
941 wxString dirs = callActivityMethod_vs("getSystemDirs");
942 qDebug() << "dirs: " << dirs.mb_str();
943
944 wxStringTokenizer tk(dirs, _T(";"));
945 if (tk.HasMoreTokens()) {
946 wxString token = tk.GetNextToken();
947 if (wxNOT_FOUND != token.Find(_T("EXTAPP"))) g_bExternalApp = true;
948
949 token = tk.GetNextToken();
950 g_androidFilesDir = token; // used for "home dir"
951 token = tk.GetNextToken();
952 g_androidCacheDir = token;
953 token = tk.GetNextToken();
954 g_androidExtFilesDir =
955 token; // used as PrivateDataDir,
956 // "/storage/emulated/0/Android/data/org.opencpn.opencpn/files"
957 // if app has been moved to sdcard, this gives like (on Android
958 // 6) /storage/2385-1BF8/Android/data/org.opencpn.opencpn/files
959 token = tk.GetNextToken();
960 g_androidExtCacheDir = token;
961 token = tk.GetNextToken();
962 g_androidExtStorageDir = token;
963
964 token = tk.GetNextToken();
965 g_androidGetFilesDirs0 = token;
966 token = tk.GetNextToken();
967 g_androidGetFilesDirs1 = token;
968
969 token = tk.GetNextToken();
970 g_androidDownloadDirectory = token;
971
972 }
973
974 g_mask = -1;
975 g_sel = -1;
976
977 wxStringTokenizer tku(g_androidExtFilesDir, _T("/"));
978 while (tku.HasMoreTokens()) {
979 wxString s1 = tku.GetNextToken();
980
981 if (s1.Find(_T("org.")) != wxNOT_FOUND) {
982 if (s1 != _T("org.opencpn.opencpn")) g_bstress1 = true;
983 }
984 }
985
986 if (g_bstress1) {
987 g_androidUtilHandler->Connect(
988 g_androidUtilHandler->m_stressTimer.GetId(), wxEVT_TIMER,
989 wxTimerEventHandler(androidUtilHandler::onStressTimer), NULL,
990 g_androidUtilHandler);
991 g_androidUtilHandler->m_stressTimer.Start(1000, wxTIMER_CONTINUOUS);
992 }
993
994 return true;
995}
996
997
998wxSize getAndroidConfigSize() { return config_size; }
999
1000void resizeAndroidPersistents() {
1001 qDebug() << "resizeAndroidPersistents()";
1002
1003 if (g_androidUtilHandler) {
1004 g_androidUtilHandler->m_action = ACTION_RESIZE_PERSISTENTS;
1005 g_androidUtilHandler->m_eventTimer.Start(100, wxTIMER_ONE_SHOT);
1006 }
1007}
1008
1009jint JNI_OnLoad(JavaVM *vm, void *reserved) {
1010 java_vm = vm;
1011
1012 // Get JNI Env for all function calls
1013 if (vm->GetEnv((void **)&global_jenv, JNI_VERSION_1_6) != JNI_OK) {
1014 return -1;
1015 }
1016
1017 return JNI_VERSION_1_6;
1018}
1019
1020void sendNMEAMessageEvent(wxString &msg) {
1021 //FIXME (dave)
1022#if 0
1023 wxCharBuffer abuf = msg.ToUTF8();
1024 if (abuf.data()) { // OK conversion?
1025 std::string s(abuf.data());
1026 // qDebug() << tstr;
1027 OCPN_DataStreamEvent Nevent(wxEVT_OCPN_DATASTREAM, 0);
1028 Nevent.SetNMEAString(s);
1029 Nevent.SetStream(NULL);
1030 if (s_pAndroidNMEAMessageConsumer)
1031 s_pAndroidNMEAMessageConsumer->AddPendingEvent(Nevent);
1032 }
1033#endif
1034}
1035
1036// OCPNNativeLib
1037// This is a set of methods which can be called from the android activity
1038// context.
1039
1040extern "C" {
1041JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_test(JNIEnv *env,
1042 jobject obj) {
1043 // qDebug() << "test";
1044
1045 return 55;
1046}
1047}
1048
1049extern "C" {
1050JNIEXPORT jint JNICALL
1051 Java_org_opencpn_OCPNNativeLib_onSoundDone(JNIEnv *env,
1052 jobject obj,
1053 long soundPtr) {
1054 auto sound = reinterpret_cast<AndroidSound*>(soundPtr);
1055 DEBUG_LOG << "on SoundDone, ptr: " << soundPtr;
1056 sound->OnSoundDone();
1057 return 57;
1058}
1059}
1060
1061
1062extern "C" {
1063JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_processSailTimer(
1064 JNIEnv *env, jobject obj, double WindAngleMagnetic, double WindSpeedKnots) {
1065 // The NMEA message target handler may not be setup yet, if no connections
1066 // are defined or enabled. But we may want to synthesize messages from the
1067 // Java app, even without a definite connection, and we want to process these
1068 // messages too. So assume that the global MUX, if present, will handle these
1069 // synthesized messages.
1070 if (!s_pAndroidNMEAMessageConsumer && g_pMUX)
1071 s_pAndroidNMEAMessageConsumer = g_pMUX;
1072
1073 double wind_angle_mag = 0;
1074 double apparent_wind_angle = 0;
1075
1076 double app_windSpeed = 0;
1077 double true_windSpeed = 0;
1078 double true_windDirection = 0;
1079
1080 {
1081 {
1082 // Need to correct the Magnetic wind angle to True
1083 // TODO Punt for mow
1084 double variation = gVar;
1085 // qDebug() << "gVar" << gVar;
1086
1087 // What to use for TRUE ownship head?
1088 // TODO Look for HDT message contents, if available
1089 double osHead = gCog;
1090 bool buseCOG = true;
1091 // qDebug() << "gHdt" << gHdt;
1092
1093 if (!wxIsNaN(gHdt)) {
1094 osHead = gHdt;
1095 buseCOG = false;
1096 }
1097
1098 // What SOG to use?
1099 double osSog = gSog;
1100
1101 wind_angle_mag = WindAngleMagnetic;
1102 app_windSpeed = WindSpeedKnots;
1103
1104 // Compute the apparent wind angle
1105 // If using gCog for ownship head, require speed to be > 0.2 knots
1106 // If not useing cGog for head, assume we must be using a true heading
1107 // sensor, so always valid
1108 if (!wxIsNaN(osHead) && ((!buseCOG) || (buseCOG && osSog > 0.2))) {
1109 apparent_wind_angle = wind_angle_mag - (osHead - variation);
1110 } else {
1111 apparent_wind_angle = 0;
1112 }
1113 if (apparent_wind_angle < 0) apparent_wind_angle += 360.;
1114 if (apparent_wind_angle > 360.) apparent_wind_angle -= 360.;
1115
1116 // Using the "Law of cosines", compute the true wind speed
1117 if (!wxIsNaN(osSog)) {
1118 true_windSpeed = sqrt(
1119 (osSog * osSog) + (app_windSpeed * app_windSpeed) -
1120 (2 * osSog * app_windSpeed * cos(apparent_wind_angle * PI / 180.)));
1121 } else {
1122 true_windSpeed = app_windSpeed;
1123 }
1124
1125 // Rearranging the Law of cosines, we calculate True Wind Direction
1126 if ((!wxIsNaN(osSog)) && (!wxIsNaN(osHead)) && (osSog > 0.2) &&
1127 (true_windSpeed > 1)) {
1128 double acosTW = ((osSog * osSog) + (true_windSpeed * true_windSpeed) -
1129 (app_windSpeed * app_windSpeed)) /
1130 (2 * osSog * true_windSpeed);
1131
1132 double twd0 = acos(acosTW) * (180. / PI);
1133
1134 // OK on the beat...
1135 if (apparent_wind_angle > 180.) {
1136 true_windDirection = osHead + 180 + twd0;
1137 } else {
1138 true_windDirection = osHead + 180 - twd0;
1139 }
1140 } else {
1141 true_windDirection = wind_angle_mag + variation;
1142 }
1143
1144 if (true_windDirection < 0) true_windDirection += 360.;
1145 if (true_windDirection > 360.) true_windDirection -= 360.;
1146
1147 // qDebug() << wind_angle_mag << app_windSpeed << apparent_wind_angle <<
1148 // true_windSpeed << true_windDirection;
1149
1150 if (s_pAndroidNMEAMessageConsumer) {
1151 NMEA0183 parser;
1152
1153 // Now make some NMEA messages
1154 // We dont want to pass the incoming MWD message thru directly, since it
1155 // is not really correct. The angle is correct, but the speed is
1156 // relative.
1157 // Make a new MWD sentence with calculated values
1158 parser.TalkerID = _T("OS");
1159
1160 // MWD
1161 SENTENCE sntd;
1162 parser.Mwd.WindAngleTrue = true_windDirection;
1163 parser.Mwd.WindAngleMagnetic = wind_angle_mag;
1164 parser.Mwd.WindSpeedKnots = true_windSpeed;
1165 parser.Mwd.WindSpeedms = true_windSpeed * 0.5144; // convert kts to m/s
1166 parser.Mwd.Write(sntd);
1167 sendNMEAMessageEvent(sntd.Sentence);
1168
1169 // Now make two MWV sentences
1170 // Apparent
1171 SENTENCE snt;
1172 parser.Mwv.WindAngle = apparent_wind_angle;
1173 parser.Mwv.WindSpeed = app_windSpeed;
1174 parser.Mwv.WindSpeedUnits = _T("N");
1175 parser.Mwv.Reference = _T("R");
1176 parser.Mwv.IsDataValid = NTrue;
1177 parser.Mwv.Write(snt);
1178 sendNMEAMessageEvent(snt.Sentence);
1179
1180 // True
1181 SENTENCE sntt;
1182 double true_relHead = 0;
1183 if (!wxIsNaN(osHead) && ((!buseCOG) || (buseCOG && osSog > 0.2)))
1184 true_relHead = true_windDirection - osHead;
1185
1186 if (true_relHead < 0) true_relHead += 360.;
1187 if (true_relHead > 360.) true_relHead -= 360.;
1188
1189 parser.Mwv.WindAngle = true_relHead;
1190 parser.Mwv.WindSpeed = true_windSpeed;
1191 parser.Mwv.WindSpeedUnits = _T("N");
1192 parser.Mwv.Reference = _T("T");
1193 parser.Mwv.IsDataValid = NTrue;
1194 parser.Mwv.Write(sntt);
1195 sendNMEAMessageEvent(sntt.Sentence);
1196 }
1197 }
1198 }
1199
1200 return 52;
1201}
1202}
1203
1204extern "C" {
1205JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_processNMEA(
1206 JNIEnv *env, jobject obj, jstring nmea_string) {
1207 // The NMEA message target handler may not be setup yet, if no connections
1208 // are defined or enabled. But we may get synthesized messages from the Java
1209 // app, even without a definite connection, and we want to process these
1210 // messages too. So assume that the global MUX, if present, will handle these
1211 // messages.
1212 wxEvtHandler *consumer = s_pAndroidNMEAMessageConsumer;
1213
1214 if (!consumer && g_pMUX) consumer = g_pMUX;
1215
1216 const char *string = env->GetStringUTFChars(nmea_string, NULL);
1217
1218 // qDebug() << "ProcessNMEA: " << string;
1219
1220 char tstr[200];
1221 strncpy(tstr, string, 190);
1222 strcat(tstr, "\r\n");
1223
1224 // FIXME (dave)
1225// if (consumer) {
1226// OCPN_DataStreamEvent Nevent(wxEVT_OCPN_DATASTREAM, 0);
1227// Nevent.SetNMEAString(tstr);
1228// Nevent.SetStream(NULL);
1229//
1230// consumer->AddPendingEvent(Nevent);
1231// }
1232
1233 return 66;
1234}
1235}
1236
1237extern "C" {
1238JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_processBTNMEA(
1239 JNIEnv *env, jobject obj, jstring nmea_string) {
1240 const char *string = env->GetStringUTFChars(nmea_string, NULL);
1241 wxString wstring = wxString(string, wxConvUTF8);
1242
1243 char tstr[200];
1244 strncpy(tstr, string, 190);
1245 strcat(tstr, "\r\n");
1246
1247//FIXME (dave)
1248// if (s_pAndroidBTNMEAMessageConsumer) {
1249// OCPN_DataStreamEvent Nevent(wxEVT_OCPN_DATASTREAM, 0);
1250// Nevent.SetNMEAString(tstr);
1251// Nevent.SetStream(NULL);
1252//
1253// s_pAndroidBTNMEAMessageConsumer->AddPendingEvent(Nevent);
1254// }
1255
1256 return 77;
1257}
1258}
1259
1260extern "C" {
1261JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onConfigChange(
1262 JNIEnv *env, jobject obj, int orientation) {
1263 g_orientation = orientation;
1264 qDebug() << "onConfigChange";
1265
1266 wxLogMessage(_T("onConfigChange"));
1267 GetAndroidDisplaySize();
1268
1269 wxSize new_size = getAndroidDisplayDimensions();
1270 qDebug() << "NewSize: " << new_size.x << new_size.y;
1271 config_size = new_size;
1272
1273 // wxCommandEvent evts(wxEVT_COMMAND_MENU_SELECTED);
1274 // evts.SetId( ID_CMD_STOP_RESIZE );
1275 // g_androidUtilHandler->AddPendingEvent(evts);
1276
1277 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1278 evt.SetId(ID_CMD_TRIGGER_RESIZE);
1279 g_androidUtilHandler->AddPendingEvent(evt);
1280
1281 return 77;
1282}
1283}
1284
1285extern "C" {
1286JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onMouseWheel(JNIEnv *env,
1287 jobject obj,
1288 int dir) {
1289 wxMouseEvent evt(wxEVT_MOUSEWHEEL);
1290 evt.m_wheelRotation = dir;
1291
1292 if (gFrame->GetPrimaryCanvas()) {
1293 gFrame->GetPrimaryCanvas()->GetEventHandler()->AddPendingEvent(evt);
1294 }
1295
1296 return 77;
1297}
1298}
1299
1300extern "C" {
1301JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onMenuKey(JNIEnv *env,
1302 jobject obj) {
1303 // if(g_MainToolbar){
1304 // g_MainToolbar->Show( !g_MainToolbar->IsShown() );
1305 // }
1306
1307 return 88;
1308}
1309}
1310
1311extern "C" {
1312JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onStop(JNIEnv *env,
1313 jobject obj) {
1314 qDebug() << "onStop";
1315 wxLogMessage(_T("onStop"));
1316
1317 // App may be summarily killed after this point due to OOM condition.
1318 // So we need to persist some dynamic data.
1319 if (pConfig) {
1320 // Persist the config file, especially to capture the viewport
1321 // location,scale etc.
1322 pConfig->UpdateSettings();
1323
1324 // There may be unsaved objects at this point, and a navobj.xml.changes
1325 // restore file We commit the navobj deltas, and flush the restore file
1326 // Pass flag "true" to also recreate a new empty "changes" file
1327 pConfig->UpdateNavObj(true);
1328 }
1329
1330 g_running = false;
1331
1332 qDebug() << "onStop return 98";
1333 return 98;
1334}
1335}
1336
1337extern "C" {
1338JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onStart(JNIEnv *env,
1339 jobject obj) {
1340 qDebug() << "onStart";
1341 wxLogMessage(_T("onStart"));
1342
1343 if (g_bstress1) ShowNavWarning();
1344
1345 g_running = true;
1346
1347 return 99;
1348}
1349}
1350
1351extern "C" {
1352JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onPause(JNIEnv *env,
1353 jobject obj) {
1354 qDebug() << "onPause";
1355 wxLogMessage(_T("onPause"));
1356 g_bSleep = true;
1357
1358 callActivityMethod_is("setTrackContinuous", (int)g_btrackContinuous);
1359
1360 if (!g_btrackContinuous) androidGPSService(GPS_OFF);
1361
1362 return 97;
1363}
1364}
1365
1366extern "C" {
1367JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onResume(JNIEnv *env,
1368 jobject obj) {
1369 qDebug() << "onResume";
1370 wxLogMessage(_T("onResume"));
1371
1372 int ret = 96;
1373
1374 g_bSleep = false;
1375
1376 if (bGPSEnabled) androidGPSService(GPS_ON);
1377
1378 wxCommandEvent evt0(wxEVT_COMMAND_MENU_SELECTED);
1379 evt0.SetId(ID_CMD_CLOSE_ALL_DIALOGS);
1380 if (gFrame && gFrame->GetEventHandler())
1381 gFrame->GetEventHandler()->AddPendingEvent(evt0);
1382
1383 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1384 evt.SetId(ID_CMD_INVALIDATE);
1385 if (gFrame) gFrame->GetEventHandler()->AddPendingEvent(evt);
1386
1387 // Check screen orientation is sensible
1388 int orient = androidGetScreenOrientation();
1389 qDebug() << "Orient: " << orient;
1390 if (gFrame && gFrame->GetPrimaryCanvas()) {
1391 qDebug() << "Size: " << gFrame->GetSize().x << gFrame->GetSize().y;
1392 qDebug() << "CanvasSize: " << gFrame->GetPrimaryCanvas()->GetSize().x
1393 << gFrame->GetPrimaryCanvas()->GetSize().y;
1394
1395 if (gFrame->GetSize().y > gFrame->GetSize().x) {
1396 qDebug() << "gFrame is Portrait";
1397 if ((orient == 2) || (orient == 4)) {
1398 qDebug() << "NEEDS RESIZE";
1399 GetAndroidDisplaySize();
1400 wxSize new_size = getAndroidDisplayDimensions();
1401 qDebug() << "NewSize: " << new_size.x << new_size.y;
1402 config_size = new_size;
1403
1404 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1405 evt.SetId(ID_CMD_TRIGGER_RESIZE);
1406 if (g_androidUtilHandler) g_androidUtilHandler->AddPendingEvent(evt);
1407 }
1408 } else {
1409 qDebug() << "gFrame is Landscape";
1410 if ((orient == 1) || (orient == 3)) {
1411 qDebug() << "NEEDS RESIZE";
1412 GetAndroidDisplaySize();
1413 wxSize new_size = getAndroidDisplayDimensions();
1414 qDebug() << "NewSize: " << new_size.x << new_size.y;
1415 config_size = new_size;
1416
1417 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1418 evt.SetId(ID_CMD_TRIGGER_RESIZE);
1419 if (g_androidUtilHandler) g_androidUtilHandler->AddPendingEvent(evt);
1420 }
1421 }
1422 }
1423
1424 return ret;
1425}
1426}
1427
1428extern "C" {
1429JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_onDestroy(JNIEnv *env,
1430 jobject obj) {
1431 qDebug() << "onDestroy";
1432 wxLogMessage(_T("onDestroy"));
1433
1434 if (pConfig) {
1435 // Persist the config file, especially to capture the viewport
1436 // location,scale, locale etc.
1437 pConfig->UpdateSettings();
1438 }
1439
1440 g_running = false;
1441
1442 return 98;
1443}
1444}
1445
1446extern "C" {
1447JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_selectChartDisplay(
1448 JNIEnv *env, jobject obj, int type, int family) {
1449 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1450 if (type == CHART_TYPE_CM93COMP) {
1451 evt.SetId(ID_CMD_SELECT_CHART_TYPE);
1452 evt.SetExtraLong(CHART_TYPE_CM93COMP);
1453 } else {
1454 evt.SetId(ID_CMD_SELECT_CHART_FAMILY);
1455 evt.SetExtraLong(family);
1456 }
1457
1458 if (gFrame) gFrame->GetEventHandler()->AddPendingEvent(evt);
1459
1460 return 74;
1461}
1462}
1463
1464extern "C" {
1465JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_invokeCmdEventCmdString(
1466 JNIEnv *env, jobject obj, int cmd_id, jstring s) {
1467 const char *sparm;
1468 wxString wx_sparm;
1469 JNIEnv *jenv;
1470
1471 // Need a Java environment to decode the string parameter
1472 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1473 // qDebug() << "GetEnv failed.";
1474 } else {
1475 sparm = (jenv)->GetStringUTFChars(s, NULL);
1476 wx_sparm = wxString(sparm, wxConvUTF8);
1477 }
1478
1479 // qDebug() << "invokeCmdEventCmdString" << cmd_id << s;
1480
1481 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1482 evt.SetId(cmd_id);
1483 evt.SetString(wx_sparm);
1484
1485 if (gFrame) {
1486 qDebug() << "add event" << cmd_id << wx_sparm.mbc_str();
1487 gFrame->GetEventHandler()->AddPendingEvent(evt);
1488 } else
1489 qDebug() << "No frame for EventCmdString";
1490
1491 return 71;
1492}
1493}
1494
1495extern "C" {
1496JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_invokeMenuItem(
1497 JNIEnv *env, jobject obj, int item) {
1498 if (!gFrame) // App Frame not yet set up, on slow devices
1499 return 71;
1500
1501 wxString msg1;
1502 msg1.Printf(_T("invokeMenuItem: %d"), item);
1503 wxLogMessage(msg1);
1504
1505 // If in Route Create, disable all other menu items
1506 if (gFrame && (gFrame->GetFocusCanvas()->m_routeState > 1) &&
1507 (OCPN_ACTION_ROUTE != item)) {
1508 wxLogMessage(_T("invokeMenuItem A"));
1509 return 72;
1510 }
1511
1512 wxLogMessage(_T("invokeMenuItem B"));
1513
1514 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
1515
1516 switch (item) {
1517 case OCPN_ACTION_FOLLOW:
1518 evt.SetId(ID_MENU_NAV_FOLLOW);
1519 gFrame->GetEventHandler()->AddPendingEvent(evt);
1520 break;
1521
1522 case OCPN_ACTION_ROUTE:
1523 evt.SetId(ID_MENU_ROUTE_NEW);
1524 gFrame->GetEventHandler()->AddPendingEvent(evt);
1525 break;
1526
1527 case OCPN_ACTION_RMD:
1528 evt.SetId(ID_MENU_ROUTE_MANAGER);
1529 gFrame->GetEventHandler()->AddPendingEvent(evt);
1530 break;
1531
1532 case OCPN_ACTION_SETTINGS_BASIC:
1533 evt.SetId(ID_MENU_SETTINGS_BASIC);
1534 wxLogMessage(_T("invokeMenuItem OCPN_ACTION_SETTINGS_BASIC"));
1535 gFrame->GetEventHandler()->AddPendingEvent(evt);
1536 break;
1537
1538 case OCPN_ACTION_TRACK_TOGGLE:
1539 evt.SetId(ID_MENU_NAV_TRACK);
1540 gFrame->GetEventHandler()->AddPendingEvent(evt);
1541 break;
1542
1543 case OCPN_ACTION_MOB:
1544 evt.SetId(ID_MENU_MARK_MOB);
1545 gFrame->GetEventHandler()->AddPendingEvent(evt);
1546 break;
1547
1548 case OCPN_ACTION_TIDES_TOGGLE:
1549 evt.SetId(ID_MENU_SHOW_TIDES);
1550 gFrame->GetEventHandler()->AddPendingEvent(evt);
1551 break;
1552
1553 case OCPN_ACTION_CURRENTS_TOGGLE:
1554 evt.SetId(ID_MENU_SHOW_CURRENTS);
1555 gFrame->GetEventHandler()->AddPendingEvent(evt);
1556 break;
1557
1558 case OCPN_ACTION_ENCTEXT_TOGGLE:
1559 evt.SetId(ID_MENU_ENC_TEXT);
1560 gFrame->GetEventHandler()->AddPendingEvent(evt);
1561 break;
1562
1563 case OCPN_ACTION_ENCSOUNDINGS_TOGGLE:
1564 evt.SetId(ID_MENU_ENC_SOUNDINGS);
1565 gFrame->GetEventHandler()->AddPendingEvent(evt);
1566 break;
1567
1568 case OCPN_ACTION_ENCLIGHTS_TOGGLE:
1569 evt.SetId(ID_MENU_ENC_LIGHTS);
1570 gFrame->GetEventHandler()->AddPendingEvent(evt);
1571 break;
1572
1573 default:
1574 break;
1575 }
1576
1577 return 73;
1578}
1579}
1580
1581extern "C" {
1582JNIEXPORT jstring JNICALL
1583Java_org_opencpn_OCPNNativeLib_getVPCorners(JNIEnv *env, jobject obj) {
1584 // qDebug() << "getVPCorners";
1585
1586 wxString s;
1587
1588 if (gFrame->GetPrimaryCanvas()) {
1589 LLBBox vbox;
1590 vbox = gFrame->GetPrimaryCanvas()->GetVP().GetBBox();
1591 s.Printf(_T("%g;%g;%g;%g;"), vbox.GetMaxLat(), vbox.GetMaxLon(),
1592 vbox.GetMinLat(), vbox.GetMinLon());
1593 }
1594
1595 jstring ret = (env)->NewStringUTF(s.c_str());
1596
1597 return ret;
1598}
1599}
1600
1601extern "C" {
1602JNIEXPORT jstring JNICALL Java_org_opencpn_OCPNNativeLib_getVPS(JNIEnv *env,
1603 jobject obj) {
1604 wxString s;
1605
1606 if (gFrame->GetPrimaryCanvas()) {
1607 ViewPort vp = gFrame->GetPrimaryCanvas()->GetVP();
1608 s.Printf(_T("%g;%g;%g;%g;%g;"), vp.clat, vp.clon, vp.view_scale_ppm, gLat,
1609 gLon);
1610 }
1611
1612 jstring ret = (env)->NewStringUTF(s.c_str());
1613
1614 return ret;
1615}
1616}
1617
1618extern "C" {
1619JNIEXPORT int JNICALL Java_org_opencpn_OCPNNativeLib_getTLWCount(JNIEnv *env,
1620 jobject obj) {
1621 int ret = 0;
1622 wxWindowList::compatibility_iterator node = wxTopLevelWindows.GetFirst();
1623 while (node) {
1624 wxWindow *win = node->GetData();
1625 if (win->IsShown() && !win->IsKindOf(CLASSINFO(CanvasOptions))) ret++;
1626
1627 node = node->GetNext();
1628 }
1629 return ret;
1630}
1631}
1632
1633extern "C" {
1634JNIEXPORT int JNICALL Java_org_opencpn_OCPNNativeLib_notifyFullscreenChange(
1635 JNIEnv *env, jobject obj, bool bFull) {
1636 g_bFullscreen = bFull;
1637 return 1;
1638}
1639}
1640
1641extern "C" {
1642JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_setDownloadStatus(
1643 JNIEnv *env, jobject obj, int status, jstring url) {
1644 // qDebug() << "setDownloadStatus";
1645
1646 const char *sparm;
1647 wxString wx_sparm;
1648 JNIEnv *jenv;
1649
1650 // Need a Java environment to decode the string parameter
1651 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1652 // qDebug() << "GetEnv failed.";
1653 } else {
1654 sparm = (jenv)->GetStringUTFChars(url, NULL);
1655 wx_sparm = wxString(sparm, wxConvUTF8);
1656 }
1657
1658 if (s_bdownloading && wx_sparm.IsSameAs(s_requested_url)) {
1659 // qDebug() << "Maybe mine...";
1660 // We simply pass the event on to the core download manager methods,
1661 // with parameters crafted to the event
1662 OCPN_downloadEvent ev(wxEVT_DOWNLOAD_EVENT, 0);
1663
1664 OCPN_DLCondition dl_condition = OCPN_DL_EVENT_TYPE_UNKNOWN;
1665 OCPN_DLStatus dl_status = OCPN_DL_UNKNOWN;
1666
1667 // Translate Android status values to OCPN
1668 switch (status) {
1669 case 16: // STATUS_FAILED
1670 dl_condition = OCPN_DL_EVENT_TYPE_END;
1671 dl_status = OCPN_DL_FAILED;
1672 break;
1673
1674 case 8: // STATUS_SUCCESSFUL
1675 dl_condition = OCPN_DL_EVENT_TYPE_END;
1676 dl_status = OCPN_DL_NO_ERROR;
1677 break;
1678
1679 case 4: // STATUS_PAUSED
1680 case 2: // STATUS_RUNNING
1681 case 1: // STATUS_PENDING
1682 dl_condition = OCPN_DL_EVENT_TYPE_PROGRESS;
1683 dl_status = OCPN_DL_NO_ERROR;
1684 }
1685
1686 ev.setDLEventCondition(dl_condition);
1687 ev.setDLEventStatus(dl_status);
1688
1689 if (s_download_evHandler) {
1690 // qDebug() << "Sending event...";
1691 s_download_evHandler->AddPendingEvent(ev);
1692 }
1693 }
1694
1695 return 77;
1696}
1697}
1698
1699extern "C" {
1700JNIEXPORT jint JNICALL Java_org_opencpn_OCPNNativeLib_sendPluginMessage(
1701 JNIEnv *env, jobject obj, jstring msgID, jstring msg) {
1702 const char *sparm;
1703 wxString MsgID;
1704 wxString Msg;
1705 JNIEnv *jenv;
1706
1707 // Need a Java environment to decode the string parameter
1708 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1709 // qDebug() << "GetEnv failed.";
1710 } else {
1711 sparm = (jenv)->GetStringUTFChars(msgID, NULL);
1712 MsgID = wxString(sparm, wxConvUTF8);
1713
1714 sparm = (jenv)->GetStringUTFChars(msg, NULL);
1715 Msg = wxString(sparm, wxConvUTF8);
1716 }
1717
1718 SendPluginMessage(MsgID, Msg);
1719
1720 return 74;
1721}
1722}
1723
1724void androidTerminate() { callActivityMethod_vs("terminateApp"); }
1725
1726bool CheckPendingJNIException() {
1727 JNIEnv *jenv;
1728
1729 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) return true;
1730
1731 if ((jenv)->ExceptionCheck() == JNI_TRUE) {
1732 // Handle exception here.
1733 (jenv)->ExceptionDescribe(); // writes to logcat
1734 (jenv)->ExceptionClear();
1735
1736 return false; // There was a pending exception, but cleared OK
1737 // interesting discussion:
1738 // http://blog.httrack.com/blog/2013/08/23/catching-posix-signals-on-android/
1739 }
1740
1741 return false;
1742}
1743
1744wxString callActivityMethod_vs(const char *method) {
1745 if (CheckPendingJNIException()) return _T("NOK");
1746
1747 JNIEnv *jenv;
1748
1749 wxString return_string;
1750 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1751 "org/qtproject/qt5/android/QtNative", "activity",
1752 "()Landroid/app/Activity;");
1753 if (CheckPendingJNIException()) return _T("NOK");
1754
1755 if (!activity.isValid()) {
1756 // qDebug() << "Activity is not valid";
1757 return return_string;
1758 }
1759
1760 // Call the desired method
1761 QAndroidJniObject data =
1762 activity.callObjectMethod(method, "()Ljava/lang/String;");
1763 if (CheckPendingJNIException()) return _T("NOK");
1764
1765 jstring s = data.object<jstring>();
1766 // qDebug() << s;
1767
1768 if (s) {
1769 // Need a Java environment to decode the resulting string
1770 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1771 // qDebug() << "GetEnv failed.";
1772 } else {
1773 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1774 return_string = wxString(ret_string, wxConvUTF8);
1775 }
1776 }
1777
1778 return return_string;
1779}
1780
1781wxString callActivityMethod_is(const char *method, int parm) {
1782 if (CheckPendingJNIException()) return _T("NOK");
1783 JNIEnv *jenv;
1784
1785 wxString return_string;
1786 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1787 "org/qtproject/qt5/android/QtNative", "activity",
1788 "()Landroid/app/Activity;");
1789
1790 if (!activity.isValid()) {
1791 // qDebug() << "Activity is not valid";
1792 return return_string;
1793 }
1794
1795 // Call the desired method
1796 QAndroidJniObject data =
1797 activity.callObjectMethod(method, "(I)Ljava/lang/String;", parm);
1798 if (CheckPendingJNIException()) return _T("NOK");
1799
1800 jstring s = data.object<jstring>();
1801
1802 // Need a Java environment to decode the resulting string
1803 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1804 // qDebug() << "GetEnv failed.";
1805 } else {
1806 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1807 return_string = wxString(ret_string, wxConvUTF8);
1808 }
1809
1810 return return_string;
1811}
1812
1813wxString callActivityMethod_iis(const char *method, int parm1, int parm2) {
1814 if (CheckPendingJNIException()) return _T("NOK");
1815
1816 JNIEnv *jenv;
1817
1818 wxString return_string;
1819 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1820 "org/qtproject/qt5/android/QtNative", "activity",
1821 "()Landroid/app/Activity;");
1822 if (CheckPendingJNIException()) return _T("NOK");
1823
1824 if (!activity.isValid()) {
1825 // qDebug() << "Activity is not valid";
1826 return return_string;
1827 }
1828
1829 // Call the desired method
1830 QAndroidJniObject data =
1831 activity.callObjectMethod(method, "(II)Ljava/lang/String;", parm1, parm2);
1832 if (CheckPendingJNIException()) return _T("NOK");
1833
1834 jstring s = data.object<jstring>();
1835
1836 // Need a Java environment to decode the resulting string
1837 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1838 // qDebug() << "GetEnv failed.";
1839 } else {
1840 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1841 return_string = wxString(ret_string, wxConvUTF8);
1842 }
1843
1844 return return_string;
1845}
1846
1847wxString callActivityMethod_ss(const char *method, wxString parm) {
1848 if (CheckPendingJNIException()) return _T("NOK");
1849 JNIEnv *jenv;
1850
1851 wxString return_string;
1852 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1853 "org/qtproject/qt5/android/QtNative", "activity",
1854 "()Landroid/app/Activity;");
1855 if (CheckPendingJNIException()) return _T("NOK");
1856
1857 if (!activity.isValid()) {
1858 // qDebug() << "Activity is not valid";
1859 return return_string;
1860 }
1861
1862 // Need a Java environment to decode the resulting string
1863 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1864 // qDebug() << "GetEnv failed.";
1865 return _T("jenv Error");
1866 }
1867
1868 jstring p = (jenv)->NewStringUTF(parm.c_str());
1869
1870 // Call the desired method
1871 // qDebug() << "Calling method_ss";
1872 // qDebug() << method;
1873
1874 QAndroidJniObject data = activity.callObjectMethod(
1875 method, "(Ljava/lang/String;)Ljava/lang/String;", p);
1876
1877 (jenv)->DeleteLocalRef(p);
1878
1879 if (CheckPendingJNIException()) return _T("NOK");
1880
1881 // qDebug() << "Back from method_ss";
1882
1883 jstring s = data.object<jstring>();
1884
1885 if ((jenv)->GetStringLength(s)) {
1886 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1887 return_string = wxString(ret_string, wxConvUTF8);
1888 }
1889
1890 return return_string;
1891}
1892
1893wxString callActivityMethod_s2s(const char *method, const wxString parm1,
1894 const wxString parm2) {
1895 if (CheckPendingJNIException()) return _T("NOK");
1896 JNIEnv *jenv;
1897
1898 wxString return_string;
1899 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1900 "org/qtproject/qt5/android/QtNative", "activity",
1901 "()Landroid/app/Activity;");
1902 if (CheckPendingJNIException()) return _T("NOK");
1903
1904 if (!activity.isValid()) {
1905 // qDebug() << "Activity is not valid";
1906 return return_string;
1907 }
1908
1909 // Need a Java environment to decode the resulting string
1910 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1911 // qDebug() << "GetEnv failed.";
1912 return _T("jenv Error");
1913 }
1914
1915 wxCharBuffer p1b = parm1.ToUTF8();
1916 jstring p1 = (jenv)->NewStringUTF(p1b.data());
1917
1918 wxCharBuffer p2b = parm2.ToUTF8();
1919 jstring p2 = (jenv)->NewStringUTF(p2b.data());
1920
1921 // Call the desired method
1922 // qDebug() << "Calling method_s2s" << " (" << method << ")";
1923
1924 QAndroidJniObject data = activity.callObjectMethod(
1925 method, "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", p1,
1926 p2);
1927
1928 (jenv)->DeleteLocalRef(p1);
1929 (jenv)->DeleteLocalRef(p2);
1930
1931 if (CheckPendingJNIException()) return _T("NOK");
1932
1933 // qDebug() << "Back from method_s2s";
1934
1935 jstring s = data.object<jstring>();
1936
1937 if ((jenv)->GetStringLength(s)) {
1938 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1939 return_string = wxString(ret_string, wxConvUTF8);
1940 }
1941
1942 return return_string;
1943}
1944
1945wxString callActivityMethod_s3s(const char *method, wxString parm1,
1946 wxString parm2, wxString parm3) {
1947 if (CheckPendingJNIException()) return _T("NOK");
1948 JNIEnv *jenv;
1949
1950 wxString return_string;
1951 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
1952 "org/qtproject/qt5/android/QtNative", "activity",
1953 "()Landroid/app/Activity;");
1954 if (CheckPendingJNIException()) return _T("NOK");
1955
1956 if (!activity.isValid()) {
1957 return return_string;
1958 }
1959
1960 // Need a Java environment to decode the resulting string
1961 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
1962 return _T("jenv Error");
1963 }
1964
1965 wxCharBuffer p1b = parm1.ToUTF8();
1966 jstring p1 = (jenv)->NewStringUTF(p1b.data());
1967
1968 wxCharBuffer p2b = parm2.ToUTF8();
1969 jstring p2 = (jenv)->NewStringUTF(p2b.data());
1970
1971 wxCharBuffer p3b = parm3.ToUTF8();
1972 jstring p3 = (jenv)->NewStringUTF(p3b.data());
1973
1974 // Call the desired method
1975 // qDebug() << "Calling method_s3s" << " (" << method << ")";
1976
1977 QAndroidJniObject data =
1978 activity.callObjectMethod(method,
1979 "(Ljava/lang/String;Ljava/lang/String;Ljava/"
1980 "lang/String;)Ljava/lang/String;",
1981 p1, p2, p3);
1982 (jenv)->DeleteLocalRef(p1);
1983 (jenv)->DeleteLocalRef(p2);
1984 (jenv)->DeleteLocalRef(p3);
1985
1986 if (CheckPendingJNIException()) return _T("NOK");
1987
1988 // qDebug() << "Back from method_s3s";
1989
1990 jstring s = data.object<jstring>();
1991
1992 if ((jenv)->GetStringLength(s)) {
1993 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
1994 return_string = wxString(ret_string, wxConvUTF8);
1995 }
1996
1997 return return_string;
1998}
1999
2000wxString callActivityMethod_s4s(const char *method, wxString parm1,
2001 wxString parm2, wxString parm3,
2002 wxString parm4) {
2003 if (CheckPendingJNIException()) return _T("NOK");
2004 JNIEnv *jenv;
2005
2006 wxString return_string;
2007 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2008 "org/qtproject/qt5/android/QtNative", "activity",
2009 "()Landroid/app/Activity;");
2010 if (CheckPendingJNIException()) return _T("NOK");
2011
2012 if (!activity.isValid()) {
2013 // qDebug() << "Activity is not valid";
2014 return return_string;
2015 }
2016
2017 // Need a Java environment to decode the resulting string
2018 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2019 // qDebug() << "GetEnv failed.";
2020 return _T("jenv Error");
2021 }
2022
2023 wxCharBuffer p1b = parm1.ToUTF8();
2024 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2025
2026 wxCharBuffer p2b = parm2.ToUTF8();
2027 jstring p2 = (jenv)->NewStringUTF(p2b.data());
2028
2029 wxCharBuffer p3b = parm3.ToUTF8();
2030 jstring p3 = (jenv)->NewStringUTF(p3b.data());
2031
2032 wxCharBuffer p4b = parm4.ToUTF8();
2033 jstring p4 = (jenv)->NewStringUTF(p4b.data());
2034
2035 // const char *ts = (jenv)->GetStringUTFChars(p2, NULL);
2036 // qDebug() << "Test String p2" << ts;
2037
2038 // Call the desired method
2039 // qDebug() << "Calling method_s4s" << " (" << method << ")";
2040
2041 QAndroidJniObject data = activity.callObjectMethod(
2042 method,
2043 "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
2044 "String;)Ljava/lang/String;",
2045 p1, p2, p3, p4);
2046 (jenv)->DeleteLocalRef(p1);
2047 (jenv)->DeleteLocalRef(p2);
2048 (jenv)->DeleteLocalRef(p3);
2049 (jenv)->DeleteLocalRef(p4);
2050
2051 if (CheckPendingJNIException()) return _T("NOK");
2052
2053 // qDebug() << "Back from method_s4s";
2054
2055 jstring s = data.object<jstring>();
2056
2057 if ((jenv)->GetStringLength(s)) {
2058 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2059 return_string = wxString(ret_string, wxConvUTF8);
2060 }
2061
2062 return return_string;
2063}
2064
2065wxString callActivityMethod_s2s2i(const char *method, wxString parm1,
2066 wxString parm2, int parm3, int parm4) {
2067 if (CheckPendingJNIException()) return _T("NOK");
2068
2069 wxString return_string;
2070 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2071 "org/qtproject/qt5/android/QtNative", "activity",
2072 "()Landroid/app/Activity;");
2073 if (CheckPendingJNIException()) return _T("NOK");
2074
2075 if (!activity.isValid()) {
2076 return return_string;
2077 }
2078
2079 // Need a Java environment to decode the resulting string
2080 JNIEnv *jenv;
2081
2082 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2083 // qDebug() << "GetEnv failed.";
2084 return _T("jenv Error");
2085 }
2086
2087 wxCharBuffer p1b = parm1.ToUTF8();
2088 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2089
2090 wxCharBuffer p2b = parm2.ToUTF8();
2091 jstring p2 = (jenv)->NewStringUTF(p2b.data());
2092
2093 // qDebug() << "Calling method_s2s2i" << " (" << method << ")";
2094 // qDebug() << parm3 << parm4;
2095
2096 QAndroidJniObject data = activity.callObjectMethod(
2097 method, "(Ljava/lang/String;Ljava/lang/String;II)Ljava/lang/String;", p1,
2098 p2, parm3, parm4);
2099
2100 (jenv)->DeleteLocalRef(p1);
2101 (jenv)->DeleteLocalRef(p2);
2102
2103 if (CheckPendingJNIException()) return _T("NOK");
2104
2105 jstring s = data.object<jstring>();
2106
2107 if ((jenv)->GetStringLength(s)) {
2108 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2109 return_string = wxString(ret_string, wxConvUTF8);
2110 }
2111
2112 return return_string;
2113}
2114
2115wxString callActivityMethod_ssi(const char *method, wxString parm1, int parm2) {
2116 if (CheckPendingJNIException()) return _T("NOK");
2117
2118 wxString return_string;
2119 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2120 "org/qtproject/qt5/android/QtNative", "activity",
2121 "()Landroid/app/Activity;");
2122 if (CheckPendingJNIException()) return _T("NOK");
2123
2124 if (!activity.isValid()) {
2125 return return_string;
2126 }
2127
2128 // Need a Java environment to decode the resulting string
2129 JNIEnv *jenv;
2130
2131 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2132 // qDebug() << "GetEnv failed.";
2133 return _T("jenv Error");
2134 }
2135
2136 wxCharBuffer p1b = parm1.ToUTF8();
2137 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2138
2139 QAndroidJniObject data = activity.callObjectMethod(
2140 method, "(Ljava/lang/String;I)Ljava/lang/String;", p1, parm2);
2141
2142 (jenv)->DeleteLocalRef(p1);
2143
2144 if (CheckPendingJNIException()) return _T("NOK");
2145
2146 jstring s = data.object<jstring>();
2147
2148 if ((jenv)->GetStringLength(s)) {
2149 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2150 return_string = wxString(ret_string, wxConvUTF8);
2151 }
2152
2153 return return_string;
2154}
2155
2156wxString androidGetAndroidSystemLocale() {
2157 return callActivityMethod_vs("getAndroidLocaleString");
2158}
2159
2160bool androidGetFullscreen() {
2161 wxString s = callActivityMethod_vs("getFullscreen");
2162
2163 return s == _T("YES");
2164}
2165
2166bool androidSetFullscreen(bool bFull) {
2167 callActivityMethod_is("setFullscreen", (int)bFull);
2168
2169 return true;
2170}
2171
2172void androidDisableFullScreen() {
2173 if (g_bFullscreen) {
2174 g_bFullscreenSave = true;
2175 androidSetFullscreen(false);
2176 }
2177}
2178
2179void androidRestoreFullScreen() {
2180 if (g_bFullscreenSave) {
2181 g_bFullscreenSave = false;
2182 androidSetFullscreen(true);
2183 }
2184}
2185
2186int androidGetScreenOrientation() {
2187 wxString s = callActivityMethod_vs("getScreenOrientation");
2188 long result = -1;
2189 s.ToLong(&result);
2190 return result;
2191}
2192
2193void androidLaunchHelpView() {
2194 qDebug() << "androidLaunchHelpView ";
2195 wxString val = callActivityMethod_vs("isHelpAvailable");
2196 if (val.IsSameAs(_T("YES"))) {
2197 callActivityMethod_vs("launchHelpBook");
2198 } else {
2199 wxString msg =
2200 _("OpenCPN Help is not installed.\nWould you like to install from "
2201 "Google PlayStore now?");
2202 if (androidShowSimpleYesNoDialog(_T("OpenCPN"), msg))
2203 androidInstallPlaystoreHelp();
2204 }
2205}
2206
2207void androidLaunchBrowser(wxString URL) {
2208 qDebug() << "androidLaunchBrowser";
2209 callActivityMethod_ss("launchWebView", URL);
2210}
2211
2212void androidDisplayTimedToast(wxString message, int timeMillisec) {
2213 callActivityMethod_ssi("showTimedToast", message, timeMillisec);
2214}
2215
2216void androidCancelTimedToast() { callActivityMethod_vs("cancelTimedToast"); }
2217
2218void androidDisplayToast(wxString message) {
2219 callActivityMethod_ss("showToast", message);
2220}
2221
2222void androidEnableRotation(void) {
2223 // if(g_detect_smt590)
2224 // return;
2225
2226 callActivityMethod_vs("EnableRotation");
2227}
2228
2229void androidDisableRotation(void) { callActivityMethod_vs("DisableRotation"); }
2230
2231bool androidShowDisclaimer(wxString title, wxString msg) {
2232 if (CheckPendingJNIException()) return false;
2233
2234 wxString return_string;
2235 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2236 "org/qtproject/qt5/android/QtNative", "activity",
2237 "()Landroid/app/Activity;");
2238 if (CheckPendingJNIException()) return false;
2239
2240 if (!activity.isValid()) return false;
2241
2242 JNIEnv *jenv;
2243
2244 // Need a Java environment to decode the resulting string
2245 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) return false;
2246
2247 wxCharBuffer p1b = title.ToUTF8();
2248 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2249
2250 // Convert for wxString-UTF8 to jstring-UTF16
2251 wxWCharBuffer b = msg.wc_str();
2252 jstring p2 = (jenv)->NewString((jchar *)b.data(), msg.Len() * 2);
2253
2254 QAndroidJniObject data = activity.callObjectMethod(
2255 "disclaimerDialog",
2256 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", p1, p2);
2257
2258 (jenv)->DeleteLocalRef(p1);
2259 (jenv)->DeleteLocalRef(p2);
2260
2261 if (CheckPendingJNIException()) return false;
2262
2263 jstring s = data.object<jstring>();
2264
2265 if ((jenv)->GetStringLength(s)) {
2266 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2267 return_string = wxString(ret_string, wxConvUTF8);
2268 }
2269
2270 return (return_string == _T("OK"));
2271}
2272
2273bool androidShowSimpleOKDialog(wxString title, wxString msg) {
2274 if (CheckPendingJNIException()) return false;
2275
2276 wxString return_string;
2277 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2278 "org/qtproject/qt5/android/QtNative", "activity",
2279 "()Landroid/app/Activity;");
2280 if (CheckPendingJNIException()) return false;
2281
2282 if (!activity.isValid()) return false;
2283
2284 JNIEnv *jenv;
2285
2286 // Need a Java environment to decode the resulting string
2287 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) return false;
2288
2289 wxCharBuffer p1b = title.ToUTF8();
2290 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2291
2292 // Convert for wxString-UTF8 to jstring-UTF16
2293 wxWCharBuffer b = msg.wc_str();
2294 jstring p2 = (jenv)->NewString((jchar *)b.data(), msg.Len() * 2);
2295
2296 QAndroidJniObject data = activity.callObjectMethod(
2297 "simpleOKDialog",
2298 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", p1, p2);
2299
2300 (jenv)->DeleteLocalRef(p1);
2301 (jenv)->DeleteLocalRef(p2);
2302
2303 if (CheckPendingJNIException()) return false;
2304
2305 jstring s = data.object<jstring>();
2306
2307 if ((jenv)->GetStringLength(s)) {
2308 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2309 return_string = wxString(ret_string, wxConvUTF8);
2310 }
2311
2312 return (return_string == _T("OK"));
2313}
2314
2315bool androidShowSimpleYesNoDialog(wxString title, wxString msg) {
2316 if (CheckPendingJNIException()) return false;
2317
2318 wxString return_string;
2319 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2320 "org/qtproject/qt5/android/QtNative", "activity",
2321 "()Landroid/app/Activity;");
2322 if (CheckPendingJNIException()) return false;
2323
2324 if (!activity.isValid()) return false;
2325
2326 JNIEnv *jenv;
2327
2328 // Need a Java environment to decode the resulting string
2329 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) return false;
2330
2331 wxCharBuffer p1b = title.ToUTF8();
2332 jstring p1 = (jenv)->NewStringUTF(p1b.data());
2333
2334 // Convert for wxString-UTF8 to jstring-UTF16
2335 wxWCharBuffer b = msg.wc_str();
2336 jstring p2 = (jenv)->NewString((jchar *)b.data(), msg.Len() * 2);
2337
2338 QAndroidJniObject data = activity.callObjectMethod(
2339 "simpleYesNoDialog",
2340 "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", p1, p2);
2341
2342 (jenv)->DeleteLocalRef(p1);
2343 (jenv)->DeleteLocalRef(p2);
2344
2345 if (CheckPendingJNIException()) return false;
2346
2347 jstring s = data.object<jstring>();
2348
2349 if ((jenv)->GetStringLength(s)) {
2350 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2351 return_string = wxString(ret_string, wxConvUTF8);
2352 }
2353
2354 return (return_string == _T("YES"));
2355}
2356
2357bool androidInstallPlaystoreHelp() {
2358 qDebug() << "androidInstallPlaystoreHelp";
2359 // return false;
2360
2361 if (CheckPendingJNIException()) return false;
2362
2363 wxString return_string;
2364 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2365 "org/qtproject/qt5/android/QtNative", "activity",
2366 "()Landroid/app/Activity;");
2367 if (CheckPendingJNIException()) return false;
2368
2369 if (!activity.isValid()) return false;
2370
2371 JNIEnv *jenv;
2372
2373 // Need a Java environment to decode the resulting string
2374 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) return false;
2375
2376 QAndroidJniObject data =
2377 activity.callObjectMethod("installPlaystoreHelp", "()Ljava/lang/String;");
2378
2379 if (CheckPendingJNIException()) return false;
2380
2381 jstring s = data.object<jstring>();
2382
2383 if ((jenv)->GetStringLength(s)) {
2384 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2385 return_string = wxString(ret_string, wxConvUTF8);
2386 }
2387
2388 return (return_string == _T("OK"));
2389}
2390
2391int androidGetTZOffsetMins() {
2392 // Get the TZ offset (from UTC) of the local machine, in minutes. Includes
2393 // DST, if applicable
2394 wxString result = callActivityMethod_vs("getAndroidTZOffsetMinutes");
2395 qDebug() << "androidGetTZOffsetMins result: " << result.mb_str();
2396 long value = 0;
2397 result.ToLong(&value);
2398 return (int)value;
2399}
2400
2401extern PlatSpec android_plat_spc;
2402
2403wxString androidGetDeviceInfo() {
2404 if (!g_deviceInfo.Length())
2405 g_deviceInfo = callActivityMethod_vs("getDeviceInfo");
2406
2407 wxStringTokenizer tkz(g_deviceInfo, _T("\n"));
2408 while (tkz.HasMoreTokens()) {
2409 wxString s1 = tkz.GetNextToken();
2410 if (wxNOT_FOUND != s1.Find(_T("OS API Level"))) {
2411 int a = s1.Find(_T("{"));
2412 if (wxNOT_FOUND != a) {
2413 wxString b = s1.Mid(a + 1, 2);
2414 memset(android_plat_spc.msdk, 0, sizeof(android_plat_spc.msdk));
2415 strncpy(android_plat_spc.msdk, b.c_str(), 2);
2416 g_Android_SDK_Version = atoi( android_plat_spc.msdk );
2417 }
2418 }
2419 if (wxNOT_FOUND != s1.Find(_T("opencpn"))) {
2420 strcpy(&android_plat_spc.hn[0], s1.c_str());
2421 }
2422
2423 // Look for some specific device identifiers, for special processing as
2424 // implemented.
2425
2426 // (1) Samsung SM-T590 running Android/10{29}
2427 if (wxNOT_FOUND != s1.Find(_T("SM-T590"))) {
2428 if (!strncmp(android_plat_spc.msdk, "29",
2429 2)) // Assumes API comes before Model/Product.
2430 g_detect_smt590 = true;
2431 }
2432 }
2433
2434 return g_deviceInfo;
2435}
2436
2437bool androidIsDirWritable( wxString dir )
2438{
2439 if (g_Android_SDK_Version < 30)
2440 return true;
2441 else{
2442 // This is theorectically most accurate, but slow to execute
2443 //wxString result = callActivityMethod_ss("isDirWritable", dir);
2444 //return (result.IsSameAs("YES"));
2445
2446 // This is a practical alternative, for things like chart storage qualification.
2447 return (dir.Contains("org.opencpn.opencpn"));
2448 }
2449}
2450
2451wxString androidGetHomeDir() { return g_androidFilesDir + _T("/"); }
2452
2453wxString
2454androidGetPrivateDir() // Used for logfile, config file, navobj, and the like
2455{
2456 if (g_bExternalApp) {
2457 // should check storage availability
2458#if 0
2459/* Checks if external storage is available for read and write */
2460 public boolean isExternalStorageWritable() {
2461 String state = Environment.getExternalStorageState();
2462 if (Environment.MEDIA_MOUNTED.equals(state)) {
2463 return true;
2464 }
2465 return false;
2466 }
2467
2468 /* Checks if external storage is available to at least read */
2469 public boolean isExternalStorageReadable() {
2470 String state = Environment.getExternalStorageState();
2471 if (Environment.MEDIA_MOUNTED.equals(state) ||
2472 Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
2473 return true;
2474 }
2475 return false;
2476 }
2477#endif
2478 return g_androidExtFilesDir;
2479 }
2480
2481 // We choose to use the ExtFiles directory always , so that the contents of
2482 // logfiles, navobjs, etc. may always be accessible by simple Android File
2483 // Explorers...
2484 return g_androidExtFilesDir;
2485}
2486
2487wxString androidGetSharedDir() // Used for assets like uidata, s57data, etc
2488{
2489 // if(g_bExternalApp){
2490 // if(g_androidExtFilesDir.Length())
2491 // return g_androidExtFilesDir + _T("/");
2492 // }
2493
2494 return g_androidExtFilesDir + _T("/");
2495}
2496
2497wxString
2498androidGetCacheDir() // Used for raster_texture_cache, mmsitoname.csv, etc
2499{
2500 // if(g_bExternalApp){
2501 // if(g_androidExtCacheDir.Length())
2502 // return g_androidExtCacheDir;
2503 // }
2504
2505 return g_androidExtCacheDir;
2506}
2507
2508// Android notes:
2509/* Note: don't be confused by the word "external" here.
2510 * This directory can better be thought as media/shared storage.
2511 * It is a filesystem that can hold a relatively large amount of data
2512 * and that is shared across all applications (does not enforce permissions).
2513 * Traditionally this is an SD card, but it may also be implemented as built-in
2514 * storage in a device that is distinct from the protected internal storage and
2515 * can be mounted as a filesystem on a computer.
2516 */
2517
2518wxString androidGetExtStorageDir() // Used for Chart storage, typically
2519{
2520 if (g_Android_SDK_Version >= 30)
2521 return g_androidExtFilesDir; // Scoped storage model
2522 else
2523 return g_androidExtStorageDir;
2524}
2525
2526extern void androidSetRouteAnnunciator(bool viz) {
2527 callActivityMethod_is("setRouteAnnunciator", viz ? 1 : 0);
2528}
2529
2530extern void androidSetFollowTool(int state, bool forceUpdate) {
2531 // qDebug() << "setFollowIconState" << bactive;
2532
2533 if ((g_follow_state != state) || forceUpdate)
2534 callActivityMethod_is("setFollowIconState", state);
2535
2536 g_follow_state = state;
2537}
2538
2539extern void androidSetTrackTool(bool bactive) {
2540 if (g_track_active != bactive)
2541 callActivityMethod_is("setTrackIconState", bactive ? 1 : 0);
2542
2543 g_track_active = bactive;
2544}
2545
2546void androidSetChartTypeMaskSel(int mask, wxString &indicator) {
2547 int sel = 0;
2548 if (wxNOT_FOUND != indicator.Find(_T("raster")))
2549 sel = 1;
2550 else if (wxNOT_FOUND != indicator.Find(_T("vector")))
2551 sel = 2;
2552 else if (wxNOT_FOUND != indicator.Find(_T("cm93")))
2553 sel = 4;
2554
2555 if ((g_mask != mask) || (g_sel != sel)) {
2556 // qDebug() << "androidSetChartTypeMaskSel" << mask << sel;
2557 callActivityMethod_iis("configureNavSpinnerTS", mask, sel);
2558 g_mask = mask;
2559 g_sel = sel;
2560 }
2561}
2562
2563void androidEnableBackButton(bool benable) {
2564 callActivityMethod_is("setBackButtonState", benable ? 1 : 0);
2565 g_backEnabled = benable;
2566}
2567
2568void androidEnableBackButtonCheck(bool benable) {
2569 if (g_backEnabled != benable) androidEnableBackButton(benable);
2570}
2571
2572void androidEnableOptionItems(bool benable) {
2573 callActivityMethod_is("enableOptionItemAction", benable ? 1 : 0);
2574}
2575
2576void androidEnableMulticast(bool benable) {
2577 callActivityMethod_is("enableMulticast", benable ? 1 : 0);
2578}
2579
2580void androidLastCall(void) {
2581 CheckMigrateCharts();
2582 callActivityMethod_is("lastCallOnInit", 1);
2583}
2584
2585bool androidGetMemoryStatus(int *mem_total, int *mem_used) {
2586 // On android, We arbitrarily declare that we have used 50% of available
2587 // memory.
2588 if (mem_total) *mem_total = s_androidMemTotal * 1024;
2589 if (mem_used) *mem_used = s_androidMemUsed * 1024;
2590 return true;
2591
2592#if 0
2593
2594 // Get a reference to the running native activity
2595 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
2596 "activity", "()Landroid/app/Activity;");
2597
2598 if ( !activity.isValid() ){
2599 qDebug() << "Activity is not valid";
2600 return false;
2601 }
2602
2603 unsigned long android_processID = wxGetProcessId();
2604
2605 // Call the desired method
2606 QAndroidJniObject data = activity.callObjectMethod("getMemInfo", "(I)Ljava/lang/String;", (int)android_processID);
2607
2608// wxString return_string;
2609 jstring s = data.object<jstring>();
2610
2611 int mu = 50;
2612 // Need a Java environment to decode the resulting string
2613 if (java_vm->GetEnv( (void **) &jenv, JNI_VERSION_1_6) != JNI_OK) {
2614 qDebug() << "GetEnv failed.";
2615 }
2616 else {
2617 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2618 mu = atoi(ret_string);
2619
2620 }
2621
2622 if(mem_used)
2623 *mem_used = mu;
2624
2625
2626 return true;
2627#endif
2628}
2629
2630double GetAndroidDisplaySize() {
2631 double ret = 200.; // sane default
2632
2633 // Get a reference to the running native activity
2634 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2635 "org/qtproject/qt5/android/QtNative", "activity",
2636 "()Landroid/app/Activity;");
2637
2638 if (!activity.isValid()) {
2639 // qDebug() << "Activity is not valid";
2640 return false;
2641 }
2642
2643 // Call the desired method
2644 QAndroidJniObject data =
2645 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
2646
2647 wxString return_string;
2648 jstring s = data.object<jstring>();
2649
2650 JNIEnv *jenv;
2651 // Need a Java environment to decode the resulting string
2652 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2653 // qDebug() << "GetEnv failed.";
2654 } else {
2655 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2656 return_string = wxString(ret_string, wxConvUTF8);
2657 }
2658
2659 // Return string may have commas instead of periods, if using Euro locale
2660 // We just fix it here...
2661 return_string.Replace(_T(","), _T("."));
2662
2663 wxSize screen_size = wxSize(1, 2);
2664
2665 if (QApplication::desktop()) { // Desktop might not yet be initialized
2666 screen_size = ::wxGetDisplaySize();
2667 }
2668
2669 wxString msg;
2670
2671 // int ssx, ssy;
2672 //::wxDisplaySize(&ssx, &ssy);
2673 // msg.Printf(_T("wxDisplaySize(): %d %d"), ssx, ssy);
2674 // wxLogMessage(msg);
2675
2676 double density = 1.0;
2677 long androidWidth = 2;
2678 long androidHeight = 1;
2679 long androidDmWidth = 2;
2680 long androidDmHeight = 1;
2681 long abh = 1;
2682
2683 wxStringTokenizer tk(return_string, _T(";"));
2684 if (tk.HasMoreTokens()) {
2685 wxString token = tk.GetNextToken(); // xdpi
2686 token = tk.GetNextToken(); // density
2687
2688 token.ToDouble(&density);
2689
2690 token = tk.GetNextToken(); // ldpi
2691
2692 token = tk.GetNextToken(); // width
2693 token.ToLong(&androidWidth);
2694 token = tk.GetNextToken(); // height - statusBarHeight
2695 token = tk.GetNextToken(); // width
2696 token = tk.GetNextToken(); // height
2697 token.ToLong(&androidHeight);
2698
2699 token = tk.GetNextToken(); // dm.widthPixels
2700 token.ToLong(&androidDmWidth);
2701 token = tk.GetNextToken(); // dm.heightPixels
2702 token.ToLong(&androidDmHeight);
2703
2704 token = tk.GetNextToken(); // actionBarHeight
2705 token.ToLong(&abh);
2706 }
2707
2708 double ldpi = 160. * density;
2709 if (ldpi < 160) ldpi = 160.;
2710
2711 // Find the max dimension among all possibilities
2712// double maxDim = wxMax(screen_size.x, screen_size.y);
2713// maxDim = wxMax(maxDim, androidHeight);
2714// maxDim = wxMax(maxDim, androidWidth);
2715
2716 double maxDim = screen_size.x;
2717 maxDim = wxMax(maxDim, androidWidth);
2718
2719 ret = (maxDim / ldpi) * 25.4;
2720
2721 if (ret < 50) { // 2 inches wide is too small....
2722 double ret_bad = ret;
2723 ret = 100;
2724 msg.Printf(
2725 _T("WARNING: Android Auto Display Size OVERRIDE_TOO_SMALL: %g ldpi: ")
2726 _T("%g density: %g correctedsize: %g "),
2727 ret_bad, ldpi, density, ret);
2728 } else if (ret > 400) { // Too large
2729 double ret_bad = ret;
2730 ret = 400;
2731 msg.Printf(
2732 _T("WARNING: Android Auto Display Size OVERRIDE_TOO_LARGE: %g ldpi: ")
2733 _T("%g density: %g corrected size: %g"),
2734 ret_bad, ldpi, density, ret);
2735 } else {
2736 msg.Printf(
2737 _T("Android Auto Display Size (mm, est.): %g ldpi: %g density: %g"),
2738 ret, ldpi, density);
2739 }
2740
2741 // Save some items as global statics for convenience
2742 g_androidDPmm = ldpi / 25.4;
2743 g_androidDensity = density;
2744 g_ActionBarHeight = wxMax(abh, 50);
2745
2746 // qDebug() << "GetAndroidDisplaySize" << ldpi << g_androidDPmm;
2747
2748 return ret;
2749}
2750
2751int getAndroidActionBarHeight() { return g_ActionBarHeight; }
2752
2753double getAndroidDPmm() {
2754 // Returns an estimate based on the pixel density reported
2755 GetAndroidDisplaySize();
2756
2757 // qDebug() << "getAndroidDPmm" << g_androidDPmm;
2758
2759 // User override?
2760 if (g_config_display_size_manual && (g_config_display_size_mm > 0)) {
2761 double maxDim = wxMax(::wxGetDisplaySize().x, ::wxGetDisplaySize().y);
2762 double size_mm = g_config_display_size_mm;
2763 size_mm = wxMax(size_mm, 50);
2764 size_mm = wxMin(size_mm, 400);
2765 double ret = maxDim / size_mm;
2766 // qDebug() << "getAndroidDPmm override" << maxDim << size_mm <<
2767 // g_config_display_size_mm;
2768
2769 return ret;
2770 }
2771
2772 if (g_androidDPmm > 0.01)
2773 return g_androidDPmm;
2774 else
2775 return 160. / 25.4;
2776}
2777
2778double getAndroidDisplayDensity() {
2779 if (g_androidDensity < 0.01) {
2780 GetAndroidDisplaySize();
2781 }
2782
2783 // qDebug() << "g_androidDensity" << g_androidDensity;
2784
2785 if (g_androidDensity > 0.01)
2786 return g_androidDensity;
2787 else
2788 return 1.0;
2789}
2790
2791wxSize getAndroidDisplayDimensions(void) {
2792 wxSize sz_ret = ::wxGetDisplaySize(); // default, probably reasonable, but
2793 // maybe not accurate
2794
2795 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2796 "org/qtproject/qt5/android/QtNative", "activity",
2797 "()Landroid/app/Activity;");
2798
2799 if (!activity.isValid()) {
2800 // qDebug() << "Activity is not valid";
2801 return sz_ret;
2802 }
2803
2804 // Call the desired method
2805 QAndroidJniObject data =
2806 activity.callObjectMethod("getDisplayMetrics", "()Ljava/lang/String;");
2807
2808 wxString return_string;
2809 jstring s = data.object<jstring>();
2810
2811 // Need a Java environment to decode the resulting string
2812 JNIEnv *jenv;
2813 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2814 // qDebug() << "GetEnv failed.";
2815 } else {
2816 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2817 return_string = wxString(ret_string, wxConvUTF8);
2818 }
2819
2820 // 167.802994;1.000000;160;1024;527;1024;552;1024;552;56
2821 wxStringTokenizer tk(return_string, _T(";"));
2822 if (tk.HasMoreTokens()) {
2823 wxString token = tk.GetNextToken(); // xdpi
2824 token = tk.GetNextToken(); // density
2825 token = tk.GetNextToken(); // densityDPI
2826
2827 token = tk.GetNextToken();
2828 long a = 1000;
2829 if (token.ToLong(&a)) sz_ret.x = a;
2830
2831 token = tk.GetNextToken();
2832 long b = 1000;
2833 if (token.ToLong(&b)) sz_ret.y = b;
2834 token = tk.GetNextToken();
2835 token = tk.GetNextToken();
2836
2837 token = tk.GetNextToken();
2838 token = tk.GetNextToken();
2839
2840 long abh = 0;
2841 token = tk.GetNextToken(); // ActionBar height, if shown
2842 if (token.ToLong(&abh)) sz_ret.y -= abh;
2843 }
2844
2845 // Samsung sm-t590/Android 10 has some display problems in portrait mode.....
2846 if (g_detect_smt590) {
2847 if (sz_ret.x < sz_ret.y) sz_ret.y = 1650;
2848 }
2849
2850 // qDebug() << "getAndroidDisplayDimensions" << sz_ret.x << sz_ret.y;
2851
2852 return sz_ret;
2853}
2854
2855void androidConfirmSizeCorrection() {
2856 // There is some confusion about the ActionBar size during configuration
2857 // changes. We need to confirm the calculated display size, and fix it if
2858 // necessary. This happens during staged resize events
2859
2860 wxLogMessage(_T("androidConfirmSizeCorrection"));
2861 wxSize targetSize = getAndroidDisplayDimensions();
2862 qDebug() << "Confirming" << targetSize.y << config_size.y;
2863 if (config_size != targetSize) {
2864 qDebug() << "Correcting";
2865 gFrame->SetSize(targetSize);
2866 config_size = targetSize;
2867 }
2868}
2869
2870void androidForceFullRepaint(bool b_skipConfirm) {
2871 wxLogMessage(_T("androidForceFullRepaint"));
2872 wxSize targetSize = getAndroidDisplayDimensions();
2873 wxSize tempSize = targetSize;
2874 tempSize.y--;
2875 gFrame->SetSize(tempSize);
2876
2877 GetAndroidDisplaySize();
2878
2879 wxSize new_size = getAndroidDisplayDimensions();
2880 config_size = new_size;
2881
2882 g_androidUtilHandler->m_bskipConfirm = b_skipConfirm;
2883
2884 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
2885 evt.SetId(ID_CMD_TRIGGER_RESIZE);
2886 if (gFrame && gFrame->GetEventHandler()) {
2887 g_androidUtilHandler->AddPendingEvent(evt);
2888 }
2889}
2890
2891void androidShowBusyIcon() {
2892 if (b_androidBusyShown) return;
2893
2894 // qDebug() << "ShowBusy";
2895
2896 // Get a reference to the running native activity
2897 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2898 "org/qtproject/qt5/android/QtNative", "activity",
2899 "()Landroid/app/Activity;");
2900 if (!activity.isValid()) {
2901 // qDebug() << "Activity is not valid";
2902 return;
2903 }
2904
2905 // Call the desired method
2906 QAndroidJniObject data =
2907 activity.callObjectMethod("showBusyCircle", "()Ljava/lang/String;");
2908
2909 b_androidBusyShown = true;
2910}
2911
2912void androidHideBusyIcon() {
2913 if (!b_androidBusyShown) return;
2914
2915 // Get a reference to the running native activity
2916 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2917 "org/qtproject/qt5/android/QtNative", "activity",
2918 "()Landroid/app/Activity;");
2919
2920 if (!activity.isValid()) {
2921 // qDebug() << "Activity is not valid";
2922 return;
2923 }
2924
2925 // Call the desired method
2926 QAndroidJniObject data =
2927 activity.callObjectMethod("hideBusyCircle", "()Ljava/lang/String;");
2928
2929 b_androidBusyShown = false;
2930}
2931
2932int androidGetVersionCode() {
2933 // Get a reference to the running native activity
2934 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2935 "org/qtproject/qt5/android/QtNative", "activity",
2936 "()Landroid/app/Activity;");
2937
2938 if (!activity.isValid()) {
2939 // qDebug() << "Activity is not valid";
2940 return false;
2941 }
2942
2943 // Call the desired method
2944 QAndroidJniObject data = activity.callObjectMethod("getAndroidVersionCode",
2945 "()Ljava/lang/String;");
2946
2947 wxString return_string;
2948 jstring s = data.object<jstring>();
2949
2950 JNIEnv *jenv;
2951 // Need a Java environment to decode the resulting string
2952 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2953 // qDebug() << "GetEnv failed.";
2954 } else {
2955 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2956 return_string = wxString(ret_string, wxConvUTF8);
2957 }
2958
2959 long rv;
2960 return_string.ToLong(&rv);
2961
2962 return rv;
2963}
2964
2965wxString androidGetVersionName() {
2966 // Get a reference to the running native activity
2967 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
2968 "org/qtproject/qt5/android/QtNative", "activity",
2969 "()Landroid/app/Activity;");
2970
2971 if (!activity.isValid()) {
2972 // qDebug() << "Activity is not valid";
2973 return _T("ERROR");
2974 }
2975
2976 // Call the desired method
2977 QAndroidJniObject data = activity.callObjectMethod("getAndroidVersionName",
2978 "()Ljava/lang/String;");
2979
2980 wxString return_string;
2981 jstring s = data.object<jstring>();
2982
2983 JNIEnv *jenv;
2984 // Need a Java environment to decode the resulting string
2985 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
2986 // qDebug() << "GetEnv failed.";
2987 } else {
2988 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
2989 return_string = wxString(ret_string, wxConvUTF8);
2990 }
2991
2992 return return_string;
2993}
2994
2995//---------------------------------------------------------------
2996// GPS Device Support
2997//---------------------------------------------------------------
2998bool androidDeviceHasGPS() {
2999 wxString query = androidGPSService(GPS_PROVIDER_AVAILABLE);
3000 wxLogMessage(query);
3001
3002 bool result = query.Upper().IsSameAs(_T("YES"));
3003 if (result) {
3004 // qDebug() << "Android Device has internal GPS";
3005 wxLogMessage(_T("Android Device has internal GPS"));
3006 } else {
3007 // qDebug() << "Android Device has NO internal GPS";
3008 wxLogMessage(_T("Android Device has NO internal GPS"));
3009 }
3010 return result;
3011}
3012
3013bool androidStartNMEA(wxEvtHandler *consumer) {
3014 s_pAndroidNMEAMessageConsumer = consumer;
3015
3016 // qDebug() << "androidStartNMEA";
3017 wxString s;
3018
3019 s = androidGPSService(GPS_ON);
3020 wxLogMessage(s);
3021 if (s.Upper().Find(_T("DISABLED")) != wxNOT_FOUND) {
3022 OCPNMessageBox(
3023 NULL, _("Your android device has an internal GPS, but it is disabled.\n\
3024 Please visit android Settings/Location dialog to enable GPS"),
3025 _T("OpenCPN"), wxOK);
3026
3027 androidStopNMEA();
3028 return false;
3029 } else
3030 bGPSEnabled = true;
3031
3032 return true;
3033}
3034
3035bool androidStopNMEA() {
3036 s_pAndroidNMEAMessageConsumer = NULL;
3037
3038 wxString s = androidGPSService(GPS_OFF);
3039
3040 bGPSEnabled = false;
3041
3042 return true;
3043}
3044
3045wxString androidGPSService(int parm) {
3046 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
3047 "org/qtproject/qt5/android/QtNative", "activity",
3048 "()Landroid/app/Activity;");
3049
3050 if (!activity.isValid()) {
3051 // qDebug() << "Activity is not valid";
3052 return _T("Activity is not valid");
3053 }
3054
3055 // Call the desired method
3056 QAndroidJniObject data = activity.callObjectMethod(
3057 "queryGPSServer", "(I)Ljava/lang/String;", parm);
3058
3059 wxString return_string;
3060 jstring s = data.object<jstring>();
3061
3062 if (s == NULL) return return_string;
3063
3064 // Need a Java environment to decode the resulting string
3065 JNIEnv *jenv;
3066 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
3067 // qDebug() << "GetEnv failed.";
3068 } else {
3069 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
3070 return_string = wxString(ret_string, wxConvUTF8);
3071 }
3072
3073 return return_string;
3074}
3075
3076bool androidDeviceHasBlueTooth() {
3077 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
3078 "org/qtproject/qt5/android/QtNative", "activity",
3079 "()Landroid/app/Activity;");
3080
3081 if (!activity.isValid()) {
3082 // qDebug() << "Activity is not valid";
3083 return _T("Activity is not valid");
3084 }
3085
3086 // Call the desired method
3087 QAndroidJniObject data =
3088 activity.callObjectMethod("hasBluetooth", "(I)Ljava/lang/String;", 0);
3089
3090 wxString query;
3091 jstring s = data.object<jstring>();
3092
3093 // Need a Java environment to decode the resulting string
3094 JNIEnv *jenv;
3095 if (java_vm->GetEnv((void **)&jenv, JNI_VERSION_1_6) != JNI_OK) {
3096 // qDebug() << "GetEnv failed.";
3097 } else {
3098 const char *ret_string = (jenv)->GetStringUTFChars(s, NULL);
3099 query = wxString(ret_string, wxConvUTF8);
3100 }
3101
3102 bool result = query.Upper().IsSameAs(_T("YES"));
3103
3104 if (result) {
3105 // qDebug() << "Android Device has internal Bluetooth";
3106 wxLogMessage(_T("Android Device has internal Bluetooth"));
3107 } else {
3108 // qDebug() << "Android Device has NO internal Bluetooth";
3109 wxLogMessage(_T("Android Device has NO internal Bluetooth"));
3110 }
3111
3112 return result;
3113}
3114
3115bool androidStartBluetoothScan() {
3116 wxString result = callActivityMethod_is("startBlueToothScan", 0);
3117
3118 return true;
3119}
3120
3121bool androidStopBluetoothScan() {
3122 wxString result = callActivityMethod_is("stopBlueToothScan", 0);
3123
3124 return true;
3125}
3126
3127bool androidStartBT(wxEvtHandler *consumer, wxString mac_address) {
3128 s_pAndroidBTNMEAMessageConsumer = consumer;
3129
3130 if (mac_address.Find(':') ==
3131 wxNOT_FOUND) // does not look like a mac address
3132 return false;
3133
3134 wxString result = callActivityMethod_ss("startBTService", mac_address);
3135
3136 return true;
3137}
3138
3139bool androidStopBT() {
3140 // qDebug() << "androidStopBT";
3141
3142 s_pAndroidBTNMEAMessageConsumer = NULL;
3143
3144 wxString result = callActivityMethod_is("stopBTService", 0);
3145
3146 return true;
3147}
3148
3149wxArrayString androidGetBluetoothScanResults() {
3150 wxArrayString ret_array;
3151
3152 wxString result = callActivityMethod_is("getBlueToothScanResults", 0);
3153
3154 wxStringTokenizer tk(result, _T(";"));
3155 while (tk.HasMoreTokens()) {
3156 wxString token = tk.GetNextToken();
3157 ret_array.Add(token);
3158 }
3159
3160 if (!ret_array.GetCount()) ret_array.Add(_("Nothing found"));
3161
3162 return ret_array;
3163}
3164
3165bool androidSendBTMessage(wxString &payload) {
3166 wxString result = callActivityMethod_ss("sendBTMessage", payload);
3167
3168 return true;
3169}
3170
3171bool androidCheckOnline() {
3172 wxString val = callActivityMethod_vs("isNetworkAvailable");
3173 return val.IsSameAs(_T("YES"));
3174}
3175
3176wxArrayString *androidGetSerialPortsArray(void) {
3177 wxArrayString *pret_array = new wxArrayString;
3178 wxString result = callActivityMethod_is("scanSerialPorts", 0);
3179
3180 wxStringTokenizer tk(result, _T(";"));
3181 while (tk.HasMoreTokens()) {
3182 wxString token = tk.GetNextToken();
3183 pret_array->Add(token);
3184 }
3185
3186 return pret_array;
3187}
3188
3189bool androidStartUSBSerial(wxString &portname, wxString baudRate,
3190 wxEvtHandler *consumer) {
3191 wxString result =
3192 callActivityMethod_s2s("startSerialPort", portname, baudRate);
3193
3194 s_pAndroidNMEAMessageConsumer = consumer;
3195
3196 return true;
3197}
3198
3199bool androidStopUSBSerial(wxString &portname) {
3200 s_pAndroidNMEAMessageConsumer = NULL;
3201
3202 // If app is closing down, the USB serial ports will go away automatically.
3203 // So no need here.
3204 // In fact, stopSerialPort() causes an occasional error when closing app.
3205 // Dunno why, difficult to debug.
3206 if (!b_inCloseWindow)
3207 wxString result = callActivityMethod_ss("stopSerialPort", portname);
3208
3209 return true;
3210}
3211
3212bool androidWriteSerial(wxString &portname, wxString &message) {
3213 wxString result =
3214 callActivityMethod_s2s("writeSerialPort", portname, message);
3215 return true;
3216}
3217
3218int androidFileChooser(wxString *result, const wxString &initDir,
3219 const wxString &title, const wxString &suggestion,
3220 const wxString &wildcard, bool dirOnly, bool addFile) {
3221 wxString tresult;
3222
3223 // Start a timer to poll for results
3224 if (g_androidUtilHandler) {
3225 g_androidUtilHandler->m_eventTimer.Stop();
3226 g_androidUtilHandler->m_done = false;
3227
3228 wxString activityResult;
3229 if (dirOnly)
3230 activityResult = callActivityMethod_s2s2i("DirChooserDialog", initDir,
3231 title, addFile, 0);
3232
3233 else
3234 activityResult = callActivityMethod_s4s("FileChooserDialog", initDir,
3235 title, suggestion, wildcard);
3236
3237 if (activityResult == _T("OK")) {
3238 // qDebug() << "ResultOK, starting spin loop";
3239 g_androidUtilHandler->m_action = ACTION_FILECHOOSER_END;
3240 g_androidUtilHandler->m_eventTimer.Start(1000, wxTIMER_CONTINUOUS);
3241
3242 // Spin, waiting for result
3243 while (!g_androidUtilHandler->m_done) {
3244 wxMilliSleep(50);
3245 wxSafeYield(NULL, true);
3246 }
3247
3248 // qDebug() << "out of spin loop";
3249 g_androidUtilHandler->m_action = ACTION_NONE;
3250 g_androidUtilHandler->m_eventTimer.Stop();
3251
3252 tresult = g_androidUtilHandler->GetStringResult();
3253
3254 if (tresult.StartsWith(_T("cancel:"))) {
3255 // qDebug() << "Cancel1";
3256 return wxID_CANCEL;
3257 } else if (tresult.StartsWith(_T("file:"))) {
3258 if (result) {
3259 *result = tresult.AfterFirst(':');
3260 // qDebug() << "OK";
3261 return wxID_OK;
3262 } else {
3263 // qDebug() << "Cancel2";
3264 return wxID_CANCEL;
3265 }
3266 }
3267 } else {
3268 // qDebug() << "Result NOT OK";
3269 }
3270 }
3271
3272 return wxID_CANCEL;
3273}
3274
3275bool InvokeJNIPreferences(wxString &initial_settings) {
3276 bool ret = true;
3277 wxCharBuffer abuf = initial_settings.ToUTF8();
3278 if (!abuf.data()) return false;
3279
3280 // Create the method parameter(s)
3281 QAndroidJniObject param1 = QAndroidJniObject::fromString(abuf.data());
3282
3283 // Get a reference to the running native activity
3284 QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod(
3285 "org/qtproject/qt5/android/QtNative", "activity",
3286 "()Landroid/app/Activity;");
3287
3288 if (!activity.isValid()) {
3289 // qDebug() << "Activity is not valid";
3290 return false;
3291 }
3292
3293 // Call the desired method
3294 activity.callObjectMethod("doAndroidSettings",
3295 "(Ljava/lang/String;)Ljava/lang/String;",
3296 param1.object<jstring>());
3297
3298 return ret;
3299}
3300
3301wxString BuildAndroidSettingsString(void) {
3302 wxString result;
3303
3304 // Start with chart dirs
3305 if (ChartData) {
3306 wxArrayString chart_dir_array = ChartData->GetChartDirArrayString();
3307
3308 for (unsigned int i = 0; i < chart_dir_array.GetCount(); i++) {
3309 result += _T("ChartDir:");
3310 result += chart_dir_array[i];
3311 result += _T(";");
3312 }
3313 }
3314
3315 // Now the simple Boolean parameters
3316 result += _T("prefb_lookahead:") +
3317 wxString(g_bLookAhead == 1 ? _T("1;") : _T("0;"));
3318 result +=
3319 _T("prefb_quilt:") + wxString(g_bQuiltEnable == 1 ? _T("1;") : _T("0;"));
3320 result += _T("prefb_showgrid:") +
3321 wxString(g_bDisplayGrid == 1 ? _T("1;") : _T("0;"));
3322 result += _T("prefb_showoutlines:") +
3323 wxString(g_bShowOutlines == 1 ? _T("1;") : _T("0;"));
3324 result += _T("prefb_showdepthunits:") +
3325 wxString(g_bShowDepthUnits == 1 ? _T("1;") : _T("0;"));
3326 result += _T("prefb_lockwp:") +
3327 wxString(g_bWayPointPreventDragging == 1 ? _T("1;") : _T("0;"));
3328 result += _T("prefb_confirmdelete:") +
3329 wxString(g_bConfirmObjectDelete == 1 ? _T("1;") : _T("0;"));
3330 result += _T("prefb_expertmode:") +
3331 wxString(g_bUIexpert == 1 ? _T("1;") : _T("0;"));
3332
3333 if (ps52plib) {
3334 result += _T("prefb_showlightldesc:") +
3335 wxString(ps52plib->m_bShowLdisText == 1 ? _T("1;") : _T("0;"));
3336 result += _T("prefb_showimptext:") +
3337 wxString(ps52plib->m_bShowS57ImportantTextOnly == 1 ? _T("1;")
3338 : _T("0;"));
3339 result += _T("prefb_showSCAMIN:") +
3340 wxString(ps52plib->m_bUseSCAMIN == 1 ? _T("1;") : _T("0;"));
3341 result += _T("prefb_showsound:") +
3342 wxString(ps52plib->m_bShowSoundg == 1 ? _T("1;") : _T("0;"));
3343 result += _T("prefb_showATONLabels:") +
3344 wxString(ps52plib->m_bShowAtonText == 1 ? _T("1;") : _T("0;"));
3345 }
3346 // Some other assorted values
3347 result += _T("prefs_navmode:") +
3348 wxString(g_bCourseUp == 0 ? _T("North Up;") : _T("Course Up;"));
3349 result += _T("prefs_chartInitDir:") + *pInit_Chart_Dir + _T(";");
3350
3351 wxString s;
3352 double sf = (g_GUIScaleFactor * 10) + 50.;
3353 s.Printf(_T("%3.0f;"), sf);
3354 s.Trim(false);
3355 result += _T("prefs_UIScaleFactor:") + s;
3356
3357 sf = (g_ChartScaleFactor * 10) + 50.;
3358 s.Printf(_T("%3.0f;"), sf);
3359 s.Trim(false);
3360 result += _T("prefs_chartScaleFactor:") + s;
3361
3362 if (ps52plib) {
3363 wxString nset = _T("Base");
3364 switch (ps52plib->GetDisplayCategory()) {
3365 case (DISPLAYBASE):
3366 nset = _T("Base;");
3367 break;
3368 case (STANDARD):
3369 nset = _T("Standard;");
3370 break;
3371 case (OTHER):
3372 nset = _T("All;");
3373 break;
3374 case (MARINERS_STANDARD):
3375 nset = _T("Mariner Standard;");
3376 break;
3377 default:
3378 nset = _T("Base;");
3379 break;
3380 }
3381 result += _T("prefs_displaycategory:") + nset;
3382
3383 if (ps52plib->m_nSymbolStyle == PAPER_CHART)
3384 nset = _T("Paper Chart;");
3385 else
3386 nset = _T("Simplified;");
3387 result += _T("prefs_vectorgraphicsstyle:") + nset;
3388
3389 if (ps52plib->m_nBoundaryStyle == PLAIN_BOUNDARIES)
3390 nset = _T("Plain;");
3391 else
3392 nset = _T("Symbolized;");
3393 result += _T("prefs_vectorboundarystyle:") + nset;
3394
3395 if (S52_getMarinerParam(S52_MAR_TWO_SHADES) == 1.0)
3396 nset = _T("2;");
3397 else
3398 nset = _T("4;");
3399 result += _T("prefs_vectorchartcolors:") + nset;
3400
3401 // depth unit conversion factor
3402
3403 float conv = 1;
3404 int depthUnit = ps52plib->m_nDepthUnitDisplay;
3405 if (depthUnit == 0) // feet
3406 conv = 0.3048f; // international definiton of 1 foot is 0.3048 metres
3407 else if (depthUnit == 2) // fathoms
3408 conv = 0.3048f * 6; // 1 fathom is 6 feet
3409
3410 s.Printf(_T("%4.2f;"), S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR) / conv);
3411 s.Trim(false);
3412 result += _T("prefs_shallowdepth:") + s;
3413
3414 s.Printf(_T("%4.2f;"), S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR) / conv);
3415 s.Trim(false);
3416 result += _T("prefs_safetydepth:") + s;
3417
3418 s.Printf(_T("%4.2f;"), S52_getMarinerParam(S52_MAR_DEEP_CONTOUR) / conv);
3419 s.Trim(false);
3420 result += _T("prefs_deepdepth:") + s;
3421
3422 // Scale slider range from -5 -- 5 in OCPN options.
3423 // On Android, the range is 0 -- 100
3424 // So, convert
3425 }
3426
3427 // Connections
3428
3429 // Internal GPS.
3430 for (size_t i = 0; i < TheConnectionParams()->Count(); i++) {
3431 ConnectionParams *cp = TheConnectionParams()->Item(i);
3432 if (INTERNAL_GPS == cp->Type) {
3433 result += _T("prefb_internalGPS:");
3434 result += cp->bEnabled ? _T("1;") : _T("0;");
3435 }
3436 if (SERIAL == cp->Type) {
3437 if (wxNOT_FOUND != cp->GetPortStr().Find(_T("PL2303"))) {
3438 result += _T("prefb_PL2303:");
3439 result += cp->bEnabled ? _T("1;") : _T("0;");
3440 } else if (wxNOT_FOUND != cp->GetPortStr().Find(_T("dAISy"))) {
3441 result += _T("prefb_dAISy:");
3442 result += cp->bEnabled ? _T("1;") : _T("0;");
3443 } else if (wxNOT_FOUND != cp->GetPortStr().Find(_T("FT232R"))) {
3444 result += _T("prefb_FT232R:");
3445 result += cp->bEnabled ? _T("1;") : _T("0;");
3446 } else if (wxNOT_FOUND != cp->GetPortStr().Find(_T("FT231X"))) {
3447 result += _T("prefb_FT231X:");
3448 result += cp->bEnabled ? _T("1;") : _T("0;");
3449 } else if (wxNOT_FOUND != cp->GetPortStr().Find(_T("USBDP"))) {
3450 result += _T("prefb_USBDP:");
3451 result += cp->bEnabled ? _T("1;") : _T("0;");
3452 }
3453 }
3454 }
3455
3456 wxLogMessage(_T("BuildAndroidSettingsString: ") + result);
3457
3458 return result;
3459}
3460
3461const wxString AUSBNames[] = {
3462 _T("AUSBSerial:Prolific_PL2303"), _T("AUSBSerial:FTDI_FT232R"),
3463 _T("AUSBSerial:FTDI_FT231X"), _T("AUSBSerial:dAISy"),
3464 _T("AUSBSerial:USBDP"), _T("LASTENTRY")};
3465const wxString AUSBPrefs[] = {_T("prefb_PL2303"), _T("prefb_FT232R"),
3466 _T("prefb_FT231X"), _T("prefb_dAISy"),
3467 _T("prefb_USBDP"), _T("LASTENTRY")};
3468
3469int androidApplySettingsString(wxString settings, ArrayOfCDI *pACDI) {
3470 // Parse the passed settings string
3471 bool bproc_InternalGPS = false;
3472 bool benable_InternalGPS = false;
3473
3474 int rr = GENERIC_CHANGED;
3475
3476 // extract chart directories
3477
3478 if (ChartData) {
3479 wxStringTokenizer tkd(settings, _T(";"));
3480 while (tkd.HasMoreTokens()) {
3481 wxString token = tkd.GetNextToken();
3482
3483 if (token.StartsWith(_T("ChartDir"))) {
3484 wxString dir = token.AfterFirst(':');
3485 if (dir.Length()) {
3486 ChartDirInfo cdi;
3487 cdi.fullpath = dir.Trim();
3488 cdi.magic_number = ChartData->GetMagicNumberCached(dir.Trim());
3489 pACDI->Add(cdi);
3490 }
3491 }
3492 }
3493
3494 // Scan for changes
3495 if (!ChartData->CompareChartDirArray(*pACDI)) {
3496 rr |= VISIT_CHARTS;
3497 rr |= CHANGE_CHARTS;
3498 wxLogMessage(_T("Chart Dir List change detected"));
3499 }
3500 }
3501
3502 wxStringTokenizer tk(settings, _T(";"));
3503 while (tk.HasMoreTokens()) {
3504 wxString token = tk.GetNextToken();
3505 wxString val = token.AfterFirst(':');
3506
3507 // Binary switches
3508
3509 if (token.StartsWith(_T("prefb_lookahead"))) {
3510 g_bLookAhead = val.IsSameAs(_T("1"));
3511 } else if (token.StartsWith(_T("prefb_quilt"))) {
3512 g_bQuiltEnable = val.IsSameAs(_T("1"));
3513 } else if (token.StartsWith(_T("prefb_lockwp"))) {
3514 g_bWayPointPreventDragging = val.IsSameAs(_T("1"));
3515 } else if (token.StartsWith(_T("prefb_showdepthunits"))) {
3516 g_bShowDepthUnits = val.IsSameAs(_T("1"));
3517 } else if (token.StartsWith(_T("prefb_confirmdelete"))) {
3518 g_bConfirmObjectDelete = val.IsSameAs(_T("1"));
3519 } else if (token.StartsWith(_T("prefb_showgrid"))) {
3520 g_bDisplayGrid = val.IsSameAs(_T("1"));
3521 } else if (token.StartsWith(_T("prefb_showoutlines"))) {
3522 g_bShowOutlines = val.IsSameAs(_T("1"));
3523 } else if (token.StartsWith(_T("prefb_expertmode"))) {
3524 g_bUIexpert = val.IsSameAs(_T("1"));
3525 } else if (token.StartsWith(_T("prefb_internalGPS"))) {
3526 bproc_InternalGPS = true;
3527 benable_InternalGPS = val.IsSameAs(_T("1"));
3528 } else if (token.StartsWith(_T("prefs_navmode"))) {
3529 g_bCourseUp = val.IsSameAs(_T("Course Up"));
3530 } else if (token.StartsWith(_T("prefb_trackOnPause"))) {
3531 g_btrackContinuous = val.IsSameAs(_T("1"));
3532 }
3533
3534 // Strings, etc.
3535
3536 else if (token.StartsWith(_T("prefs_UIScaleFactor"))) {
3537 double a;
3538 if (val.ToDouble(&a)) g_GUIScaleFactor = wxRound((a / 10.) - 5.);
3539 }
3540
3541 else if (token.StartsWith(_T("prefs_chartScaleFactor"))) {
3542 double a;
3543 if (val.ToDouble(&a)) {
3544 g_ChartScaleFactor = wxRound((a / 10.) - 5.);
3545 g_ChartScaleFactorExp =
3546 g_Platform->GetChartScaleFactorExp(g_ChartScaleFactor);
3547 }
3548 }
3549
3550 else if (token.StartsWith(_T("prefs_chartInitDir"))) {
3551 *pInit_Chart_Dir = val;
3552 }
3553
3554 if (ps52plib) {
3555 float conv = 1;
3556 int depthUnit = ps52plib->m_nDepthUnitDisplay;
3557 if (depthUnit == 0) // feet
3558 conv = 0.3048f; // international definiton of 1 foot is 0.3048 metres
3559 else if (depthUnit == 2) // fathoms
3560 conv = 0.3048f * 6; // 1 fathom is 6 feet
3561
3562 if (token.StartsWith(_T("prefb_showsound"))) {
3563 bool old_val = ps52plib->m_bShowSoundg;
3564 ps52plib->m_bShowSoundg = val.IsSameAs(_T("1"));
3565 if (old_val != ps52plib->m_bShowSoundg) rr |= S52_CHANGED;
3566 } else if (token.StartsWith(_T("prefb_showSCAMIN"))) {
3567 bool old_val = ps52plib->m_bUseSCAMIN;
3568 ps52plib->m_bUseSCAMIN = val.IsSameAs(_T("1"));
3569 if (old_val != ps52plib->m_bUseSCAMIN) rr |= S52_CHANGED;
3570 } else if (token.StartsWith(_T("prefb_showimptext"))) {
3571 bool old_val = ps52plib->m_bShowS57ImportantTextOnly;
3572 ps52plib->m_bShowS57ImportantTextOnly = val.IsSameAs(_T("1"));
3573 if (old_val != ps52plib->m_bShowS57ImportantTextOnly) rr |= S52_CHANGED;
3574 } else if (token.StartsWith(_T("prefb_showlightldesc"))) {
3575 bool old_val = ps52plib->m_bShowLdisText;
3576 ps52plib->m_bShowLdisText = val.IsSameAs(_T("1"));
3577 if (old_val != ps52plib->m_bShowLdisText) rr |= S52_CHANGED;
3578 } else if (token.StartsWith(_T("prefb_showATONLabels"))) {
3579 bool old_val = ps52plib->m_bShowAtonText;
3580 ps52plib->m_bShowAtonText = val.IsSameAs(_T("1"));
3581 if (old_val != ps52plib->m_bShowAtonText) rr |= S52_CHANGED;
3582 }
3583
3584 else if (token.StartsWith(_T("prefs_displaycategory"))) {
3585 _DisCat old_nset = ps52plib->GetDisplayCategory();
3586
3587 _DisCat nset = DISPLAYBASE;
3588 if (wxNOT_FOUND != val.Lower().Find(_T("base")))
3589 nset = DISPLAYBASE;
3590 else if (wxNOT_FOUND != val.Lower().Find(_T("mariner")))
3591 nset = MARINERS_STANDARD;
3592 else if (wxNOT_FOUND != val.Lower().Find(_T("standard")))
3593 nset = STANDARD;
3594 else if (wxNOT_FOUND != val.Lower().Find(_T("all")))
3595 nset = OTHER;
3596
3597 if (nset != old_nset) {
3598 rr |= S52_CHANGED;
3599 ps52plib->SetDisplayCategory(nset);
3600 }
3601 }
3602
3603 else if (token.StartsWith(_T("prefs_shallowdepth"))) {
3604 double old_dval = S52_getMarinerParam(S52_MAR_SHALLOW_CONTOUR);
3605 double dval;
3606 if (val.ToDouble(&dval)) {
3607 if (fabs(dval - old_dval) > .001) {
3608 S52_setMarinerParam(S52_MAR_SHALLOW_CONTOUR, dval * conv);
3609 rr |= S52_CHANGED;
3610 }
3611 }
3612 }
3613
3614 else if (token.StartsWith(_T("prefs_safetydepth"))) {
3615 double old_dval = S52_getMarinerParam(S52_MAR_SAFETY_CONTOUR);
3616 double dval;
3617 if (val.ToDouble(&dval)) {
3618 if (fabs(dval - old_dval) > .001) {
3619 S52_setMarinerParam(S52_MAR_SAFETY_CONTOUR, dval * conv);
3620 rr |= S52_CHANGED;
3621 }
3622 }
3623 }
3624
3625 else if (token.StartsWith(_T("prefs_deepdepth"))) {
3626 double old_dval = S52_getMarinerParam(S52_MAR_DEEP_CONTOUR);
3627 double dval;
3628 if (val.ToDouble(&dval)) {
3629 if (fabs(dval - old_dval) > .001) {
3630 S52_setMarinerParam(S52_MAR_DEEP_CONTOUR, dval * conv);
3631 rr |= S52_CHANGED;
3632 }
3633 }
3634 }
3635
3636 else if (token.StartsWith(_T("prefs_vectorgraphicsstyle"))) {
3637 LUPname old_LUP = ps52plib->m_nSymbolStyle;
3638
3639 if (wxNOT_FOUND != val.Lower().Find(_T("paper")))
3640 ps52plib->m_nSymbolStyle = PAPER_CHART;
3641 else if (wxNOT_FOUND != val.Lower().Find(_T("simplified")))
3642 ps52plib->m_nSymbolStyle = SIMPLIFIED;
3643
3644 if (old_LUP != ps52plib->m_nSymbolStyle) rr |= S52_CHANGED;
3645
3646 }
3647
3648 else if (token.StartsWith(_T("prefs_vectorboundarystyle"))) {
3649 LUPname old_LUP = ps52plib->m_nBoundaryStyle;
3650
3651 if (wxNOT_FOUND != val.Lower().Find(_T("plain")))
3652 ps52plib->m_nBoundaryStyle = PLAIN_BOUNDARIES;
3653 else if (wxNOT_FOUND != val.Lower().Find(_T("symbolized")))
3654 ps52plib->m_nBoundaryStyle = SYMBOLIZED_BOUNDARIES;
3655
3656 if (old_LUP != ps52plib->m_nBoundaryStyle) rr |= S52_CHANGED;
3657
3658 }
3659
3660 else if (token.StartsWith(_T("prefs_vectorchartcolors"))) {
3661 double old_dval = S52_getMarinerParam(S52_MAR_TWO_SHADES);
3662
3663 if (wxNOT_FOUND != val.Lower().Find(_T("2")))
3664 S52_setMarinerParam(S52_MAR_TWO_SHADES, 1.);
3665 else if (wxNOT_FOUND != val.Lower().Find(_T("4")))
3666 S52_setMarinerParam(S52_MAR_TWO_SHADES, 0.);
3667
3668 double new_dval = S52_getMarinerParam(S52_MAR_TWO_SHADES);
3669 if (fabs(new_dval - old_dval) > .1) {
3670 rr |= S52_CHANGED;
3671 }
3672 }
3673 }
3674 }
3675
3676 // Process Internal GPS Connection
3677 if (bproc_InternalGPS) {
3678 // Does the connection already exist?
3679 ConnectionParams *pExistingParams = NULL;
3680 ConnectionParams *cp = NULL;
3681
3682 for (size_t i = 0; i < TheConnectionParams()->Count(); i++) {
3683 ConnectionParams *xcp = TheConnectionParams()->Item(i);
3684 if (INTERNAL_GPS == xcp->Type) {
3685 pExistingParams = xcp;
3686 cp = xcp;
3687 break;
3688 }
3689 }
3690
3691 bool b_action = true;
3692 if (pExistingParams) {
3693 if (pExistingParams->bEnabled == benable_InternalGPS)
3694 b_action = false; // nothing to do...
3695 else
3696 cp->bEnabled = benable_InternalGPS;
3697 } else if (benable_InternalGPS) { // Need a new Params
3698 // make a generic config string for InternalGPS.
3699 wxString sGPS = _T("2;3;;0;0;;0;1;0;0;;0;;1;0;0;0;0"); // 17 parms
3700 ConnectionParams *new_params = new ConnectionParams(sGPS);
3701
3702 new_params->bEnabled = benable_InternalGPS;
3703 TheConnectionParams()->Add(new_params);
3704 cp = new_params;
3705 }
3706
3707 if (b_action && cp) { // something to do?
3708//FIXME (dave)
3709#if 0
3710
3711 // Terminate and remove any existing stream with the same port name
3712 DataStream *pds_existing = g_pMUX->FindStream(cp->GetDSPort());
3713 if (pds_existing) g_pMUX->StopAndRemoveStream(pds_existing);
3714
3715 if (cp->bEnabled) {
3716 dsPortType port_type = cp->IOSelect;
3717 DataStream *dstr =
3718 makeSerialDataStream(g_pMUX, cp->Type, cp->GetDSPort(),
3719 wxString::Format(wxT("%i"), cp->Baudrate),
3720 port_type, cp->Priority, cp->Garmin);
3721
3722#if 0
3723 DataStream *dstr = new DataStream( g_pMUX,
3724 cp->Type,
3725 cp->GetDSPort(),
3726 wxString::Format(wxT("%i"), cp->Baudrate),
3727 port_type,
3728 cp->Priority,
3729 cp->Garmin);
3730#endif
3731 dstr->SetInputFilter(cp->InputSentenceList);
3732 dstr->SetInputFilterType(cp->InputSentenceListType);
3733 dstr->SetOutputFilter(cp->OutputSentenceList);
3734 dstr->SetOutputFilterType(cp->OutputSentenceListType);
3735 dstr->SetChecksumCheck(cp->ChecksumCheck);
3736
3737 g_pMUX->AddStream(dstr);
3738
3739 cp->b_IsSetup = true;
3740 }
3741#endif
3742 }
3743 }
3744
3745 // Process USB Serial Connections
3746 bool b_newGlobalSettings = false;
3747 int i = 0;
3748 while (wxNOT_FOUND == AUSBPrefs[i].Find(_T("LASTENTRY"))) {
3749 wxStringTokenizer tk(settings, _T(";"));
3750 while (tk.HasMoreTokens()) {
3751 wxString token = tk.GetNextToken();
3752 wxString pref = token.BeforeFirst(':');
3753 wxString val = token.AfterFirst(':');
3754 wxString extraString;
3755
3756 bool benabled = false;
3757
3758 if (pref.IsSameAs(AUSBPrefs[i])) {
3759 wxLogMessage(_T("pref: ") + pref);
3760 wxLogMessage(_T("val: ") + val);
3761
3762 if (pref.Contains(_T("USBDP"))) {
3763 extraString = val.AfterFirst(':');
3764 wxLogMessage(_T("extra: ") + extraString);
3765 }
3766
3767 wxLogMessage(_T("found pref ") + pref);
3768
3769 // Does the connection already exist?
3770 ConnectionParams *pExistingParams = NULL;
3771 ConnectionParams *cp = NULL;
3772
3773 wxString target = AUSBNames[i] + _T("-") + extraString;
3774
3775 for (unsigned int j = 0; j < TheConnectionParams()->Count(); j++) {
3776 ConnectionParams *xcp = TheConnectionParams()->Item(j);
3777 wxLogMessage(_T(" Checking: ") + target + " .. " +
3778 xcp->GetDSPort());
3779
3780 if ((SERIAL == xcp->Type) &&
3781 (target.IsSameAs(xcp->GetDSPort().AfterFirst(':')))) {
3782 pExistingParams = xcp;
3783 cp = xcp;
3784 benabled = val.BeforeFirst(':').IsSameAs(_T("1"));
3785 break;
3786 }
3787 }
3788
3789 bool b_action = true;
3790 if (pExistingParams) {
3791 wxLogMessage(_T("Using existing connection ") + target);
3792
3793 if (pExistingParams->bEnabled == benabled) {
3794 b_action = false; // nothing to do...
3795 } else
3796 cp->bEnabled = benabled;
3797 } else if (val.BeforeFirst(':').IsSameAs(
3798 _T("1"))) { // Need a new Params
3799 // make a generic config string.
3800 // 0;1;;0;0;/dev/ttyS0;4800;1;0;0;;0;;1;0;0;0;0 17 parms
3801
3802 wxString sSerial = _T("0;1;;0;0;");
3803 sSerial += AUSBNames[i];
3804 sSerial += _T("-") + extraString;
3805 sSerial += _T(";4800;1;0;0;;0;;1;0;0;0;0");
3806
3807 wxLogMessage(_T("Adding connection ") + sSerial);
3808
3809 ConnectionParams *new_params = new ConnectionParams(sSerial);
3810
3811 new_params->bEnabled = true;
3812 TheConnectionParams()->Add(new_params);
3813 cp = new_params;
3814 rr |= NEED_NEW_OPTIONS;
3815 }
3816
3817 if (b_action && cp) { // something to do?
3818//FIXME (dave)
3819#if 0
3820 rr |= NEED_NEW_OPTIONS;
3821
3822 // Terminate and remove any existing stream with the same port name
3823 DataStream *pds_existing = g_pMUX->FindStream(cp->GetDSPort());
3824 if (pds_existing) g_pMUX->StopAndRemoveStream(pds_existing);
3825
3826 if (cp->bEnabled) {
3827 dsPortType port_type = cp->IOSelect;
3828#if 0
3829 DataStream *dstr = new DataStream( g_pMUX,
3830 cp->Type,
3831 cp->GetDSPort(),
3832 wxString::Format(wxT("%i"), cp->Baudrate),
3833 port_type,
3834 cp->Priority,
3835 cp->Garmin);
3836#endif
3837 DataStream *dstr = makeSerialDataStream(
3838 g_pMUX, cp->Type, cp->GetDSPort(),
3839 wxString::Format(wxT("%i"), cp->Baudrate), port_type,
3840 cp->Priority, cp->Garmin);
3841
3842 dstr->SetInputFilter(cp->InputSentenceList);
3843 dstr->SetInputFilterType(cp->InputSentenceListType);
3844 dstr->SetOutputFilter(cp->OutputSentenceList);
3845 dstr->SetOutputFilterType(cp->OutputSentenceListType);
3846 dstr->SetChecksumCheck(cp->ChecksumCheck);
3847
3848 g_pMUX->AddStream(dstr);
3849 cp->b_IsSetup = true;
3850 }
3851#endif
3852 }
3853 }
3854 } // found pref
3855
3856 i++;
3857 } // while
3858
3859 return rr;
3860}
3861
3862bool DoAndroidPreferences(void) {
3863 wxLogMessage(_T("Start DoAndroidPreferences"));
3864
3865 wxString settings = BuildAndroidSettingsString();
3866
3867 wxLogMessage(_T("Call InvokeJNIPreferences"));
3868 InvokeJNIPreferences(settings);
3869
3870 return true;
3871}
3872
3873wxString doAndroidPOST(const wxString &url, wxString &parms, int timeoutMsec) {
3874 // Start a timer to poll for results
3875 if (g_androidUtilHandler) {
3876 g_androidUtilHandler->m_eventTimer.Stop();
3877 g_androidUtilHandler->m_done = false;
3878
3879 androidShowBusyIcon();
3880
3881 wxString stimeout;
3882 stimeout.Printf(_T("%d"), timeoutMsec);
3883 wxString result =
3884 callActivityMethod_s3s("doHttpPostAsync", url, parms, stimeout);
3885
3886 if (result == _T("OK")) {
3887 qDebug() << "doHttpPostAsync ResultOK, starting spin loop";
3888 g_androidUtilHandler->m_action = ACTION_POSTASYNC_END;
3889 g_androidUtilHandler->m_eventTimer.Start(500, wxTIMER_CONTINUOUS);
3890
3891 // Spin, waiting for result
3892 while (!g_androidUtilHandler->m_done) {
3893 wxMilliSleep(50);
3894 wxSafeYield(NULL, true);
3895 }
3896
3897 qDebug() << "out of spin loop";
3898 g_androidUtilHandler->m_action = ACTION_NONE;
3899 g_androidUtilHandler->m_eventTimer.Stop();
3900 androidHideBusyIcon();
3901
3902 wxString presult = g_androidUtilHandler->GetStringResult();
3903
3904 return presult;
3905 } else {
3906 qDebug() << "doHttpPostAsync Result NOT OK";
3907 androidHideBusyIcon();
3908 }
3909 }
3910
3911 return wxEmptyString;
3912}
3913
3914int validateAndroidWriteLocation(const wxString &destination) {
3915 // validate the destination, as it might be on SDCard
3916 wxString val_result =
3917 callActivityMethod_s2s2i("validateWriteLocation", destination, _T(""),
3918 OCPN_ACTION_DOWNLOAD_VALID, 0);
3919 if (val_result.IsSameAs(_T("Pending")))
3920 return 0; // SAF Dialog is going to run
3921 else
3922 return 1; // All well.
3923}
3924
3925int startAndroidFileDownload(const wxString &url, const wxString &destination,
3926 wxEvtHandler *evh, long *dl_id) {
3927 // if(evh)
3928 {
3929 s_bdownloading = true;
3930 s_requested_url = url;
3931 s_download_evHandler = evh;
3932 s_download_destination = destination;
3933
3934 wxString result = callActivityMethod_s2s("downloadFile", url, destination);
3935
3936 androidShowBusyIcon();
3937
3938 if (result.IsSameAs(_T("NOK"))) return 1; // general error
3939
3940 // wxLogMessage(_T("downloads2s result: ") + result);
3941 long dl_ID;
3942 wxStringTokenizer tk(result, _T(";"));
3943 if (tk.HasMoreTokens()) {
3944 wxString token = tk.GetNextToken();
3945 if (token.IsSameAs(_T("OK"))) {
3946 token = tk.GetNextToken();
3947 token.ToLong(&dl_ID);
3948 *dl_id = dl_ID;
3949 // qDebug() << dl_ID;
3950 return 0;
3951 }
3952 }
3953 }
3954
3955 return -1;
3956}
3957
3958int queryAndroidFileDownload(long dl_ID, wxString *result) {
3959 // qDebug() << dl_ID;
3960
3961 wxString stat = callActivityMethod_is("getDownloadStatus", (int)dl_ID);
3962 if (result) *result = stat;
3963
3964 // wxLogMessage( _T("queryAndroidFileDownload: ") + stat);
3965
3966 if (stat.IsSameAs(_T("NOK")))
3967 return 1; // general error
3968 else
3969 return 0;
3970}
3971
3972void finishAndroidFileDownload(void) {
3973 s_bdownloading = false;
3974 s_requested_url.Clear();
3975 s_download_evHandler = NULL;
3976 s_download_destination.Clear();
3977 androidHideBusyIcon();
3978
3979 return;
3980}
3981
3982void cancelAndroidFileDownload(long dl_ID) {
3983 wxString stat = callActivityMethod_is("cancelDownload", (int)dl_ID);
3984}
3985
3986bool AndroidUnzip(wxString &zipFile, wxString &destDir, int nStrip,
3987 bool bRemoveZip) {
3988 wxString ns;
3989 ns.Printf(_T("%d"), nStrip);
3990
3991 wxString br;
3992 br.Printf(_T("%d"), bRemoveZip);
3993
3994 wxString stat = callActivityMethod_s4s("unzipFile", zipFile, destDir, ns, br);
3995
3996 if (wxNOT_FOUND == stat.Find(_T("OK"))) return false;
3997
3998 qDebug() << "unzip start";
3999
4000 bool bDone = false;
4001 while (!bDone) {
4002 wxMilliSleep(1000);
4003 wxSafeYield(NULL, true);
4004
4005 qDebug() << "unzip poll";
4006
4007 wxString result = callActivityMethod_ss("getUnzipStatus", _T(""));
4008 if (wxNOT_FOUND != result.Find(_T("DONE"))) bDone = true;
4009 }
4010 qDebug() << "unzip done";
4011
4012 return true;
4013}
4014
4015wxString getFontQtStylesheet(wxFont *font) {
4016 // wxString classes = _T("QLabel, QPushButton, QTreeWidget, QTreeWidgetItem,
4017 // QCheckBox");
4018 wxString classes = _T("QWidget ");
4019
4020 wxString qstyle = classes + _T("{ font-family: ") + font->GetFaceName() +
4021 _T(";font-style: ");
4022 switch (font->GetStyle()) {
4023 case wxFONTSTYLE_ITALIC:
4024 qstyle += _T("italic;");
4025 break;
4026 case wxFONTSTYLE_NORMAL:
4027 default:
4028 qstyle += _T("normal;");
4029 break;
4030 }
4031 qstyle += _T("font-weight: ");
4032 switch (font->GetWeight()) {
4033 case wxFONTWEIGHT_BOLD:
4034 qstyle += _T("bold;");
4035 break;
4036 case wxFONTWEIGHT_LIGHT:
4037 qstyle += _T("light;");
4038 break;
4039 case wxFONTWEIGHT_NORMAL:
4040 default:
4041 qstyle += _T("normal;");
4042 break;
4043 }
4044
4045 qstyle += _T("font-size: ");
4046 wxString fontSize;
4047 fontSize.Printf(_T("%dpt }"), font->GetPointSize());
4048 qstyle += fontSize;
4049
4050 // Oddity here....
4051 // If this line is active, this particular style is applied to ListCtrl() in
4052 // PlugIns, But not TreeCtrl.....
4053 // ????
4054 // qstyle += _T("QTreeWidget::item{ border-color:red; border-style:outset;
4055 // border-width:2px; color:black; }");
4056
4057 return qstyle;
4058}
4059
4060bool androidPlaySound(const wxString soundfile, AndroidSound* sound) {
4061 DEBUG_LOG << "androidPlaySound";
4062 std::ostringstream oss;
4063 oss << sound;
4064 wxString wxSound(oss.str());
4065 wxString result = callActivityMethod_s2s("playSound", soundfile, wxSound.Mid(2));
4066 return true;
4067}
4068
4069extern "C" {
4070JNIEXPORT jint JNICALL
4071Java_org_opencpn_OCPNNativeLib_onSoundFinished(JNIEnv *env, jobject obj) {
4072 qDebug() << "onSoundFinished";
4073
4074 if (s_soundCallBack) {
4075 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
4076 evt.SetId(ID_CMD_SOUND_FINISHED);
4077 if (g_androidUtilHandler) g_androidUtilHandler->AddPendingEvent(evt);
4078 }
4079
4080 return 98;
4081}
4082}
4083
4084wxString androidGetSupplementalLicense(void) {
4085 wxString result = callActivityMethod_vs("getGMAPILicense");
4086
4087 result += AndroidSuppLicense;
4088
4089 return result;
4090}
4091
4092wxArrayString androidTraverseDir(wxString dir, wxString filespec) {
4093 wxArrayString result;
4094 if (g_Android_SDK_Version != 17) // skip unless running Android 4.2.2, especially Samsung...
4095 return result;
4096
4097 wxString ir =
4098 callActivityMethod_s2s("getAllFilesWithFilespec", dir, filespec);
4099
4100 wxStringTokenizer tk(ir, _T(";"));
4101 while (tk.HasMoreTokens()) {
4102 result.Add(tk.GetNextToken());
4103 }
4104
4105 return result;
4106}
4107
4108void androidEnableOptionsMenu(bool bEnable) {
4109 callActivityMethod_is("enableOptionsMenu", bEnable ? 1 : 0);
4110}
4111
4112// Android specific style sheet management
4113
4114// ------------Runtime modified globals
4115QString qtStyleSheetDialog;
4116QString qtStyleSheetListBook;
4117QString qtStyleSheetScrollbars;
4118
4119//--------------Stylesheet prototypes
4120
4121// Generic dialog stylesheet
4122// Typically adjusted at runtime for display density
4123
4124QString qtStyleSheetDialogProto =
4125 "\
4126QSlider::groove\
4127{\
4128 border: 1px solid #999999;\
4129 background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #E6E6E6, stop:1 #EEEEEE);\
4130}\
4131QSlider::groove:disabled\
4132{\
4133 background: #efefef;\
4134}\
4135\
4136QSlider::handle\
4137{\
4138 background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #7cb0e9, stop:1 #7cb0e9);\
4139 border: 1px solid #5c5c5c;\
4140 border-radius: 3px;\
4141 width: 80px;\
4142 height: 45px;\
4143}\
4144\
4145QSlider::handle:disabled\
4146{\
4147 background: #D3D0CD;\
4148}\
4149\
4150QScrollBar:horizontal {\
4151 border: 0px solid grey;\
4152 background-color: transparent;\
4153 height: 4px;\
4154 margin: 0px 1px 0 1px;\
4155}\
4156QScrollBar::handle:horizontal {\
4157 background-color: rgb(150, 150, 150);\
4158 min-width: 20px;\
4159}\
4160QScrollBar::add-line:horizontal {\
4161 border: 0px solid grey;\
4162 background: transparent;\
4163 width: 1px;\
4164 subcontrol-position: right;\
4165 subcontrol-origin: margin;\
4166}\
4167\
4168QScrollBar::sub-line:horizontal {\
4169 border: 0px solid grey;\
4170 background: transparent;\
4171 width: 1px;\
4172 subcontrol-position: left;\
4173 subcontrol-origin: margin;\
4174}\
4175\
4176QScrollBar:vertical {\
4177 border: 0px solid grey;\
4178 background-color: transparent;\
4179 width: 4px;\
4180 margin: 1px 0px 1px 0px;\
4181}\
4182QScrollBar::handle:vertical {\
4183 background-color: rgb(150, 150, 150);\
4184 min-height: 20px;\
4185}\
4186QScrollBar::add-line:vertical {\
4187 border: 0px solid grey;\
4188 background: transparent;\
4189 height: 1px;\
4190 subcontrol-position: top;\
4191 subcontrol-origin: margin;\
4192}\
4193\
4194QScrollBar::sub-line:vertical {\
4195 border: 0px solid grey;\
4196 background: transparent;\
4197 height: 1px;\
4198 subcontrol-position: bottom;\
4199 subcontrol-origin: margin;\
4200}\
4201\
4202QTreeWidget QScrollBar:vertical {\
4203 border: 0px solid grey;\
4204 background-color: rgb(240, 240, 240);\
4205 width: 35px;\
4206 margin: 1px 0px 1px 0px;\
4207}\
4208QTreeWidget QScrollBar::handle:vertical {\
4209 background-color: rgb(200, 200, 200);\
4210 min-height: 20px;\
4211 border-radius: 10px;\
4212}\
4213QTreeWidget QScrollBar::add-line:vertical {\
4214 border: 0px solid grey;\
4215 background: #32CC99;\
4216 height: 0px;\
4217 subcontrol-position: top;\
4218 subcontrol-origin: margin;\
4219}\
4220\
4221QTreeWidget QScrollBar::sub-line:vertical {\
4222 border: 0px solid grey;\
4223 background: #32CC99;\
4224 height: 0px;\
4225 subcontrol-position: bottom;\
4226 subcontrol-origin: margin;\
4227}\
4228\
4229QTreeWidget QScrollBar:horizontal {\
4230 border: 0px solid grey;\
4231 background-color: rgb(240, 240, 240);\
4232 height: 35px;\
4233 margin: 0px 1px 0 1px;\
4234}\
4235QTreeWidget QScrollBar::handle:horizontal {\
4236 background-color: rgb(200, 200, 200);\
4237 min-width: 20px;\
4238 border-radius: 10px;\
4239}\
4240QTreeWidget QScrollBar::add-line:horizontal {\
4241 border: 0px solid grey;\
4242 background: #32CC99;\
4243 width: 0px;\
4244 subcontrol-position: right;\
4245 subcontrol-origin: margin;\
4246}\
4247\
4248QTreeWidget QScrollBar::sub-line:horizontal {\
4249 border: 0px solid grey;\
4250 background: #32CC99;\
4251 width: 0px;\
4252 subcontrol-position: left;\
4253 subcontrol-origin: margin;\
4254}\
4255\
4256#OCPNCheckedListCtrl QScrollBar::vertical {\
4257 border: 0px solid grey;\
4258 background-color: rgb(240, 240, 240);\
4259 width: 45px;\
4260 margin: 1px 0px 1px 0px;\
4261}\
4262#OCPNCheckedListCtrl QScrollBar::handle:vertical {\
4263 background-color: rgb(180, 180, 180);\
4264 min-height: 45px;\
4265 border-radius: 6px;\
4266}\
4267#OCPNCheckedListCtrl QScrollBar::add-line:vertical {\
4268 border: 0px solid grey;\
4269 background: #32CC99;\
4270 height: 0px;\
4271 subcontrol-position: top;\
4272 subcontrol-origin: margin;\
4273}\
4274\
4275#OCPNCheckedListCtrl QScrollBar::sub-line:vertical {\
4276 border: 0px solid grey;\
4277 background: #32CC99;\
4278 height: 0px;\
4279 subcontrol-position: bottom;\
4280 subcontrol-origin: margin;\
4281}";
4282
4283QString qtStyleSheetScrollbarsProto =
4284 "\
4285QScrollBar:horizontal {\
4286 border: 0px solid grey;\
4287 background-color: transparent;\
4288 height: 35px;\
4289 margin: 0px 1px 0 1px;\
4290}\
4291QScrollBar::handle:horizontal {\
4292 background-color: #7cb0e9;\
4293 min-width: 20px;\
4294}\
4295QScrollBar::add-line:horizontal {\
4296 border: 0px solid grey;\
4297 background: transparent;\
4298 width: 1px;\
4299 subcontrol-position: right;\
4300 subcontrol-origin: margin;\
4301}\
4302\
4303QScrollBar::sub-line:horizontal {\
4304 border: 0px solid grey;\
4305 background: transparent;\
4306 width: 1px;\
4307 subcontrol-position: left;\
4308 subcontrol-origin: margin;\
4309}\
4310\
4311QScrollBar:vertical {\
4312 border: 0px solid grey;\
4313 background-color: transparent;\
4314 width: 35px;\
4315 margin: 1px 0px 1px 0px;\
4316}\
4317QScrollBar::handle:vertical {\
4318 background-color: #7cb0e9;\
4319 min-height: 20px;\
4320}\
4321QScrollBar::add-line:vertical {\
4322 border: 0px solid grey;\
4323 background: transparent;\
4324 height: 1px;\
4325 subcontrol-position: top;\
4326 subcontrol-origin: margin;\
4327}\
4328\
4329QScrollBar::sub-line:vertical {\
4330 border: 0px solid grey;\
4331 background: transparent;\
4332 height: 1px;\
4333 subcontrol-position: bottom;\
4334 subcontrol-origin: margin;\
4335}";
4336
4337std::string prepareStyleIcon(wxString icon_file, int size) {
4338 wxString data_locn = g_Platform->GetSharedDataDir();
4339 data_locn.Append(_T("styles/"));
4340
4341 wxString file = data_locn + icon_file;
4342
4343 wxImage Image(file, wxBITMAP_TYPE_PNG);
4344 wxImage scaledImage = Image.Scale(size, size, wxIMAGE_QUALITY_HIGH);
4345
4346 wxString save_file = g_Platform->GetPrivateDataDir() + _T("/") + icon_file;
4347 scaledImage.SaveFile(save_file, wxBITMAP_TYPE_PNG);
4348
4349 wxCharBuffer buf = save_file.ToUTF8();
4350 std::string ret(buf);
4351 return ret;
4352}
4353
4354QString prepareAndroidSliderStyleSheet(int sliderWidth) {
4355 QString qtStyleSheetSlider;
4356
4357 // Create and fix up the qtStyleSheetDialog for generic dialog
4358
4359 // adjust the Slider specification
4360
4361 int slider_handle_width =
4362 wxMax(g_Platform->GetDisplayDPmm() * 6, sliderWidth / 5);
4363
4364 char sb[600];
4365 snprintf(
4366 sb, sizeof(sb),
4367 "QSlider::groove { border: 1px solid #999999; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #E6E6E6, stop:1 #EEEEEE); } \
4368 QSlider::groove:disabled { background: #efefef; } \
4369 QSlider::handle { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #7cb0e9, stop:1 #7cb0e9); border: 1px solid #5c5c5c; \
4370 border-radius: 3px; width: %dpx; height: 45px; } \
4371 QSlider::handle:disabled { background: #D3D0CD;}",
4372 slider_handle_width);
4373
4374 qtStyleSheetSlider.append(sb);
4375
4376 return qtStyleSheetSlider;
4377}
4378
4379void prepareAndroidStyleSheets() {
4380 // Create and fix up the qtStyleSheetDialog for generic dialog
4381 qtStyleSheetDialog.clear();
4382 qtStyleSheetDialog.append(qtStyleSheetDialogProto);
4383
4384 // add the Slider specification
4385
4386 int slider_handle_width = g_Platform->GetDisplayDPmm() * 6;
4387
4388 char sb[400];
4389 snprintf(
4390 sb, sizeof(sb),
4391 "QSlider::groove { border: 1px solid #999999; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #E6E6E6, stop:1 #EEEEEE); } \
4392 QSlider::groove:disabled { background: #efefef; } \
4393 QSlider::handle { background: qlineargradient(x1:0, y1:0, x2:1, y2:1, stop:0 #7cb0e9, stop:1 #7cb0e9); border: 1px solid #5c5c5c; \
4394 border-radius: 3px; width: %dpx; height: 45px; } \
4395 QSlider::handle:disabled { background: #D3D0CD;}",
4396 slider_handle_width);
4397
4398 qtStyleSheetDialog.append(sb);
4399
4400 // add the checkbox specification
4401 int cbSize = 30 * getAndroidDisplayDensity();
4402 char cb[400];
4403
4404 // icons
4405 // Checked box
4406 std::string cbs = prepareStyleIcon(_T("chek_full.png"), cbSize);
4407 // Empty box
4408 std::string ucbs = prepareStyleIcon(_T("chek_empty.png"), cbSize);
4409
4410 snprintf(cb, sizeof(cb),
4411 "QCheckBox { spacing: 25px;}\
4412 QCheckBox::indicator { width: %dpx; height: %dpx;}\
4413 QCheckBox::indicator:checked {image: url(%s);}\
4414 QCheckBox::indicator:unchecked {image: url(%s);}",
4415 cbSize, cbSize, cbs.c_str(), ucbs.c_str());
4416
4417 qtStyleSheetDialog.append(cb);
4418
4419 // The qTabBar buttons as in a listbook
4420 qtStyleSheetListBook.clear();
4421
4422 // compute the tabbar button size
4423 int tbbSize = 50 * getAndroidDisplayDensity();
4424 char tbb[400];
4425
4426 std::string tbbl = prepareStyleIcon(_T("tabbar_button_left.png"), tbbSize);
4427 std::string tbbr = prepareStyleIcon(_T("tabbar_button_right.png"), tbbSize);
4428
4429 snprintf(tbb, sizeof(tbb),
4430 "QTabBar::scroller { width: %dpx; }\
4431 QTabBar QToolButton::right-arrow { image: url(%s); }\
4432 QTabBar QToolButton::left-arrow { image: url(%s); }",
4433 tbbSize, tbbr.c_str(), tbbl.c_str());
4434
4435 qtStyleSheetListBook.append(tbb);
4436
4437 // A simple stylesheet with scrollbars only
4438 qtStyleSheetScrollbars.clear();
4439 qtStyleSheetScrollbars.append(qtStyleSheetScrollbarsProto);
4440}
4441
4442void setChoiceStyleSheet(wxChoice *win, int refDim) {
4443 // qDebug() << "refDim" << refDim;
4444
4445 float fontDimFloat = ((float)refDim) * 0.5;
4446 int fontDim = (int)fontDimFloat;
4447 int pixRadius = refDim / 4;
4448
4449 QString styleString;
4450 char sb[1400];
4451
4452 // This one control the appearance of the "un-dropped" control.
4453 snprintf(sb, sizeof(sb),
4454 "QComboBox { font-size: %dpx; font-weight: bold; min-height: %dpx; "
4455 "color: rgb(0,0,0); background-color: rgb(250,250,250); }",
4456 fontDim, refDim);
4457 styleString.append(sb);
4458
4459 // This one controls the color and style of the drop list items
4460 snprintf(sb, sizeof(sb),
4461 "QComboBox QListView::item { color: rgb(0,0,0); background-color: "
4462 "rgb(95, 163, 237); }");
4463 styleString.append(sb);
4464
4465 // This one controls the drop list font
4466 snprintf(sb, sizeof(sb),
4467 "QComboBox QAbstractItemView { font-size: %dpx; font-weight: bold;}",
4468 fontDim);
4469 styleString.append(sb);
4470
4471 // This one is necessary to set min height of drop list items, otherwise they
4472 // are squished.
4473 snprintf(sb, sizeof(sb),
4474 "QComboBox QAbstractItemView::item { min-height: %dpx; border: "
4475 "10px outset darkgray; border-radius: %dpx; }",
4476 refDim, pixRadius);
4477 styleString.append(sb);
4478
4479 // qDebug() << styleString;
4480
4481 win->GetHandle()->setView(new QListView()); // Magic
4482 win->GetHandle()->setStyleSheet(styleString);
4483}
4484
4485void setMenuStyleSheet(wxMenu *win, const wxFont &font) {
4486 if (!win) return;
4487
4488 int points = font.GetPointSize();
4489 int fontPix = points / g_Platform->getFontPointsperPixel();
4490
4491 // qDebug() << points << g_Platform->getFontPointsperPixel() << fontPix;
4492
4493 QString styleString;
4494 char sb[1400];
4495
4496 snprintf(sb, sizeof(sb), "QMenu { font: bold %dpx; }", fontPix);
4497 styleString.append(sb);
4498
4499 snprintf(sb, sizeof(sb),
4500 "QMenu::separator { height: 4px; background: lightblue; "
4501 "margin-left: 10px; margin-right: 5px; }");
4502 styleString.append(sb);
4503
4504 // qDebug() << styleString;
4505
4506 win->GetHandle()->setStyleSheet(styleString);
4507}
4508
4509QString getAdjustedDialogStyleSheet() { return qtStyleSheetDialog; }
4510
4511QString getListBookStyleSheet() { return qtStyleSheetListBook; }
4512
4513QString getScrollBarsStyleSheet() { return qtStyleSheetScrollbars; }
4514
4515// SVG Support
4516wxBitmap loadAndroidSVG(const wxString filename, unsigned int width,
4517 unsigned int height) {
4518 wxCharBuffer abuf = filename.ToUTF8();
4519 if (abuf.data()) { // OK conversion?
4520 std::string s(abuf.data());
4521 // qDebug() << "loadAndroidSVG" << s.c_str();
4522 } else {
4523 qDebug() << "loadAndroidSVG FAIL";
4524 }
4525
4526 // Destination file location
4527 wxString save_file_dir =
4528 g_Platform->GetPrivateDataDir() + _T("/") + _T("icons");
4529 if (!wxDirExists(save_file_dir)) wxMkdir(save_file_dir);
4530
4531 wxFileName fsvg(filename);
4532 wxFileName fn(save_file_dir + _T("/") + fsvg.GetFullName());
4533 fn.SetExt(_T("png"));
4534
4535 /*
4536 //Caching does not work well, since we always build each icon twice.
4537 if(fn.FileExists()){
4538 wxBitmap bmp_test(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
4539 if(bmp_test.IsOk()){
4540 if((bmp_test.GetWidth() == (int)width) && (bmp_test.GetHeight() ==
4541 (int)height)) return bmp_test;
4542 }
4543 }
4544 */
4545
4546 wxString val = callActivityMethod_s2s2i("buildSVGIcon", filename,
4547 fn.GetFullPath(), width, height);
4548 if (val == _T("OK")) {
4549 // qDebug() << "OK";
4550
4551 return wxBitmap(fn.GetFullPath(), wxBITMAP_TYPE_PNG);
4552 } else {
4553 return wxBitmap(width, height);
4554 }
4555}
4556
4557void androidTestCPP() { callActivityMethod_vs("callFromCpp"); }
4558
4559unsigned int androidColorPicker(unsigned int initialColor) {
4560 if (g_androidUtilHandler) {
4561 g_androidUtilHandler->m_eventTimer.Stop();
4562 g_androidUtilHandler->m_done = false;
4563
4564 wxString val = callActivityMethod_is("doColorPickerDialog", initialColor);
4565
4566 if (val == _T("OK")) {
4567 // qDebug() << "ResultOK, starting spin loop";
4568 g_androidUtilHandler->m_action = ACTION_COLORDIALOG_END;
4569 g_androidUtilHandler->m_eventTimer.Start(1000, wxTIMER_CONTINUOUS);
4570
4571 // Spin, waiting for result
4572 while (!g_androidUtilHandler->m_done) {
4573 wxMilliSleep(50);
4574 wxSafeYield(NULL, true);
4575 }
4576
4577 // qDebug() << "out of spin loop";
4578 g_androidUtilHandler->m_action = ACTION_NONE;
4579 g_androidUtilHandler->m_eventTimer.Stop();
4580
4581 wxString tresult = g_androidUtilHandler->GetStringResult();
4582
4583 if (tresult.StartsWith(_T("cancel:"))) {
4584 // qDebug() << "Cancel1";
4585 return initialColor;
4586 } else if (tresult.StartsWith(_T("color:"))) {
4587 wxString color = tresult.AfterFirst(':');
4588 long a;
4589 color.ToLong(&a);
4590 unsigned int b = a;
4591
4592 // char cc[30];
4593 // sprintf(cc, "%0X", b);
4594 // qDebug() << "OK " << cc;
4595
4596 return b;
4597 }
4598 } else {
4599 qDebug() << "Result NOT OK";
4600 }
4601 }
4602 return 0;
4603}
4604
4605bool AndroidSecureCopyFile(wxString in, wxString out) {
4606 bool bret = true;
4607
4608 wxString result = callActivityMethod_s2s("SecureFileCopy", in, out);
4609
4610 if (wxNOT_FOUND == result.Find(_T("OK"))) bret = false;
4611
4612 return bret;
4613}
4614
4615int doAndroidPersistState() {
4616 qDebug() << "doAndroidPersistState() starting...";
4617 wxLogMessage(_T("doAndroidPersistState() starting..."));
4618
4619 // We save perspective before closing to restore position next time
4620 // Pane is not closed so the child is not notified (OnPaneClose)
4621 if (g_pauimgr) {
4622 if (g_pAISTargetList) {
4623 wxAuiPaneInfo &pane = g_pauimgr->GetPane(g_pAISTargetList);
4624 g_AisTargetList_perspective = g_pauimgr->SavePaneInfo(pane);
4625 g_pauimgr->DetachPane(g_pAISTargetList);
4626
4627 pConfig->SetPath(_T ( "/AUI" ));
4628 pConfig->Write(_T ( "AUIPerspective" ), g_pauimgr->SavePerspective());
4629 }
4630 }
4631
4632 // Deactivate the PlugIns, allowing them to save state
4633 PluginLoader::getInstance()->DeactivateAllPlugIns();
4634
4635 /*
4636 Automatically drop an anchorage waypoint, if enabled
4637 On following conditions:
4638 1. In "Cruising" mode, meaning that speed has at some point exceeded 3.0
4639 kts.
4640 2. Current speed is less than 0.5 kts.
4641 3. Opencpn has been up at least 30 minutes
4642 4. And, of course, opencpn is going down now.
4643 5. And if there is no anchor watch set on "anchor..." icon mark //
4644 pjotrc 2010.02.15
4645 */
4646 if (g_bAutoAnchorMark) {
4647 bool watching_anchor = false; // pjotrc 2010.02.15
4648 if (pAnchorWatchPoint1) // pjotrc 2010.02.15
4649 watching_anchor = (pAnchorWatchPoint1->GetIconName().StartsWith(
4650 _T("anchor"))); // pjotrc 2010.02.15
4651 if (pAnchorWatchPoint2) // pjotrc 2010.02.15
4652 watching_anchor |= (pAnchorWatchPoint2->GetIconName().StartsWith(
4653 _T("anchor"))); // pjotrc 2010.02.15
4654
4655 wxDateTime now = wxDateTime::Now();
4656 wxTimeSpan uptime = now.Subtract(g_start_time);
4657
4658 if (!watching_anchor && (g_bCruising) && (gSog < 0.5) &&
4659 (uptime.IsLongerThan(wxTimeSpan(0, 30, 0, 0)))) // pjotrc 2010.02.15
4660 {
4661 // First, delete any single anchorage waypoint closer than 0.25 NM from
4662 // this point This will prevent clutter and database congestion....
4663
4664 wxRoutePointListNode *node = pWayPointMan->GetWaypointList()->GetFirst();
4665 while (node) {
4666 RoutePoint *pr = node->GetData();
4667 if (pr->GetName().StartsWith(_T("Anchorage"))) {
4668 double a = gLat - pr->m_lat;
4669 double b = gLon - pr->m_lon;
4670 double l = sqrt((a * a) + (b * b));
4671
4672 // caveat: this is accurate only on the Equator
4673 if ((l * 60. * 1852.) < (.25 * 1852.)) {
4674 pConfig->DeleteWayPoint(pr);
4675 pSelect->DeleteSelectablePoint(pr, SELTYPE_ROUTEPOINT);
4676 delete pr;
4677 break;
4678 }
4679 }
4680
4681 node = node->GetNext();
4682 }
4683
4684 wxString name = now.Format();
4685 name.Prepend(_("Anchorage created "));
4686 RoutePoint *pWP =
4687 new RoutePoint(gLat, gLon, _T("anchorage"), name, _T(""));
4688 pWP->m_bShowName = false;
4689 pWP->m_bIsolatedMark = true;
4690
4691 pConfig->AddNewWayPoint(pWP, -1); // use auto next num
4692 }
4693 }
4694
4695 if (gFrame->GetPrimaryCanvas()->GetpCurrentStack()) {
4696 g_restore_stackindex =
4697 gFrame->GetPrimaryCanvas()->GetpCurrentStack()->CurrentStackEntry;
4698 g_restore_dbindex = gFrame->GetPrimaryCanvas()
4699 ->GetpCurrentStack()
4700 ->GetCurrentEntrydbIndex();
4701 if (gFrame->GetPrimaryCanvas() &&
4702 gFrame->GetPrimaryCanvas()->GetQuiltMode())
4703 g_restore_dbindex =
4704 gFrame->GetPrimaryCanvas()->GetQuiltReferenceChartIndex();
4705 }
4706
4707 if (g_MainToolbar) {
4708 wxPoint tbp = g_MainToolbar->GetPosition();
4709 wxPoint tbp_incanvas = gFrame->GetPrimaryCanvas()->ScreenToClient(tbp);
4710 g_maintoolbar_x = tbp_incanvas.x;
4711 g_maintoolbar_y = tbp_incanvas.y;
4712 g_maintoolbar_orient = g_MainToolbar->GetOrient();
4713 }
4714
4715 if (g_iENCToolbar) {
4716 wxPoint locn = g_iENCToolbar->GetPosition();
4717 wxPoint tbp_incanvas = gFrame->GetPrimaryCanvas()->ScreenToClient(locn);
4718 g_iENCToolbarPosY = tbp_incanvas.y;
4719 g_iENCToolbarPosX = tbp_incanvas.x;
4720 }
4721
4722 pConfig->UpdateSettings();
4723 pConfig->UpdateNavObj();
4724
4725 pConfig->m_pNavObjectChangesSet->reset();
4726
4727 // Remove any leftover Routes and Waypoints from config file as they were
4728 // saved to navobj before
4729 pConfig->DeleteGroup(_T ( "/Routes" ));
4730 pConfig->DeleteGroup(_T ( "/Marks" ));
4731 pConfig->Flush();
4732
4733 delete pConfig; // All done
4734 pConfig = NULL;
4735 InitBaseConfig(0);
4736
4737 // Unload the PlugIns
4738 // Note that we are waiting until after the canvas is destroyed,
4739 // since some PlugIns may have created children of canvas.
4740 // Such a PlugIn must stay intact for the canvas dtor to call
4741 // DestoryChildren()
4742
4743 if (ChartData) ChartData->PurgeCachePlugins();
4744
4745 PluginLoader::getInstance()->UnLoadAllPlugIns();
4746 if (g_pi_manager) {
4747 delete g_pi_manager;
4748 g_pi_manager = NULL;
4749 }
4750
4751 wxLogMessage(_T("doAndroidPersistState() finished cleanly."));
4752 qDebug() << "doAndroidPersistState() finished cleanly.";
4753
4754 wxLogMessage(_T("Closing logfile, Terminating App."));
4755
4756 wxLog::FlushActive();
4757 g_Platform->CloseLogFile();
4758
4759 return 0;
4760}
4761
4762extern "C" {
4763JNIEXPORT int JNICALL
4764Java_org_opencpn_OCPNNativeLib_ScheduleCleanExit(JNIEnv *env, jobject obj) {
4765 qDebug() << "Java_org_opencpn_OCPNNativeLib_ScheduleCleanExit";
4766 wxCommandEvent evt(wxEVT_COMMAND_MENU_SELECTED);
4767 evt.SetId(SCHEDULED_EVENT_CLEAN_EXIT);
4768 if (g_androidUtilHandler) {
4769 g_androidUtilHandler->AddPendingEvent(evt);
4770 }
4771
4772 return 1;
4773}
4774}
4775
4776void CheckMigrateCharts()
4777{
4778 qDebug() << "CheckMigrateCharts";
4779 if (g_Android_SDK_Version < 30) // Only on Android/11 +
4780 return;
4781
4782 // Force access to correct home directory, as a hint....
4783 pInit_Chart_Dir->Clear();
4784
4785 // Scan the config file chart directory array.
4786 wxArrayString chartDirs = GetConfigChartDirectories(); //GetChartDirArrayString();
4787 wxArrayString migrateDirs;
4788 qDebug() << chartDirs.GetCount();
4789
4790 for (unsigned int i=0; i < chartDirs.GetCount(); i++){
4791 qDebug() << chartDirs[i].mb_str();
4792
4793 bool bOK = false;
4794 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs0) )
4795 bOK = true;
4796
4797 else if (!g_androidGetFilesDirs1.StartsWith("?")){
4798 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs1) )
4799 bOK = true;
4800 }
4801 if (!bOK) {
4802 migrateDirs.Add(chartDirs[i]);
4803 }
4804 }
4805
4806 if (!migrateDirs.GetCount())
4807 return;
4808
4809
4810 // Run the chart migration assistant
4811 g_migrateDialog = new MigrateAssistantDialog(gFrame, false);
4812 g_migrateDialog->SetSize( gFrame->GetSize());
4813 g_migrateDialog->Centre();
4814 g_migrateDialog->Raise();
4815 g_migrateDialog->ShowModal();
4816
4817
4818}
4819
4820wxString androidGetDownloadDirectory()
4821{
4822 return g_androidDownloadDirectory;
4823}
4824
4825
4826
4827wxString WrapText(wxWindow *win, const wxString& text, int widthMax)
4828{
4829 class HardBreakWrapper : public wxTextWrapper
4830 {
4831 public:
4832 HardBreakWrapper(wxWindow *win, const wxString& text, int widthMax)
4833 {
4834 Wrap(win, text, widthMax);
4835 }
4836 wxString const& GetWrapped() const { return m_wrapped; }
4837 protected:
4838 virtual void OnOutputLine(const wxString& line)
4839 {
4840 m_wrapped += line;
4841 }
4842 virtual void OnNewLine()
4843 {
4844 m_wrapped += '\n';
4845 }
4846 private:
4847 wxString m_wrapped;
4848 };
4849 HardBreakWrapper wrapper(win, text, widthMax);
4850 return wrapper.GetWrapped();
4851}
4852
4856
4857
4858BEGIN_EVENT_TABLE(MigrateAssistantDialog, wxDialog)
4859EVT_BUTTON(ID_MIGRATE_CANCEL, MigrateAssistantDialog::OnMigrateCancelClick)
4860EVT_BUTTON(ID_MIGRATE_OK, MigrateAssistantDialog::OnMigrateOKClick)
4861EVT_BUTTON(ID_MIGRATE_START, MigrateAssistantDialog::OnMigrateClick)
4862EVT_BUTTON(ID_MIGRATE_CONTINUE, MigrateAssistantDialog::OnMigrate1Click)
4863EVT_TIMER(MIGRATION_STATUS_TIMER, MigrateAssistantDialog::onTimerEvent)
4864END_EVENT_TABLE()
4865
4866MigrateAssistantDialog::MigrateAssistantDialog(wxWindow* parent, bool bskipScan,
4867 wxWindowID id, const wxString& caption,
4868 const wxPoint& pos, const wxSize& size,
4869 long style)
4870 : wxDialog(parent, id, caption, pos, size,
4871 wxDEFAULT_DIALOG_STYLE | wxRESIZE_BORDER)
4872{
4873 m_Status = "";
4874 m_permissionResult = "";
4875 m_bsdcard = false;
4876 m_radioSDCard = 0;
4877 m_bskipScan = bskipScan;
4878
4879 m_statusTimer.SetOwner(this, MIGRATION_STATUS_TIMER);
4880
4881 wxFont *qFont = OCPNGetFont(_("Dialog"), 10);
4882 SetFont(*qFont);
4883
4884 CreateControls();
4885 GetSizer()->SetSizeHints(this);
4886 Centre();
4887}
4888
4889MigrateAssistantDialog::~MigrateAssistantDialog(void) {
4890
4891}
4892
4893
4894void MigrateAssistantDialog::CreateControls(void) {
4895 wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL);
4896 SetSizer(mainSizer);
4897
4898 wxStaticBox* mmsiBox =
4899 new wxStaticBox(this, wxID_ANY, _("OpenCPN for Android Migration Assistant"));
4900
4901 wxStaticBoxSizer* infoSizer = new wxStaticBoxSizer(mmsiBox, wxVERTICAL);
4902 mainSizer->Add(infoSizer, 0, wxEXPAND | wxALL, 5);
4903 m_infoText = NULL;
4904
4905 if (!m_bskipScan){
4906 // Scan the chart directory array from the config file.
4907 wxArrayString chartDirs = GetConfigChartDirectories();
4908
4909 for (unsigned int i=0; i < chartDirs.GetCount(); i++){
4910 bool bOK = false;
4911 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs0) )
4912 bOK = true;
4913
4914 else if (!g_androidGetFilesDirs1.StartsWith("?")){
4915 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs1) )
4916 bOK = true;
4917 }
4918 if (!bOK) {
4919 m_migrateDirs.Add(chartDirs[i]);
4920 }
4921 }
4922 }
4923
4924 if (m_migrateDirs.GetCount()){
4925 wxString infoText1(_("OpenCPN has detected chart folders in your configuration file that cannot be accessed on this version of Android"));
4926
4927 wxString infoText1w = WrapText(this, infoText1, gFrame->GetSize().x * 95 / 100);
4928
4929 m_infoText = new wxStaticText(this, wxID_STATIC, infoText1w);
4930
4931 infoSizer->AddSpacer( 1 * GetCharWidth());
4932 infoSizer->Add(m_infoText, 0, wxALIGN_LEFT | wxLEFT | wxRIGHT | wxTOP, 10);
4933 infoSizer->AddSpacer( 1 * GetCharWidth());
4934
4935 wxString dirsMsg;
4936
4937 for (unsigned int i=0; i < m_migrateDirs.GetCount(); i++){
4938 dirsMsg += wxString(" ");
4939 dirsMsg += m_migrateDirs[i];
4940 dirsMsg += wxString("\n");
4941 }
4942 //dirsMsg += wxString("\n");
4943
4944 m_infoDirs = new wxStaticText(this, wxID_STATIC, dirsMsg);
4945
4946 infoSizer->Add(m_infoDirs, 0, wxALIGN_LEFT | wxLEFT | wxRIGHT | wxTOP, 10);
4947
4948 wxString migrateMsg1 = _("OpenCPN can copy these chart folders to a suitable location, if desired.");
4949 migrateMsg1 += "\n\n";
4950 migrateMsg1 += _("To proceed with chart folder migration, choose the chart source folder, and follow the instructions given.");
4951
4952 wxString migrateMsg1w = WrapText(this, migrateMsg1, gFrame->GetSize().x * 95/ 100);
4953
4954 m_migrateStep1 = new wxStaticText(this, wxID_STATIC, migrateMsg1w);
4955 infoSizer->Add(m_migrateStep1, 0, wxALIGN_LEFT | wxLEFT | wxRIGHT | wxTOP, 10);
4956
4957 }
4958 else {
4959
4960 wxString migrateMsg1 = _("Some chart folders may be inaccessible to OpenCPN on this version of Android. ");
4961 migrateMsg1 += _("OpenCPN can copy these chart folders to a suitable location, if desired.");
4962 migrateMsg1 += "\n\n";
4963 migrateMsg1 += _("To proceed with chart folder migration, choose the chart source folder, and follow the instructions given.");
4964
4965 wxString migrateMsg1w = WrapText(this, migrateMsg1, gFrame->GetSize().x * 9 / 10);
4966
4967 m_migrateStep1 = new wxStaticText(this, wxID_STATIC, migrateMsg1w);
4968 infoSizer->Add(m_migrateStep1, 0, wxALIGN_LEFT | wxLEFT | wxRIGHT | wxTOP, 10);
4969 }
4970
4971 mainSizer->AddSpacer( 1 * GetCharWidth());
4972
4973 // Is SDCard available?
4974 if (!g_androidGetFilesDirs1.StartsWith("?")){
4975
4976 wxStaticBoxSizer* sourceSizer = new wxStaticBoxSizer(
4977 new wxStaticBox(this, wxID_ANY, _("Migrate destination")), wxVERTICAL);
4978 mainSizer->Add(sourceSizer, 0, wxEXPAND | wxALL, 5);
4979 mainSizer->AddSpacer( 2 * GetCharWidth());
4980
4981 m_radioInternal = new wxRadioButton (this, wxID_ANY, _("OpenCPN Internal Storage"),wxDefaultPosition, wxDefaultSize, wxRB_GROUP);
4982 sourceSizer->Add( m_radioInternal, 0, /*wxEXPAND |*/ wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
4983
4984 m_radioSDCard = new wxRadioButton (this, wxID_ANY, _("OpenCPN SDCard Storage"),wxDefaultPosition, wxDefaultSize);
4985 sourceSizer->Add( m_radioSDCard, 0, /*wxEXPAND |*/ wxALL | wxALIGN_CENTER_HORIZONTAL, 5);
4986
4987 m_radioInternal->SetValue( true );
4988 }
4989
4990
4991 // control buttons
4992 m_migrateButton = new wxButton(this, ID_MIGRATE_START, _("Choose chart source folder."));
4993 mainSizer->Add(m_migrateButton, 0, wxEXPAND | wxALL, 5);
4994
4995 //mainSizer->AddSpacer( 1 * GetCharWidth());
4996
4997 statusSizer = new wxStaticBoxSizer(
4998 new wxStaticBox(this, wxID_ANY, _("Status")), wxVERTICAL);
4999 mainSizer->Add(statusSizer, 0, wxEXPAND | wxALL, 5);
5000
5001
5002 m_ipGauge = new InProgressIndicator(this, wxID_ANY, 100, wxDefaultPosition,
5003 wxSize(gFrame->GetSize().x * 8 / 10, gFrame->GetCharHeight() * 2));
5004 statusSizer->Add(m_ipGauge, 0, wxALL|wxALIGN_CENTER_HORIZONTAL, 5);
5005
5006 mainSizer->AddSpacer( 1 * GetCharWidth());
5007
5008 m_statusText = new wxStaticText(this, wxID_STATIC, m_Status);
5009 statusSizer->Add(m_statusText, 0, wxEXPAND | wxALL, 5);
5010
5011 GetSizer()->Hide(statusSizer);
5012
5013 wxBoxSizer* btnSizer = new wxBoxSizer(wxHORIZONTAL);
5014 mainSizer->Add(btnSizer, 0, wxALIGN_RIGHT | wxALL, 5);
5015 m_CancelButton = new wxButton(this, ID_MIGRATE_CANCEL, _("Cancel"));
5016 m_CancelButton->SetDefault();
5017 btnSizer->Add(m_CancelButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
5018
5019 m_OKButton = new wxButton(this, ID_MIGRATE_OK, _("OK"));
5020 btnSizer->Add(m_OKButton, 0, wxALIGN_CENTER_VERTICAL | wxALL, 5);
5021 m_OKButton->Hide();
5022
5023}
5024
5025
5026void MigrateAssistantDialog::OnMigrateCancelClick(wxCommandEvent& event) {
5027
5028 m_statusTimer.Stop();
5029 callActivityMethod_vs("cancelMigration");
5030
5031 EndModal(wxID_CANCEL);
5032}
5033
5034void MigrateAssistantDialog::OnMigrateOKClick(wxCommandEvent& event) {
5035 EndModal(wxID_OK);
5036}
5037
5038void MigrateAssistantDialog::OnMigrateClick(wxCommandEvent& event)
5039{
5040
5041 wxString clickText1(_("On the next page, find and choose the root folder containing chart files to migrate\n\n\
5042Example: /storage/emulated/0/Charts\n\n"));
5043 clickText1 += _("This entire folder will be migrated.\n");
5044 clickText1 += _("Proceed?");
5045
5046 if (wxID_OK == OCPNMessageBox(
5047 NULL, clickText1, _("OpenCPN for Android Migration Assistant"), wxOK | wxCANCEL )){
5048
5049 if(m_infoText) m_infoText->Hide();
5050 m_migrateStep1->Hide();
5051 GetSizer()->Show(statusSizer);
5052 Layout();
5053
5054 if (g_androidUtilHandler) {
5055
5056 m_Status = _("Waiting for permission grant....");
5057 setStatus( m_Status );
5058
5059 g_androidUtilHandler->m_eventTimer.Stop();
5060 g_androidUtilHandler->m_migratePermissionSetDone = false;
5061
5062 wxString activityResult;
5063 activityResult = callActivityMethod_vs("migrateSetup");
5064
5065 if (activityResult == _T("OK")) {
5066 // qDebug() << "Migrate Result1 OK, enabling timer wait";
5067 g_androidUtilHandler->m_action = ACTION_SAF_PERMISSION_END;
5068 g_androidUtilHandler->m_eventTimer.Start(1000, wxTIMER_CONTINUOUS);
5069 }
5070 }
5071 }
5072}
5073
5074void MigrateAssistantDialog::OnMigrate1Click(wxCommandEvent& event) {
5075
5076 // Construct the migration arguments
5077
5078 //Destination is either internal, or SDCard, if available
5079 if( !m_bsdcard )
5080 m_migrateDestinationFolder = g_androidGetFilesDirs0 + "/Charts";
5081 else
5082 m_migrateDestinationFolder = g_androidGetFilesDirs1 + "/Charts";
5083
5084 qDebug() << "m_migrateSourceFolder" << m_migrateSourceFolder.mb_str();
5085 qDebug() << "m_migrateDestinationFolder" << m_migrateDestinationFolder.mb_str();
5086
5087 wxString activityResult;
5088 activityResult = callActivityMethod_s2s("migrateFolder", m_migrateSourceFolder, m_migrateDestinationFolder);
5089
5090 m_Status = _("Migration started...");
5091 setStatus( m_Status );
5092
5093 m_statusTimer.Start(500, wxTIMER_CONTINUOUS);
5094 m_ipGauge->Show();
5095 Layout();
5096
5097
5098}
5099
5100void MigrateAssistantDialog::onPermissionGranted( wxString result ) {
5101 m_permissionResult = result;
5102 qDebug() << "onPermissionGranted " << result.mb_str();
5103 if(result.StartsWith("file")){
5104 m_migrateSourceFolder = result.Mid(5);
5105
5106 m_Status = _("Permission granted to ");
5107 m_Status += m_migrateSourceFolder;
5108 setStatus( m_Status );
5109
5110 // Carry on to the next step
5111 m_migrateButton->Hide();
5112 Layout();
5113
5114 // Capture the destination
5115 if(m_radioSDCard)
5116 m_bsdcard = m_radioSDCard->GetValue();
5117
5118 wxString clickText2(_("OpenCPN has obtained temporary permission to access the selected chart folders."));
5119 clickText2 += "\n\n";
5120 clickText2 += _("Chart migration is ready to proceed.");
5121 clickText2 += "\n\n";
5122 clickText2 += _("Source: ");
5123 clickText2 += m_migrateSourceFolder;
5124 clickText2 += "\n\n";
5125 if(!m_bsdcard)
5126 clickText2 += _("Destination: OpenCPN Internal Storage");
5127 else
5128 clickText2 += _("Destination: OpenCPN SDCard Storage");
5129 clickText2 += "\n\n";
5130 clickText2 += _("Migrate charts now?");
5131
5132
5133
5134 if (wxID_OK == OCPNMessageBox(
5135 NULL, clickText2, _("OpenCPN for Android Migration Assistant"), wxOK | wxCANCEL )){
5136
5137 wxCommandEvent evt(wxEVT_BUTTON);
5138 evt.SetId(ID_MIGRATE_CONTINUE);
5139 AddPendingEvent(evt);
5140 }
5141 else{
5142 m_Status = "";
5143 setStatus( m_Status );
5144
5145 m_migrateButton->Show();
5146 Layout();
5147 }
5148 }
5149 else{
5150 m_Status = "";
5151 setStatus( m_Status );
5152 }
5153
5154}
5155
5156void MigrateAssistantDialog::onTimerEvent(wxTimerEvent &event)
5157{
5158 // Get and show the current status from Java upstream
5159 qDebug() << "Migration: onTimerEvent";
5160
5161 m_Status = callActivityMethod_vs("getMigrateStatus");
5162 setStatus( m_Status );
5163
5164 if (m_Status.StartsWith("Counting"))
5165 m_ipGauge->Pulse();
5166
5167 if (m_Status.StartsWith("Migrating")){
5168 wxString prog = m_Status.Mid(10);
5169 //qDebug() << prog.mb_str();
5170 wxString np = prog.BeforeFirst('/');
5171 //qDebug() << np.mb_str();
5172 wxString np1 = prog.AfterFirst('/');
5173 wxString np2 = np1.BeforeFirst(';');
5174 //qDebug() << np2.mb_str();
5175
5176 long i, n;
5177 np.ToLong(&i);
5178 np2.ToLong(&n);
5179 if (m_ipGauge->GetRange() != n)
5180 m_ipGauge->SetRange( n );
5181 m_ipGauge->SetValue( i );
5182 }
5183
5184
5185
5186
5187 // Finished?
5188 if (m_Status.Contains("Migration complete")){
5189 m_statusTimer.Stop();
5190
5191 wxString clickText3(_("Chart migration is finished."));
5192 clickText3 += "\n\n";
5193 clickText3 += _("Migrated chart folders are now accessible to OpenCPN.");
5194 clickText3 += "\n";
5195 clickText3 += _("You may need to adjust your chart folders further, to accommodate individual chart groups");
5196 clickText3 += "\n\n";
5197 clickText3 += _("OpenCPN will now restart to apply changes.");
5198
5199 if (wxID_OK == OCPNMessageBox(
5200 NULL, clickText3, _("OpenCPN for Android Migration Assistant"), wxOK )){
5201
5202 FinishMigration();
5203
5204 }
5205 }
5206
5207}
5208
5209wxArrayString GetConfigChartDirectories()
5210{
5211 wxArrayString rv;
5212 pConfig->SetPath(_T ( "/ChartDirectories" ));
5213 int iDirMax = pConfig->GetNumberOfEntries();
5214 if (iDirMax) {
5215 wxString str, val;
5216 long dummy;
5217 bool bCont = pConfig->GetFirstEntry(str, dummy);
5218 while (bCont) {
5219 pConfig->Read(str, &val); // Get a Directory name
5220 rv.Add(val.BeforeFirst('^'));
5221 bCont = pConfig->GetNextEntry(str, dummy);
5222
5223 }
5224 }
5225
5226 return rv;
5227}
5228
5229
5230void MigrateAssistantDialog::FinishMigration()
5231{
5232 m_Status = _("Finishing migration");
5233 setStatus( m_Status );
5234
5235 // Craft the migrated (destination) folder
5236
5237 qDebug() << "m_migrateSourceFolder " << m_migrateSourceFolder.mb_str();
5238 qDebug() << "m_migrateDestinationFolder " << m_migrateDestinationFolder.mb_str();
5239
5240
5241
5242 // Edit the config file, removing old inaccessible folders,
5243 // and adding migrated folders.
5244
5245 wxArrayString finalArray;
5246 wxArrayString chartDirs = GetConfigChartDirectories(); //ChartData->GetChartDirArrayString();
5247 for (unsigned int i=0; i < chartDirs.GetCount(); i++){
5248
5249 //qDebug() << "Checking: " << chartDirs[i].mb_str();
5250
5251 // Leave the OK folders
5252 bool bOK = false;
5253 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs0) )
5254 bOK = true;
5255
5256 else if (!g_androidGetFilesDirs1.StartsWith("?")){
5257 if ( chartDirs[i].StartsWith(g_androidGetFilesDirs1) )
5258 bOK = true;
5259 }
5260
5261 // Check inaccessible folders to see if they were (part of) the migration
5262 if (!bOK) {
5263 if(!chartDirs[i].StartsWith(m_migrateSourceFolder)) // not part of migration
5264 bOK = true; // so, keep it.
5265 // To be migrated on next round
5266 }
5267
5268 if(bOK){
5269 //qDebug() << "Add: " << chartDirs[i].mb_str();
5270 finalArray.Add(chartDirs[i]);
5271 }
5272
5273 }
5274
5275 finalArray.Add(m_migrateDestinationFolder + "/MigratedCharts");
5276
5277#if 0
5278 // Now manage the migrate folder
5279 // OCPN works faster if the chart dirs are at fine granularity
5280 // This is due to the expense of traversing a very deep directory tree.
5281 // If the migrated directory contains only subdirectories, and no files,
5282 // then add the subdirs to the chart dir array.
5283
5284 wxString migratedFolder = m_migrateDestinationFolder + "/MigratedCharts/";
5285
5286 // If the migrate source is a shallow(single dir) copy,
5287 // and it happens that the full path contains a folder that is already in the destination tree:
5288 // Example: source: /storage/xxxx-yyyy/Charts/ENC/R7
5289 // destination" {internal}/files/Charts/MigratedCharts/Charts
5290 // If destination already contains .../ENC
5291 // then the migration would have merged the current contents.
5292 // In this case, we need to determine the actual migrated folder
5293
5294 wxFileName fn(m_migrateSourceFolder);
5295 migratedFolder += fn.GetName();
5296 qDebug() << "migratedFolder " << migratedFolder.mb_str();
5297
5298 wxDir migratedDir(migratedFolder + "/");
5299 if (migratedDir.HasFiles()){
5300 qDebug() << "Add A";
5301 finalArray.Add(migratedFolder);
5302 }
5303 else {
5304 qDebug() << "Add CSD";
5305
5306 if (migratedDir.HasSubDirs())
5307 {
5308 qDebug() << "Add SD";
5309 wxArrayString children;
5310 DIR *dir;
5311 struct dirent *ent;
5312 if ((dir = opendir (migratedFolder.c_str())) != NULL) {
5313 while ((ent = readdir (dir)) != NULL) {
5314 wxString sent(ent->d_name);
5315 qDebug() << "sent: " << sent.mb_str();
5316 if (!sent.StartsWith('.')){
5317 wxString dirToAdd = migratedFolder + "/" + sent;
5318 children.Add( wxString(dirToAdd));
5319 }
5320 }
5321 closedir (dir);
5322 }
5323
5324 for (unsigned int j=0 ; j < children.GetCount() ; j++){
5325 qDebug() << "Child: " << children[j].mb_str();
5326 if(wxFileName::DirExists(children[j])){
5327 qDebug() << "ChildDir: " << children[j].mb_str();
5328 qDebug() << "Add B";
5329 finalArray.Add(children[j]);
5330 }
5331 }
5332 }
5333 }
5334
5335#endif
5336 for (unsigned int j=0 ; j < finalArray.GetCount() ; j++){
5337 qDebug() << "finalEntry: " << finalArray[j].mb_str();
5338 }
5339
5340
5341 // Now delete and replace the chart directory list in the config file
5342 wxRemoveFile(ChartListFileName);
5343
5344 pConfig->SetPath(_T ( "/ChartDirectories" ));
5345 pConfig->DeleteGroup(_T ( "/ChartDirectories" ));
5346
5347 pConfig->SetPath(_T ( "/ChartDirectories" ));
5348 for (int iDir = 0; iDir < finalArray.GetCount(); iDir++) {
5349 wxString dirn = finalArray[iDir];
5350 dirn.Append(_T("^"));
5351
5352 wxString str_buf;
5353 str_buf.Printf(_T ( "ChartDir%d" ), iDir + 1);
5354 pConfig->Write(str_buf, dirn);
5355 }
5356 pConfig->Flush();
5357
5358 // Restart
5359 callActivityMethod_vs("restartOCPNAfterMigrate");
5360
5361}
5362
5363 BEGIN_EVENT_TABLE( InProgressIndicator, wxGauge )
5364 EVT_TIMER( 4356, InProgressIndicator::OnTimer )
5365 END_EVENT_TABLE()
5366
5368 {
5369 }
5370
5371 InProgressIndicator::InProgressIndicator(wxWindow* parent, wxWindowID id, int range,
5372 const wxPoint& pos, const wxSize& size,
5373 long style, const wxValidator& validator, const wxString& name)
5374{
5375 wxGauge::Create(parent, id, range, pos, size, style, validator, name);
5376
5377 m_timer.SetOwner( this, 4356 );
5378
5379 SetValue(0);
5380 m_bAlive = false;
5381
5382}
5383
5384InProgressIndicator::~InProgressIndicator()
5385{
5386 Stop();
5387}
5388
5389void InProgressIndicator::OnTimer(wxTimerEvent &evt)
5390{
5391 if(m_bAlive)
5392 Pulse();
5393}
5394
5395
5396void InProgressIndicator::Start()
5397{
5398 m_bAlive = true;
5399 m_timer.Start( 50 );
5400
5401}
5402
5403void InProgressIndicator::Stop()
5404{
5405 m_bAlive = false;
5406 SetValue(0);
5407 m_timer.Stop();
5408
5409}
5410