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.
11 #include "AndroidKey.h"
12 #include "CompileInfo.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"
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>
103 #include <rapidjson/document.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
;
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());
124 return std::shared_ptr
<CNativeWindow
>(new CNativeWindow(window
));
129 CNativeWindow::CNativeWindow(ANativeWindow
* window
) : m_window(window
)
133 CNativeWindow::~CNativeWindow()
136 ANativeWindow_release(m_window
);
139 bool CNativeWindow::SetBuffersGeometry(int width
, int height
, int format
)
142 return (ANativeWindow_setBuffersGeometry(m_window
, width
, height
, format
) == 0);
147 int32_t CNativeWindow::GetWidth() const
150 return ANativeWindow_getWidth(m_window
);
155 int32_t CNativeWindow::GetHeight() const
158 return ANativeWindow_getHeight(m_window
);
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");
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();
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
)
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")
209 else if (message
== "OnPause")
211 else if (message
== "OnStop")
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__
);
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"
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);
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
)
296 return audioManager
.isWiredHeadsetOn() || audioManager
.isBluetoothA2dpOn();
301 void CXBMCApp::onResume()
303 android_printf("%s: ", __PRETTY_FUNCTION__
);
305 if (g_application
.IsInitialized() &&
306 CServiceBroker::GetWinSystem()->GetOSScreenSaver()->IsInhibited())
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;
367 CGUIComponent
* gui
= CServiceBroker::GetGUI();
370 gui
->GetWindowManager().SwitchToFullScreen(true);
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__
);
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__
);
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__
);
455 void CXBMCApp::RegisterDisplayListenerCallback(void*)
457 CJNIDisplayManager
displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE
));
460 android_printf("CXBMCApp: installing DisplayManager::DisplayListener");
461 displayManager
.registerDisplayListener(CXBMCApp::Get().getDisplayListener());
465 void CXBMCApp::UnregisterDisplayListener()
467 CJNIDisplayManager
displayManager(getSystemService(CJNIContext::DISPLAY_SERVICE
));
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
)
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...");
513 case EXITCODE_POWERDOWN
:
514 msgId
= TMSG_POWERDOWN
;
516 case EXITCODE_REBOOT
:
517 msgId
= TMSG_RESTART
;
519 case EXITCODE_RESTARTAPP
:
520 msgId
= TMSG_RESTARTAPP
;
523 CLog::Log(LOGWARNING
, "CXBMCApp::Stop : Unexpected exit code. Defaulting to QUIT.");
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
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();
544 CJNIWindow window
= getWindow();
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
));
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());
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");
599 bool CXBMCApp::ReleaseAudioFocus()
601 CJNIAudioManager
audioManager(getSystemService(CJNIContext::AUDIO_SERVICE
));
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());
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");
635 void CXBMCApp::RequestVisibleBehind(bool requested
)
637 if (requested
== m_hasReqVisible
)
640 m_hasReqVisible
= requestVisibleBehind(requested
);
641 CLog::Log(LOGDEBUG
, "Visible Behind request: {}", m_hasReqVisible
? "true" : "false");
650 // Wait for main window
651 if (!GetNativeWindow(30000))
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()");
668 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_DISPLAY_SETUP
, -1, -1,
669 static_cast<void*>(&result
));
673 bool CXBMCApp::XBMC_DestroyDisplay()
675 android_printf("XBMC_DestroyDisplay()");
677 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_DISPLAY_DESTROY
, -1, -1,
678 static_cast<void*>(&result
));
682 bool CXBMCApp::SetBuffersGeometry(int width
, int height
, int format
)
685 return m_window
->SetBuffersGeometry(width
, height
, format
);
690 void CXBMCApp::SetDisplayModeCallback(void* modeVariant
)
692 CVariant
* modeV
= static_cast<CVariant
*>(modeVariant
);
693 int mode
= (*modeV
)["mode"].asInteger();
696 CJNIWindow window
= getWindow();
699 CJNIWindowManagerLayoutParams params
= window
.getAttributes();
700 if (params
.getpreferredDisplayModeId() != mode
)
702 params
.setpreferredDisplayModeId(mode
);
703 window
.setAttributes(params
);
707 CXBMCApp::Get().m_displayChangeEvent
.Set();
710 void CXBMCApp::SetDisplayMode(int mode
, float rate
)
715 CJNIWindow window
= getWindow();
718 CJNIWindowManagerLayoutParams params
= window
.getAttributes();
719 if (params
.getpreferredDisplayModeId() == mode
)
723 m_displayChangeEvent
.Reset();
726 dynamic_cast<CWinSystemAndroid
*>(CServiceBroker::GetWinSystem())->InitiateModeChange();
728 std::map
<std::string
, CVariant
> vmap
;
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
);
745 if (CServiceBroker::IsLoggingUp())
748 int len
= vsnprintf(0, 0, format
, args_copy
);
750 result
= vsnprintf(&message
[0], len
+ 1, format
, args
);
751 CLog::Log(LOGDEBUG
, "{}", message
);
755 result
= __android_log_vprint(ANDROID_LOG_VERBOSE
, "Kodi", format
, args
);
764 int CXBMCApp::GetDPI() const
766 if (m_activity
->assetManager
== nullptr)
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
);
779 CRect
CXBMCApp::MapRenderToDroid(const CRect
& srcRect
)
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
;
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)
813 if (m_playback_state
& PLAYBACK_STATE_VIDEO
)
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
)
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
);
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
;
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
;
857 m_playback_state
&= ~PLAYBACK_STATE_VIDEO
;
859 if (appPlayer
->HasAudio())
860 m_playback_state
|= PLAYBACK_STATE_AUDIO
;
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
;
870 state
= CJNIPlaybackState::STATE_PAUSED
;
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
);
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);
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);
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
++)
967 GetPackageManager().getLaunchIntentForPackage(packageList
.get(i
).packageName
);
970 GetPackageManager().getLeanbackLaunchIntentForPackage(packageList
.get(i
).packageName
);
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
) :
1011 if (intent
.empty() && GetPackageManager().hasSystemFeature(CJNIPackageManager::FEATURE_LEANBACK
))
1013 CJNIIntent leanbackIntent
= GetPackageManager().getLeanbackLaunchIntentForPackage(package
);
1015 newIntent
= leanbackIntent
;
1021 if (!dataURI
.empty())
1023 CJNIURI jniURI
= CJNIURI::parse(dataURI
);
1028 newIntent
.setDataAndType(jniURI
, dataType
);
1031 if (!action
.empty())
1032 newIntent
.setAction(action
);
1034 if (!category
.empty())
1035 newIntent
.addCategory(category
);
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());
1055 CLog::LogF(LOGDEBUG
, "Invalid intent extras format: Needs to be an array");
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");
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());
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();
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);
1112 int CXBMCApp::GetMaxSystemVolume(JNIEnv
*env
)
1114 CJNIAudioManager
audioManager(getSystemService(CJNIContext::AUDIO_SERVICE
));
1116 return audioManager
.getStreamMaxVolume();
1117 android_printf("CXBMCApp::SetSystemVolume: Could not get Audio Manager");
1121 float CXBMCApp::GetSystemVolume()
1123 CJNIAudioManager
audioManager(getSystemService(CJNIContext::AUDIO_SERVICE
));
1125 return (float)audioManager
.getStreamVolume() / GetMaxSystemVolume();
1128 android_printf("CXBMCApp::GetSystemVolume: Could not get Audio Manager");
1133 void CXBMCApp::SetSystemVolume(float percent
)
1135 CJNIAudioManager
audioManager(getSystemService(CJNIContext::AUDIO_SERVICE
));
1136 int maxVolume
= (int)(GetMaxSystemVolume() * percent
);
1138 audioManager
.setStreamVolume(maxVolume
);
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",
1157 if (action
== CJNIIntent::ACTION_BATTERY_CHANGED
)
1158 m_batteryLevel
= intent
.getIntExtra("level", -1);
1159 else if (action
== CJNIIntent::ACTION_DREAMING_STOPPED
)
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
)));
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();
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();
1220 if (m_hdmiPlugged
&& m_wakeUp
)
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
1233 CLog::Log(LOGINFO
, "Got device wakeup intent");
1237 // wake-up sequence continues in ACTION_HDMI_AUDIO_PLUG intent
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
1247 CLog::Log(LOGINFO
, "Got device sleep intent");
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");
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();
1291 static_cast<CAndroidPowerSyscall
*>(syscall
)->SetSuspended();
1294 void CXBMCApp::OnWakeup()
1296 CLog::Log(LOGDEBUG
, "CXBMCApp::OnWakeup");
1297 IPowerSyscall
* syscall
= CServiceBroker::GetPowerManager().GetPowerSyscall();
1299 static_cast<CAndroidPowerSyscall
*>(syscall
)->SetResumed();
1303 auto& components
= CServiceBroker::GetAppComponents();
1304 const auto appPower
= components
.GetComponent
<CApplicationPowerHandling
>();
1305 appPower
->WakeUpScreenSaverAndDPMS();
1309 void CXBMCApp::onNewIntent(CJNIIntent intent
)
1313 CLog::Log(LOGINFO
, "CXBMCApp::onNewIntent - Got invalid intent.");
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
);
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
);
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
)));
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
)
1433 m_syncImpl
->FrameCallback(frameTimeNanos
);
1435 // Calculate the time, when next surface buffer should be rendered
1436 m_frameTimeNanos
= frameTimeNanos
;
1441 int64_t CXBMCApp::GetNextFrameTime() const
1443 if (m_refreshRate
> 0.0001f
)
1444 return m_frameTimeNanos
+ static_cast<int64_t>(1500000000ll / m_refreshRate
);
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);
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("");
1494 androidPath
= getDir(className
, 1);
1497 externalDir
= androidPath
.getAbsolutePath();
1500 if (!externalDir
.empty())
1501 setenv("HOME", externalDir
.c_str(), 0);
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
)
1517 CJNIURI data
= intent
.getData();
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
);
1534 else if(scheme
== "file")
1535 ret
= data
.getPath();
1537 ret
= data
.toString();
1541 std::shared_ptr
<CNativeWindow
> CXBMCApp::GetNativeWindow(int timeout
) const
1544 m_mainView
->waitForSurface(timeout
);
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();
1558 CJNIView view
= findViewById(viewId
);
1561 view
.setBackgroundColor(color
);
1565 void CXBMCApp::SetVideoLayoutBackgroundColor(const int color
)
1567 CJNIResources resources
= CJNIContext::getResources();
1570 int id
= resources
.getIdentifier("VideoLayout", "id", CJNIContext::getPackageName());
1573 // this object is deallocated in the callback
1574 CVariant
* msg
= new CVariant(CVariant::VariantTypeObject
);
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
);
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
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");
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);
1697 XBMC_DestroyDisplay();