@@ -194,6 +194,8 @@
<browser_back>Backspace</browser_back>
<browser_back mod="longpress">PreviousMenu</browser_back>
<play_pause mod="longpress">Enter</play_pause>
<browser_search>VoiceRecognizer</browser_search>
<menu>VoiceRecognizer</menu>
</keyboard>
</VirtualKeyboard>
<TVChannels>
@@ -31,6 +31,7 @@ public class Main extends NativeActivity implements Choreographer.FrameCallback
private Intent mNewIntent = null;

native void _onNewIntent(Intent intent);
native void _onActivityResult(int requestCode, int resultCode, Intent resultData);
native void _doFrame(long frameTimeNanos);

private Runnable leanbackUpdateRunnable = new Runnable()
@@ -246,6 +247,14 @@ public class Main extends NativeActivity implements Choreographer.FrameCallback
super.onPause();
}

@Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData)
{
_onActivityResult(requestCode, resultCode, resultData);
}

@Override
public void onDestroy()
{
// unregister the InputDeviceListener implementation
@@ -3,7 +3,7 @@ DEPS= ../../Makefile.include Makefile

# lib name, version
LIBNAME=libandroidjni
VERSION=92131093edc024e4dd957f76d3b302d493ae625d
VERSION=746a3e231e42b7407d896e824497124403394974
SOURCE=archive
ARCHIVE=$(VERSION).tar.gz
GIT_BASE_URL=https://github.com/xbmc
@@ -39,6 +39,17 @@
#include "messaging/ApplicationMessenger.h"
#include "utils/CharsetConverter.h"
#include "windowing/WindowingFactory.h"
#include "utils/log.h"

#ifdef TARGET_ANDROID
#include <androidjni/Intent.h>
#include <androidjni/RecognizerIntent.h>
#include <androidjni/ArrayList.h>
#include "platform/android/activity/XBMCApp.h"

#define ACTION_RECOGNIZE_SPEECH_REQID 543

#endif

using namespace KODI::MESSAGING;

@@ -200,6 +211,8 @@ bool CGUIDialogKeyboardGeneric::OnAction(const CAction &action)
action.GetID() == ACTION_MOVE_RIGHT ||
action.GetID() == ACTION_SELECT_ITEM))
handled = false;
else if (action.GetID() == ACTION_VOICE_RECOGNIZE)
OnVoiceRecognition();
else
{
std::wstring wch = L"";
@@ -571,6 +584,21 @@ void CGUIDialogKeyboardGeneric::OnIPAddress()
SetEditText(text.substr(0, start) + ip.c_str() + text.substr(start + length));
}

void CGUIDialogKeyboardGeneric::OnVoiceRecognition()
{
#ifdef TARGET_ANDROID
CJNIIntent intent = CJNIIntent(CJNIRecognizerIntent::ACTION_RECOGNIZE_SPEECH);
intent.putExtra(CJNIRecognizerIntent::EXTRA_LANGUAGE_MODEL, CJNIRecognizerIntent::LANGUAGE_MODEL_FREE_FORM);
CJNIIntent result;
if (CXBMCApp::WaitForActivityResult(intent, ACTION_RECOGNIZE_SPEECH_REQID, result) == CJNIBase::RESULT_OK)
{
CJNIArrayList<std::string> guesses = result.getStringArrayListExtra(CJNIRecognizerIntent::EXTRA_RESULTS);
if (guesses.size())
SetEditText(guesses.get(0));
}
#endif
}

void CGUIDialogKeyboardGeneric::SetControlLabel(int id, const std::string &label)
{ // find all controls with this id, and set all their labels
CGUIMessage message(GUI_MSG_LABEL_SET, GetID(), id);
@@ -60,6 +60,7 @@ class CGUIDialogKeyboardGeneric : public CGUIDialog, public CGUIKeyboard
void OnLayout();
void OnSymbols();
void OnIPAddress();
void OnVoiceRecognition();
void OnOK();

private:
@@ -272,6 +272,10 @@
#define ACTION_VOLUME_SET 245
#define ACTION_TOGGLE_COMMSKIP 246

// Voice actions
#define ACTION_VOICE_RECOGNIZE 300

// Touch actions
#define ACTION_TOUCH_TAP 401 //!< touch actions
#define ACTION_TOUCH_TAP_TEN 410 //!< touch actions
#define ACTION_TOUCH_LONGPRESS 411 //!< touch actions
@@ -264,6 +264,9 @@ static const ActionMapping actions[] =
{ "swipeup" , ACTION_GESTURE_SWIPE_UP },
{ "swipedown" , ACTION_GESTURE_SWIPE_DOWN },

// Voice
{ "voicerecognizer" , ACTION_VOICE_RECOGNIZE },

// Do nothing / error action
{ "error" , ACTION_ERROR },
{ "noop" , ACTION_NOOP }
@@ -572,7 +572,8 @@ bool CInputManager::OnKey(const CKey& key)
action.GetID() == ACTION_SELECT_ITEM ||
action.GetID() == ACTION_ENTER ||
action.GetID() == ACTION_PREVIOUS_MENU ||
action.GetID() == ACTION_NAV_BACK))
action.GetID() == ACTION_NAV_BACK ||
action.GetID() == ACTION_VOICE_RECOGNIZE))
{
// the action isn't plain navigation - check for a keyboard-specific keymap
action = CButtonTranslator::GetInstance().GetAction(WINDOW_DIALOG_KEYBOARD, key, false);
@@ -118,7 +118,6 @@ static KeyMap keyMap[] = {
{ AKEYCODE_PLUS , XBMCK_PLUS },
{ AKEYCODE_MENU , XBMCK_MENU },
{ AKEYCODE_NOTIFICATION , XBMCK_LAST },
{ AKEYCODE_SEARCH , XBMCK_LAST },
{ AKEYCODE_MUTE , XBMCK_LAST },
{ AKEYCODE_PAGE_UP , XBMCK_PAGEUP },
{ AKEYCODE_PAGE_DOWN , XBMCK_PAGEDOWN },
@@ -182,7 +181,12 @@ static KeyMap MediakeyMap[] = {
{ AKEYCODE_MEDIA_EJECT , XBMCK_EJECT },
};

static KeyMap SearchkeyMap[] = {
{ AKEYCODE_SEARCH , XBMCK_BROWSER_SEARCH },
};

bool CAndroidKey::m_handleMediaKeys = false;
bool CAndroidKey::m_handleSearchKeys = false;

bool CAndroidKey::onKeyboardEvent(AInputEvent *event)
{
@@ -227,6 +231,18 @@ bool CAndroidKey::onKeyboardEvent(AInputEvent *event)
}
}

if (sym == XBMCK_UNKNOWN && m_handleSearchKeys)
{
for (unsigned int index = 0; index < sizeof(SearchkeyMap) / sizeof(KeyMap); index++)
{
if (keycode == SearchkeyMap[index].nativeKey)
{
sym = SearchkeyMap[index].xbmcKey;
break;
}
}
}

// check if this is a key we don't want to handle
if (sym == XBMCK_LAST || sym == XBMCK_UNKNOWN)
{
@@ -34,8 +34,10 @@ class CAndroidKey
bool onKeyboardEvent(AInputEvent *event);

static void SetHandleMediaKeys(bool enable) { m_handleMediaKeys = enable; }
static void SetHandleSearchKeys(bool enable) { m_handleSearchKeys = enable; }
static void XBMC_Key(uint8_t code, uint16_t key, uint16_t modifiers, uint16_t unicode, bool up);

protected:
static bool m_handleMediaKeys;
static bool m_handleSearchKeys;
};
@@ -47,6 +47,14 @@ void CJNIMainActivity::_onNewIntent(JNIEnv *env, jobject context, jobject intent
m_appInstance->onNewIntent(CJNIIntent(jhobject(intent)));
}

void CJNIMainActivity::_onActivityResult(JNIEnv *env, jobject context, jint requestCode, jint resultCode, jobject resultData)
{
(void)env;
(void)context;
if (m_appInstance)
m_appInstance->onActivityResult(requestCode, resultCode, CJNIIntent(jhobject(resultData)));
}

void CJNIMainActivity::_callNative(JNIEnv *env, jobject context, jlong funcAddr, jlong variantAddr)
{
(void)env;
@@ -33,6 +33,7 @@ class CJNIMainActivity : public CJNIActivity, public CJNIInputManagerInputDevice
static CJNIMainActivity* GetAppInstance() { return m_appInstance; }

static void _onNewIntent(JNIEnv *env, jobject context, jobject intent);
static void _onActivityResult(JNIEnv *env, jobject context, jint requestCode, jint resultCode, jobject resultData);
static void _onVolumeChanged(JNIEnv *env, jobject context, jint volume);
static void _onAudioFocusChange(JNIEnv *env, jobject context, jint focusChange);
static void _doFrame(JNIEnv *env, jobject context, jlong frameTimeNanos);
@@ -55,6 +56,7 @@ class CJNIMainActivity : public CJNIActivity, public CJNIInputManagerInputDevice

protected:
virtual void onNewIntent(CJNIIntent intent)=0;
virtual void onActivityResult(int requestCode, int resultCode, CJNIIntent resultData)=0;
virtual void onVolumeChanged(int volume)=0;
virtual void onAudioFocusChange(int focusChange)=0;
virtual void doFrame(int64_t frameTimeNanos)=0;
@@ -61,6 +61,7 @@

#include "AndroidKey.h"
#include "settings/AdvancedSettings.h"
#include "interfaces/AnnouncementManager.h"
#include "Application.h"
#include "AppParamParser.h"
#include "messaging/ApplicationMessenger.h"
@@ -91,6 +92,7 @@
#define GIGABYTES 1073741824

using namespace KODI::MESSAGING;
using namespace ANNOUNCEMENT;

template<class T, void(T::*fn)()>
void* thread_run(void* obj)
@@ -113,6 +115,7 @@ CCriticalSection CXBMCApp::m_applicationsMutex;
std::vector<androidPackage> CXBMCApp::m_applications;
CVideoSyncAndroid* CXBMCApp::m_syncImpl = NULL;
CEvent CXBMCApp::m_vsyncEvent;
std::vector<CActivityResultEvent*> CXBMCApp::m_activityResultEvents;


CXBMCApp::CXBMCApp(ANativeActivity* nativeActivity)
@@ -137,6 +140,17 @@ CXBMCApp::~CXBMCApp()
delete m_wakeLock;
}

void CXBMCApp::Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data)
{
if ((flag & Input) && strcmp(sender, "xbmc") == 0)
{
if (strcmp(message, "OnInputRequested") == 0)
CAndroidKey::SetHandleSearchKeys(true);
else if (strcmp(message, "OnInputFinished") == 0)
CAndroidKey::SetHandleSearchKeys(false);
}
}

void CXBMCApp::onStart()
{
android_printf("%s: ", __PRETTY_FUNCTION__);
@@ -286,6 +300,15 @@ void CXBMCApp::onLostFocus()
m_hasFocus = false;
}

void CXBMCApp::Initialize()
{
g_application.m_ServiceManager->GetAnnouncementManager().AddAnnouncer(CXBMCApp::get());
}

void CXBMCApp::Deinitialize()
{
}

bool CXBMCApp::EnableWakeLock(bool on)
{
android_printf("%s: %s", __PRETTY_FUNCTION__, on ? "true" : "false");
@@ -867,6 +890,36 @@ void CXBMCApp::onNewIntent(CJNIIntent intent)
}
}

void CXBMCApp::onActivityResult(int requestCode, int resultCode, CJNIIntent resultData)
{
for (auto it = m_activityResultEvents.begin(); it != m_activityResultEvents.end(); ++it)
{
if ((*it)->GetRequestCode() == requestCode)
{
m_activityResultEvents.erase(it);
(*it)->SetResultCode(resultCode);
(*it)->SetResultData(resultData);
(*it)->Set();
break;
}
}
}

int CXBMCApp::WaitForActivityResult(const CJNIIntent &intent, int requestCode, CJNIIntent &result)
{
int ret = 0;
CActivityResultEvent* event = new CActivityResultEvent(requestCode);
m_activityResultEvents.push_back(event);
startActivityForResult(intent, requestCode);
if (event->Wait())
{
result = event->GetResultData();
ret = event->GetResultCode();
}
delete event;
return ret;
}

void CXBMCApp::onVolumeChanged(int volume)
{
// System volume was used; Reset Kodi volume to 100% if it isn't, already
@@ -25,6 +25,7 @@
#include <pthread.h>
#include <string>
#include <vector>
#include <map>

#include <android/native_activity.h>

@@ -34,6 +35,8 @@
#include <androidjni/View.h>

#include "threads/Event.h"
#include "interfaces/IAnnouncer.h"

#include "guilib/Geometry.h"
#include "IActivityHandler.h"
#include "IInputHandler.h"
@@ -63,15 +66,41 @@ struct androidPackage
int icon;
};

class CXBMCApp : public IActivityHandler, public CJNIMainActivity,
public CJNIBroadcastReceiver,
public CJNIAudioManagerAudioFocusChangeListener
class CActivityResultEvent : public CEvent
{
public:
CActivityResultEvent(int requestcode)
: m_requestcode(requestcode)
{}
int GetRequestCode() const { return m_requestcode; }
int GetResultCode() const { return m_resultcode; }
void SetResultCode(int resultcode) { m_resultcode = resultcode; }
CJNIIntent GetResultData() const { return m_resultdata; }
void SetResultData(const CJNIIntent &resultdata) { m_resultdata = resultdata; }

protected:
int m_requestcode;
CJNIIntent m_resultdata;
int m_resultcode;
};

class CXBMCApp
: public IActivityHandler
, public CJNIMainActivity
, public CJNIBroadcastReceiver
, public CJNIAudioManagerAudioFocusChangeListener
, public ANNOUNCEMENT::IAnnouncer
{
public:
CXBMCApp(ANativeActivity *nativeActivity);
virtual ~CXBMCApp();

// IAnnouncer IF
virtual void Announce(ANNOUNCEMENT::AnnouncementFlag flag, const char *sender, const char *message, const CVariant &data);

virtual void onReceive(CJNIIntent intent);
virtual void onNewIntent(CJNIIntent intent);
virtual void onActivityResult(int requestCode, int resultCode, CJNIIntent resultData);
virtual void onVolumeChanged(int volume);
virtual void onAudioFocusChange(int focusChange);
virtual void doFrame(int64_t frameTimeNanos);
@@ -100,6 +129,8 @@ class CXBMCApp : public IActivityHandler, public CJNIMainActivity,
void onGainFocus();
void onLostFocus();

void Initialize();
void Deinitialize();

static const ANativeWindow** GetNativeWindow(int timeout);
static int SetBuffersGeometry(int width, int height, int format);
@@ -131,6 +162,7 @@ class CXBMCApp : public IActivityHandler, public CJNIMainActivity,
static int GetDPI();

static CRect MapRenderToDroid(const CRect& srcRect);
static int WaitForActivityResult(const CJNIIntent &intent, int requestCode, CJNIIntent& result);

// Playback callbacks
static void OnPlayBackStarted();
@@ -185,6 +217,7 @@ class CXBMCApp : public IActivityHandler, public CJNIMainActivity,
pthread_t m_thread;
static CCriticalSection m_applicationsMutex;
static std::vector<androidPackage> m_applications;
static std::vector<CActivityResultEvent*> m_activityResultEvents;

static ANativeWindow* m_window;
static CEvent m_windowCreated;