diff --git a/android_webview/Android.mk b/android_webview/Android.mk index dd9e13b843d970..525fb8422e99a8 100644 --- a/android_webview/Android.mk +++ b/android_webview/Android.mk @@ -61,6 +61,8 @@ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chro $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/GestureEventType.java \ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/PageTransitionTypes.java \ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/SpeechRecognitionError.java \ +$(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/input/CanonicalAxisIndex.java \ +$(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/input/CanonicalButtonIndex.java \ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/browser/input/PopupItemType.java \ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/common/ResultCodes.java \ $(call intermediates-dir-for,GYP,shared,,,$(TARGET_2ND_ARCH))/templates/org/chromium/content/common/ScreenOrientationValues.java \ diff --git a/android_webview/android_webview.gyp b/android_webview/android_webview.gyp index d8fd181f24c361..2c64cf8f1f3910 100644 --- a/android_webview/android_webview.gyp +++ b/android_webview/android_webview.gyp @@ -26,6 +26,7 @@ 'dependencies': [ '../base/base.gyp:base_java_application_state', '../base/base.gyp:base_java_memory_pressure_level_list', + '../content/content.gyp:content_gamepad_mapping', '../content/content.gyp:gesture_event_type_java', '../content/content.gyp:page_transition_types_java', '../content/content.gyp:popup_item_type_java', diff --git a/base/android/jni_generator/jni_generator.py b/base/android/jni_generator/jni_generator.py index a1a654a472bbcf..757714c1c9a7c7 100755 --- a/base/android/jni_generator/jni_generator.py +++ b/base/android/jni_generator/jni_generator.py @@ -408,6 +408,7 @@ def GetStaticCastForReturnType(return_type): 'short[]': 'jshortArray', 'int[]': 'jintArray', 'long[]': 'jlongArray', + 'float[]': 'jfloatArray', 'double[]': 'jdoubleArray' } ret = type_map.get(return_type, None) if ret: diff --git a/content/browser/android/browser_jni_registrar.cc b/content/browser/android/browser_jni_registrar.cc index 34314796c67d9d..47ff5d1dd47f5c 100644 --- a/content/browser/android/browser_jni_registrar.cc +++ b/content/browser/android/browser_jni_registrar.cc @@ -26,6 +26,7 @@ #include "content/browser/battery_status/battery_status_manager.h" #include "content/browser/device_sensors/sensor_manager_android.h" #include "content/browser/frame_host/navigation_controller_android.h" +#include "content/browser/gamepad/gamepad_platform_data_fetcher_android.h" #include "content/browser/geolocation/location_api_adapter_android.h" #include "content/browser/media/android/media_drm_credential_manager.h" #include "content/browser/media/android/media_resource_getter_impl.h" @@ -62,6 +63,8 @@ base::android::RegistrationMethod kContentRegisteredMethods[] = { {"DateTimePickerAndroid", content::RegisterDateTimeChooserAndroid}, {"DownloadControllerAndroidImpl", content::DownloadControllerAndroidImpl::RegisterDownloadController}, + {"GamepadList", content::GamepadPlatformDataFetcherAndroid:: + RegisterGamepadPlatformDataFetcherAndroid}, {"InterstitialPageDelegateAndroid", content::InterstitialPageDelegateAndroid:: RegisterInterstitialPageDelegateAndroid}, diff --git a/content/browser/gamepad/canonical_axis_index_list.h b/content/browser/gamepad/canonical_axis_index_list.h new file mode 100644 index 00000000000000..58b85a6e18928e --- /dev/null +++ b/content/browser/gamepad/canonical_axis_index_list.h @@ -0,0 +1,16 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum values. + +// This file defines the canonical axes mapping order for gamepad-like devices. + +// TODO(SaurabhK): Consolidate with CanonicalAxisIndex enum in +// gamepad_standard_mappings.h, crbug.com/351558. +CANONICAL_AXIS_INDEX(AXIS_LEFT_STICK_X, 0) +CANONICAL_AXIS_INDEX(AXIS_LEFT_STICK_Y, 1) +CANONICAL_AXIS_INDEX(AXIS_RIGHT_STICK_X, 2) +CANONICAL_AXIS_INDEX(AXIS_RIGHT_STICK_Y, 3) +CANONICAL_AXIS_INDEX(NUM_CANONICAL_AXES, 4) diff --git a/content/browser/gamepad/canonical_button_index_list.h b/content/browser/gamepad/canonical_button_index_list.h new file mode 100644 index 00000000000000..153ce0ea3dddb9 --- /dev/null +++ b/content/browser/gamepad/canonical_button_index_list.h @@ -0,0 +1,28 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This file intentionally does not have header guards, it's included +// inside a macro to generate enum values. +// This defines the canonical button mapping order for gamepad-like devices. + +// TODO(SaurabhK): Consolidate with CanonicalButtonIndex enum in +// gamepad_standard_mappings.h, crbug.com/351558. +CANONICAL_BUTTON_INDEX(BUTTON_PRIMARY, 0) +CANONICAL_BUTTON_INDEX(BUTTON_SECONDARY, 1) +CANONICAL_BUTTON_INDEX(BUTTON_TERTIARY, 2) +CANONICAL_BUTTON_INDEX(BUTTON_QUATERNARY, 3) +CANONICAL_BUTTON_INDEX(BUTTON_LEFT_SHOULDER, 4) +CANONICAL_BUTTON_INDEX(BUTTON_RIGHT_SHOULDER, 5) +CANONICAL_BUTTON_INDEX(BUTTON_LEFT_TRIGGER, 6) +CANONICAL_BUTTON_INDEX(BUTTON_RIGHT_TRIGGER, 7) +CANONICAL_BUTTON_INDEX(BUTTON_BACK_SELECT, 8) +CANONICAL_BUTTON_INDEX(BUTTON_START, 9) +CANONICAL_BUTTON_INDEX(BUTTON_LEFT_THUMBSTICK, 10) +CANONICAL_BUTTON_INDEX(BUTTON_RIGHT_THUMBSTICK, 11) +CANONICAL_BUTTON_INDEX(BUTTON_DPAD_UP, 12) +CANONICAL_BUTTON_INDEX(BUTTON_DPAD_DOWN, 13) +CANONICAL_BUTTON_INDEX(BUTTON_DPAD_LEFT, 14) +CANONICAL_BUTTON_INDEX(BUTTON_DPAD_RIGHT, 15) +CANONICAL_BUTTON_INDEX(BUTTON_META, 16) +CANONICAL_BUTTON_INDEX(NUM_CANONICAL_BUTTONS, 17) \ No newline at end of file diff --git a/content/browser/gamepad/gamepad_platform_data_fetcher.h b/content/browser/gamepad/gamepad_platform_data_fetcher.h index 247306295e78dc..f3cf7cc7055ee5 100644 --- a/content/browser/gamepad/gamepad_platform_data_fetcher.h +++ b/content/browser/gamepad/gamepad_platform_data_fetcher.h @@ -12,7 +12,9 @@ #include "base/compiler_specific.h" #include "content/browser/gamepad/gamepad_data_fetcher.h" -#if defined(OS_WIN) +#if defined(OS_ANDROID) +#include "content/browser/gamepad/gamepad_platform_data_fetcher_android.h" +#elif defined(OS_WIN) #include "content/browser/gamepad/gamepad_platform_data_fetcher_win.h" #elif defined(OS_MACOSX) #include "content/browser/gamepad/gamepad_platform_data_fetcher_mac.h" @@ -22,7 +24,11 @@ namespace content { -#if defined(OS_WIN) +#if defined(OS_ANDROID) + +typedef GamepadPlatformDataFetcherAndroid GamepadPlatformDataFetcher; + +#elif defined(OS_WIN) typedef GamepadPlatformDataFetcherWin GamepadPlatformDataFetcher; diff --git a/content/browser/gamepad/gamepad_platform_data_fetcher_android.cc b/content/browser/gamepad/gamepad_platform_data_fetcher_android.cc new file mode 100644 index 00000000000000..a8673fa32524e9 --- /dev/null +++ b/content/browser/gamepad/gamepad_platform_data_fetcher_android.cc @@ -0,0 +1,149 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "content/browser/gamepad/gamepad_platform_data_fetcher_android.h" + +#include "base/android/jni_android.h" +#include "base/android/jni_array.h" +#include "base/android/jni_string.h" +#include "base/debug/trace_event.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "base/strings/utf_string_conversions.h" + +#include "jni/GamepadList_jni.h" + +#include "third_party/WebKit/public/platform/WebGamepads.h" + +using base::android::AttachCurrentThread; +using base::android::CheckException; +using base::android::ClearException; +using base::android::ConvertJavaStringToUTF8; +using base::android::ScopedJavaLocalRef; +using blink::WebGamepad; +using blink::WebGamepads; + +namespace content { + +bool +GamepadPlatformDataFetcherAndroid::RegisterGamepadPlatformDataFetcherAndroid( + JNIEnv* env) { + return RegisterNativesImpl(env); +} + +GamepadPlatformDataFetcherAndroid::GamepadPlatformDataFetcherAndroid() { + PauseHint(false); +} + +GamepadPlatformDataFetcherAndroid::~GamepadPlatformDataFetcherAndroid() { + PauseHint(true); +} + +void GamepadPlatformDataFetcherAndroid::GetGamepadData( + blink::WebGamepads* pads, + bool devices_changed_hint) { + TRACE_EVENT0("GAMEPAD", "GetGamepadData"); + + pads->length = 0; + + JNIEnv* env = AttachCurrentThread(); + if (!env) + return; + + Java_GamepadList_updateGamepadData(env, reinterpret_cast(pads)); +} + +void GamepadPlatformDataFetcherAndroid::PauseHint(bool paused) { + JNIEnv* env = AttachCurrentThread(); + if (!env) + return; + + Java_GamepadList_notifyForGamepadsAccess(env, paused); +} + +static void SetGamepadData(JNIEnv* env, + jobject obj, + jlong gamepads, + jint index, + jboolean mapping, + jboolean connected, + jstring devicename, + jlong timestamp, + jfloatArray jaxes, + jfloatArray jbuttons) { + DCHECK(gamepads); + blink::WebGamepads* pads = reinterpret_cast(gamepads); + DCHECK_EQ(pads->length, unsigned(index)); + DCHECK_LT(index, static_cast(blink::WebGamepads::itemsLengthCap)); + + ++pads->length; + + blink::WebGamepad& pad = pads->items[index]; + + pad.connected = connected; + + pad.timestamp = timestamp; + + // Do not set gamepad parameters for all the gamepad devices that are not + // attached. + if (!connected) + return; + + // Map the Gamepad DeviceName String to the WebGamepad Id. Ideally it should + // be mapped to vendor and product information but it is only available at + // kernel level and it can not be queried using class + // android.hardware.input.InputManager. + // TODO(SaurabhK): Store a cached WebGamePad object in + // GamepadPlatformDataFetcherAndroid and only update constant WebGamepad + // values when a device has changed. + base::string16 device_name; + base::android::ConvertJavaStringToUTF16(env, devicename, &device_name); + const size_t name_to_copy = + std::min(device_name.size(), WebGamepad::idLengthCap - 1); + memcpy(pad.id, + device_name.data(), + name_to_copy * sizeof(base::string16::value_type)); + pad.id[name_to_copy] = 0; + + base::string16 mapping_name = base::UTF8ToUTF16(mapping ? "standard" : ""); + const size_t mapping_to_copy = + std::min(mapping_name.size(), WebGamepad::mappingLengthCap - 1); + memcpy(pad.mapping, + mapping_name.data(), + mapping_to_copy * sizeof(base::string16::value_type)); + pad.mapping[mapping_to_copy] = 0; + + pad.timestamp = timestamp; + + std::vector axes; + base::android::JavaFloatArrayToFloatVector(env, jaxes, &axes); + + // Set WebGamepad axeslength to total number of axes on the gamepad device. + // Only return the first axesLengthCap if axeslength captured by GamepadList + // is larger than axesLengthCap. + pad.axesLength = std::min(static_cast(axes.size()), + static_cast(WebGamepad::axesLengthCap)); + + // Copy axes state to the WebGamepad axes[]. + for (unsigned int i = 0; i < pad.axesLength; i++) { + pad.axes[i] = static_cast(axes[i]); + } + + std::vector buttons; + base::android::JavaFloatArrayToFloatVector(env, jbuttons, &buttons); + + // Set WebGamepad buttonslength to total number of axes on the gamepad + // device. Only return the first buttonsLengthCap if axeslength captured by + // GamepadList is larger than buttonsLengthCap. + pad.buttonsLength = std::min(static_cast(buttons.size()), + static_cast(WebGamepad::buttonsLengthCap)); + + // Copy buttons state to the WebGamepad buttons[]. + for (unsigned int j = 0; j < pad.buttonsLength; j++) { + pad.buttons[j].pressed = buttons[j]; + pad.buttons[j].value = buttons[j]; + } +} + +} // namespace content diff --git a/content/browser/gamepad/gamepad_platform_data_fetcher_android.h b/content/browser/gamepad/gamepad_platform_data_fetcher_android.h new file mode 100644 index 00000000000000..bbbdc1f2c0589b --- /dev/null +++ b/content/browser/gamepad/gamepad_platform_data_fetcher_android.h @@ -0,0 +1,40 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Define the data fetcher that GamepadProvider will use for android port. +// (GamepadPlatformDataFetcher). + +#ifndef CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_ANDROID_H_ +#define CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_ANDROID_H_ + +#include + +#include "base/android/jni_android.h" +#include "content/browser/gamepad/gamepad_data_fetcher.h" +#include "content/browser/gamepad/gamepad_provider.h" +#include "content/browser/gamepad/gamepad_standard_mappings.h" +#include "third_party/WebKit/public/platform/WebGamepads.h" + +namespace content { + +class GamepadPlatformDataFetcherAndroid : public GamepadDataFetcher { + public: + GamepadPlatformDataFetcherAndroid(); + virtual ~GamepadPlatformDataFetcherAndroid(); + + virtual void PauseHint(bool paused) OVERRIDE; + + virtual void GetGamepadData(blink::WebGamepads* pads, + bool devices_changed_hint) OVERRIDE; + + // Registers the JNI methods for GamepadsReader. + static bool RegisterGamepadPlatformDataFetcherAndroid(JNIEnv* env); + + private: + DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherAndroid); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_GAMEPAD_GAMEPAD_PLATFORM_DATA_FETCHER_ANDROID_H_ diff --git a/content/browser/gamepad/gamepad_provider.cc b/content/browser/gamepad/gamepad_provider.cc index 8e7644ef46ec4c..16976d965385f4 100644 --- a/content/browser/gamepad/gamepad_provider.cc +++ b/content/browser/gamepad/gamepad_provider.cc @@ -131,6 +131,10 @@ void GamepadProvider::Initialize(scoped_ptr fetcher) { // On Linux, the data fetcher needs to watch file descriptors, so the message // loop needs to be a libevent loop. const base::MessageLoop::Type kMessageLoopType = base::MessageLoop::TYPE_IO; +#elif defined(OS_ANDROID) + // On Android, keeping a message loop of default type. + const base::MessageLoop::Type kMessageLoopType = + base::MessageLoop::TYPE_DEFAULT; #else // On Mac, the data fetcher uses IOKit which depends on CFRunLoop, so the // message loop needs to be a UI-type loop. On Windows it must be a UI loop diff --git a/content/child/runtime_features.cc b/content/child/runtime_features.cc index 916f08d31b0475..3dd2ad3a801cc0 100644 --- a/content/child/runtime_features.cc +++ b/content/child/runtime_features.cc @@ -12,6 +12,7 @@ #if defined(OS_ANDROID) #include +#include "base/android/build_info.h" #include "media/base/android/media_codec_bridge.h" #endif @@ -33,8 +34,10 @@ static void SetRuntimeFeatureDefaultsForPlatform() { media::MediaCodecBridge::IsAvailable() && ((android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM) || (android_getCpuFamily() == ANDROID_CPU_FAMILY_X86))); - // Android does not support the Gamepad API. - WebRuntimeFeatures::enableGamepad(false); + + // Android supports gamepad API for JellyBean and beyond + WebRuntimeFeatures::enableGamepad( + base::android::BuildInfo::GetInstance()->sdk_int() >= 16); // Android does not have support for PagePopup WebRuntimeFeatures::enablePagePopup(false); // Android does not yet support the Web Notification API. crbug.com/115320 diff --git a/content/content.gyp b/content/content.gyp index d7b332437008a3..96daa441ad7625 100644 --- a/content/content.gyp +++ b/content/content.gyp @@ -384,6 +384,7 @@ 'common_aidl', 'content_common', 'content_strings_grd', + 'content_gamepad_mapping', 'gesture_event_type_java', 'page_transition_types_java', 'popup_item_type_java', @@ -545,6 +546,22 @@ }], ], }, + { + 'target_name': 'content_gamepad_mapping', + 'type': 'none', + 'sources': [ + 'public/android/java/src/org/chromium/content/browser/input/CanonicalButtonIndex.template', + 'public/android/java/src/org/chromium/content/browser/input/CanonicalAxisIndex.template', + ], + 'variables': { + 'package_name': 'org/chromium/content/browser/input', + 'template_deps': [ + 'browser/gamepad/canonical_axis_index_list.h', + 'browser/gamepad/canonical_button_index_list.h', + ], + }, + 'includes': [ '../build/android/java_cpp_template.gypi' ], + }, ], }], # OS == "android" ], diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 43d75e200f3658..97112b6ed37341 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -596,6 +596,8 @@ 'browser/gamepad/gamepad_consumer.h', 'browser/gamepad/gamepad_data_fetcher.h', 'browser/gamepad/gamepad_platform_data_fetcher.h', + 'browser/gamepad/gamepad_platform_data_fetcher_android.cc', + 'browser/gamepad/gamepad_platform_data_fetcher_android.h', 'browser/gamepad/gamepad_platform_data_fetcher_linux.cc', 'browser/gamepad/gamepad_platform_data_fetcher_linux.h', 'browser/gamepad/gamepad_platform_data_fetcher_mac.h', @@ -1426,7 +1428,7 @@ 'browser/power_profiler/power_data_provider_dummy.cc' ] }], - ['OS!="win" and OS!="mac" and (OS!="linux" or use_udev==0)', { + ['OS!="win" and OS!="mac" and OS!="android" and (OS!="linux" or use_udev==0)', { 'sources': [ 'browser/gamepad/gamepad_platform_data_fetcher.cc', ] diff --git a/content/content_jni.gypi b/content/content_jni.gypi index fb1f7760fdbb28..42699aa6e0800f 100644 --- a/content/content_jni.gypi +++ b/content/content_jni.gypi @@ -22,6 +22,7 @@ 'public/android/java/src/org/chromium/content/browser/ContentViewStatics.java', 'public/android/java/src/org/chromium/content/browser/DeviceSensors.java', 'public/android/java/src/org/chromium/content/browser/DownloadController.java', + 'public/android/java/src/org/chromium/content/browser/input/GamepadList.java', 'public/android/java/src/org/chromium/content/browser/input/ImeAdapter.java', 'public/android/java/src/org/chromium/content/browser/input/DateTimeChooserAndroid.java', 'public/android/java/src/org/chromium/content/browser/InterstitialPageDelegateAndroid.java', diff --git a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java index cd68715aec0a9a..2fa3eff0720b62 100644 --- a/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java +++ b/content/public/android/java/src/org/chromium/content/browser/ContentViewCore.java @@ -59,6 +59,7 @@ import org.chromium.content.browser.accessibility.AccessibilityInjector; import org.chromium.content.browser.accessibility.BrowserAccessibilityManager; import org.chromium.content.browser.input.AdapterInputConnection; +import org.chromium.content.browser.input.GamepadList; import org.chromium.content.browser.input.HandleView; import org.chromium.content.browser.input.ImeAdapter; import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory; @@ -1386,6 +1387,7 @@ public void onAttachedToWindow() { setAccessibilityState(mAccessibilityManager.isEnabled()); ScreenOrientationListener.getInstance().addObserver(this, mContext); + GamepadList.onAttachedToWindow(mContext); } /** @@ -1400,6 +1402,7 @@ public void onDetachedFromWindow() { unregisterAccessibilityContentObserver(); ScreenOrientationListener.getInstance().removeObserver(this); + GamepadList.onDetachedFromWindow(); } /** @@ -1601,6 +1604,7 @@ public boolean dispatchKeyEventPreIme(KeyEvent event) { * @see View#dispatchKeyEvent(KeyEvent) */ public boolean dispatchKeyEvent(KeyEvent event) { + if (GamepadList.dispatchKeyEvent(event)) return true; if (getContentViewClient().shouldOverrideKeyEvent(event)) { return mContainerViewInternals.super_dispatchKeyEvent(event); } @@ -1646,6 +1650,7 @@ public boolean onHoverEvent(MotionEvent event) { * @see View#onGenericMotionEvent(MotionEvent) */ public boolean onGenericMotionEvent(MotionEvent event) { + if (GamepadList.onGenericMotionEvent(event)) return true; if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: diff --git a/content/public/android/java/src/org/chromium/content/browser/input/CanonicalAxisIndex.template b/content/public/android/java/src/org/chromium/content/browser/input/CanonicalAxisIndex.template new file mode 100644 index 00000000000000..81520562779290 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/input/CanonicalAxisIndex.template @@ -0,0 +1,10 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.input; + +public class CanonicalAxisIndex { +#define CANONICAL_AXIS_INDEX(name, value) public static final int name = value; +#include "content/browser/gamepad/canonical_axis_index_list.h" +} \ No newline at end of file diff --git a/content/public/android/java/src/org/chromium/content/browser/input/CanonicalButtonIndex.template b/content/public/android/java/src/org/chromium/content/browser/input/CanonicalButtonIndex.template new file mode 100644 index 00000000000000..fc28f9ce2b3230 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/input/CanonicalButtonIndex.template @@ -0,0 +1,10 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.input; + +public class CanonicalButtonIndex { +#define CANONICAL_BUTTON_INDEX(name, value) public static final int name = value; +#include "content/browser/gamepad/canonical_button_index_list.h" +} \ No newline at end of file diff --git a/content/public/android/java/src/org/chromium/content/browser/input/GamepadDevice.java b/content/public/android/java/src/org/chromium/content/browser/input/GamepadDevice.java new file mode 100644 index 00000000000000..3e9ab5ec9ba9e9 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/input/GamepadDevice.java @@ -0,0 +1,166 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.input; + +import android.os.SystemClock; +import android.view.InputDevice; +import android.view.InputDevice.MotionRange; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import java.util.Arrays; + +/** + * Manages information related to each connected gamepad device. + */ +class GamepadDevice { + // An id for the gamepad. + private int mDeviceId; + // The index of the gamepad in the Navigator. + private int mDeviceIndex; + // Last time the data for this gamepad was updated. + private long mTimestamp; + // If this gamepad is mapped to standard gamepad? + private boolean mIsStandardGamepad; + + // Array of values for all axes of the gamepad. + // All axis values must be linearly normalized to the range [-1.0 .. 1.0]. + // As appropriate, -1.0 should correspond to "up" or "left", and 1.0 + // should correspond to "down" or "right". + private float[] mAxisValues; + + private float[] mButtonsValues; + + // When the user agent recognizes the attached inputDevice, it is recommended + // that it be remapped to a canonical ordering when possible. Devices that are + // not recognized should still be exposed in their raw form. Therefore we must + // pass the raw Button and raw Axis values. + private float[] mRawButtons; + private float[] mRawAxes; + + // An identification string for the gamepad. + private String mDeviceName; + + // Array of axes ids. + private int[] mAxes; + + GamepadDevice(int index, InputDevice inputDevice) { + mDeviceIndex = index; + mDeviceId = inputDevice.getId(); + mDeviceName = inputDevice.getName(); + mTimestamp = SystemClock.uptimeMillis(); + mButtonsValues = new float[CanonicalButtonIndex.NUM_CANONICAL_BUTTONS]; + mAxisValues = new float[CanonicalAxisIndex.NUM_CANONICAL_AXES]; + mRawButtons = new float[256]; + // Get the total number of axes supported by gamepad. + int totalAxes = 0; + for (MotionRange range : inputDevice.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + totalAxes++; + } + } + // Get axis ids and initialize axes values. + mAxes = new int[totalAxes]; + mRawAxes = new float[totalAxes]; + int i = 0; + for (MotionRange range : inputDevice.getMotionRanges()) { + if ((range.getSource() & InputDevice.SOURCE_CLASS_JOYSTICK) != 0) { + mAxes[i] = range.getAxis(); + mRawAxes[i] = 0.0f; + i++; + } + } + } + + /** + * Updates the axes and buttons maping of a gamepad device to a standard gamepad format. + */ + public void updateButtonsAndAxesMapping() { + mIsStandardGamepad = GamepadMappings.mapToStandardGamepad( + mAxisValues, mButtonsValues, mRawAxes, mRawButtons, mDeviceName); + } + + /** + * @return Device Id of the gamepad device. + */ + public int getId() { return mDeviceId; } + + /** + * @return Mapping status of the gamepad device. + */ + public boolean isStandardGamepad() { return mIsStandardGamepad; } + + /** + * @return Device name of the gamepad device. + */ + public String getName() { return mDeviceName; } + + /** + * @return Device index of the gamepad device. + */ + public int getIndex() { return mDeviceIndex; } + + /** + * @return The timestamp when the gamepad device was last interacted. + */ + public long getTimestamp() { return mTimestamp; } + + /** + * @return The axes state of the gamepad device. + */ + public float[] getAxes() { return mAxisValues; } + + /** + * @return The buttons state of the gamepad device. + */ + public float[] getButtons() { return mButtonsValues; } + + /** + * Reset the axes and buttons data of the gamepad device everytime gamepad data access is + * paused. + */ + public void clearData() { + Arrays.fill(mAxisValues, 0); + Arrays.fill(mRawAxes, 0); + Arrays.fill(mButtonsValues, 0); + Arrays.fill(mRawButtons, 0); + } + + /** + * Handles key event from the gamepad device. + * @return True if the key event from the gamepad device has been consumed. + */ + public boolean handleKeyEvent(KeyEvent event) { + // Ignore event if it is not for standard gamepad key. + if (!GamepadList.isGamepadEvent(event)) return false; + int keyCode = event.getKeyCode(); + assert keyCode < 256; + // Button value 0.0 must mean fully unpressed, and 1.0 must mean fully pressed. + if (event.getAction() == KeyEvent.ACTION_DOWN) { + mRawButtons[keyCode] = 1.0f; + } else if (event.getAction() == KeyEvent.ACTION_UP) { + mRawButtons[keyCode] = 0.0f; + } + mTimestamp = event.getEventTime(); + + return true; + } + + /** + * Handles motion event from the gamepad device. + * @return True if the motion event from the gamepad device has been consumed. + */ + public boolean handleMotionEvent(MotionEvent event) { + // Ignore event if it is not a standard gamepad motion event. + if (!GamepadList.isGamepadEvent(event)) return false; + // Update axes values. + for (int i = 0; i < mAxes.length; i++) { + mRawAxes[i] = event.getAxisValue(mAxes[i]); + } + mTimestamp = event.getEventTime(); + return true; + } + +} diff --git a/content/public/android/java/src/org/chromium/content/browser/input/GamepadList.java b/content/public/android/java/src/org/chromium/content/browser/input/GamepadList.java new file mode 100644 index 00000000000000..034839c317cb0c --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/input/GamepadList.java @@ -0,0 +1,328 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.input; + +import android.content.Context; +import android.hardware.input.InputManager; +import android.hardware.input.InputManager.InputDeviceListener; +import android.os.Build; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import org.chromium.base.CalledByNative; +import org.chromium.base.JNINamespace; +import org.chromium.base.ThreadUtils; + +/** + * Class to manage connected gamepad devices list. + * + * It is a Java counterpart of GamepadPlatformDataFetcherAndroid and feeds Gamepad API with input + * data. + */ +@JNINamespace("content") +public class GamepadList { + private static final int MAX_GAMEPADS = 4; + + private final Object mLock = new Object(); + + private final GamepadDevice[] mGamepadDevices = new GamepadDevice[MAX_GAMEPADS]; + private InputManager mInputManager; + private int mAttachedToWindowCounter; + private boolean mIsGamepadAccessed; + private InputDeviceListener mInputDeviceListener; + + private GamepadList() { + mInputDeviceListener = new InputDeviceListener() { + // Override InputDeviceListener methods + @Override + public void onInputDeviceChanged(int deviceId) { + onInputDeviceChangedImpl(deviceId); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + onInputDeviceRemovedImpl(deviceId); + } + + @Override + public void onInputDeviceAdded(int deviceId) { + onInputDeviceAddedImpl(deviceId); + } + }; + } + + private void initializeDevices() { + // Get list of all the attached input devices. + int[] deviceIds = mInputManager.getInputDeviceIds(); + for (int i = 0; i < deviceIds.length; i++) { + InputDevice inputDevice = InputDevice.getDevice(deviceIds[i]); + // Check for gamepad device + if (isGamepadDevice(inputDevice)) { + // Register a new gamepad device. + registerGamepad(inputDevice); + } + } + } + + /** + * Notifies the GamepadList that a {@link ContentView} is attached to a window and it should + * prepare itself for gamepad input. It must be called before {@link onGenericMotionEvent} and + * {@link dispatchKeyEvent}. + */ + public static void onAttachedToWindow(Context context) { + assert ThreadUtils.runningOnUiThread(); + if (!isGamepadSupported()) return; + getInstance().attachedToWindow(context); + } + + private void attachedToWindow(Context context) { + if (mAttachedToWindowCounter++ == 0) { + mInputManager = (InputManager) context.getSystemService(Context.INPUT_SERVICE); + synchronized (mLock) { + initializeDevices(); + } + // Register an input device listener. + mInputManager.registerInputDeviceListener(mInputDeviceListener, null); + } + } + + /** + * Notifies the GamepadList that a {@link ContentView} is detached from it's window. + */ + public static void onDetachedFromWindow() { + assert ThreadUtils.runningOnUiThread(); + if (!isGamepadSupported()) return; + getInstance().detachedFromWindow(); + } + + private void detachedFromWindow() { + if (--mAttachedToWindowCounter == 0) { + synchronized (mLock) { + for (int i = 0; i < MAX_GAMEPADS; ++i) { + mGamepadDevices[i] = null; + } + } + mInputManager.unregisterInputDeviceListener(mInputDeviceListener); + mInputManager = null; + } + } + + // ------------------------------------------------------------ + + private void onInputDeviceChangedImpl(int deviceId) {} + + private void onInputDeviceRemovedImpl(int deviceId) { + synchronized (mLock) { + unregisterGamepad(deviceId); + } + } + + private void onInputDeviceAddedImpl(int deviceId) { + InputDevice inputDevice = InputDevice.getDevice(deviceId); + if (!isGamepadDevice(inputDevice)) return; + synchronized (mLock) { + registerGamepad(inputDevice); + } + } + + // ------------------------------------------------------------ + + private static GamepadList getInstance() { + assert isGamepadSupported(); + return LazyHolder.INSTANCE; + } + + private int getDeviceCount() { + int count = 0; + for (int i = 0; i < MAX_GAMEPADS; i++) { + if (getDevice(i) != null) { + count++; + } + } + return count; + } + + private boolean isDeviceConnected(int index) { + if (index < MAX_GAMEPADS && getDevice(index) != null) { + return true; + } + return false; + } + + private GamepadDevice getDeviceById(int deviceId) { + for (int i = 0; i < MAX_GAMEPADS; i++) { + GamepadDevice gamepad = mGamepadDevices[i]; + if (gamepad != null && gamepad.getId() == deviceId) { + return gamepad; + } + } + return null; + } + + private GamepadDevice getDevice(int index) { + // Maximum 4 Gamepads can be connected at a time starting at index zero. + assert index >= 0 && index < MAX_GAMEPADS; + return mGamepadDevices[index]; + } + + /** + * Handles key events from the gamepad devices. + * @return True if the event has been consumed. + */ + public static boolean dispatchKeyEvent(KeyEvent event) { + if (!isGamepadSupported()) return false; + if (!isGamepadEvent(event)) return false; + return getInstance().handleKeyEvent(event); + } + + private boolean handleKeyEvent(KeyEvent event) { + synchronized (mLock) { + if (!mIsGamepadAccessed) return false; + GamepadDevice gamepad = getGamepadForEvent(event); + if (gamepad == null) return false; + return gamepad.handleKeyEvent(event); + } + } + + /** + * Handles motion events from the gamepad devices. + * @return True if the event has been consumed. + */ + public static boolean onGenericMotionEvent(MotionEvent event) { + if (!isGamepadSupported()) return false; + if (!isGamepadEvent(event)) return false; + return getInstance().handleMotionEvent(event); + } + + private boolean handleMotionEvent(MotionEvent event) { + synchronized (mLock) { + if (!mIsGamepadAccessed) return false; + GamepadDevice gamepad = getGamepadForEvent(event); + if (gamepad == null) return false; + return gamepad.handleMotionEvent(event); + } + } + + private int getNextAvailableIndex() { + // When multiple gamepads are connected to a user agent, indices must be assigned on a + // first-come first-serve basis, starting at zero. If a gamepad is disconnected, previously + // assigned indices must not be reassigned to gamepads that continue to be connected. + // However, if a gamepad is disconnected, and subsequently the same or a different + // gamepad is then connected, index entries must be reused. + + for (int i = 0; i < MAX_GAMEPADS; ++i) { + if (getDevice(i) == null) { + return i; + } + } + // Reached maximum gamepads limit. + return -1; + } + + private boolean registerGamepad(InputDevice inputDevice) { + int index = getNextAvailableIndex(); + if (index == -1) return false; // invalid index + + GamepadDevice gamepad = new GamepadDevice(index, inputDevice); + mGamepadDevices[index] = gamepad; + return true; + } + + private void unregisterGamepad(int deviceId) { + GamepadDevice gamepadDevice = getDeviceById(deviceId); + if (gamepadDevice == null) return; // Not a registered device. + int index = gamepadDevice.getIndex(); + mGamepadDevices[index] = null; + } + + private static boolean isGamepadDevice(InputDevice inputDevice) { + return ((inputDevice.getSources() & InputDevice.SOURCE_JOYSTICK) == + InputDevice.SOURCE_JOYSTICK); + } + + private GamepadDevice getGamepadForEvent(InputEvent event) { + return getDeviceById(event.getDeviceId()); + } + + /** + * @return True if the motion event corresponds to a gamepad event. + */ + public static boolean isGamepadEvent(MotionEvent event) { + return ((event.getSource() & InputDevice.SOURCE_JOYSTICK) == InputDevice.SOURCE_JOYSTICK); + } + + /** + * @return True if event's keycode corresponds to a gamepad key. + */ + public static boolean isGamepadEvent(KeyEvent event) { + int keyCode = event.getKeyCode(); + switch (keyCode) { + // Specific handling for dpad keys is required because + // KeyEvent.isGamepadButton doesn't consider dpad keys. + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return true; + default: + return KeyEvent.isGamepadButton(keyCode); + } + } + + private static boolean isGamepadSupported() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; + } + + @CalledByNative + static void updateGamepadData(long webGamepadsPtr) { + if (!isGamepadSupported()) return; + getInstance().grabGamepadData(webGamepadsPtr); + } + + private void grabGamepadData(long webGamepadsPtr) { + synchronized (mLock) { + for (int i = 0; i < MAX_GAMEPADS; i++) { + final GamepadDevice device = getDevice(i); + if (device != null) { + device.updateButtonsAndAxesMapping(); + nativeSetGamepadData(webGamepadsPtr, i, device.isStandardGamepad(), true, + device.getName(), device.getTimestamp(), device.getAxes(), + device.getButtons()); + } else { + nativeSetGamepadData(webGamepadsPtr, i, false, false, null, 0, null, null); + } + } + } + } + + @CalledByNative + static void notifyForGamepadsAccess(boolean isAccessPaused) { + if (!isGamepadSupported()) return; + getInstance().setIsGamepadAccessed(!isAccessPaused); + } + + private void setIsGamepadAccessed(boolean isGamepadAccessed) { + synchronized (mLock) { + mIsGamepadAccessed = isGamepadAccessed; + if (isGamepadAccessed) { + for (int i = 0; i < MAX_GAMEPADS; i++) { + GamepadDevice gamepadDevice = getDevice(i); + if (gamepadDevice == null) continue; + gamepadDevice.clearData(); + } + } + } + } + + private native void nativeSetGamepadData(long webGamepadsPtr, int index, boolean mapping, + boolean connected, String devicename, long timestamp, float[] axes, float[] buttons); + + private static class LazyHolder { + private static final GamepadList INSTANCE = new GamepadList(); + } + +} diff --git a/content/public/android/java/src/org/chromium/content/browser/input/GamepadMappings.java b/content/public/android/java/src/org/chromium/content/browser/input/GamepadMappings.java new file mode 100644 index 00000000000000..7c171ebfcd3c42 --- /dev/null +++ b/content/public/android/java/src/org/chromium/content/browser/input/GamepadMappings.java @@ -0,0 +1,134 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package org.chromium.content.browser.input; + +import android.view.KeyEvent; + +/** + * Class to manage mapping information related to each supported gamepad controller device. + */ +class GamepadMappings { + public static boolean mapToStandardGamepad(float[] mappedAxis, float[] mappedButtons, + float[] rawAxes, float[] rawButtons, String deviceName) { + if (deviceName.contains("NVIDIA Corporation NVIDIA Controller")) { + mapShieldGamepad(mappedButtons, rawButtons, mappedAxis, rawAxes); + return true; + } else if (deviceName.contains("Microsoft X-Box 360 pad")) { + mapXBox360Gamepad(mappedButtons, rawButtons, mappedAxis, rawAxes); + return true; + } + + mapUnknownGamepad(mappedButtons, rawButtons, mappedAxis, rawAxes); + return false; + } + + private static void mapCommonButtons(float[] mappedButtons, float[] rawButtons) { + mappedButtons[CanonicalButtonIndex.BUTTON_PRIMARY] = rawButtons[KeyEvent.KEYCODE_BUTTON_A]; + mappedButtons[CanonicalButtonIndex.BUTTON_SECONDARY] = + rawButtons[KeyEvent.KEYCODE_BUTTON_B]; + mappedButtons[CanonicalButtonIndex.BUTTON_TERTIARY] = + rawButtons[KeyEvent.KEYCODE_BUTTON_X]; + mappedButtons[CanonicalButtonIndex.BUTTON_QUATERNARY] = + rawButtons[KeyEvent.KEYCODE_BUTTON_Y]; + mappedButtons[CanonicalButtonIndex.BUTTON_LEFT_SHOULDER] = + rawButtons[KeyEvent.KEYCODE_BUTTON_L1]; + mappedButtons[CanonicalButtonIndex.BUTTON_RIGHT_SHOULDER] = + rawButtons[KeyEvent.KEYCODE_BUTTON_R1]; + mappedButtons[CanonicalButtonIndex.BUTTON_BACK_SELECT] = + rawButtons[KeyEvent.KEYCODE_BUTTON_SELECT]; + mappedButtons[CanonicalButtonIndex.BUTTON_START] = + rawButtons[KeyEvent.KEYCODE_BUTTON_START]; + mappedButtons[CanonicalButtonIndex.BUTTON_LEFT_THUMBSTICK] = + rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBL]; + mappedButtons[CanonicalButtonIndex.BUTTON_RIGHT_THUMBSTICK] = + rawButtons[KeyEvent.KEYCODE_BUTTON_THUMBR]; + mappedButtons[CanonicalButtonIndex.BUTTON_META] = rawButtons[KeyEvent.KEYCODE_BUTTON_MODE]; + } + + private static void mapDpadButtonsToAxes(float[] mappedButtons, float[] rawAxes) { + // Negative value indicates dpad up. + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_UP] = negativeAxisValueAsButton(rawAxes[9]); + // Positive axis value indicates dpad down. + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_DOWN] = + positiveAxisValueAsButton(rawAxes[9]); + // Positive axis value indicates dpad right. + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_RIGHT] = + positiveAxisValueAsButton(rawAxes[8]); + // Negative value indicates dpad left. + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_LEFT] = + negativeAxisValueAsButton(rawAxes[8]); + } + + private static void mapAxes(float[] mappedAxis, float[] rawAxes) { + // Standard gamepad can have only four axes. + mappedAxis[CanonicalAxisIndex.AXIS_LEFT_STICK_X] = rawAxes[0]; + mappedAxis[CanonicalAxisIndex.AXIS_LEFT_STICK_Y] = rawAxes[1]; + mappedAxis[CanonicalAxisIndex.AXIS_RIGHT_STICK_X] = rawAxes[4]; + mappedAxis[CanonicalAxisIndex.AXIS_RIGHT_STICK_Y] = rawAxes[5]; + } + + private static float negativeAxisValueAsButton(float input) { + return (input < -0.5f) ? 1.f : 0.f; + } + + private static float positiveAxisValueAsButton(float input) { + return (input > 0.5f) ? 1.f : 0.f; + } + + /** + * Method for mapping Nvidia gamepad axis and button values + * to standard gamepad button and axes values. + */ + private static void mapShieldGamepad(float[] mappedButtons, float[] rawButtons, + float[] mappedAxis, float[] rawAxes) { + mapCommonButtons(mappedButtons, rawButtons); + + mappedButtons[CanonicalButtonIndex.BUTTON_LEFT_TRIGGER] = rawAxes[2]; + mappedButtons[CanonicalButtonIndex.BUTTON_RIGHT_TRIGGER] = rawAxes[6]; + + mapDpadButtonsToAxes(mappedButtons, rawAxes); + mapAxes(mappedAxis, rawAxes); + } + + /** + * Method for mapping Microsoft XBox 360 gamepad axis and button values + * to standard gamepad button and axes values. + */ + private static void mapXBox360Gamepad(float[] mappedButtons, float[] rawButtons, + float[] mappedAxis, float[] rawAxes) { + mapCommonButtons(mappedButtons, rawButtons); + + mappedButtons[CanonicalButtonIndex.BUTTON_LEFT_TRIGGER] = rawAxes[2]; + mappedButtons[CanonicalButtonIndex.BUTTON_RIGHT_TRIGGER] = rawAxes[6]; + + mapDpadButtonsToAxes(mappedButtons, rawAxes); + mapAxes(mappedAxis, rawAxes); + } + + /** + * Method for mapping Unkown gamepad axis and button values + * to standard gamepad button and axes values. + */ + private static void mapUnknownGamepad(float[] mappedButtons, float[] rawButtons, + float[] mappedAxis, float[] rawAxes) { + mapCommonButtons(mappedButtons, rawButtons); + + mappedButtons[CanonicalButtonIndex.BUTTON_LEFT_TRIGGER] = + rawButtons[KeyEvent.KEYCODE_BUTTON_L2]; + mappedButtons[CanonicalButtonIndex.BUTTON_RIGHT_TRIGGER] = + rawButtons[KeyEvent.KEYCODE_BUTTON_R2]; + + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_UP] = rawButtons[KeyEvent.KEYCODE_DPAD_UP]; + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_DOWN] = + rawButtons[KeyEvent.KEYCODE_DPAD_DOWN]; + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_RIGHT] = + rawButtons[KeyEvent.KEYCODE_DPAD_RIGHT]; + mappedButtons[CanonicalButtonIndex.BUTTON_DPAD_LEFT] = + rawButtons[KeyEvent.KEYCODE_DPAD_LEFT]; + + mapAxes(mappedAxis, rawAxes); + } + +} \ No newline at end of file