[Windows] Remove redundant DirectSound error codes
[xbmc.git] / xbmc / platform / android / activity / XBMCApp.cpp
blob10075dc331dd7d435e5e37c3c05911fc7dd9bdcf
1 /*
2 * Copyright (C) 2012-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "XBMCApp.h"
11 #include "AndroidKey.h"
12 #include "CompileInfo.h"
13 #include "FileItem.h"
14 // Audio Engine includes for Factory and interfaces
15 #include "GUIInfoManager.h"
16 #include "ServiceBroker.h"
17 #include "TextureCache.h"
18 #include "application/AppEnvironment.h"
19 #include "application/AppParams.h"
20 #include "application/Application.h"
21 #include "application/ApplicationComponents.h"
22 #include "application/ApplicationPlayer.h"
23 #include "application/ApplicationPowerHandling.h"
24 #include "cores/AudioEngine/AESinkFactory.h"
25 #include "cores/AudioEngine/Interfaces/AE.h"
26 #include "cores/AudioEngine/Sinks/AESinkAUDIOTRACK.h"
27 #include "cores/VideoPlayer/VideoRenderers/RenderManager.h"
28 #include "filesystem/SpecialProtocol.h"
29 #include "filesystem/VideoDatabaseFile.h"
30 #include "guilib/GUIComponent.h"
31 #include "guilib/GUIWindowManager.h"
32 #include "guilib/guiinfo/GUIInfoLabels.h"
33 #include "input/actions/Action.h"
34 #include "input/actions/ActionIDs.h"
35 #include "input/mouse/MouseStat.h"
36 #include "interfaces/AnnouncementManager.h"
37 #include "messaging/ApplicationMessenger.h"
38 #include "platform/xbmc.h"
39 #include "powermanagement/PowerManager.h"
40 #include "settings/AdvancedSettings.h"
41 #include "settings/DisplaySettings.h"
42 #include "settings/Settings.h"
43 #include "settings/SettingsComponent.h"
44 #include "threads/Event.h"
45 #include "utils/StringUtils.h"
46 #include "utils/TimeUtils.h"
47 #include "utils/URIUtils.h"
48 #include "utils/Variant.h"
49 #include "utils/log.h"
50 #include "video/VideoFileItemClassify.h"
51 #include "video/VideoInfoTag.h"
52 #include "windowing/GraphicContext.h"
53 #include "windowing/WinEvents.h"
54 #include "windowing/android/VideoSyncAndroid.h"
55 #include "windowing/android/WinSystemAndroid.h"
57 #include "platform/android/activity/IInputDeviceCallbacks.h"
58 #include "platform/android/activity/IInputDeviceEventHandler.h"
59 #include "platform/android/powermanagement/AndroidPowerSyscall.h"
61 #include <memory>
62 #include <mutex>
63 #include <sstream>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <time.h>
68 #include <android/bitmap.h>
69 #include <android/configuration.h>
70 #include <android/log.h>
71 #include <android/native_window.h>
72 #include <android/native_window_jni.h>
73 #include <androidjni/ApplicationInfo.h>
74 #include <androidjni/BitmapFactory.h>
75 #include <androidjni/BroadcastReceiver.h>
76 #include <androidjni/Build.h>
77 #include <androidjni/CharSequence.h>
78 #include <androidjni/ConnectivityManager.h>
79 #include <androidjni/ContentResolver.h>
80 #include <androidjni/Context.h>
81 #include <androidjni/Cursor.h>
82 #include <androidjni/Display.h>
83 #include <androidjni/DisplayManager.h>
84 #include <androidjni/File.h>
85 #include <androidjni/Intent.h>
86 #include <androidjni/IntentFilter.h>
87 #include <androidjni/JNIThreading.h>
88 #include <androidjni/KeyEvent.h>
89 #include <androidjni/MediaStore.h>
90 #include <androidjni/NetworkInfo.h>
91 #include <androidjni/PackageManager.h>
92 #include <androidjni/Resources.h>
93 #include <androidjni/System.h>
94 #include <androidjni/SystemClock.h>
95 #include <androidjni/SystemProperties.h>
96 #include <androidjni/URI.h>
97 #include <androidjni/View.h>
98 #include <androidjni/Window.h>
99 #include <androidjni/WindowManager.h>
100 #include <crossguid/guid.hpp>
101 #include <dlfcn.h>
102 #include <jni.h>
103 #include <rapidjson/document.h>
104 #include <unistd.h>
106 #define ACTION_XBMC_RESUME "android.intent.XBMC_RESUME"
108 #define PLAYBACK_STATE_STOPPED 0x0000
109 #define PLAYBACK_STATE_PLAYING 0x0001
110 #define PLAYBACK_STATE_VIDEO 0x0100
111 #define PLAYBACK_STATE_AUDIO 0x0200
112 #define PLAYBACK_STATE_CANNOT_PAUSE 0x0400
114 using namespace ANNOUNCEMENT;
115 using namespace jni;
116 using namespace KODI::GUILIB;
117 using namespace KODI::VIDEO;
118 using namespace std::chrono_literals;
120 std::shared_ptr<CNativeWindow> CNativeWindow::CreateFromSurface(CJNISurfaceHolder holder)
122 ANativeWindow* window = ANativeWindow_fromSurface(xbmc_jnienv(), holder.getSurface().get_raw());
123 if (window)
124 return std::shared_ptr<CNativeWindow>(new CNativeWindow(window));
126 return {};
129 CNativeWindow::CNativeWindow(ANativeWindow* window) : m_window(window)
133 CNativeWindow::~CNativeWindow()
135 if (m_window)
136 ANativeWindow_release(m_window);
139 bool CNativeWindow::SetBuffersGeometry(int width, int height, int format)
141 if (m_window)
142 return (ANativeWindow_setBuffersGeometry(m_window, width, height, format) == 0);
144 return false;
147 int32_t CNativeWindow::GetWidth() const
149 if (m_window)
150 return ANativeWindow_getWidth(m_window);
152 return -1;
155 int32_t CNativeWindow::GetHeight() const
157 if (m_window)
158 return ANativeWindow_getHeight(m_window);
160 return -1;
163 std::unique_ptr<CXBMCApp> CXBMCApp::m_appinstance;
165 CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity, IInputHandler& inputHandler)
166 : CJNIMainActivity(nativeActivity),
167 CJNIBroadcastReceiver(CJNIContext::getPackageName() + ".XBMCBroadcastReceiver"),
168 m_inputHandler(inputHandler)
170 m_activity = nativeActivity;
171 if (m_activity == nullptr)
173 android_printf("CXBMCApp: invalid ANativeActivity instance");
174 exit(1);
175 return;
177 m_mainView = std::make_unique<CJNIXBMCMainView>(this);
178 m_hdmiSource = CJNISystemProperties::get("ro.hdmi.device_type", "") == "4";
179 android_printf("CXBMCApp: Created");
181 // crossguid requires init on android only once on process start
182 JNIEnv* env = xbmc_jnienv();
183 xg::initJni(env);
186 CXBMCApp::~CXBMCApp()
190 void CXBMCApp::Announce(ANNOUNCEMENT::AnnouncementFlag flag,
191 const std::string& sender,
192 const std::string& message,
193 const CVariant& data)
195 if (sender != CAnnouncementManager::ANNOUNCEMENT_SENDER)
196 return;
198 if (flag & Input)
200 if (message == "OnInputRequested")
201 CAndroidKey::SetHandleSearchKeys(true);
202 else if (message == "OnInputFinished")
203 CAndroidKey::SetHandleSearchKeys(false);
205 else if (flag & Player)
207 if (message == "OnPlay" || message == "OnResume")
208 OnPlayBackStarted();
209 else if (message == "OnPause")
210 OnPlayBackPaused();
211 else if (message == "OnStop")
212 OnPlayBackStopped();
213 else if (message == "OnSeek")
215 m_mediaSessionUpdated = false;
216 UpdateSessionState();
218 else if (message == "OnSpeedChanged")
220 m_mediaSessionUpdated = false;
221 UpdateSessionState();
223 else if (message == "OnAVStart")
225 m_mediaSessionUpdated = false;
226 UpdateSessionState();
229 else if (flag & Info)
231 if (message == "OnChanged")
233 m_mediaSessionUpdated = false;
234 UpdateSessionMetadata();
239 void CXBMCApp::onStart()
241 android_printf("%s: ", __PRETTY_FUNCTION__);
243 if (m_firstrun)
245 // Register sink
246 AE::CAESinkFactory::ClearSinks();
247 CAESinkAUDIOTRACK::Register();
249 // Create thread to run Kodi main event loop
250 m_thread = std::thread(&CXBMCApp::run, this);
252 // Some intent filters MUST be registered in code rather than through the manifest
253 CJNIIntentFilter intentFilter;
254 intentFilter.addAction(CJNIIntent::ACTION_BATTERY_CHANGED);
255 intentFilter.addAction(CJNIIntent::ACTION_SCREEN_ON);
256 intentFilter.addAction(CJNIIntent::ACTION_HEADSET_PLUG);
257 // We currently use HDMI_AUDIO_PLUG for mode switch, don't use it on TV's (device_type = "0"
258 if (m_hdmiSource)
259 intentFilter.addAction(CJNIAudioManager::ACTION_HDMI_AUDIO_PLUG);
261 intentFilter.addAction(CJNIIntent::ACTION_SCREEN_OFF);
262 intentFilter.addAction(CJNIConnectivityManager::CONNECTIVITY_ACTION);
263 registerReceiver(*this, intentFilter);
264 m_mediaSession = std::make_unique<CJNIXBMCMediaSession>();
265 m_inputHandler.setDPI(GetDPI());
266 runNativeOnUiThread(RegisterDisplayListenerCallback, nullptr);
270 namespace
272 bool isHeadsetPlugged()
274 CJNIAudioManager audioManager(CXBMCApp::getSystemService(CJNIContext::AUDIO_SERVICE));
276 if (CJNIBuild::SDK_INT >= 26)
278 const CJNIAudioDeviceInfos devices =
279 audioManager.getDevices(CJNIAudioManager::GET_DEVICES_OUTPUTS);
281 for (const CJNIAudioDeviceInfo& device : devices)
283 const int type = device.getType();
284 if (type == CJNIAudioDeviceInfo::TYPE_WIRED_HEADSET ||
285 type == CJNIAudioDeviceInfo::TYPE_WIRED_HEADPHONES ||
286 type == CJNIAudioDeviceInfo::TYPE_BLUETOOTH_A2DP ||
287 type == CJNIAudioDeviceInfo::TYPE_BLUETOOTH_SCO)
289 return true;
292 return false;
294 else
296 return audioManager.isWiredHeadsetOn() || audioManager.isBluetoothA2dpOn();
299 } // namespace
301 void CXBMCApp::onResume()
303 android_printf("%s: ", __PRETTY_FUNCTION__);
305 if (g_application.IsInitialized() &&
306 CServiceBroker::GetWinSystem()->GetOSScreenSaver()->IsInhibited())
307 KeepScreenOn(true);
309 // Reset shutdown timer on wake up
310 if (g_application.IsInitialized() &&
311 CServiceBroker::GetSettingsComponent()->GetSettings()->GetInt(
312 CSettings::SETTING_POWERMANAGEMENT_SHUTDOWNTIME))
314 auto& components = CServiceBroker::GetAppComponents();
315 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
316 appPower->ResetShutdownTimers();
319 m_headsetPlugged = isHeadsetPlugged();
321 // Clear the applications cache. We could have installed/deinstalled apps
323 std::unique_lock<CCriticalSection> lock(m_applicationsMutex);
324 m_applications.clear();
327 const auto& components = CServiceBroker::GetAppComponents();
328 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
329 if (m_bResumePlayback && appPlayer->IsPlaying())
331 if (appPlayer->HasVideo())
333 if (appPlayer->IsPaused())
334 CServiceBroker::GetAppMessenger()->SendMsg(
335 TMSG_GUI_ACTION, WINDOW_INVALID, -1,
336 static_cast<void*>(new CAction(ACTION_PLAYER_PLAY)));
340 // Re-request Visible Behind
341 if ((m_playback_state & PLAYBACK_STATE_PLAYING) && (m_playback_state & PLAYBACK_STATE_VIDEO))
342 RequestVisibleBehind(true);
345 void CXBMCApp::onPause()
347 android_printf("%s: ", __PRETTY_FUNCTION__);
348 m_bResumePlayback = false;
350 const auto& components = CServiceBroker::GetAppComponents();
351 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
352 if (appPlayer->IsPlaying())
354 if (appPlayer->HasVideo())
356 if (!appPlayer->IsPaused() && !m_hasReqVisible)
358 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
359 static_cast<void*>(new CAction(ACTION_PAUSE)));
360 m_bResumePlayback = true;
365 if (m_hasReqVisible)
367 CGUIComponent* gui = CServiceBroker::GetGUI();
368 if (gui)
370 gui->GetWindowManager().SwitchToFullScreen(true);
374 KeepScreenOn(false);
375 m_hasReqVisible = false;
378 void CXBMCApp::onStop()
380 android_printf("%s: ", __PRETTY_FUNCTION__);
382 if ((m_playback_state & PLAYBACK_STATE_PLAYING) && !m_hasReqVisible)
384 if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
385 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
386 static_cast<void*>(new CAction(ACTION_STOP)));
387 else if (m_playback_state & PLAYBACK_STATE_VIDEO)
388 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
389 static_cast<void*>(new CAction(ACTION_PAUSE)));
393 void CXBMCApp::onDestroy()
395 android_printf("%s", __PRETTY_FUNCTION__);
397 unregisterReceiver(*this);
399 UnregisterDisplayListener();
400 CServiceBroker::GetAnnouncementManager()->RemoveAnnouncer(this);
402 m_mediaSession.release();
405 void CXBMCApp::onSaveState(void **data, size_t *size)
407 android_printf("%s: ", __PRETTY_FUNCTION__);
408 // no need to save anything as XBMC is running in its own thread
411 void CXBMCApp::onConfigurationChanged()
413 android_printf("%s: ", __PRETTY_FUNCTION__);
414 // ignore any configuration changes like screen rotation etc
417 void CXBMCApp::onLowMemory()
419 android_printf("%s: ", __PRETTY_FUNCTION__);
420 // can't do much as we don't want to close completely
423 void CXBMCApp::onCreateWindow(ANativeWindow* window)
425 android_printf("%s: ", __PRETTY_FUNCTION__);
428 void CXBMCApp::onResizeWindow()
430 android_printf("%s: ", __PRETTY_FUNCTION__);
431 m_window.reset();
432 // no need to do anything because we are fixed in fullscreen landscape mode
435 void CXBMCApp::onDestroyWindow()
437 android_printf("%s: ", __PRETTY_FUNCTION__);
440 void CXBMCApp::onGainFocus()
442 android_printf("%s: ", __PRETTY_FUNCTION__);
443 m_hasFocus = true;
444 auto& components = CServiceBroker::GetAppComponents();
445 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
446 appPower->WakeUpScreenSaverAndDPMS();
449 void CXBMCApp::onLostFocus()
451 android_printf("%s: ", __PRETTY_FUNCTION__);
452 m_hasFocus = false;
455 void CXBMCApp::RegisterDisplayListenerCallback(void*)
457 CJNIDisplayManager displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE));
458 if (displayManager)
460 android_printf("CXBMCApp: installing DisplayManager::DisplayListener");
461 displayManager.registerDisplayListener(CXBMCApp::Get().getDisplayListener());
465 void CXBMCApp::UnregisterDisplayListener()
467 CJNIDisplayManager displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE));
468 if (displayManager)
470 android_printf("CXBMCApp: removing DisplayManager::DisplayListener");
471 displayManager.unregisterDisplayListener(m_displayListener.get_raw());
475 void CXBMCApp::Initialize()
477 CServiceBroker::GetAnnouncementManager()->AddAnnouncer(
478 this, ANNOUNCEMENT::Input | ANNOUNCEMENT::Player | ANNOUNCEMENT::Info);
481 void CXBMCApp::Deinitialize()
485 bool CXBMCApp::Stop(int exitCode)
487 if (m_exiting)
488 return true; // stage two: android activity has finished
490 // enter stage one: tell android to finish the activity
491 CLog::Log(LOGINFO, "XBMCApp: Finishing the activity");
493 m_exitCode = exitCode;
495 // Notify Android its finish routine.
496 // This will cause Android to run through its teardown events, it calls:
497 // onPause(), onLostFocus(), onDestroyWindow(), onStop(), onDestroy().
498 ANativeActivity_finish(m_activity);
500 return false; // stage one: let android finish the activity
503 void CXBMCApp::Quit()
505 CLog::Log(LOGINFO, "XBMCApp: Stopping the application...");
507 uint32_t msgId;
508 switch (m_exitCode)
510 case EXITCODE_QUIT:
511 msgId = TMSG_QUIT;
512 break;
513 case EXITCODE_POWERDOWN:
514 msgId = TMSG_POWERDOWN;
515 break;
516 case EXITCODE_REBOOT:
517 msgId = TMSG_RESTART;
518 break;
519 case EXITCODE_RESTARTAPP:
520 msgId = TMSG_RESTARTAPP;
521 break;
522 default:
523 CLog::Log(LOGWARNING, "CXBMCApp::Stop : Unexpected exit code. Defaulting to QUIT.");
524 msgId = TMSG_QUIT;
525 break;
528 m_exiting = true; // enter stage two: android activity has finished. go on with stopping Kodi
529 CServiceBroker::GetAppMessenger()->PostMsg(msgId);
531 // wait for the run thread to finish
532 m_thread.join();
534 // Note: CLog no longer available here.
535 android_printf("%s: Application stopped!", __PRETTY_FUNCTION__);
538 void CXBMCApp::KeepScreenOnCallback(void* onVariant)
540 CVariant* onV = static_cast<CVariant*>(onVariant);
541 bool on = onV->asBoolean();
542 delete onV;
544 CJNIWindow window = getWindow();
545 if (window)
547 on ? window.addFlags(CJNIWindowManagerLayoutParams::FLAG_KEEP_SCREEN_ON)
548 : window.clearFlags(CJNIWindowManagerLayoutParams::FLAG_KEEP_SCREEN_ON);
552 void CXBMCApp::KeepScreenOn(bool on)
554 android_printf("%s: %s", __PRETTY_FUNCTION__, on ? "true" : "false");
555 // this object is deallocated in the callback
556 CVariant* variant = new CVariant(on);
557 runNativeOnUiThread(KeepScreenOnCallback, variant);
560 bool CXBMCApp::AcquireAudioFocus()
562 CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE));
564 int result;
566 if (CJNIBuild::SDK_INT >= 26)
568 CJNIAudioFocusRequestClassBuilder audioFocusBuilder(CJNIAudioManager::AUDIOFOCUS_GAIN);
569 CJNIAudioAttributesBuilder audioAttrBuilder;
571 audioAttrBuilder.setUsage(CJNIAudioAttributes::USAGE_MEDIA);
572 audioAttrBuilder.setContentType(CJNIAudioAttributes::CONTENT_TYPE_MUSIC);
574 audioFocusBuilder.setAudioAttributes(audioAttrBuilder.build());
575 audioFocusBuilder.setAcceptsDelayedFocusGain(true);
576 audioFocusBuilder.setWillPauseWhenDucked(true);
577 audioFocusBuilder.setOnAudioFocusChangeListener(m_audioFocusListener);
579 // Request audio focus for playback
580 result = audioManager.requestAudioFocus(audioFocusBuilder.build());
582 else
584 // Request audio focus for playback
585 result = audioManager.requestAudioFocus(m_audioFocusListener,
586 // Use the music stream.
587 CJNIAudioManager::STREAM_MUSIC,
588 // Request permanent focus.
589 CJNIAudioManager::AUDIOFOCUS_GAIN);
591 if (result != CJNIAudioManager::AUDIOFOCUS_REQUEST_GRANTED)
593 android_printf("Audio Focus request failed");
594 return false;
596 return true;
599 bool CXBMCApp::ReleaseAudioFocus()
601 CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE));
602 int result;
604 if (CJNIBuild::SDK_INT >= 26)
606 // Abandon requires the same AudioFocusRequest as the request
607 CJNIAudioFocusRequestClassBuilder audioFocusBuilder(CJNIAudioManager::AUDIOFOCUS_GAIN);
608 CJNIAudioAttributesBuilder audioAttrBuilder;
610 audioAttrBuilder.setUsage(CJNIAudioAttributes::USAGE_MEDIA);
611 audioAttrBuilder.setContentType(CJNIAudioAttributes::CONTENT_TYPE_MUSIC);
613 audioFocusBuilder.setAudioAttributes(audioAttrBuilder.build());
614 audioFocusBuilder.setAcceptsDelayedFocusGain(true);
615 audioFocusBuilder.setWillPauseWhenDucked(true);
616 audioFocusBuilder.setOnAudioFocusChangeListener(m_audioFocusListener);
618 // Release audio focus after playback
619 result = audioManager.abandonAudioFocusRequest(audioFocusBuilder.build());
621 else
623 // Release audio focus after playback
624 result = audioManager.abandonAudioFocus(m_audioFocusListener);
627 if (result != CJNIAudioManager::AUDIOFOCUS_REQUEST_GRANTED)
629 android_printf("Audio Focus abandon failed");
630 return false;
632 return true;
635 void CXBMCApp::RequestVisibleBehind(bool requested)
637 if (requested == m_hasReqVisible)
638 return;
640 m_hasReqVisible = requestVisibleBehind(requested);
641 CLog::Log(LOGDEBUG, "Visible Behind request: {}", m_hasReqVisible ? "true" : "false");
644 void CXBMCApp::run()
646 int status = 0;
648 SetupEnv();
650 // Wait for main window
651 if (!GetNativeWindow(30000))
652 return;
654 m_firstrun = false;
655 android_printf(" => running XBMC_Run...");
657 CAppEnvironment::SetUp(std::make_shared<CAppParams>());
658 status = XBMC_Run(true);
659 CAppEnvironment::TearDown();
661 android_printf(" => XBMC_Run finished with %d", status);
664 bool CXBMCApp::XBMC_SetupDisplay()
666 android_printf("XBMC_SetupDisplay()");
667 bool result;
668 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_DISPLAY_SETUP, -1, -1,
669 static_cast<void*>(&result));
670 return result;
673 bool CXBMCApp::XBMC_DestroyDisplay()
675 android_printf("XBMC_DestroyDisplay()");
676 bool result;
677 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_DISPLAY_DESTROY, -1, -1,
678 static_cast<void*>(&result));
679 return result;
682 bool CXBMCApp::SetBuffersGeometry(int width, int height, int format)
684 if (m_window)
685 return m_window->SetBuffersGeometry(width, height, format);
687 return false;
690 void CXBMCApp::SetDisplayModeCallback(void* modeVariant)
692 CVariant* modeV = static_cast<CVariant*>(modeVariant);
693 int mode = (*modeV)["mode"].asInteger();
694 delete modeV;
696 CJNIWindow window = getWindow();
697 if (window)
699 CJNIWindowManagerLayoutParams params = window.getAttributes();
700 if (params.getpreferredDisplayModeId() != mode)
702 params.setpreferredDisplayModeId(mode);
703 window.setAttributes(params);
704 return;
707 CXBMCApp::Get().m_displayChangeEvent.Set();
710 void CXBMCApp::SetDisplayMode(int mode, float rate)
712 if (mode < 1.0)
713 return;
715 CJNIWindow window = getWindow();
716 if (window)
718 CJNIWindowManagerLayoutParams params = window.getAttributes();
719 if (params.getpreferredDisplayModeId() == mode)
720 return;
723 m_displayChangeEvent.Reset();
725 if (m_hdmiSource)
726 dynamic_cast<CWinSystemAndroid*>(CServiceBroker::GetWinSystem())->InitiateModeChange();
728 std::map<std::string, CVariant> vmap;
729 vmap["mode"] = mode;
730 m_refreshRate = rate;
731 CVariant *variant = new CVariant(vmap);
732 runNativeOnUiThread(SetDisplayModeCallback, variant);
733 if (g_application.IsInitialized())
734 m_displayChangeEvent.Wait(5000ms);
737 int CXBMCApp::android_printf(const char* format, ...)
739 // For use before CLog is setup by XBMC_Run()
740 va_list args, args_copy;
741 va_start(args, format);
742 va_copy(args_copy, args);
743 int result;
745 if (CServiceBroker::IsLoggingUp())
747 std::string message;
748 int len = vsnprintf(0, 0, format, args_copy);
749 message.resize(len);
750 result = vsnprintf(&message[0], len + 1, format, args);
751 CLog::Log(LOGDEBUG, "{}", message);
753 else
755 result = __android_log_vprint(ANDROID_LOG_VERBOSE, "Kodi", format, args);
758 va_end(args_copy);
759 va_end(args);
761 return result;
764 int CXBMCApp::GetDPI() const
766 if (m_activity->assetManager == nullptr)
767 return 0;
769 // grab DPI from the current configuration - this is approximate
770 // but should be close enough for what we need
771 AConfiguration *config = AConfiguration_new();
772 AConfiguration_fromAssetManager(config, m_activity->assetManager);
773 int dpi = AConfiguration_getDensity(config);
774 AConfiguration_delete(config);
776 return dpi;
779 CRect CXBMCApp::MapRenderToDroid(const CRect& srcRect)
781 float scaleX = 1.0;
782 float scaleY = 1.0;
784 CJNIRect r = getDisplayRect();
785 if (r.width() && r.height())
787 RESOLUTION_INFO renderRes = CDisplaySettings::GetInstance().GetResolutionInfo(CServiceBroker::GetWinSystem()->GetGfxContext().GetVideoResolution());
788 scaleX = (double)r.width() / renderRes.iWidth;
789 scaleY = (double)r.height() / renderRes.iHeight;
792 return CRect(srcRect.x1 * scaleX, srcRect.y1 * scaleY, srcRect.x2 * scaleX, srcRect.y2 * scaleY);
795 void CXBMCApp::UpdateSessionMetadata()
797 const auto& components = CServiceBroker::GetAppComponents();
798 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
799 CGUIInfoManager& infoMgr = CServiceBroker::GetGUI()->GetInfoManager();
800 CJNIMediaMetadataBuilder builder;
801 builder
802 .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_TITLE,
803 infoMgr.GetLabel(PLAYER_TITLE, INFO::DEFAULT_CONTEXT))
804 .putString(CJNIMediaMetadata::METADATA_KEY_TITLE,
805 infoMgr.GetLabel(PLAYER_TITLE, INFO::DEFAULT_CONTEXT))
806 .putLong(CJNIMediaMetadata::METADATA_KEY_DURATION, appPlayer->GetTotalTime())
807 // .putString(CJNIMediaMetadata::METADATA_KEY_ART_URI, thumb)
808 // .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_ICON_URI, thumb)
809 // .putString(CJNIMediaMetadata::METADATA_KEY_ALBUM_ART_URI, thumb)
812 std::string thumb;
813 if (m_playback_state & PLAYBACK_STATE_VIDEO)
815 builder
816 .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_SUBTITLE,
817 infoMgr.GetLabel(VIDEOPLAYER_TAGLINE, INFO::DEFAULT_CONTEXT))
818 .putString(CJNIMediaMetadata::METADATA_KEY_ARTIST,
819 infoMgr.GetLabel(VIDEOPLAYER_DIRECTOR, INFO::DEFAULT_CONTEXT));
820 thumb = infoMgr.GetImage(VIDEOPLAYER_COVER, -1);
822 else if (m_playback_state & PLAYBACK_STATE_AUDIO)
824 builder
825 .putString(CJNIMediaMetadata::METADATA_KEY_DISPLAY_SUBTITLE,
826 infoMgr.GetLabel(MUSICPLAYER_ARTIST, INFO::DEFAULT_CONTEXT))
827 .putString(CJNIMediaMetadata::METADATA_KEY_ARTIST,
828 infoMgr.GetLabel(MUSICPLAYER_ARTIST, INFO::DEFAULT_CONTEXT));
829 thumb = infoMgr.GetImage(MUSICPLAYER_COVER, -1);
831 bool needrecaching = false;
832 std::string cachefile = CServiceBroker::GetTextureCache()->CheckCachedImage(thumb, needrecaching);
833 if (!cachefile.empty())
835 std::string actualfile = CSpecialProtocol::TranslatePath(cachefile);
836 CJNIBitmap bmp = CJNIBitmapFactory::decodeFile(actualfile);
837 if (bmp)
838 builder.putBitmap(CJNIMediaMetadata::METADATA_KEY_ART, bmp);
840 m_mediaSession->updateMetadata(builder.build());
843 void CXBMCApp::UpdateSessionState()
845 CJNIPlaybackStateBuilder builder;
846 int state = CJNIPlaybackState::STATE_NONE;
847 int64_t pos = 0;
848 float speed = 0.0;
849 const auto& components = CServiceBroker::GetAppComponents();
850 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
851 uint32_t oldPlayState = m_playback_state;
852 if (m_playback_state != PLAYBACK_STATE_STOPPED)
854 if (appPlayer->HasVideo())
855 m_playback_state |= PLAYBACK_STATE_VIDEO;
856 else
857 m_playback_state &= ~PLAYBACK_STATE_VIDEO;
859 if (appPlayer->HasAudio())
860 m_playback_state |= PLAYBACK_STATE_AUDIO;
861 else
862 m_playback_state &= ~PLAYBACK_STATE_AUDIO;
864 pos = appPlayer->GetTime();
865 speed = appPlayer->GetPlaySpeed();
867 if (m_playback_state & PLAYBACK_STATE_PLAYING)
868 state = CJNIPlaybackState::STATE_PLAYING;
869 else
870 state = CJNIPlaybackState::STATE_PAUSED;
872 else
873 state = CJNIPlaybackState::STATE_STOPPED;
875 if ((oldPlayState != m_playback_state) || !m_mediaSessionUpdated)
877 builder.setState(state, pos, speed, CJNISystemClock::elapsedRealtime())
878 .setActions(CJNIPlaybackState::PLAYBACK_POSITION_UNKNOWN);
879 m_mediaSession->updatePlaybackState(builder.build());
880 m_mediaSessionUpdated = true;
884 void CXBMCApp::OnPlayBackStarted()
886 CLog::Log(LOGDEBUG, "{}", __PRETTY_FUNCTION__);
887 const auto& components = CServiceBroker::GetAppComponents();
888 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
890 m_playback_state = PLAYBACK_STATE_PLAYING;
891 if (appPlayer->HasVideo())
892 m_playback_state |= PLAYBACK_STATE_VIDEO;
893 if (appPlayer->HasAudio())
894 m_playback_state |= PLAYBACK_STATE_AUDIO;
895 if (!appPlayer->CanPause())
896 m_playback_state |= PLAYBACK_STATE_CANNOT_PAUSE;
898 m_mediaSession->activate(true);
899 m_mediaSessionUpdated = false;
900 UpdateSessionState();
902 CJNIIntent intent(ACTION_XBMC_RESUME, CJNIURI::EMPTY, *this, get_class(CJNIContext::get_raw()));
903 m_mediaSession->updateIntent(intent);
905 AcquireAudioFocus();
906 CAndroidKey::SetHandleMediaKeys(false);
908 RequestVisibleBehind(true);
911 void CXBMCApp::OnPlayBackPaused()
913 CLog::Log(LOGDEBUG, "{}", __PRETTY_FUNCTION__);
915 m_playback_state &= ~PLAYBACK_STATE_PLAYING;
916 m_mediaSessionUpdated = false;
917 UpdateSessionState();
919 RequestVisibleBehind(false);
920 ReleaseAudioFocus();
923 void CXBMCApp::OnPlayBackStopped()
925 CLog::Log(LOGDEBUG, "{}", __PRETTY_FUNCTION__);
927 m_playback_state = PLAYBACK_STATE_STOPPED;
928 m_mediaSessionUpdated = false;
929 UpdateSessionState();
930 m_mediaSession->activate(false);
932 RequestVisibleBehind(false);
933 CAndroidKey::SetHandleMediaKeys(true);
934 ReleaseAudioFocus();
937 const CJNIViewInputDevice CXBMCApp::GetInputDevice(int deviceId)
939 CJNIInputManager inputManager(getSystemService(CJNIContext::INPUT_SERVICE));
940 return inputManager.getInputDevice(deviceId);
943 std::vector<int> CXBMCApp::GetInputDeviceIds()
945 CJNIInputManager inputManager(getSystemService(CJNIContext::INPUT_SERVICE));
946 return inputManager.getInputDeviceIds();
949 void CXBMCApp::ProcessSlow()
951 if ((m_playback_state & PLAYBACK_STATE_PLAYING) && !m_mediaSessionUpdated &&
952 m_mediaSession->isActive())
953 UpdateSessionState();
956 std::vector<androidPackage> CXBMCApp::GetApplications() const
958 std::unique_lock<CCriticalSection> lock(m_applicationsMutex);
959 if (m_applications.empty())
961 CJNIList<CJNIApplicationInfo> packageList =
962 GetPackageManager().getInstalledApplications(CJNIPackageManager::GET_ACTIVITIES);
963 int numPackages = packageList.size();
964 for (int i = 0; i < numPackages; i++)
966 CJNIIntent intent =
967 GetPackageManager().getLaunchIntentForPackage(packageList.get(i).packageName);
968 if (!intent)
969 intent =
970 GetPackageManager().getLeanbackLaunchIntentForPackage(packageList.get(i).packageName);
971 if (!intent)
972 continue;
974 androidPackage newPackage;
975 newPackage.packageName = packageList.get(i).packageName;
976 newPackage.packageLabel =
977 GetPackageManager().getApplicationLabel(packageList.get(i)).toString();
978 newPackage.icon = packageList.get(i).icon;
979 m_applications.emplace_back(newPackage);
983 return m_applications;
986 // Note intent, dataType, dataURI, action, category, flags, extras, className all default to ""
987 bool CXBMCApp::StartActivity(const std::string& package,
988 const std::string& intent,
989 const std::string& dataType,
990 const std::string& dataURI,
991 const std::string& flags,
992 const std::string& extras,
993 const std::string& action,
994 const std::string& category,
995 const std::string& className)
997 CLog::LogF(LOGDEBUG, "package: {}", package);
998 CLog::LogF(LOGDEBUG, "intent: {}", intent);
999 CLog::LogF(LOGDEBUG, "dataType: {}", dataType);
1000 CLog::LogF(LOGDEBUG, "dataURI: {}", dataURI);
1001 CLog::LogF(LOGDEBUG, "flags: {}", flags);
1002 CLog::LogF(LOGDEBUG, "extras: {}", extras);
1003 CLog::LogF(LOGDEBUG, "action: {}", action);
1004 CLog::LogF(LOGDEBUG, "category: {}", category);
1005 CLog::LogF(LOGDEBUG, "className: {}", className);
1007 CJNIIntent newIntent = intent.empty() ?
1008 GetPackageManager().getLaunchIntentForPackage(package) :
1009 CJNIIntent(intent);
1011 if (intent.empty() && GetPackageManager().hasSystemFeature(CJNIPackageManager::FEATURE_LEANBACK))
1013 CJNIIntent leanbackIntent = GetPackageManager().getLeanbackLaunchIntentForPackage(package);
1014 if (leanbackIntent)
1015 newIntent = leanbackIntent;
1018 if (!newIntent)
1019 return false;
1021 if (!dataURI.empty())
1023 CJNIURI jniURI = CJNIURI::parse(dataURI);
1025 if (!jniURI)
1026 return false;
1028 newIntent.setDataAndType(jniURI, dataType);
1031 if (!action.empty())
1032 newIntent.setAction(action);
1034 if (!category.empty())
1035 newIntent.addCategory(category);
1037 if (!flags.empty())
1041 newIntent.setFlags(std::stoi(flags));
1043 catch (const std::exception& e)
1045 CLog::LogF(LOGDEBUG, "Invalid flags given, ignore them");
1049 if (!extras.empty())
1051 rapidjson::Document doc;
1052 doc.Parse(extras.c_str());
1053 if (!doc.IsArray())
1055 CLog::LogF(LOGDEBUG, "Invalid intent extras format: Needs to be an array");
1056 return false;
1059 for (auto& e : doc.GetArray())
1061 if (!e.IsObject() || !e.HasMember("type") || !e.HasMember("key") || !e.HasMember("value"))
1063 CLog::LogF(LOGDEBUG, "Invalid intent extras value format");
1064 continue;
1067 if (e["type"] == "string")
1069 newIntent.putExtra(e["key"].GetString(), e["value"].GetString());
1070 CLog::LogF(LOGDEBUG, "Putting extra key: {}, value: {}", e["key"].GetString(),
1071 e["value"].GetString());
1073 else
1074 CLog::LogF(LOGDEBUG, "Intent extras data type ({}) not implemented", e["type"].GetString());
1078 newIntent.setPackage(package);
1079 if (!className.empty())
1080 newIntent.setClassName(package, className);
1082 startActivity(newIntent);
1083 if (xbmc_jnienv()->ExceptionCheck())
1085 CLog::LogF(LOGERROR, "ExceptionOccurred launching {}", package);
1086 xbmc_jnienv()->ExceptionDescribe();
1087 xbmc_jnienv()->ExceptionClear();
1088 return false;
1091 return true;
1094 int CXBMCApp::GetBatteryLevel() const
1096 return m_batteryLevel;
1099 // Used in Application.cpp to figure out volume steps
1100 int CXBMCApp::GetMaxSystemVolume()
1102 JNIEnv* env = xbmc_jnienv();
1103 static int maxVolume = -1;
1104 if (maxVolume == -1)
1106 maxVolume = GetMaxSystemVolume(env);
1108 //android_printf("CXBMCApp::GetMaxSystemVolume: %i",maxVolume);
1109 return maxVolume;
1112 int CXBMCApp::GetMaxSystemVolume(JNIEnv *env)
1114 CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE));
1115 if (audioManager)
1116 return audioManager.getStreamMaxVolume();
1117 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
1118 return 0;
1121 float CXBMCApp::GetSystemVolume()
1123 CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE));
1124 if (audioManager)
1125 return (float)audioManager.getStreamVolume() / GetMaxSystemVolume();
1126 else
1128 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
1129 return 0;
1133 void CXBMCApp::SetSystemVolume(float percent)
1135 CJNIAudioManager audioManager(getSystemService(CJNIContext::AUDIO_SERVICE));
1136 int maxVolume = (int)(GetMaxSystemVolume() * percent);
1137 if (audioManager)
1138 audioManager.setStreamVolume(maxVolume);
1139 else
1140 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
1143 void CXBMCApp::onReceive(CJNIIntent intent)
1145 std::string action = intent.getAction();
1146 android_printf("CXBMCApp::onReceive - Got intent. Action: %s", action.c_str());
1148 // Most actions can be processed only after the app is fully initialized,
1149 // but some actions should be processed even during initilization phase.
1150 if (!g_application.IsInitialized() && action != CJNIAudioManager::ACTION_HDMI_AUDIO_PLUG)
1152 android_printf("CXBMCApp::onReceive - ignoring action %s during app initialization phase",
1153 action.c_str());
1154 return;
1157 if (action == CJNIIntent::ACTION_BATTERY_CHANGED)
1158 m_batteryLevel = intent.getIntExtra("level", -1);
1159 else if (action == CJNIIntent::ACTION_DREAMING_STOPPED)
1161 if (HasFocus())
1163 auto& components = CServiceBroker::GetAppComponents();
1164 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
1165 appPower->WakeUpScreenSaverAndDPMS();
1168 else if (action == CJNIIntent::ACTION_HEADSET_PLUG ||
1169 action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
1171 bool newstate = m_headsetPlugged;
1172 if (action == CJNIIntent::ACTION_HEADSET_PLUG)
1174 newstate = (intent.getIntExtra("state", 0) != 0);
1176 // If unplugged headset and playing content then pause or stop playback
1177 if (!newstate && (m_playback_state & PLAYBACK_STATE_PLAYING))
1179 const auto& components = CServiceBroker::GetAppComponents();
1180 const auto appPlayer = components.GetComponent<CApplicationPlayer>();
1181 if (appPlayer->CanPause())
1183 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1184 static_cast<void*>(new CAction(ACTION_PAUSE)));
1186 else
1188 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1189 static_cast<void*>(new CAction(ACTION_STOP)));
1193 else if (action == "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED")
1194 newstate = (intent.getIntExtra("android.bluetooth.profile.extra.STATE", 0) == 2 /* STATE_CONNECTED */);
1196 if (newstate != m_headsetPlugged)
1198 m_headsetPlugged = newstate;
1199 IAE *iae = CServiceBroker::GetActiveAE();
1200 if (iae)
1201 iae->DeviceChange();
1204 else if (action == CJNIAudioManager::ACTION_HDMI_AUDIO_PLUG)
1206 m_hdmiPlugged = (intent.getIntExtra(CJNIAudioManager::EXTRA_AUDIO_PLUG_STATE, 0) != 0);
1207 android_printf("-- HDMI is plugged in: %s", m_hdmiPlugged ? "yes" : "no");
1208 if (g_application.IsInitialized())
1210 CWinSystemBase* winSystem = CServiceBroker::GetWinSystem();
1211 if (winSystem && dynamic_cast<CWinSystemAndroid*>(winSystem))
1212 dynamic_cast<CWinSystemAndroid*>(winSystem)->SetHdmiState(m_hdmiPlugged);
1214 if (m_hdmiPlugged && m_aeReset)
1216 android_printf("CXBMCApp::onReceive: Reset audio engine");
1217 CServiceBroker::GetActiveAE()->DeviceChange();
1218 m_aeReset = false;
1220 if (m_hdmiPlugged && m_wakeUp)
1222 OnWakeup();
1223 m_wakeUp = false;
1226 else if (action == CJNIIntent::ACTION_SCREEN_ON)
1228 // Sent when the device wakes up and becomes interactive.
1230 // For historical reasons, the name of this broadcast action refers to the power state of the
1231 // screen but it is actually sent in response to changes in the overall interactive state of
1232 // the device.
1233 CLog::Log(LOGINFO, "Got device wakeup intent");
1234 if (m_hdmiPlugged)
1235 OnWakeup();
1236 else
1237 // wake-up sequence continues in ACTION_HDMI_AUDIO_PLUG intent
1238 m_wakeUp = true;
1240 else if (action == CJNIIntent::ACTION_SCREEN_OFF)
1242 // Sent when the device goes to sleep and becomes non-interactive.
1244 // For historical reasons, the name of this broadcast action refers to the power state of the
1245 // screen but it is actually sent in response to changes in the overall interactive state of
1246 // the device.
1247 CLog::Log(LOGINFO, "Got device sleep intent");
1248 OnSleep();
1250 else if (action == CJNIIntent::ACTION_MEDIA_BUTTON)
1252 if (m_playback_state == PLAYBACK_STATE_STOPPED)
1254 CLog::Log(LOGINFO, "Ignore MEDIA_BUTTON intent: no media playing");
1255 return;
1257 CJNIKeyEvent keyevt = (CJNIKeyEvent)intent.getParcelableExtra(CJNIIntent::EXTRA_KEY_EVENT);
1259 int keycode = keyevt.getKeyCode();
1260 bool up = (keyevt.getAction() == CJNIKeyEvent::ACTION_UP);
1262 CLog::Log(LOGINFO, "Got MEDIA_BUTTON intent: {}, up:{}", keycode, up ? "true" : "false");
1263 if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_RECORD)
1264 CAndroidKey::XBMC_Key(keycode, XBMCK_RECORD, 0, 0, up);
1265 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_EJECT)
1266 CAndroidKey::XBMC_Key(keycode, XBMCK_EJECT, 0, 0, up);
1267 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_FAST_FORWARD)
1268 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_FASTFORWARD, 0, 0, up);
1269 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_NEXT)
1270 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_NEXT_TRACK, 0, 0, up);
1271 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PAUSE)
1272 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1273 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PLAY)
1274 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1275 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PLAY_PAUSE)
1276 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PLAY_PAUSE, 0, 0, up);
1277 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_PREVIOUS)
1278 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_PREV_TRACK, 0, 0, up);
1279 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_REWIND)
1280 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_REWIND, 0, 0, up);
1281 else if (keycode == CJNIKeyEvent::KEYCODE_MEDIA_STOP)
1282 CAndroidKey::XBMC_Key(keycode, XBMCK_MEDIA_STOP, 0, 0, up);
1286 void CXBMCApp::OnSleep()
1288 CLog::Log(LOGDEBUG, "CXBMCApp::OnSleep");
1289 IPowerSyscall* syscall = CServiceBroker::GetPowerManager().GetPowerSyscall();
1290 if (syscall)
1291 static_cast<CAndroidPowerSyscall*>(syscall)->SetSuspended();
1294 void CXBMCApp::OnWakeup()
1296 CLog::Log(LOGDEBUG, "CXBMCApp::OnWakeup");
1297 IPowerSyscall* syscall = CServiceBroker::GetPowerManager().GetPowerSyscall();
1298 if (syscall)
1299 static_cast<CAndroidPowerSyscall*>(syscall)->SetResumed();
1301 if (HasFocus())
1303 auto& components = CServiceBroker::GetAppComponents();
1304 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
1305 appPower->WakeUpScreenSaverAndDPMS();
1309 void CXBMCApp::onNewIntent(CJNIIntent intent)
1311 if (!intent)
1313 CLog::Log(LOGINFO, "CXBMCApp::onNewIntent - Got invalid intent.");
1314 return;
1317 std::string action = intent.getAction();
1318 CLog::Log(LOGDEBUG, "CXBMCApp::onNewIntent - Got intent. Action: {}", action);
1319 std::string targetFile = GetFilenameFromIntent(intent);
1320 if (!targetFile.empty() &&
1321 (action == CJNIIntent::ACTION_VIEW || action == CJNIIntent::ACTION_GET_CONTENT))
1323 CLog::Log(LOGDEBUG, "-- targetFile: {}", targetFile);
1325 CURL targeturl(targetFile);
1326 std::string value;
1327 if (action == CJNIIntent::ACTION_GET_CONTENT ||
1328 (targeturl.GetOption("showinfo", value) && value == "true"))
1330 if (targeturl.IsProtocol("videodb")
1331 || (targeturl.IsProtocol("special") && targetFile.find("playlists/video") != std::string::npos)
1332 || (targeturl.IsProtocol("special") && targetFile.find("playlists/mixed") != std::string::npos)
1335 std::vector<std::string> params;
1336 params.push_back(targeturl.Get());
1337 params.emplace_back("return");
1338 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTIVATE_WINDOW, WINDOW_VIDEO_NAV, 0,
1339 nullptr, "", params);
1341 else if (targeturl.IsProtocol("musicdb")
1342 || (targeturl.IsProtocol("special") && targetFile.find("playlists/music") != std::string::npos))
1344 std::vector<std::string> params;
1345 params.push_back(targeturl.Get());
1346 params.emplace_back("return");
1347 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTIVATE_WINDOW, WINDOW_MUSIC_NAV, 0,
1348 nullptr, "", params);
1351 else
1353 CFileItem* item = new CFileItem(targetFile, false);
1354 if (IsVideoDb(*item))
1356 *(item->GetVideoInfoTag()) = XFILE::CVideoDatabaseFile::GetVideoTag(CURL(item->GetPath()));
1357 item->SetPath(item->GetVideoInfoTag()->m_strFileNameAndPath);
1359 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item));
1362 else if (action == ACTION_XBMC_RESUME)
1364 if (m_playback_state != PLAYBACK_STATE_STOPPED)
1366 if (m_playback_state & PLAYBACK_STATE_VIDEO)
1367 RequestVisibleBehind(true);
1368 if (!(m_playback_state & PLAYBACK_STATE_PLAYING))
1369 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1370 static_cast<void*>(new CAction(ACTION_PAUSE)));
1375 void CXBMCApp::onActivityResult(int requestCode, int resultCode, CJNIIntent resultData)
1379 void CXBMCApp::onVisibleBehindCanceled()
1381 CLog::Log(LOGDEBUG, "Visible Behind Cancelled");
1382 m_hasReqVisible = false;
1384 // Pressing the pause button calls OnStop() (cf. https://code.google.com/p/android/issues/detail?id=186469)
1385 if ((m_playback_state & PLAYBACK_STATE_PLAYING))
1387 if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
1388 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1389 static_cast<void*>(new CAction(ACTION_STOP)));
1390 else if (m_playback_state & PLAYBACK_STATE_VIDEO)
1391 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1392 static_cast<void*>(new CAction(ACTION_PAUSE)));
1396 void CXBMCApp::onVolumeChanged(int volume)
1398 // don't do anything. User wants to use kodi's internal volume freely while
1399 // using the external volume to change it relatively
1400 // See: https://forum.kodi.tv/showthread.php?tid=350764
1403 void CXBMCApp::onAudioFocusChange(int focusChange)
1405 android_printf("Audio Focus changed: %d", focusChange);
1406 if (focusChange == CJNIAudioManager::AUDIOFOCUS_LOSS)
1408 if ((m_playback_state & PLAYBACK_STATE_PLAYING))
1410 if (m_playback_state & PLAYBACK_STATE_CANNOT_PAUSE)
1411 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1412 static_cast<void*>(new CAction(ACTION_STOP)));
1413 else
1414 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_GUI_ACTION, WINDOW_INVALID, -1,
1415 static_cast<void*>(new CAction(ACTION_PAUSE)));
1420 void CXBMCApp::InitFrameCallback(CVideoSyncAndroid* syncImpl)
1422 m_syncImpl = syncImpl;
1425 void CXBMCApp::DeinitFrameCallback()
1427 m_syncImpl = nullptr;
1430 void CXBMCApp::doFrame(int64_t frameTimeNanos)
1432 if (m_syncImpl)
1433 m_syncImpl->FrameCallback(frameTimeNanos);
1435 // Calculate the time, when next surface buffer should be rendered
1436 m_frameTimeNanos = frameTimeNanos;
1438 m_vsyncEvent.Set();
1441 int64_t CXBMCApp::GetNextFrameTime() const
1443 if (m_refreshRate > 0.0001f)
1444 return m_frameTimeNanos + static_cast<int64_t>(1500000000ll / m_refreshRate);
1445 else
1446 return m_frameTimeNanos;
1449 float CXBMCApp::GetFrameLatencyMs() const
1451 return (CurrentHostCounter() - m_frameTimeNanos) * 0.000001;
1454 bool CXBMCApp::WaitVSync(unsigned int milliSeconds)
1456 return m_vsyncEvent.Wait(std::chrono::milliseconds(milliSeconds));
1459 void CXBMCApp::SetupEnv()
1461 setenv("KODI_ANDROID_SYSTEM_LIBS", CJNISystem::getProperty("java.library.path").c_str(), 0);
1462 setenv("KODI_ANDROID_LIBS", getApplicationInfo().nativeLibraryDir.c_str(), 0);
1463 setenv("KODI_ANDROID_APK", getPackageResourcePath().c_str(), 0);
1465 std::string appName = CCompileInfo::GetAppName();
1466 StringUtils::ToLower(appName);
1467 std::string className = CCompileInfo::GetPackage();
1469 std::string cacheDir = getCacheDir().getAbsolutePath();
1470 std::string xbmcTemp = CJNISystem::getProperty("xbmc.temp", "");
1471 if (!xbmcTemp.empty())
1473 setenv("KODI_TEMP", xbmcTemp.c_str(), 0);
1476 std::string xbmcHome = CJNISystem::getProperty("xbmc.home", "");
1477 if (xbmcHome.empty())
1479 setenv("KODI_BIN_HOME", (cacheDir + "/apk/assets").c_str(), 0);
1480 setenv("KODI_HOME", (cacheDir + "/apk/assets").c_str(), 0);
1482 else
1484 setenv("KODI_BIN_HOME", (xbmcHome + "/assets").c_str(), 0);
1485 setenv("KODI_HOME", (xbmcHome + "/assets").c_str(), 0);
1487 setenv("KODI_BINADDON_PATH", (cacheDir + "/lib").c_str(), 0);
1489 std::string externalDir = CJNISystem::getProperty("xbmc.data", "");
1490 if (externalDir.empty())
1492 CJNIFile androidPath = getExternalFilesDir("");
1493 if (!androidPath)
1494 androidPath = getDir(className, 1);
1496 if (androidPath)
1497 externalDir = androidPath.getAbsolutePath();
1500 if (!externalDir.empty())
1501 setenv("HOME", externalDir.c_str(), 0);
1502 else
1503 setenv("HOME", getenv("KODI_TEMP"), 0);
1505 std::string pythonPath = cacheDir + "/apk/assets/python" + CCompileInfo::GetPythonVersion();
1506 setenv("PYTHONHOME", pythonPath.c_str(), 1);
1507 setenv("PYTHONPATH", "", 1);
1508 setenv("PYTHONOPTIMIZE","", 1);
1509 setenv("PYTHONNOUSERSITE", "1", 1);
1512 std::string CXBMCApp::GetFilenameFromIntent(const CJNIIntent &intent)
1514 std::string ret;
1515 if (!intent)
1516 return ret;
1517 CJNIURI data = intent.getData();
1518 if (!data)
1519 return ret;
1520 std::string scheme = data.getScheme();
1521 StringUtils::ToLower(scheme);
1522 if (scheme == "content")
1524 std::vector<std::string> filePathColumn;
1525 filePathColumn.push_back(CJNIMediaStoreMediaColumns::DATA);
1526 CJNICursor cursor = getContentResolver().query(data, filePathColumn, std::string(), std::vector<std::string>(), std::string());
1527 if(cursor.moveToFirst())
1529 int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
1530 ret = cursor.getString(columnIndex);
1532 cursor.close();
1534 else if(scheme == "file")
1535 ret = data.getPath();
1536 else
1537 ret = data.toString();
1538 return ret;
1541 std::shared_ptr<CNativeWindow> CXBMCApp::GetNativeWindow(int timeout) const
1543 if (!m_window)
1544 m_mainView->waitForSurface(timeout);
1546 return m_window;
1549 // The map must contain keys "id" and "color", both are integers
1550 void CXBMCApp::SetViewBackgroundColorCallback(void* mapVariant)
1552 CVariant* mapV = static_cast<CVariant*>(mapVariant);
1553 int viewId = (*mapV)["id"].asInteger();
1554 int color = (*mapV)["color"].asInteger();
1556 delete mapV;
1558 CJNIView view = findViewById(viewId);
1559 if (view)
1561 view.setBackgroundColor(color);
1565 void CXBMCApp::SetVideoLayoutBackgroundColor(const int color)
1567 CJNIResources resources = CJNIContext::getResources();
1568 if (resources)
1570 int id = resources.getIdentifier("VideoLayout", "id", CJNIContext::getPackageName());
1571 if (id > 0)
1573 // this object is deallocated in the callback
1574 CVariant* msg = new CVariant(CVariant::VariantTypeObject);
1575 (*msg)["id"] = id;
1576 (*msg)["color"] = color;
1578 runNativeOnUiThread(SetViewBackgroundColorCallback, msg);
1583 void CXBMCApp::RegisterInputDeviceCallbacks(IInputDeviceCallbacks* handler)
1585 if (handler != nullptr)
1586 m_inputDeviceCallbacks = handler;
1589 void CXBMCApp::UnregisterInputDeviceCallbacks()
1591 m_inputDeviceCallbacks = nullptr;
1594 void CXBMCApp::onInputDeviceAdded(int deviceId)
1596 android_printf("Input device added: %d", deviceId);
1598 if (m_inputDeviceCallbacks != nullptr)
1599 m_inputDeviceCallbacks->OnInputDeviceAdded(deviceId);
1602 void CXBMCApp::onInputDeviceChanged(int deviceId)
1604 android_printf("Input device changed: %d", deviceId);
1606 if (m_inputDeviceCallbacks != nullptr)
1607 m_inputDeviceCallbacks->OnInputDeviceChanged(deviceId);
1610 void CXBMCApp::onInputDeviceRemoved(int deviceId)
1612 android_printf("Input device removed: %d", deviceId);
1614 if (m_inputDeviceCallbacks != nullptr)
1615 m_inputDeviceCallbacks->OnInputDeviceRemoved(deviceId);
1618 void CXBMCApp::RegisterInputDeviceEventHandler(IInputDeviceEventHandler* handler)
1620 if (handler != nullptr)
1621 m_inputDeviceEventHandler = handler;
1624 void CXBMCApp::UnregisterInputDeviceEventHandler()
1626 m_inputDeviceEventHandler = nullptr;
1629 bool CXBMCApp::onInputDeviceEvent(const AInputEvent* event)
1631 if (m_inputDeviceEventHandler != nullptr)
1632 return m_inputDeviceEventHandler->OnInputDeviceEvent(event);
1634 return false;
1637 void CXBMCApp::onDisplayAdded(int displayId)
1639 android_printf("%s: ", __PRETTY_FUNCTION__);
1642 void CXBMCApp::onDisplayChanged(int displayId)
1644 CLog::Log(LOGDEBUG, "CXBMCApp::{}: id: {}", __FUNCTION__, displayId);
1646 if (!g_application.IsInitialized())
1647 // Display mode has beed changed during app startup; we want to reset audio engine on next ACTION_HDMI_AUDIO_PLUG event
1648 m_aeReset = true;
1650 // Update display modes
1651 CWinSystemAndroid* winSystemAndroid = dynamic_cast<CWinSystemAndroid*>(CServiceBroker::GetWinSystem());
1652 if (winSystemAndroid)
1653 winSystemAndroid->UpdateDisplayModes();
1655 m_displayChangeEvent.Set();
1656 m_inputHandler.setDPI(GetDPI());
1657 android_printf("%s: ", __PRETTY_FUNCTION__);
1660 void CXBMCApp::onDisplayRemoved(int displayId)
1662 android_printf("%s: ", __PRETTY_FUNCTION__);
1665 void CXBMCApp::surfaceChanged(CJNISurfaceHolder holder, int format, int width, int height)
1667 android_printf("%s: ", __PRETTY_FUNCTION__);
1670 void CXBMCApp::surfaceCreated(CJNISurfaceHolder holder)
1672 android_printf("%s: ", __PRETTY_FUNCTION__);
1674 m_window = CNativeWindow::CreateFromSurface(holder);
1675 if (m_window == nullptr)
1677 android_printf(" => invalid ANativeWindow object");
1678 return;
1681 if (!m_firstrun)
1682 XBMC_SetupDisplay();
1684 auto& components = CServiceBroker::GetAppComponents();
1685 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
1686 appPower->SetRenderGUI(true);
1689 void CXBMCApp::surfaceDestroyed(CJNISurfaceHolder holder)
1691 android_printf("%s: ", __PRETTY_FUNCTION__);
1692 // If we have exited XBMC, it no longer exists.
1693 auto& components = CServiceBroker::GetAppComponents();
1694 const auto appPower = components.GetComponent<CApplicationPowerHandling>();
1695 appPower->SetRenderGUI(false);
1696 if (!m_exiting)
1697 XBMC_DestroyDisplay();
1699 m_window.reset();