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