1 /*
2  *  Copyright (C) 2012-2018 Team Kodi
3  *  This file is part of Kodi - https://kodi.tv
4  *
5  *  SPDX-License-Identifier: GPL-2.0-or-later
6  *  See LICENSES/README.md for more information.
7  */
8 
9 #include "XBMCApp.h"
10 
11 #include <sstream>
12 #include <unistd.h>
13 #include <stdlib.h>
14 #include <dlfcn.h>
15 #include <string.h>
16 
17 #include <jni.h>
18 #include <android/configuration.h>
19 #include <android/bitmap.h>
20 #include <android/log.h>
21 #include <android/native_window.h>
22 #include <android/native_window_jni.h>
23 
24 #include <androidjni/ActivityManager.h>
25 #include <androidjni/ApplicationInfo.h>
26 #include <androidjni/BitmapFactory.h>
27 #include <androidjni/BroadcastReceiver.h>
28 #include <androidjni/Build.h>
29 #include <androidjni/CharSequence.h>
30 #include <androidjni/ConnectivityManager.h>
31 #include <androidjni/ContentResolver.h>
32 #include <androidjni/Context.h>
33 #include <androidjni/Cursor.h>
34 #include <androidjni/Display.h>
35 #include <androidjni/DisplayManager.h>
36 #include <androidjni/Environment.h>
37 #include <androidjni/File.h>
38 #include <androidjni/Intent.h>
39 #include <androidjni/IntentFilter.h>
40 #include <androidjni/JNIThreading.h>
41 #include <androidjni/KeyEvent.h>
42 #include <androidjni/MediaStore.h>
43 #include <androidjni/NetworkInfo.h>
44 #include <androidjni/PackageManager.h>
45 #include <androidjni/PowerManager.h>
46 #include <androidjni/StatFs.h>
47 #include <androidjni/System.h>
48 #include <androidjni/SystemClock.h>
49 #include <androidjni/SystemProperties.h>
50 #include <androidjni/URI.h>
51 #include <androidjni/View.h>
52 #include <androidjni/WakeLock.h>
53 #include <androidjni/Window.h>
54 #include <androidjni/WindowManager.h>
55 
56 #include "AndroidKey.h"
57 #include "settings/AdvancedSettings.h"
58 #include "interfaces/AnnouncementManager.h"
59 #include "Application.h"
60 #include "AppParamParser.h"
61 #include "messaging/ApplicationMessenger.h"
62 #include "CompileInfo.h"
63 #include "settings/DisplaySettings.h"
64 #include "windowing/GraphicContext.h"
65 #include "guilib/GUIWindowManager.h"
66 // Audio Engine includes for Factory and interfaces
67 #include "GUIInfoManager.h"
68 #include "ServiceBroker.h"
69 #include "TextureCache.h"
70 #include "cores/AudioEngine/AESinkFactory.h"
71 #include "cores/AudioEngine/Interfaces/AE.h"
72 #include "cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h"
73 #include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
74 #include "filesystem/SpecialProtocol.h"
75 #include "filesystem/VideoDatabaseFile.h"
76 #include "guilib/GUIComponent.h"
77 #include "guilib/guiinfo/GUIInfoLabels.h"
78 #include "input/Key.h"
79 #include "input/mouse/MouseStat.h"
80 #include "platform/xbmc.h"
81 #include "powermanagement/PowerManager.h"
82 #include "utils/StringUtils.h"
83 #include "utils/TimeUtils.h"
84 #include "utils/URIUtils.h"
85 #include "utils/Variant.h"
86 #include "utils/log.h"
87 #include "windowing/WinEvents.h"
88 #include "windowing/android/VideoSyncAndroid.h"
89 #include "windowing/android/WinSystemAndroid.h"
90 
91 #include "platform/android/activity/IInputDeviceCallbacks.h"
92 #include "platform/android/activity/IInputDeviceEventHandler.h"
93 #include "platform/android/network/NetworkAndroid.h"
94 #include "platform/android/powermanagement/AndroidPowerSyscall.h"
95 
96 #define GIGABYTES       1073741824
97 
98 #define ACTION_XBMC_RESUME "android.intent.XBMC_RESUME"
99 
100 #define PLAYBACK_STATE_STOPPED  0x0000
101 #define PLAYBACK_STATE_PLAYING  0x0001
102 #define PLAYBACK_STATE_VIDEO    0x0100
103 #define PLAYBACK_STATE_AUDIO    0x0200
104 #define PLAYBACK_STATE_CANNOT_PAUSE 0x0400
105 
106 using namespace KODI::MESSAGING;
107 using namespace ANNOUNCEMENT;
108 using namespace jni;
109 
110 template<class T, void(T::*fn)()>
thread_run(void * obj)111 void* thread_run(void* obj)
112 {
113   (static_cast<T*>(obj)->*fn)();
114   return NULL;
115 }
116 
117 CXBMCApp* CXBMCApp::m_xbmcappinstance = NULL;
118 std::unique_ptr<CJNIXBMCMainView> CXBMCApp::m_mainView;
119 ANativeActivity *CXBMCApp::m_activity = NULL;
120 CJNIWakeLock *CXBMCApp::m_wakeLock = NULL;
121 ANativeWindow* CXBMCApp::m_window = NULL;
122 int CXBMCApp::m_batteryLevel = 0;
123 bool CXBMCApp::m_hasFocus = false;
124 bool CXBMCApp::m_headsetPlugged = false;
125 bool CXBMCApp::m_hdmiPlugged = true;
126 bool CXBMCApp::m_hdmiSource = false;
127 IInputDeviceCallbacks* CXBMCApp::m_inputDeviceCallbacks = nullptr;
128 IInputDeviceEventHandler* CXBMCApp::m_inputDeviceEventHandler = nullptr;
129 bool CXBMCApp::m_hasReqVisible = false;
130 CCriticalSection CXBMCApp::m_applicationsMutex;
131 CCriticalSection CXBMCApp::m_activityResultMutex;
132 std::vector<androidPackage> CXBMCApp::m_applications;
133 CVideoSyncAndroid* CXBMCApp::m_syncImpl = NULL;
134 CEvent CXBMCApp::m_vsyncEvent;
135 CEvent CXBMCApp::m_displayChangeEvent;
136 std::vector<CActivityResultEvent*> CXBMCApp::m_activityResultEvents;
137 
138 int64_t CXBMCApp::m_frameTimeNanos = 0;
139 float CXBMCApp::m_refreshRate = 0.0f;
140 
141 uint32_t CXBMCApp::m_playback_state = PLAYBACK_STATE_STOPPED;
142 
CXBMCApp(ANativeActivity * nativeActivity,IInputHandler & inputHandler)143 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity, IInputHandler& inputHandler)
144   : CJNIMainActivity(nativeActivity),
145     CJNIBroadcastReceiver(CJNIContext::getPackageName() + ".XBMCBroadcastReceiver"),
146     m_inputHandler(inputHandler),
147     m_videosurfaceInUse(false)
148 {
149   m_xbmcappinstance = this;
150   m_activity = nativeActivity;
151   if (m_activity == NULL)
152   {
153     android_printf("CXBMCApp: invalid ANativeActivity instance");
154     exit(1);
155     return;
156   }
157   m_mainView.reset(new CJNIXBMCMainView(this));
158   m_firstrun = true;
159   m_exiting = false;
160   m_hdmiSource = CJNISystemProperties::get("ro.hdmi.device_type", "") == "4";
161   android_printf("CXBMCApp: Created");
162 }
163 
~CXBMCApp()164 CXBMCApp::~CXBMCApp()
165 {
166   m_xbmcappinstance = NULL;
167   delete m_wakeLock;
168 }
169 
Announce(ANNOUNCEMENT::AnnouncementFlag flag,const std::string & sender,const std::string & message,const CVariant & data)170 void CXBMCApp::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
171                         const std::string& sender,
172                         const std::string& message,
173                         const CVariant& data)
174 {
175   if (sender != CAnnouncementManager::ANNOUNCEMENT_SENDER)
176     return;
177 
178   if (flag & Input)
179   {
180     if (message == "OnInputRequested")
181       CAndroidKey::SetHandleSearchKeys(true);
182     else if (message == "OnInputFinished")
183       CAndroidKey::SetHandleSearchKeys(false);
184   }
185   else if (flag & Player)
186   {
187     if (message == "OnPlay" || message == "OnResume")
188       OnPlayBackStarted();
189     else if (message == "OnPause")
190       OnPlayBackPaused();
191     else if (message == "OnStop")
192       OnPlayBackStopped();
193     else if (message == "OnSeek")
194       UpdateSessionState();
195     else if (message == "OnSpeedChanged")
196       UpdateSessionState();
197   }
198   else if (flag & Info)
199   {
200     if (message == "OnChanged")
201       UpdateSessionMetadata();
202   }
203 }
204 
onStart()205 void CXBMCApp::onStart()
206 {
207   android_printf("%s: ", __PRETTY_FUNCTION__);
208 
209   if (m_firstrun)
210   {
211     // Register sink
212     AE::CAESinkFactory::ClearSinks();
213     CAESinkAUDIOTRACK::Register();
214     pthread_attr_t attr;
215     pthread_attr_init(&attr);
216     pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
217     pthread_create(&m_thread, &attr, thread_run<CXBMCApp, &CXBMCApp::run>, this);
218     pthread_attr_destroy(&attr);
219 
220     // Some intent filters MUST be registered in code rather than through the manifest
221     CJNIIntentFilter intentFilter;
222     intentFilter.addAction("android.intent.action.BATTERY_CHANGED");
223     intentFilter.addAction("android.intent.action.SCREEN_ON");
224     intentFilter.addAction("android.intent.action.HEADSET_PLUG");
225     // We currently use HDMI_AUDIO_PLUG for mode switch, don't use it on TV's (device_type = "0"
226     if (m_hdmiSource)
227       intentFilter.addAction("android.media.action.HDMI_AUDIO_PLUG");
228 
229     intentFilter.addAction("android.intent.action.SCREEN_OFF");
230     intentFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
231     registerReceiver(*this, intentFilter);
232     m_mediaSession.reset(new CJNIXBMCMediaSession());
233   }
234 }
235 
onResume()236 void CXBMCApp::onResume()
237 {
238   android_printf("%s: ", __PRETTY_FUNCTION__);
239 
240   if (g_application.IsInitialized() && CServiceBroker::GetWinSystem()->GetOSScreenSaver()->IsInhibited())
241     EnableWakeLock(true);
242 
243   CJNIAudioManager audioManager(getSystemService("audio"));
244   m_headsetPlugged = audioManager.isWiredHeadsetOn() || audioManager.isBluetoothA2dpOn();
245 
246   // Clear the applications cache. We could have installed/deinstalled apps
247   {
248     CSingleLock lock(m_applicationsMutex);
249     m_applications.clear();
250   }
251 
252   if (m_bResumePlayback && g_application.GetAppPlayer().IsPlaying())
253   {
254     if (g_application.GetAppPlayer().HasVideo())
255     {
256       if (g_application.GetAppPlayer().IsPaused())
257         CApplicationMessenger::GetInstance().SendMsg(
258             TMSG_GUI_ACTION, WINDOW_INVALID, -1,
259             static_cast<void*>(new CAction(ACTION_PLAYER_PLAY)));
260     }
261   }
262 
263   // Re-request Visible Behind
264   if ((m_playback_state & PLAYBACK_STATE_PLAYING) && (m_playback_state & PLAYBACK_STATE_VIDEO))
265     RequestVisibleBehind(true);
266 }
267 
onPause()268 void CXBMCApp::onPause()
269 {
270   android_printf("%s: ", __PRETTY_FUNCTION__);
271   m_bResumePlayback = false;
272 
273   if (g_application.GetAppPlayer().IsPlaying())
274   {
275     if (g_application.GetAppPlayer().HasVideo())
276     {
277       if (!g_application.GetAppPlayer().IsPaused() && !m_hasReqVisible)
278       {
279         CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
280         m_bResumePlayback = true;
281       }
282     }
283   }
284 
285   if (m_hasReqVisible)
286     g_application.SwitchToFullScreen(true);
287 
288   EnableWakeLock(false);
289   m_hasReqVisible = false;
290 }
291 
onStop()292 void CXBMCApp::onStop()
293 {
294   android_printf("%s: ", __PRETTY_FUNCTION__);
295 
296   if ((m_playback_state & PLAYBACK_STATE_PLAYING) && !m_hasReqVisible)
297   {
298     if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
299       CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP)));
300     else if (m_playback_state & PLAYBACK_STATE_VIDEO)
301       CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
302   }
303 }
304 
onDestroy()305 void CXBMCApp::onDestroy()
306 {
307   android_printf("%s", __PRETTY_FUNCTION__);
308 
309   unregisterReceiver(*this);
310 
311   m_mediaSession.release();
312 
313   // If android is forcing us to stop, ask XBMC to exit then wait until it's
314   // been destroyed.
315   if (!m_exiting)
316   {
317     XBMC_Stop();
318     pthread_join(m_thread, NULL);
319     android_printf(" => XBMC finished");
320   }
321 }
322 
onSaveState(void ** data,size_t * size)323 void CXBMCApp::onSaveState(void **data, size_t *size)
324 {
325   android_printf("%s: ", __PRETTY_FUNCTION__);
326   // no need to save anything as XBMC is running in its own thread
327 }
328 
onConfigurationChanged()329 void CXBMCApp::onConfigurationChanged()
330 {
331   android_printf("%s: ", __PRETTY_FUNCTION__);
332   // ignore any configuration changes like screen rotation etc
333 }
334 
onLowMemory()335 void CXBMCApp::onLowMemory()
336 {
337   android_printf("%s: ", __PRETTY_FUNCTION__);
338   // can't do much as we don't want to close completely
339 }
340 
onCreateWindow(ANativeWindow * window)341 void CXBMCApp::onCreateWindow(ANativeWindow* window)
342 {
343   android_printf("%s: ", __PRETTY_FUNCTION__);
344 }
345 
onResizeWindow()346 void CXBMCApp::onResizeWindow()
347 {
348   android_printf("%s: ", __PRETTY_FUNCTION__);
349   m_window = NULL;
350   // no need to do anything because we are fixed in fullscreen landscape mode
351 }
352 
onDestroyWindow()353 void CXBMCApp::onDestroyWindow()
354 {
355   android_printf("%s: ", __PRETTY_FUNCTION__);
356 }
357 
onGainFocus()358 void CXBMCApp::onGainFocus()
359 {
360   android_printf("%s: ", __PRETTY_FUNCTION__);
361   m_hasFocus = true;
362   g_application.WakeUpScreenSaverAndDPMS();
363 }
364 
onLostFocus()365 void CXBMCApp::onLostFocus()
366 {
367   android_printf("%s: ", __PRETTY_FUNCTION__);
368   m_hasFocus = false;
369 }
370 
RegisterDisplayListener(CVariant * variant)371 void CXBMCApp::RegisterDisplayListener(CVariant* variant)
372 {
373   CJNIDisplayManager displayManager(getSystemService("display"));
374   if (displayManager)
375   {
376     android_printf("CXBMCApp: installing DisplayManager::DisplayListener");
377     displayManager.registerDisplayListener(CXBMCApp::get()->getDisplayListener());
378   }
379 }
380 
Initialize()381 void CXBMCApp::Initialize()
382 {
383   CServiceBroker::GetAnnouncementManager()->AddAnnouncer(CXBMCApp::get());
384   runNativeOnUiThread(RegisterDisplayListener, nullptr);
385   m_activityManager.reset(new CJNIActivityManager(getSystemService(CJNIContext::ACTIVITY_SERVICE)));
386   m_inputHandler.setDPI(GetDPI());
387 }
388 
Deinitialize()389 void CXBMCApp::Deinitialize()
390 {
391 }
392 
EnableWakeLock(bool on)393 bool CXBMCApp::EnableWakeLock(bool on)
394 {
395   android_printf("%s: %s", __PRETTY_FUNCTION__, on ? "true" : "false");
396   if (!m_wakeLock)
397   {
398     std::string appName = CCompileInfo::GetAppName();
399     StringUtils::ToLower(appName);
400     std::string className = CCompileInfo::GetPackage();
401     // SCREEN_BRIGHT_WAKE_LOCK is marked as deprecated but there is no real alternatives for now
402     m_wakeLock = new CJNIWakeLock(CJNIPowerManager(getSystemService("power")).newWakeLock(CJNIPowerManager::SCREEN_BRIGHT_WAKE_LOCK | CJNIPowerManager::ON_AFTER_RELEASE, className.c_str()));
403     if (m_wakeLock)
404       m_wakeLock->setReferenceCounted(false);
405     else
406       return false;
407   }
408 
409   if (on)
410   {
411     if (!m_wakeLock->isHeld())
412       m_wakeLock->acquire();
413   }
414   else
415   {
416     if (m_wakeLock->isHeld())
417       m_wakeLock->release();
418   }
419 
420   return true;
421 }
422 
AcquireAudioFocus()423 bool CXBMCApp::AcquireAudioFocus()
424 {
425   if (!m_xbmcappinstance)
426     return false;
427 
428   CJNIAudioManager audioManager(getSystemService("audio"));
429 
430   // Request audio focus for playback
431   int result = audioManager.requestAudioFocus(m_audioFocusListener,
432                                               // Use the music stream.
433                                               CJNIAudioManager::STREAM_MUSIC,
434                                               // Request permanent focus.
435                                               CJNIAudioManager::AUDIOFOCUS_GAIN);
436 
437   if (result != CJNIAudioManager::AUDIOFOCUS_REQUEST_GRANTED)
438   {
439     CXBMCApp::android_printf("Audio Focus request failed");
440     return false;
441   }
442   return true;
443 }
444 
ReleaseAudioFocus()445 bool CXBMCApp::ReleaseAudioFocus()
446 {
447   if (!m_xbmcappinstance)
448     return false;
449 
450   CJNIAudioManager audioManager(getSystemService("audio"));
451 
452   // Release audio focus after playback
453   int result = audioManager.abandonAudioFocus(m_audioFocusListener);
454   if (result != CJNIAudioManager::AUDIOFOCUS_REQUEST_GRANTED)
455   {
456     CXBMCApp::android_printf("Audio Focus abandon failed");
457     return false;
458   }
459   return true;
460 }
461 
RequestVisibleBehind(bool requested)462 void CXBMCApp::RequestVisibleBehind(bool requested)
463 {
464   if (requested == m_hasReqVisible)
465     return;
466 
467   m_hasReqVisible = requestVisibleBehind(requested);
468   CLog::Log(LOGDEBUG, "Visible Behind request: %s", m_hasReqVisible ? "true" : "false");
469 }
470 
IsHeadsetPlugged()471 bool CXBMCApp::IsHeadsetPlugged()
472 {
473   return m_headsetPlugged;
474 }
475 
IsHDMIPlugged()476 bool CXBMCApp::IsHDMIPlugged()
477 {
478   return m_hdmiPlugged;
479 }
480 
run()481 void CXBMCApp::run()
482 {
483   int status = 0;
484 
485   SetupEnv();
486 
487   // Wait for main window
488   ANativeWindow* nativeWindow = CXBMCApp::GetNativeWindow(30000);
489   if (!nativeWindow)
490     return;
491 
492   m_firstrun=false;
493   android_printf(" => running XBMC_Run...");
494 
495   CAppParamParser appParamParser;
496   status = XBMC_Run(true, appParamParser);
497   android_printf(" => XBMC_Run finished with %d", status);
498 
499   // If we are have not been force by Android to exit, notify its finish routine.
500   // This will cause android to run through its teardown events, it calls:
501   // onPause(), onLostFocus(), onDestroyWindow(), onStop(), onDestroy().
502   ANativeActivity_finish(m_activity);
503   m_exiting=true;
504 }
505 
XBMC_Pause(bool pause)506 void CXBMCApp::XBMC_Pause(bool pause)
507 {
508   android_printf("XBMC_Pause(%s)", pause ? "true" : "false");
509 }
510 
XBMC_Stop()511 void CXBMCApp::XBMC_Stop()
512 {
513   CApplicationMessenger::GetInstance().PostMsg(TMSG_QUIT);
514 }
515 
XBMC_SetupDisplay()516 bool CXBMCApp::XBMC_SetupDisplay()
517 {
518   android_printf("XBMC_SetupDisplay()");
519   bool result;
520   CApplicationMessenger::GetInstance().SendMsg(TMSG_DISPLAY_SETUP, -1, -1, static_cast<void*>(&result));
521   return result;
522 }
523 
XBMC_DestroyDisplay()524 bool CXBMCApp::XBMC_DestroyDisplay()
525 {
526   android_printf("XBMC_DestroyDisplay()");
527   bool result;
528   CApplicationMessenger::GetInstance().SendMsg(TMSG_DISPLAY_DESTROY, -1, -1, static_cast<void*>(&result));
529   return result;
530 }
531 
SetBuffersGeometry(int width,int height,int format)532 int CXBMCApp::SetBuffersGeometry(int width, int height, int format)
533 {
534   return ANativeWindow_setBuffersGeometry(m_window, width, height, format);
535 }
536 
537 #include "threads/Event.h"
538 #include <time.h>
539 
SetRefreshRateCallback(CVariant * rateVariant)540 void CXBMCApp::SetRefreshRateCallback(CVariant* rateVariant)
541 {
542   float rate = rateVariant->asFloat();
543   delete rateVariant;
544 
545   CJNIWindow window = getWindow();
546   if (window)
547   {
548     CJNIWindowManagerLayoutParams params = window.getAttributes();
549     if (fabs(params.getpreferredRefreshRate() - rate) > 0.001)
550     {
551       params.setpreferredRefreshRate(rate);
552       if (params.getpreferredRefreshRate() > 0.0)
553       {
554         window.setAttributes(params);
555         return;
556       }
557     }
558   }
559   m_displayChangeEvent.Set();
560 }
561 
SetDisplayModeCallback(CVariant * variant)562 void CXBMCApp::SetDisplayModeCallback(CVariant* variant)
563 {
564   int mode = (*variant)["mode"].asInteger();
565   float rate = (*variant)["rate"].asFloat();
566   delete variant;
567 
568   CJNIWindow window = getWindow();
569   if (window)
570   {
571     CJNIWindowManagerLayoutParams params = window.getAttributes();
572     if (params.getpreferredDisplayModeId() != mode)
573     {
574       params.setpreferredDisplayModeId(mode);
575       params.setpreferredRefreshRate(rate);
576       window.setAttributes(params);
577       return;
578     }
579   }
580   m_displayChangeEvent.Set();
581 }
582 
SetRefreshRate(float rate)583 void CXBMCApp::SetRefreshRate(float rate)
584 {
585   if (rate < 1.0)
586     return;
587 
588   CJNIWindow window = getWindow();
589   if (window)
590   {
591     CJNIWindowManagerLayoutParams params = window.getAttributes();
592     if (fabs(params.getpreferredRefreshRate() - rate) <= 0.001)
593       return;
594   }
595 
596   m_refreshRate = rate;
597 
598   m_displayChangeEvent.Reset();
599   CVariant *variant = new CVariant(rate);
600   runNativeOnUiThread(SetRefreshRateCallback, variant);
601   if (g_application.IsInitialized())
602   {
603     m_displayChangeEvent.WaitMSec(5000);
604     if (m_hdmiSource && g_application.GetAppPlayer().IsPlaying())
605       dynamic_cast<CWinSystemAndroid*>(CServiceBroker::GetWinSystem())->InitiateModeChange();
606   }
607 }
608 
SetDisplayMode(int mode,float rate)609 void CXBMCApp::SetDisplayMode(int mode, float rate)
610 {
611   if (mode < 1.0)
612     return;
613 
614   CJNIWindow window = getWindow();
615   if (window)
616   {
617     CJNIWindowManagerLayoutParams params = window.getAttributes();
618     if (params.getpreferredDisplayModeId() == mode)
619       return;
620   }
621 
622   m_displayChangeEvent.Reset();
623   std::map<std::string, CVariant> vmap;
624   vmap["mode"] = mode;
625   vmap["rate"] = rate;
626   m_refreshRate = rate;
627   CVariant *variant = new CVariant(vmap);
628   runNativeOnUiThread(SetDisplayModeCallback, variant);
629   if (g_application.IsInitialized())
630   {
631     m_displayChangeEvent.WaitMSec(5000);
632     if (m_hdmiSource && g_application.GetAppPlayer().IsPlaying())
633       dynamic_cast<CWinSystemAndroid*>(CServiceBroker::GetWinSystem())->InitiateModeChange();
634   }
635 }
636 
android_printf(const char * format,...)637 int CXBMCApp::android_printf(const char *format, ...)
638 {
639   // For use before CLog is setup by XBMC_Run()
640   va_list args;
641   va_start(args, format);
642   int result = __android_log_vprint(ANDROID_LOG_VERBOSE, "Kodi", format, args);
643   va_end(args);
644   return result;
645 }
646 
GetDPI()647 int CXBMCApp::GetDPI()
648 {
649   if (m_activity == NULL || m_activity->assetManager == NULL)
650     return 0;
651 
652   // grab DPI from the current configuration - this is approximate
653   // but should be close enough for what we need
654   AConfiguration *config = AConfiguration_new();
655   AConfiguration_fromAssetManager(config, m_activity->assetManager);
656   int dpi = AConfiguration_getDensity(config);
657   AConfiguration_delete(config);
658 
659   return dpi;
660 }
661 
MapRenderToDroid(const CRect & srcRect)662 CRect CXBMCApp::MapRenderToDroid(const CRect& srcRect)
663 {
664   float scaleX = 1.0;
665   float scaleY = 1.0;
666 
667   CJNIRect r = m_xbmcappinstance->getDisplayRect();
668   if (r.width() && r.height())
669   {
670     RESOLUTION_INFO renderRes = CDisplaySettings::GetInstance().GetResolutionInfo(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
671     scaleX = (double)r.width() / renderRes.iWidth;
672     scaleY = (double)r.height() / renderRes.iHeight;
673   }
674 
675   return CRect(srcRect.x1 * scaleX, srcRect.y1 * scaleY, srcRect.x2 * scaleX, srcRect.y2 * scaleY);
676 }
677 
UpdateSessionMetadata()678 void CXBMCApp::UpdateSessionMetadata()
679 {
680   CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
681   CJNIMediaMetadataBuilder builder;
682   builder
683       .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_TITLE, infoMgr.GetLabel(PLAYER_TITLE))
684       .putString(CJNIMediaMetadata::METADATA_KEY_TITLE, infoMgr.GetLabel(PLAYER_TITLE))
685       .putLong(CJNIMediaMetadata::METADATA_KEY_DURATION, g_application.GetAppPlayer().GetTotalTime())
686 //      .putString(CJNIMediaMetadata::METADATA_KEY_ART_URI, thumb)
687 //      .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_ICON_URI, thumb)
688 //      .putString(CJNIMediaMetadata::METADATA_KEY_ALBUM_ART_URI, thumb)
689       ;
690 
691   std::string thumb;
692   if (m_playback_state & PLAYBACK_STATE_VIDEO)
693   {
694     builder
695         .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_SUBTITLE, infoMgr.GetLabel(VIDEOPLAYER_TAGLINE))
696         .putString(CJNIMediaMetadata::METADATA_KEY_ARTIST, infoMgr.GetLabel(VIDEOPLAYER_DIRECTOR))
697         ;
698     thumb = infoMgr.GetImage(VIDEOPLAYER_COVER, -1);
699   }
700   else if (m_playback_state & PLAYBACK_STATE_AUDIO)
701   {
702     builder
703         .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_SUBTITLE, infoMgr.GetLabel(MUSICPLAYER_ARTIST))
704         .putString(CJNIMediaMetadata::METADATA_KEY_ARTIST, infoMgr.GetLabel(MUSICPLAYER_ARTIST))
705         ;
706     thumb = infoMgr.GetImage(MUSICPLAYER_COVER, -1);
707   }
708   bool needrecaching = false;
709   std::string cachefile = CTextureCache::GetInstance().CheckCachedImage(thumb, needrecaching);
710   if (!cachefile.empty())
711   {
712     std::string actualfile = CSpecialProtocol::TranslatePath(cachefile);
713     CJNIBitmap bmp = CJNIBitmapFactory::decodeFile(actualfile);
714     if (bmp)
715       builder.putBitmap(CJNIMediaMetadata::METADATA_KEY_ART, bmp);
716   }
717   m_mediaSession->updateMetadata(builder.build());
718 }
719 
UpdateSessionState()720 void CXBMCApp::UpdateSessionState()
721 {
722   CJNIPlaybackStateBuilder builder;
723   int state = CJNIPlaybackState::STATE_NONE;
724   int64_t pos = 0;
725   float speed = 0.0;
726   if (m_playback_state != PLAYBACK_STATE_STOPPED)
727   {
728     if (g_application.GetAppPlayer().HasVideo())
729       m_playback_state |= PLAYBACK_STATE_VIDEO;
730     else
731       m_playback_state &= ~PLAYBACK_STATE_VIDEO;
732     if (g_application.GetAppPlayer().HasAudio())
733       m_playback_state |= PLAYBACK_STATE_AUDIO;
734     else
735       m_playback_state &= ~PLAYBACK_STATE_AUDIO;
736     pos = g_application.GetAppPlayer().GetTime();
737     speed = g_application.GetAppPlayer().GetPlaySpeed();
738     if (m_playback_state & PLAYBACK_STATE_PLAYING)
739       state = CJNIPlaybackState::STATE_PLAYING;
740     else
741       state = CJNIPlaybackState::STATE_PAUSED;
742   }
743   else
744     state = CJNIPlaybackState::STATE_STOPPED;
745   builder
746       .setState(state, pos, speed, CJNISystemClock::elapsedRealtime())
747       .setActions(0xffffffffffffffff)
748       ;
749   m_mediaSession->updatePlaybackState(builder.build());
750 }
751 
OnPlayBackStarted()752 void CXBMCApp::OnPlayBackStarted()
753 {
754   CLog::Log(LOGDEBUG, "%s", __PRETTY_FUNCTION__);
755 
756   m_playback_state = PLAYBACK_STATE_PLAYING;
757   if (g_application.GetAppPlayer().HasVideo())
758     m_playback_state |= PLAYBACK_STATE_VIDEO;
759   if (g_application.GetAppPlayer().HasAudio())
760     m_playback_state |= PLAYBACK_STATE_AUDIO;
761   if (!g_application.GetAppPlayer().CanPause())
762     m_playback_state |= PLAYBACK_STATE_CANNOT_PAUSE;
763 
764   m_mediaSession->activate(true);
765   UpdateSessionState();
766 
767   CJNIIntent intent(ACTION_XBMC_RESUME, CJNIURI::EMPTY, *this, get_class(CJNIContext::get_raw()));
768   m_mediaSession->updateIntent(intent);
769 
770   m_xbmcappinstance->AcquireAudioFocus();
771   CAndroidKey::SetHandleMediaKeys(false);
772 
773   RequestVisibleBehind(true);
774 }
775 
OnPlayBackPaused()776 void CXBMCApp::OnPlayBackPaused()
777 {
778   CLog::Log(LOGDEBUG, "%s", __PRETTY_FUNCTION__);
779 
780   m_playback_state &= ~PLAYBACK_STATE_PLAYING;
781   UpdateSessionState();
782 
783   RequestVisibleBehind(false);
784   m_xbmcappinstance->ReleaseAudioFocus();
785 }
786 
OnPlayBackStopped()787 void CXBMCApp::OnPlayBackStopped()
788 {
789   CLog::Log(LOGDEBUG, "%s", __PRETTY_FUNCTION__);
790 
791   m_playback_state = PLAYBACK_STATE_STOPPED;
792   UpdateSessionState();
793   m_mediaSession->activate(false);
794 
795   RequestVisibleBehind(false);
796   CAndroidKey::SetHandleMediaKeys(true);
797   m_xbmcappinstance->ReleaseAudioFocus();
798 }
799 
GetInputDevice(int deviceId)800 const CJNIViewInputDevice CXBMCApp::GetInputDevice(int deviceId)
801 {
802   CJNIInputManager inputManager(getSystemService("input"));
803   return inputManager.getInputDevice(deviceId);
804 }
805 
GetInputDeviceIds()806 std::vector<int> CXBMCApp::GetInputDeviceIds()
807 {
808   CJNIInputManager inputManager(getSystemService("input"));
809   return inputManager.getInputDeviceIds();
810 }
811 
ProcessSlow()812 void CXBMCApp::ProcessSlow()
813 {
814   if ((m_playback_state & PLAYBACK_STATE_PLAYING) && m_mediaSession->isActive())
815     UpdateSessionState();
816 }
817 
GetApplications()818 std::vector<androidPackage> CXBMCApp::GetApplications()
819 {
820   CSingleLock lock(m_applicationsMutex);
821   if (m_applications.empty())
822   {
823     CJNIList<CJNIApplicationInfo> packageList = GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
824     int numPackages = packageList.size();
825     for (int i = 0; i < numPackages; i++)
826     {
827       CJNIIntent intent = GetPackageManager().getLaunchIntentForPackage(packageList.get(i).packageName);
828       if (!intent && CJNIBuild::SDK_INT >= 21)
829         intent = GetPackageManager().getLeanbackLaunchIntentForPackage(packageList.get(i).packageName);
830       if (!intent)
831         continue;
832 
833       androidPackage newPackage;
834       newPackage.packageName = packageList.get(i).packageName;
835       newPackage.packageLabel = GetPackageManager().getApplicationLabel(packageList.get(i)).toString();
836       newPackage.icon = packageList.get(i).icon;
837       m_applications.push_back(newPackage);
838     }
839   }
840 
841   return m_applications;
842 }
843 
HasLaunchIntent(const std::string & package)844 bool CXBMCApp::HasLaunchIntent(const std::string &package)
845 {
846   return GetPackageManager().getLaunchIntentForPackage(package) != NULL;
847 }
848 
849 // Note intent, dataType, dataURI all default to ""
StartActivity(const std::string & package,const std::string & intent,const std::string & dataType,const std::string & dataURI)850 bool CXBMCApp::StartActivity(const std::string &package, const std::string &intent, const std::string &dataType, const std::string &dataURI)
851 {
852   CJNIIntent newIntent = intent.empty() ?
853     GetPackageManager().getLaunchIntentForPackage(package) :
854     CJNIIntent(intent);
855 
856   if (!newIntent && CJNIBuild::SDK_INT >= 21)
857     newIntent = GetPackageManager().getLeanbackLaunchIntentForPackage(package);
858   if (!newIntent)
859     return false;
860 
861   if (!dataURI.empty())
862   {
863     CJNIURI jniURI = CJNIURI::parse(dataURI);
864 
865     if (!jniURI)
866       return false;
867 
868     newIntent.setDataAndType(jniURI, dataType);
869   }
870 
871   newIntent.setPackage(package);
872   startActivity(newIntent);
873   if (xbmc_jnienv()->ExceptionCheck())
874   {
875     CLog::Log(LOGERROR, "CXBMCApp::StartActivity - ExceptionOccurred launching %s", package.c_str());
876     xbmc_jnienv()->ExceptionClear();
877     return false;
878   }
879 
880   return true;
881 }
882 
GetBatteryLevel()883 int CXBMCApp::GetBatteryLevel()
884 {
885   return m_batteryLevel;
886 }
887 
GetExternalStorage(std::string & path,const std::string & type)888 bool CXBMCApp::GetExternalStorage(std::string &path, const std::string &type /* = "" */)
889 {
890   std::string sType;
891   std::string mountedState;
892   bool mounted = false;
893 
894   if(type == "files" || type.empty())
895   {
896     CJNIFile external = CJNIEnvironment::getExternalStorageDirectory();
897     if (external)
898       path = external.getAbsolutePath();
899   }
900   else
901   {
902     if (type == "music")
903       sType = "Music"; // Environment.DIRECTORY_MUSIC
904     else if (type == "videos")
905       sType = "Movies"; // Environment.DIRECTORY_MOVIES
906     else if (type == "pictures")
907       sType = "Pictures"; // Environment.DIRECTORY_PICTURES
908     else if (type == "photos")
909       sType = "DCIM"; // Environment.DIRECTORY_DCIM
910     else if (type == "downloads")
911       sType = "Download"; // Environment.DIRECTORY_DOWNLOADS
912     if (!sType.empty())
913     {
914       CJNIFile external = CJNIEnvironment::getExternalStoragePublicDirectory(sType);
915       if (external)
916         path = external.getAbsolutePath();
917     }
918   }
919   mountedState = CJNIEnvironment::getExternalStorageState();
920   mounted = (mountedState == "mounted" || mountedState == "mounted_ro");
921   return mounted && !path.empty();
922 }
923 
GetStorageUsage(const std::string & path,std::string & usage)924 bool CXBMCApp::GetStorageUsage(const std::string &path, std::string &usage)
925 {
926 #define PATH_MAXLEN 50
927 
928   if (path.empty())
929   {
930     std::ostringstream fmt;
931     fmt.width(PATH_MAXLEN);  fmt << std::left  << "Filesystem";
932     fmt.width(12);  fmt << std::right << "Size";
933     fmt.width(12);  fmt << "Used";
934     fmt.width(12);  fmt << "Avail";
935     fmt.width(12);  fmt << "Use %";
936 
937     usage = fmt.str();
938     return false;
939   }
940 
941   CJNIStatFs fileStat(path);
942   int blockSize = fileStat.getBlockSize();
943   int blockCount = fileStat.getBlockCount();
944   int freeBlocks = fileStat.getFreeBlocks();
945 
946   if (blockSize <= 0 || blockCount <= 0 || freeBlocks < 0)
947     return false;
948 
949   float totalSize = (float)blockSize * blockCount / GIGABYTES;
950   float freeSize = (float)blockSize * freeBlocks / GIGABYTES;
951   float usedSize = totalSize - freeSize;
952   float usedPercentage = usedSize / totalSize * 100;
953 
954   std::ostringstream fmt;
955   fmt << std::fixed;
956   fmt.precision(1);
957   fmt.width(PATH_MAXLEN);  fmt << std::left  << (path.size() < PATH_MAXLEN-1 ? path : StringUtils::Left(path, PATH_MAXLEN-4) + "...");
958   fmt.width(12);  fmt << std::right << totalSize << "G"; // size in GB
959   fmt.width(12);  fmt << usedSize << "G"; // used in GB
960   fmt.width(12);  fmt << freeSize << "G"; // free
961   fmt.precision(0);
962   fmt.width(12);  fmt << usedPercentage << "%"; // percentage used
963 
964   usage = fmt.str();
965   return true;
966 }
967 
968 // Used in Application.cpp to figure out volume steps
GetMaxSystemVolume()969 int CXBMCApp::GetMaxSystemVolume()
970 {
971   JNIEnv* env = xbmc_jnienv();
972   static int maxVolume = -1;
973   if (maxVolume == -1)
974   {
975     maxVolume = GetMaxSystemVolume(env);
976   }
977   //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
978   return maxVolume;
979 }
980 
GetMaxSystemVolume(JNIEnv * env)981 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
982 {
983   CJNIAudioManager audioManager(getSystemService("audio"));
984   if (audioManager)
985     return audioManager.getStreamMaxVolume();
986   android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
987   return 0;
988 }
989 
GetSystemVolume()990 float CXBMCApp::GetSystemVolume()
991 {
992   CJNIAudioManager audioManager(getSystemService("audio"));
993   if (audioManager)
994     return (float)audioManager.getStreamVolume() / GetMaxSystemVolume();
995   else
996   {
997     android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
998     return 0;
999   }
1000 }
1001 
SetSystemVolume(float percent)1002 void CXBMCApp::SetSystemVolume(float percent)
1003 {
1004   CJNIAudioManager audioManager(getSystemService("audio"));
1005   int maxVolume = (int)(GetMaxSystemVolume() * percent);
1006   if (audioManager)
1007     audioManager.setStreamVolume(maxVolume);
1008   else
1009     android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
1010 }
1011 
onReceive(CJNIIntent intent)1012 void CXBMCApp::onReceive(CJNIIntent intent)
1013 {
1014   if (!g_application.IsInitialized())
1015     return;
1016 
1017   std::string action = intent.getAction();
1018   CLog::Log(LOGDEBUG, "CXBMCApp::onReceive - Got intent. Action: %s", action.c_str());
1019   if (action == "android.intent.action.BATTERY_CHANGED")
1020     m_batteryLevel = intent.getIntExtra("level",-1);
1021   else if (action == "android.intent.action.DREAMING_STOPPED")
1022   {
1023     if (HasFocus())
1024       g_application.WakeUpScreenSaverAndDPMS();
1025   }
1026   else if (action == "android.intent.action.HEADSET_PLUG" ||
1027     action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
1028   {
1029     bool newstate = m_headsetPlugged;
1030     if (action == "android.intent.action.HEADSET_PLUG")
1031     {
1032       newstate = (intent.getIntExtra("state", 0) != 0);
1033 
1034       // If unplugged headset and playing content then pause or stop playback
1035       if (!newstate && (m_playback_state & PLAYBACK_STATE_PLAYING))
1036       {
1037         if (g_application.GetAppPlayer().CanPause())
1038         {
1039           CApplicationMessenger::GetInstance().PostMsg(
1040               TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
1041         }
1042         else
1043         {
1044           CApplicationMessenger::GetInstance().PostMsg(
1045               TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP)));
1046         }
1047       }
1048     }
1049     else if (action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
1050       newstate = (intent.getIntExtra("android.bluetooth.profile.extra.STATE", 0) == 2 /* STATE_CONNECTED */);
1051 
1052     if (newstate != m_headsetPlugged)
1053     {
1054       m_headsetPlugged = newstate;
1055       IAE *iae = CServiceBroker::GetActiveAE();
1056       if (iae)
1057         iae->DeviceChange();
1058     }
1059   }
1060   else if (action == "android.media.action.HDMI_AUDIO_PLUG")
1061   {
1062     m_hdmiPlugged = (intent.getIntExtra("android.media.extra.AUDIO_PLUG_STATE", 0) != 0);
1063     CLog::Log(LOGDEBUG, "-- HDMI state: %s",  m_hdmiPlugged ? "on" : "off");
1064     if (m_hdmiSource && g_application.IsInitialized())
1065     {
1066       CWinSystemBase* winSystem = CServiceBroker::GetWinSystem();
1067       if (winSystem && dynamic_cast<CWinSystemAndroid*>(winSystem))
1068         dynamic_cast<CWinSystemAndroid*>(winSystem)->SetHdmiState(m_hdmiPlugged);
1069     }
1070   }
1071   else if (action == "android.intent.action.SCREEN_ON")
1072   {
1073     // Sent when the device wakes up and becomes interactive.
1074     //
1075     // For historical reasons, the name of this broadcast action refers to the power state of the
1076     // screen but it is actually sent in response to changes in the overall interactive state of
1077     // the device.
1078     IPowerSyscall* syscall = CServiceBroker::GetPowerManager().GetPowerSyscall();
1079     if (syscall)
1080     {
1081       CLog::Log(LOGINFO, "Got device wakeup intent");
1082       static_cast<CAndroidPowerSyscall*>(syscall)->SetResumed();
1083     }
1084 
1085     if (HasFocus())
1086       g_application.WakeUpScreenSaverAndDPMS();
1087   }
1088   else if (action == "android.intent.action.SCREEN_OFF")
1089   {
1090     // Sent when the device goes to sleep and becomes non-interactive.
1091     //
1092     // For historical reasons, the name of this broadcast action refers to the power state of the
1093     // screen but it is actually sent in response to changes in the overall interactive state of
1094     // the device.
1095     IPowerSyscall* syscall = CServiceBroker::GetPowerManager().GetPowerSyscall();
1096     if (syscall)
1097     {
1098       CLog::Log(LOGINFO, "Got device sleep intent");
1099       static_cast<CAndroidPowerSyscall*>(syscall)->SetSuspended();
1100     }
1101   }
1102   else if (action == "android.intent.action.MEDIA_BUTTON")
1103   {
1104     if (m_playback_state == PLAYBACK_STATE_STOPPED)
1105     {
1106       CLog::Log(LOGINFO, "Ignore MEDIA_BUTTON intent: no media playing");
1107       return;
1108     }
1109     CJNIKeyEvent keyevt = (CJNIKeyEvent)intent.getParcelableExtra(CJNIIntent::EXTRA_KEY_EVENT);
1110 
1111     int keycode = keyevt.getKeyCode();
1112     bool up = (keyevt.getAction() == CJNIKeyEvent::ACTION_UP);
1113 
1114     CLog::Log(LOGINFO, "Got MEDIA_BUTTON intent: %d, up:%s", keycode, up ? "true" : "false");
1115     if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_RECORD)
1116       CAndroidKey::XBMC_Key(keycode, XBMCK_RECORD, 0, 0, up);
1117     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_EJECT)
1118       CAndroidKey::XBMC_Key(keycode, XBMCK_EJECT, 0, 0, up);
1119     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_FAST_FORWARD)
1120       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_FASTFORWARD, 0, 0, up);
1121     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_NEXT)
1122       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_NEXT_TRACK, 0, 0, up);
1123     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PAUSE)
1124       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1125     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PLAY)
1126       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1127     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PLAY_PAUSE)
1128       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1129     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PREVIOUS)
1130       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PREV_TRACK, 0, 0, up);
1131     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_REWIND)
1132       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_REWIND, 0, 0, up);
1133     else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_STOP)
1134       CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_STOP, 0, 0, up);
1135   }
1136   else if (action == "android.net.conn.CONNECTIVITY_CHANGE")
1137   {
1138     if (g_application.IsInitialized())
1139     {
1140       CNetworkBase& net = CServiceBroker::GetNetwork();
1141       CNetworkAndroid* netdroid = static_cast<CNetworkAndroid*>(&net);
1142       netdroid->RetrieveInterfaces();
1143     }
1144   }
1145 }
1146 
onNewIntent(CJNIIntent intent)1147 void CXBMCApp::onNewIntent(CJNIIntent intent)
1148 {
1149   if (!intent)
1150   {
1151     CLog::Log(LOGINFO, "CXBMCApp::onNewIntent - Got invalid intent.");
1152     return;
1153   }
1154 
1155   std::string action = intent.getAction();
1156   CLog::Log(LOGDEBUG, "CXBMCApp::onNewIntent - Got intent. Action: %s", action.c_str());
1157   std::string targetFile = GetFilenameFromIntent(intent);
1158   if (!targetFile.empty() &&  (action == "android.intent.action.VIEW" || action == "android.intent.action.GET_CONTENT"))
1159   {
1160     CLog::Log(LOGDEBUG, "-- targetFile: %s", targetFile.c_str());
1161 
1162     CURL targeturl(targetFile);
1163     std::string value;
1164     if (action == "android.intent.action.GET_CONTENT" || (targeturl.GetOption("showinfo", value) && value == "true"))
1165     {
1166       if (targeturl.IsProtocol("videodb")
1167           || (targeturl.IsProtocol("special") && targetFile.find("playlists/video") != std::string::npos)
1168           || (targeturl.IsProtocol("special") && targetFile.find("playlists/mixed") != std::string::npos)
1169           )
1170       {
1171         std::vector<std::string> params;
1172         params.push_back(targeturl.Get());
1173         params.emplace_back("return");
1174         CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VIDEO_NAV, 0, nullptr, "", params);
1175       }
1176       else if (targeturl.IsProtocol("musicdb")
1177                || (targeturl.IsProtocol("special") && targetFile.find("playlists/music") != std::string::npos))
1178       {
1179         std::vector<std::string> params;
1180         params.push_back(targeturl.Get());
1181         params.emplace_back("return");
1182         CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTIVATE_WINDOW, WINDOW_MUSIC_NAV, 0, nullptr, "", params);
1183       }
1184     }
1185     else
1186     {
1187       CFileItem* item = new CFileItem(targetFile, false);
1188       if (item->IsVideoDb())
1189       {
1190         *(item->GetVideoInfoTag()) = XFILE::CVideoDatabaseFile::GetVideoTag(CURL(item->GetPath()));
1191         item->SetPath(item->GetVideoInfoTag()->m_strFileNameAndPath);
1192       }
1193       CApplicationMessenger::GetInstance().PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
1194     }
1195   }
1196   else if (action == ACTION_XBMC_RESUME)
1197   {
1198     if (m_playback_state != PLAYBACK_STATE_STOPPED)
1199     {
1200       if (m_playback_state & PLAYBACK_STATE_VIDEO)
1201         RequestVisibleBehind(true);
1202       if (!(m_playback_state & PLAYBACK_STATE_PLAYING))
1203         CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
1204     }
1205   }
1206 }
1207 
onActivityResult(int requestCode,int resultCode,CJNIIntent resultData)1208 void CXBMCApp::onActivityResult(int requestCode, int resultCode, CJNIIntent resultData)
1209 {
1210   CSingleLock lock(m_activityResultMutex);
1211   for (auto it = m_activityResultEvents.begin(); it != m_activityResultEvents.end(); ++it)
1212   {
1213     if ((*it)->GetRequestCode() == requestCode)
1214     {
1215       (*it)->SetResultCode(resultCode);
1216       (*it)->SetResultData(resultData);
1217       (*it)->Set();
1218       m_activityResultEvents.erase(it);
1219       break;
1220     }
1221   }
1222 }
1223 
onVisibleBehindCanceled()1224 void CXBMCApp::onVisibleBehindCanceled()
1225 {
1226   CLog::Log(LOGDEBUG, "Visible Behind Cancelled");
1227   m_hasReqVisible = false;
1228 
1229   // Pressing the pause button calls OnStop() (cf. https://code.google.com/p/android/issues/detail?id=186469)
1230   if ((m_playback_state & PLAYBACK_STATE_PLAYING))
1231   {
1232     if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
1233       CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP)));
1234     else if (m_playback_state & PLAYBACK_STATE_VIDEO)
1235       CApplicationMessenger::GetInstance().PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
1236   }
1237 }
1238 
WaitForActivityResult(const CJNIIntent & intent,int requestCode,CJNIIntent & result)1239 int CXBMCApp::WaitForActivityResult(const CJNIIntent &intent, int requestCode, CJNIIntent &result)
1240 {
1241   int ret = 0;
1242   CActivityResultEvent* event = new CActivityResultEvent(requestCode);
1243   {
1244     CSingleLock lock(m_activityResultMutex);
1245     m_activityResultEvents.push_back(event);
1246   }
1247   startActivityForResult(intent, requestCode);
1248   if (event->Wait())
1249   {
1250     result = event->GetResultData();
1251     ret = event->GetResultCode();
1252   }
1253 
1254   // delete from m_activityResultEvents map before deleting the event
1255   CSingleLock lock(m_activityResultMutex);
1256   for (auto it = m_activityResultEvents.begin(); it != m_activityResultEvents.end(); ++it)
1257   {
1258     if ((*it)->GetRequestCode() == requestCode)
1259     {
1260       m_activityResultEvents.erase(it);
1261       break;
1262     }
1263   }
1264 
1265   delete event;
1266   return ret;
1267 }
1268 
onVolumeChanged(int volume)1269 void CXBMCApp::onVolumeChanged(int volume)
1270 {
1271   // don't do anything. User wants to use kodi's internal volume freely while
1272   // using the external volume to change it relatively
1273   // See: https://forum.kodi.tv/showthread.php?tid=350764
1274 }
1275 
onAudioFocusChange(int focusChange)1276 void CXBMCApp::onAudioFocusChange(int focusChange)
1277 {
1278   CXBMCApp::android_printf("Audio Focus changed: %d", focusChange);
1279   if (focusChange == CJNIAudioManager::AUDIOFOCUS_LOSS)
1280   {
1281     if ((m_playback_state & PLAYBACK_STATE_PLAYING))
1282     {
1283       if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
1284         CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_STOP)));
1285       else
1286         CApplicationMessenger::GetInstance().SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1, static_cast<void*>(new CAction(ACTION_PAUSE)));
1287     }
1288   }
1289 }
1290 
InitFrameCallback(CVideoSyncAndroid * syncImpl)1291 void CXBMCApp::InitFrameCallback(CVideoSyncAndroid* syncImpl)
1292 {
1293   m_syncImpl = syncImpl;
1294 }
1295 
DeinitFrameCallback()1296 void CXBMCApp::DeinitFrameCallback()
1297 {
1298   m_syncImpl = NULL;
1299 }
1300 
doFrame(int64_t frameTimeNanos)1301 void CXBMCApp::doFrame(int64_t frameTimeNanos)
1302 {
1303   if (m_syncImpl)
1304     m_syncImpl->FrameCallback(frameTimeNanos);
1305 
1306   // Calculate the time, when next surface buffer should be rendered
1307   m_frameTimeNanos = frameTimeNanos;
1308 
1309   m_vsyncEvent.Set();
1310 }
1311 
GetNextFrameTime()1312 int64_t CXBMCApp::GetNextFrameTime()
1313 {
1314   if (m_refreshRate > 0.0001f)
1315     return m_frameTimeNanos + static_cast<int64_t>(1500000000ll / m_refreshRate);
1316   else
1317     return m_frameTimeNanos;
1318 }
1319 
GetFrameLatencyMs()1320 float CXBMCApp::GetFrameLatencyMs()
1321 {
1322   return (CurrentHostCounter() - m_frameTimeNanos) * 0.000001;
1323 }
1324 
WaitVSync(unsigned int milliSeconds)1325 bool CXBMCApp::WaitVSync(unsigned int milliSeconds)
1326 {
1327   return m_vsyncEvent.WaitMSec(milliSeconds);
1328 }
1329 
GetMemoryInfo(long & availMem,long & totalMem)1330 bool CXBMCApp::GetMemoryInfo(long& availMem, long& totalMem)
1331 {
1332   if (m_activityManager)
1333   {
1334     CJNIActivityManager::MemoryInfo info;
1335     m_activityManager->getMemoryInfo(info);
1336     if (xbmc_jnienv()->ExceptionCheck())
1337     {
1338       xbmc_jnienv()->ExceptionClear();
1339       return false;
1340     }
1341 
1342     availMem = info.availMem();
1343     totalMem = info.totalMem();
1344 
1345     return true;
1346   }
1347 
1348   return false;
1349 }
1350 
SetupEnv()1351 void CXBMCApp::SetupEnv()
1352 {
1353   setenv("KODI_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0);
1354   setenv("KODI_ANDROID_LIBS", getApplicationInfo().nativeLibraryDir.c_str(), 0);
1355   setenv("KODI_ANDROID_APK", getPackageResourcePath().c_str(), 0);
1356 
1357   std::string appName = CCompileInfo::GetAppName();
1358   StringUtils::ToLower(appName);
1359   std::string className = CCompileInfo::GetPackage();
1360 
1361   std::string cacheDir = getCacheDir().getAbsolutePath();
1362   std::string xbmcHome = CJNISystem::getProperty("xbmc.home", "");
1363   if (xbmcHome.empty())
1364   {
1365     setenv("KODI_BIN_HOME", (cacheDir + "/apk/assets").c_str(), 0);
1366     setenv("KODI_HOME", (cacheDir + "/apk/assets").c_str(), 0);
1367   }
1368   else
1369   {
1370     setenv("KODI_BIN_HOME", (xbmcHome + "/assets").c_str(), 0);
1371     setenv("KODI_HOME", (xbmcHome + "/assets").c_str(), 0);
1372   }
1373   setenv("KODI_BINADDON_PATH", (cacheDir + "/lib").c_str(), 0);
1374 
1375   std::string externalDir = CJNISystem::getProperty("xbmc.data", "");
1376   if (externalDir.empty())
1377   {
1378     CJNIFile androidPath = getExternalFilesDir("");
1379     if (!androidPath)
1380       androidPath = getDir(className.c_str(), 1);
1381 
1382     if (androidPath)
1383       externalDir = androidPath.getAbsolutePath();
1384   }
1385 
1386   if (!externalDir.empty())
1387     setenv("HOME", externalDir.c_str(), 0);
1388   else
1389     setenv("HOME", getenv("KODI_TEMP"), 0);
1390 
1391   std::string pythonPath = cacheDir + "/apk/assets/python3.8";
1392   setenv("PYTHONHOME", pythonPath.c_str(), 1);
1393   setenv("PYTHONPATH", "", 1);
1394   setenv("PYTHONOPTIMIZE","", 1);
1395   setenv("PYTHONNOUSERSITE", "1", 1);
1396 }
1397 
GetFilenameFromIntent(const CJNIIntent & intent)1398 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
1399 {
1400     std::string ret;
1401     if (!intent)
1402       return ret;
1403     CJNIURI data = intent.getData();
1404     if (!data)
1405       return ret;
1406     std::string scheme = data.getScheme();
1407     StringUtils::ToLower(scheme);
1408     if (scheme == "content")
1409     {
1410       std::vector<std::string> filePathColumn;
1411       filePathColumn.push_back(CJNIMediaStoreMediaColumns::DATA);
1412       CJNICursor cursor = getContentResolver().query(data, filePathColumn, std::string(), std::vector<std::string>(), std::string());
1413       if(cursor.moveToFirst())
1414       {
1415         int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
1416         ret = cursor.getString(columnIndex);
1417       }
1418       cursor.close();
1419     }
1420     else if(scheme == "file")
1421       ret = data.getPath();
1422     else
1423       ret = data.toString();
1424   return ret;
1425 }
1426 
GetNativeWindow(int timeout)1427 ANativeWindow* CXBMCApp::GetNativeWindow(int timeout)
1428 {
1429   if (m_window)
1430     return m_window;
1431 
1432   if (m_mainView)
1433     m_mainView->waitForSurface(timeout);
1434 
1435   return m_window;
1436 }
1437 
RegisterInputDeviceCallbacks(IInputDeviceCallbacks * handler)1438 void CXBMCApp::RegisterInputDeviceCallbacks(IInputDeviceCallbacks* handler)
1439 {
1440   if (handler == nullptr)
1441     return;
1442 
1443   m_inputDeviceCallbacks = handler;
1444 }
1445 
UnregisterInputDeviceCallbacks()1446 void CXBMCApp::UnregisterInputDeviceCallbacks()
1447 {
1448   m_inputDeviceCallbacks = nullptr;
1449 }
1450 
onInputDeviceAdded(int deviceId)1451 void CXBMCApp::onInputDeviceAdded(int deviceId)
1452 {
1453   CXBMCApp::android_printf("Input device added: %d", deviceId);
1454 
1455   if (m_inputDeviceCallbacks != nullptr)
1456     m_inputDeviceCallbacks->OnInputDeviceAdded(deviceId);
1457 }
1458 
onInputDeviceChanged(int deviceId)1459 void CXBMCApp::onInputDeviceChanged(int deviceId)
1460 {
1461   CXBMCApp::android_printf("Input device changed: %d", deviceId);
1462 
1463   if (m_inputDeviceCallbacks != nullptr)
1464     m_inputDeviceCallbacks->OnInputDeviceChanged(deviceId);
1465 }
1466 
onInputDeviceRemoved(int deviceId)1467 void CXBMCApp::onInputDeviceRemoved(int deviceId)
1468 {
1469   CXBMCApp::android_printf("Input device removed: %d", deviceId);
1470 
1471   if (m_inputDeviceCallbacks != nullptr)
1472     m_inputDeviceCallbacks->OnInputDeviceRemoved(deviceId);
1473 }
1474 
RegisterInputDeviceEventHandler(IInputDeviceEventHandler * handler)1475 void CXBMCApp::RegisterInputDeviceEventHandler(IInputDeviceEventHandler* handler)
1476 {
1477   if (handler == nullptr)
1478     return;
1479 
1480   m_inputDeviceEventHandler = handler;
1481 }
1482 
UnregisterInputDeviceEventHandler()1483 void CXBMCApp::UnregisterInputDeviceEventHandler()
1484 {
1485   m_inputDeviceEventHandler = nullptr;
1486 }
1487 
onInputDeviceEvent(const AInputEvent * event)1488 bool CXBMCApp::onInputDeviceEvent(const AInputEvent* event)
1489 {
1490   if (m_inputDeviceEventHandler != nullptr)
1491     return m_inputDeviceEventHandler->OnInputDeviceEvent(event);
1492 
1493   return false;
1494 }
1495 
onDisplayAdded(int displayId)1496 void CXBMCApp::onDisplayAdded(int displayId)
1497 {
1498   android_printf("%s: ", __PRETTY_FUNCTION__);
1499 }
1500 
onDisplayChanged(int displayId)1501 void CXBMCApp::onDisplayChanged(int displayId)
1502 {
1503   CLog::Log(LOGDEBUG, "CXBMCApp::%s: id: %d", __FUNCTION__, displayId);
1504 
1505   // Update display modes
1506   CWinSystemAndroid* winSystemAndroid = dynamic_cast<CWinSystemAndroid*>(CServiceBroker::GetWinSystem());
1507   if (winSystemAndroid)
1508     winSystemAndroid->UpdateDisplayModes();
1509 
1510   m_displayChangeEvent.Set();
1511   m_inputHandler.setDPI(GetDPI());
1512   android_printf("%s: ", __PRETTY_FUNCTION__);
1513 }
1514 
onDisplayRemoved(int displayId)1515 void CXBMCApp::onDisplayRemoved(int displayId)
1516 {
1517   android_printf("%s: ", __PRETTY_FUNCTION__);
1518 }
1519 
surfaceChanged(CJNISurfaceHolder holder,int format,int width,int height)1520 void CXBMCApp::surfaceChanged(CJNISurfaceHolder holder, int format, int width, int height)
1521 {
1522   android_printf("%s: ", __PRETTY_FUNCTION__);
1523 }
1524 
surfaceCreated(CJNISurfaceHolder holder)1525 void CXBMCApp::surfaceCreated(CJNISurfaceHolder holder)
1526 {
1527   android_printf("%s: ", __PRETTY_FUNCTION__);
1528   m_window = ANativeWindow_fromSurface(xbmc_jnienv(), holder.getSurface().get_raw());
1529   if (m_window == NULL)
1530   {
1531     android_printf(" => invalid ANativeWindow object");
1532     return;
1533   }
1534   if(!m_firstrun)
1535   {
1536     XBMC_SetupDisplay();
1537   }
1538   g_application.SetRenderGUI(true);
1539 }
1540 
surfaceDestroyed(CJNISurfaceHolder holder)1541 void CXBMCApp::surfaceDestroyed(CJNISurfaceHolder holder)
1542 {
1543   android_printf("%s: ", __PRETTY_FUNCTION__);
1544   // If we have exited XBMC, it no longer exists.
1545   g_application.SetRenderGUI(false);
1546   if (!m_exiting)
1547   {
1548     XBMC_DestroyDisplay();
1549     m_window = NULL;
1550   }
1551 }
1552