Import Cobalt 19.master.0.194710
Includes the following patches:
https://cobalt-review.googlesource.com/c/cobalt/+/5190
by errong.leng@samsung.com
diff --git a/src/starboard/android/shared/__init__.py b/src/starboard/android/shared/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/starboard/android/shared/__init__.py
diff --git a/src/starboard/android/shared/accessibility_get_caption_settings.cc b/src/starboard/android/shared/accessibility_get_caption_settings.cc
new file mode 100644
index 0000000..58e3067
--- /dev/null
+++ b/src/starboard/android/shared/accessibility_get_caption_settings.cc
@@ -0,0 +1,322 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <cmath>
+#include <cstdlib>
+#include <limits>
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+
+#include "starboard/accessibility.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+namespace {
+
+const int kRgbWhite = 0xFFFFFF;
+const int kRgbBlack = 0x000000;
+const int kRgbRed = 0xFF0000;
+const int kRgbYellow = 0xFFFF00;
+const int kRgbGreen = 0x00FF00;
+const int kRgbCyan = 0x00FFFF;
+const int kRgbBlue = 0x0000FF;
+const int kRgbMagenta = 0xFF00FF;
+
+const int kRgbColors[] = {
+ kRgbWhite,
+ kRgbBlack,
+ kRgbRed,
+ kRgbYellow,
+ kRgbGreen,
+ kRgbCyan,
+ kRgbBlue,
+ kRgbMagenta,
+};
+
+SbAccessibilityCaptionColor GetClosestCaptionColor(int color) {
+ int ref_color = kRgbWhite;
+ int min_distance = std::numeric_limits<int>::max();
+
+ int r = 0xFF & (color >> 16);
+ int g = 0xFF & (color >> 8);
+ int b = 0xFF & (color);
+
+ // Find the reference color with the least distance (squared).
+ for (int i = 0; i < SB_ARRAY_SIZE(kRgbColors); i++) {
+ int r_ref = 0xFF & (kRgbColors[i] >> 16);
+ int g_ref = 0xFF & (kRgbColors[i] >> 8);
+ int b_ref = 0xFF & (kRgbColors[i]);
+ int distance_squared = pow(r - r_ref, 2) +
+ pow(g - g_ref, 2) +
+ pow(b - b_ref, 2);
+ if (distance_squared < min_distance) {
+ ref_color = kRgbColors[i];
+ min_distance = distance_squared;
+ }
+ }
+
+ switch (ref_color) {
+ case kRgbWhite:
+ return kSbAccessibilityCaptionColorWhite;
+ case kRgbBlack:
+ return kSbAccessibilityCaptionColorBlack;
+ case kRgbRed:
+ return kSbAccessibilityCaptionColorRed;
+ case kRgbYellow:
+ return kSbAccessibilityCaptionColorYellow;
+ case kRgbGreen:
+ return kSbAccessibilityCaptionColorGreen;
+ case kRgbCyan:
+ return kSbAccessibilityCaptionColorCyan;
+ case kRgbBlue:
+ return kSbAccessibilityCaptionColorBlue;
+ case kRgbMagenta:
+ return kSbAccessibilityCaptionColorMagenta;
+ default:
+ NOTREACHED() << "Invalid RGB color conversion";
+ return kSbAccessibilityCaptionColorWhite;
+ }
+}
+
+SbAccessibilityCaptionCharacterEdgeStyle
+AndroidEdgeTypeToSbEdgeStyle(int edge_type) {
+ switch (edge_type) {
+ case 0:
+ return kSbAccessibilityCaptionCharacterEdgeStyleNone;
+ case 1:
+ return kSbAccessibilityCaptionCharacterEdgeStyleUniform;
+ case 2:
+ return kSbAccessibilityCaptionCharacterEdgeStyleDropShadow;
+ case 3:
+ return kSbAccessibilityCaptionCharacterEdgeStyleRaised;
+ case 4:
+ return kSbAccessibilityCaptionCharacterEdgeStyleDepressed;
+ default:
+ NOTREACHED() << "Invalid edge type conversion";
+ return kSbAccessibilityCaptionCharacterEdgeStyleNone;
+ }
+}
+
+SbAccessibilityCaptionFontFamily AndroidFontFamilyToSbFontFamily(int family) {
+ switch (family) {
+ case 0:
+ return kSbAccessibilityCaptionFontFamilyCasual;
+ case 1:
+ return kSbAccessibilityCaptionFontFamilyCursive;
+ case 2:
+ return kSbAccessibilityCaptionFontFamilyMonospaceSansSerif;
+ case 3:
+ return kSbAccessibilityCaptionFontFamilyMonospaceSerif;
+ case 4:
+ return kSbAccessibilityCaptionFontFamilyProportionalSansSerif;
+ case 5:
+ return kSbAccessibilityCaptionFontFamilyProportionalSerif;
+ case 6:
+ return kSbAccessibilityCaptionFontFamilySmallCapitals;
+ default:
+ NOTREACHED() << "Invalid font family conversion";
+ return kSbAccessibilityCaptionFontFamilyCasual;
+ }
+}
+
+int FindClosestReferenceValue(int value, const int reference[],
+ size_t reference_size) {
+ int result = reference[0];
+ int min_difference = std::numeric_limits<int>::max();
+
+ for (int i = 0; i < reference_size; i++) {
+ int difference = abs(reference[i] - value);
+ if (difference < min_difference) {
+ result = reference[i];
+ min_difference = difference;
+ }
+ }
+ return result;
+}
+
+const int kFontSizes[] = {
+ 25,
+ 50,
+ 75,
+ 100,
+ 125,
+ 150,
+ 175,
+ 200,
+ 225,
+ 250,
+ 275,
+ 300
+};
+
+SbAccessibilityCaptionFontSizePercentage GetClosestFontSizePercentage(
+ int font_size_percent) {
+ int reference_size = FindClosestReferenceValue(
+ font_size_percent, kFontSizes, SB_ARRAY_SIZE(kFontSizes));
+ switch (reference_size) {
+ case 25:
+ return kSbAccessibilityCaptionFontSizePercentage25;
+ case 50:
+ return kSbAccessibilityCaptionFontSizePercentage50;
+ case 75:
+ return kSbAccessibilityCaptionFontSizePercentage75;
+ case 100:
+ return kSbAccessibilityCaptionFontSizePercentage100;
+ case 125:
+ return kSbAccessibilityCaptionFontSizePercentage125;
+ case 150:
+ return kSbAccessibilityCaptionFontSizePercentage150;
+ case 175:
+ return kSbAccessibilityCaptionFontSizePercentage175;
+ case 200:
+ return kSbAccessibilityCaptionFontSizePercentage200;
+ case 225:
+ return kSbAccessibilityCaptionFontSizePercentage225;
+ case 250:
+ return kSbAccessibilityCaptionFontSizePercentage250;
+ case 275:
+ return kSbAccessibilityCaptionFontSizePercentage275;
+ case 300:
+ return kSbAccessibilityCaptionFontSizePercentage300;
+ default:
+ NOTREACHED() << "Invalid font size";
+ return kSbAccessibilityCaptionFontSizePercentage100;
+ }
+}
+
+const int kOpacities[] = {
+ 0,
+ 25,
+ 50,
+ 75,
+ 100,
+};
+
+SbAccessibilityCaptionOpacityPercentage GetClosestOpacity(int opacity_percent) {
+ int reference_opacity_percent = FindClosestReferenceValue(
+ opacity_percent, kOpacities, SB_ARRAY_SIZE(kOpacities));
+ switch (reference_opacity_percent) {
+ case 0:
+ return kSbAccessibilityCaptionOpacityPercentage0;
+ case 25:
+ return kSbAccessibilityCaptionOpacityPercentage25;
+ case 50:
+ return kSbAccessibilityCaptionOpacityPercentage50;
+ case 75:
+ return kSbAccessibilityCaptionOpacityPercentage75;
+ case 100:
+ return kSbAccessibilityCaptionOpacityPercentage100;
+ default:
+ NOTREACHED() << "Invalid opacity percentage";
+ return kSbAccessibilityCaptionOpacityPercentage100;
+ }
+}
+
+SbAccessibilityCaptionState BooleanToCaptionState(bool is_set) {
+ if (is_set) {
+ return kSbAccessibilityCaptionStateSet;
+ } else {
+ return kSbAccessibilityCaptionStateUnset;
+ }
+}
+
+void SetColorProperties(jobject j_caption_settings,
+ const char* color_field,
+ const char* has_color_field,
+ SbAccessibilityCaptionColor* color,
+ SbAccessibilityCaptionState* color_state,
+ SbAccessibilityCaptionOpacityPercentage* opacity,
+ SbAccessibilityCaptionState* opacity_state) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jint j_color = env->GetIntFieldOrAbort(j_caption_settings, color_field, "I");
+ *color = GetClosestCaptionColor(j_color);
+ *opacity = GetClosestOpacity((0xFF & (j_color >> 24)) * 100 / 255);
+ *color_state = BooleanToCaptionState(
+ env->GetBooleanFieldOrAbort(j_caption_settings, has_color_field, "Z"));
+ // Color and opacity are combined into a single ARGB value.
+ // Therefore, if the color is set, so is the opacity.
+ *opacity_state = *color_state;
+}
+
+} // namespace
+
+bool SbAccessibilityGetCaptionSettings(
+ SbAccessibilityCaptionSettings* caption_settings) {
+ if (!caption_settings ||
+ !SbMemoryIsZero(caption_settings,
+ sizeof(SbAccessibilityCaptionSettings))) {
+ return false;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+
+ ScopedLocalJavaRef<jobject> j_caption_settings(
+ env->CallStarboardObjectMethodOrAbort(
+ "getCaptionSettings", "()Ldev/cobalt/media/CaptionSettings;"));
+
+ jfloat font_scale =
+ env->GetFloatFieldOrAbort(j_caption_settings.Get(), "fontScale", "F");
+ caption_settings->font_size =
+ GetClosestFontSizePercentage(100.0 * font_scale);
+ // Android's captioning API always returns a font scale of 1 (100%) if
+ // the font size has not been set. This means we have no way to check if
+ // font size is "set" vs. "unset", so we'll just return "set" every time.
+ caption_settings->font_size_state = kSbAccessibilityCaptionStateSet;
+
+ // TODO: Convert Android typeface to font family.
+ caption_settings->font_family = kSbAccessibilityCaptionFontFamilyCasual;
+ caption_settings->font_family_state = kSbAccessibilityCaptionStateUnsupported;
+
+ caption_settings->character_edge_style = AndroidEdgeTypeToSbEdgeStyle(
+ env->GetIntFieldOrAbort(j_caption_settings.Get(), "edgeType", "I"));
+ caption_settings->character_edge_style_state = BooleanToCaptionState(
+ env->GetBooleanFieldOrAbort(j_caption_settings.Get(),
+ "hasEdgeType", "Z"));
+
+ SetColorProperties(
+ j_caption_settings.Get(), "foregroundColor", "hasForegroundColor",
+ &caption_settings->font_color,
+ &caption_settings->font_color_state,
+ &caption_settings->font_opacity,
+ &caption_settings->font_opacity_state);
+
+ SetColorProperties(
+ j_caption_settings.Get(), "backgroundColor", "hasBackgroundColor",
+ &caption_settings->background_color,
+ &caption_settings->background_color_state,
+ &caption_settings->background_opacity,
+ &caption_settings->background_opacity_state);
+
+ SetColorProperties(
+ j_caption_settings.Get(), "windowColor", "hasWindowColor",
+ &caption_settings->window_color,
+ &caption_settings->window_color_state,
+ &caption_settings->window_opacity,
+ &caption_settings->window_opacity_state);
+
+ caption_settings->is_enabled =
+ env->GetBooleanFieldOrAbort(j_caption_settings.Get(), "isEnabled", "Z");
+ caption_settings->supports_is_enabled = true;
+ caption_settings->supports_set_enabled = false;
+
+ return true;
+}
diff --git a/src/starboard/android/shared/accessibility_get_display_settings.cc b/src/starboard/android/shared/accessibility_get_display_settings.cc
new file mode 100644
index 0000000..d3d10e7
--- /dev/null
+++ b/src/starboard/android/shared/accessibility_get_display_settings.cc
@@ -0,0 +1,37 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/accessibility.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/memory.h"
+
+using starboard::android::shared::JniEnvExt;
+
+bool SbAccessibilityGetDisplaySettings(
+ SbAccessibilityDisplaySettings* out_setting) {
+ if (!out_setting ||
+ !SbMemoryIsZero(out_setting,
+ sizeof(SbAccessibilityDisplaySettings))) {
+ return false;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ out_setting->has_high_contrast_text_setting = true;
+ out_setting->is_high_contrast_text_enabled =
+ env->CallStarboardBooleanMethodOrAbort(
+ "isAccessibilityHighContrastTextEnabled", "()Z");
+
+ return true;
+}
diff --git a/src/starboard/android/shared/accessibility_get_text_to_speech_settings.cc b/src/starboard/android/shared/accessibility_get_text_to_speech_settings.cc
new file mode 100644
index 0000000..8f64452
--- /dev/null
+++ b/src/starboard/android/shared/accessibility_get_text_to_speech_settings.cc
@@ -0,0 +1,44 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/accessibility.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/memory.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+bool SbAccessibilityGetTextToSpeechSettings(
+ SbAccessibilityTextToSpeechSettings* out_setting) {
+ if (!out_setting ||
+ !SbMemoryIsZero(out_setting,
+ sizeof(SbAccessibilityTextToSpeechSettings))) {
+ return false;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+
+ out_setting->has_text_to_speech_setting = true;
+ ScopedLocalJavaRef<jobject> j_tts_helper(
+ env->CallStarboardObjectMethodOrAbort(
+ "getTextToSpeechHelper",
+ "()Ldev/cobalt/coat/CobaltTextToSpeechHelper;"));
+ out_setting->is_text_to_speech_enabled =
+ env->CallBooleanMethodOrAbort(j_tts_helper.Get(),
+ "isScreenReaderEnabled", "()Z");
+
+ return true;
+}
diff --git a/src/starboard/android/shared/accessibility_set_captions_enabled.cc b/src/starboard/android/shared/accessibility_set_captions_enabled.cc
new file mode 100644
index 0000000..f39ad20
--- /dev/null
+++ b/src/starboard/android/shared/accessibility_set_captions_enabled.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/accessibility.h"
+#include "starboard/configuration.h"
+
+bool SbAccessibilitySetCaptionsEnabled(bool enabled) {
+ SB_UNREFERENCED_PARAMETER(enabled);
+ return false;
+}
diff --git a/src/starboard/android/shared/android_main.cc b/src/starboard/android/shared/android_main.cc
new file mode 100644
index 0000000..771ad25
--- /dev/null
+++ b/src/starboard/android/shared/android_main.cc
@@ -0,0 +1,214 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/application_android.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/log_internal.h"
+#include "starboard/common/semaphore.h"
+#include "starboard/shared/starboard/command_line.h"
+#include "starboard/string.h"
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+using ::starboard::shared::starboard::CommandLine;
+typedef ::starboard::android::shared::ApplicationAndroid::AndroidCommand
+ AndroidCommand;
+
+SbThread g_starboard_thread = kSbThreadInvalid;
+
+// Safeguard to avoid sending AndroidCommands either when there is no instance
+// of the Starboard application, or after the run loop has exited and the
+// ALooper receiving the commands is no longer being polled.
+bool g_app_running = false;
+
+std::vector<std::string> GetArgs() {
+ std::vector<std::string> args;
+ // Fake program name as args[0]
+ args.push_back(SbStringDuplicate("android_main"));
+
+ JniEnvExt* env = JniEnvExt::Get();
+
+ ScopedLocalJavaRef<jobjectArray> args_array(
+ env->CallStarboardObjectMethodOrAbort("getArgs",
+ "()[Ljava/lang/String;"));
+ jint argc = !args_array ? 0 : env->GetArrayLength(args_array.Get());
+
+ for (jint i = 0; i < argc; i++) {
+ ScopedLocalJavaRef<jstring> element(
+ env->GetObjectArrayElementOrAbort(args_array.Get(), i));
+ args.push_back(env->GetStringStandardUTFOrAbort(element.Get()));
+ }
+
+ return args;
+}
+
+std::string GetStartDeepLink() {
+ JniEnvExt* env = JniEnvExt::Get();
+ std::string start_url;
+
+ ScopedLocalJavaRef<jstring> j_url(env->CallStarboardObjectMethodOrAbort(
+ "getStartDeepLink", "()Ljava/lang/String;"));
+ if (j_url) {
+ start_url = env->GetStringStandardUTFOrAbort(j_url.Get());
+ }
+ return start_url;
+}
+
+void* ThreadEntryPoint(void* context) {
+ Semaphore* app_created_semaphore = static_cast<Semaphore*>(context);
+
+ ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+ ApplicationAndroid app(looper);
+
+ CommandLine command_line(GetArgs());
+ LogInit(command_line);
+
+ // Mark the app running before signaling app created so there's no race to
+ // allow sending the first AndroidCommand after onCreate() returns.
+ g_app_running = true;
+
+ // Signal ANativeActivity_onCreate() that it may proceed.
+ app_created_semaphore->Put();
+
+ // Enter the Starboard run loop until stopped.
+ int error_level =
+ app.Run(std::move(command_line), GetStartDeepLink().c_str());
+
+ // Mark the app not running before informing StarboardBridge that the app is
+ // stopped so that we won't send any more AndroidCommands as a result of
+ // shutting down the Activity.
+ g_app_running = false;
+
+ // Our launcher.py looks for this to know when the app (test) is done.
+ SB_LOG(INFO) << "***Application Stopped*** " << error_level;
+
+ // Inform StarboardBridge that the run loop has exited so it can cleanup and
+ // kill the process.
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallStarboardVoidMethodOrAbort("afterStopped", "()V");
+
+ return NULL;
+}
+
+void OnStart(ANativeActivity* activity) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStart);
+ }
+}
+
+void OnResume(ANativeActivity* activity) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kResume);
+ }
+}
+
+void OnPause(ANativeActivity* activity) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kPause);
+ }
+}
+
+void OnStop(ANativeActivity* activity) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStop);
+ }
+}
+
+void OnWindowFocusChanged(ANativeActivity* activity, int focused) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(focused
+ ? AndroidCommand::kWindowFocusGained
+ : AndroidCommand::kWindowFocusLost);
+ }
+}
+
+void OnNativeWindowCreated(ANativeActivity* activity, ANativeWindow* window) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(
+ AndroidCommand::kNativeWindowCreated, window);
+ }
+}
+
+void OnNativeWindowDestroyed(ANativeActivity* activity, ANativeWindow* window) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(
+ AndroidCommand::kNativeWindowDestroyed);
+ }
+}
+
+void OnInputQueueCreated(ANativeActivity* activity, AInputQueue* queue) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(
+ AndroidCommand::kInputQueueChanged, queue);
+ }
+}
+
+void OnInputQueueDestroyed(ANativeActivity* activity, AInputQueue* queue) {
+ if (g_app_running) {
+ ApplicationAndroid::Get()->SendAndroidCommand(
+ AndroidCommand::kInputQueueChanged, NULL);
+ }
+}
+
+extern "C" SB_EXPORT_PLATFORM void ANativeActivity_onCreate(
+ ANativeActivity *activity, void *savedState, size_t savedStateSize) {
+
+ // Start the Starboard thread the first time an Activity is created.
+ if (!SbThreadIsValid(g_starboard_thread)) {
+ Semaphore semaphore;
+
+ g_starboard_thread = SbThreadCreate(
+ 0, kSbThreadPriorityNormal, kSbThreadNoAffinity, false,
+ "StarboardMain", &ThreadEntryPoint, &semaphore);
+
+ // Wait for the ApplicationAndroid to be created.
+ semaphore.Take();
+ }
+
+ activity->callbacks->onStart = OnStart;
+ activity->callbacks->onResume = OnResume;
+ activity->callbacks->onPause = OnPause;
+ activity->callbacks->onStop = OnStop;
+ activity->callbacks->onWindowFocusChanged = OnWindowFocusChanged;
+ activity->callbacks->onNativeWindowCreated = OnNativeWindowCreated;
+ activity->callbacks->onNativeWindowDestroyed = OnNativeWindowDestroyed;
+ activity->callbacks->onInputQueueCreated = OnInputQueueCreated;
+ activity->callbacks->onInputQueueDestroyed = OnInputQueueDestroyed;
+ activity->instance = ApplicationAndroid::Get();
+}
+
+extern "C" SB_EXPORT_PLATFORM
+jboolean Java_dev_cobalt_coat_StarboardBridge_nativeIsReleaseBuild() {
+#if defined(COBALT_BUILD_TYPE_GOLD)
+ return true;
+#else
+ return false;
+#endif
+}
+
+extern "C" SB_EXPORT_PLATFORM
+void Java_dev_cobalt_coat_StarboardBridge_nativeInitialize(
+ JniEnvExt* env, jobject starboard_bridge) {
+ JniEnvExt::Initialize(env, starboard_bridge);
+}
+
+} // namespace
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/application_android.cc b/src/starboard/android/shared/application_android.cc
new file mode 100644
index 0000000..8e8b2dc
--- /dev/null
+++ b/src/starboard/android/shared/application_android.cc
@@ -0,0 +1,500 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/application_android.h"
+
+#include <android/looper.h>
+#include <android/native_activity.h>
+#include <time.h>
+#include <unistd.h>
+
+#include <string>
+#include <vector>
+
+#include "starboard/accessibility.h"
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/android/shared/input_events_generator.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/window_internal.h"
+#include "starboard/condition_variable.h"
+#include "starboard/event.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+ enum {
+ kLooperIdAndroidCommand,
+ kLooperIdAndroidInput,
+ kLooperIdKeyboardInject,
+ };
+
+ const char* AndroidCommandName(
+ ApplicationAndroid::AndroidCommand::CommandType type) {
+ switch (type) {
+ case ApplicationAndroid::AndroidCommand::kUndefined:
+ return "Undefined";
+ case ApplicationAndroid::AndroidCommand::kStart:
+ return "Start";
+ case ApplicationAndroid::AndroidCommand::kResume:
+ return "Resume";
+ case ApplicationAndroid::AndroidCommand::kPause:
+ return "Pause";
+ case ApplicationAndroid::AndroidCommand::kStop:
+ return "Stop";
+ case ApplicationAndroid::AndroidCommand::kInputQueueChanged:
+ return "InputQueueChanged";
+ case ApplicationAndroid::AndroidCommand::kNativeWindowCreated:
+ return "NativeWindowCreated";
+ case ApplicationAndroid::AndroidCommand::kNativeWindowDestroyed:
+ return "NativeWindowDestroyed";
+ case ApplicationAndroid::AndroidCommand::kWindowFocusGained:
+ return "WindowFocusGained";
+ case ApplicationAndroid::AndroidCommand::kWindowFocusLost:
+ return "WindowFocusLost";
+ default:
+ return "unknown";
+ }
+ }
+} // namespace
+
+// "using" doesn't work with class members, so make a local convenience type.
+typedef ::starboard::shared::starboard::Application::Event Event;
+
+ApplicationAndroid::ApplicationAndroid(ALooper* looper)
+ : looper_(looper),
+ native_window_(NULL),
+ input_queue_(NULL),
+ android_command_readfd_(-1),
+ android_command_writefd_(-1),
+ keyboard_inject_readfd_(-1),
+ keyboard_inject_writefd_(-1),
+ android_command_condition_(android_command_mutex_),
+ activity_state_(AndroidCommand::kUndefined),
+ window_(kSbWindowInvalid),
+ last_is_accessibility_high_contrast_text_enabled_(false) {
+
+ // Initialize Time Zone early so that local time works correctly.
+ // Called once here to help SbTimeZoneGet*Name()
+ tzset();
+
+ // Initialize Android asset access early so that ICU can load its tables
+ // from the assets. The use ICU is used in our logging.
+ SbFileAndroidInitialize();
+
+ int pipefd[2];
+ int err;
+
+ err = pipe(pipefd);
+ SB_CHECK(err >= 0) << "pipe errno is:" << errno;
+ android_command_readfd_ = pipefd[0];
+ android_command_writefd_ = pipefd[1];
+ ALooper_addFd(looper_, android_command_readfd_, kLooperIdAndroidCommand,
+ ALOOPER_EVENT_INPUT, NULL, NULL);
+
+ err = pipe(pipefd);
+ SB_CHECK(err >= 0) << "pipe errno is:" << errno;
+ keyboard_inject_readfd_ = pipefd[0];
+ keyboard_inject_writefd_ = pipefd[1];
+ ALooper_addFd(looper_, keyboard_inject_readfd_, kLooperIdKeyboardInject,
+ ALOOPER_EVENT_INPUT, NULL, NULL);
+}
+
+ApplicationAndroid::~ApplicationAndroid() {
+ ALooper_removeFd(looper_, android_command_readfd_);
+ close(android_command_readfd_);
+ close(android_command_writefd_);
+
+ ALooper_removeFd(looper_, keyboard_inject_readfd_);
+ close(keyboard_inject_readfd_);
+ close(keyboard_inject_writefd_);
+}
+
+void ApplicationAndroid::Initialize() {
+ SbAudioSinkPrivate::Initialize();
+}
+
+void ApplicationAndroid::Teardown() {
+ SbAudioSinkPrivate::TearDown();
+ SbFileAndroidTeardown();
+}
+
+SbWindow ApplicationAndroid::CreateWindow(const SbWindowOptions* options) {
+ SB_UNREFERENCED_PARAMETER(options);
+ if (SbWindowIsValid(window_)) {
+ return kSbWindowInvalid;
+ }
+ window_ = new SbWindowPrivate;
+ window_->native_window = native_window_;
+ input_events_generator_.reset(new InputEventsGenerator(window_));
+ return window_;
+}
+
+bool ApplicationAndroid::DestroyWindow(SbWindow window) {
+ if (!SbWindowIsValid(window)) {
+ return false;
+ }
+
+ input_events_generator_.reset();
+
+ SB_DCHECK(window == window_);
+ delete window_;
+ window_ = kSbWindowInvalid;
+ return true;
+}
+
+Event* ApplicationAndroid::WaitForSystemEventWithTimeout(SbTime time) {
+ // Convert from microseconds to milliseconds, taking the ceiling value.
+ // If we take the floor, or round, then we end up busy looping every time
+ // the next event time is less than one millisecond.
+ int timeout_millis = (time + kSbTimeMillisecond - 1) / kSbTimeMillisecond;
+ int looper_events;
+ int ident = ALooper_pollAll(timeout_millis, NULL, &looper_events, NULL);
+ switch (ident) {
+ case kLooperIdAndroidCommand:
+ ProcessAndroidCommand();
+ break;
+ case kLooperIdAndroidInput:
+ ProcessAndroidInput();
+ break;
+ case kLooperIdKeyboardInject:
+ ProcessKeyboardInject();
+ break;
+ }
+
+ // Always return NULL since we already dispatched our own system events.
+ return NULL;
+}
+
+void ApplicationAndroid::WakeSystemEventWait() {
+ ALooper_wake(looper_);
+}
+
+void ApplicationAndroid::OnResume() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallStarboardVoidMethodOrAbort("beforeStartOrResume", "()V");
+}
+
+void ApplicationAndroid::ProcessAndroidCommand() {
+ JniEnvExt* env = JniEnvExt::Get();
+ AndroidCommand cmd;
+ int err = read(android_command_readfd_, &cmd, sizeof(cmd));
+ SB_DCHECK(err >= 0) << "Command read failed. errno=" << errno;
+
+ SB_LOG(INFO) << "Android command: " << AndroidCommandName(cmd.type);
+
+ // The activity state to which we should sync the starboard state.
+ AndroidCommand::CommandType sync_state = AndroidCommand::kUndefined;
+
+ switch (cmd.type) {
+ case AndroidCommand::kUndefined:
+ break;
+
+ case AndroidCommand::kInputQueueChanged: {
+ ScopedLock lock(android_command_mutex_);
+ if (input_queue_) {
+ AInputQueue_detachLooper(input_queue_);
+ }
+ input_queue_ = static_cast<AInputQueue*>(cmd.data);
+ if (input_queue_) {
+ AInputQueue_attachLooper(input_queue_, looper_, kLooperIdAndroidInput,
+ NULL, NULL);
+ }
+ // Now that we've swapped our use of the input queue, signal that the
+ // Android UI thread can continue.
+ android_command_condition_.Signal();
+ break;
+ }
+
+ // Starboard resume/suspend is tied to the UI window being created/destroyed
+ // (rather than to the Activity lifecycle) since Cobalt can't do anything at
+ // all if it doesn't have a window surface to draw on.
+ case AndroidCommand::kNativeWindowCreated:
+ {
+ ScopedLock lock(android_command_mutex_);
+ native_window_ = static_cast<ANativeWindow*>(cmd.data);
+ if (window_) {
+ window_->native_window = native_window_;
+ }
+ // Now that we have the window, signal that the Android UI thread can
+ // continue, before we start or resume the Starboard app.
+ android_command_condition_.Signal();
+ }
+ if (state() == kStateUnstarted) {
+ // This is the initial launch, so we have to start Cobalt now that we
+ // have a window.
+ env->CallStarboardVoidMethodOrAbort("beforeStartOrResume", "()V");
+ DispatchStart();
+ } else {
+ // Now that we got a window back, change the command for the switch
+ // below to sync up with the current activity lifecycle.
+ sync_state = activity_state_;
+ }
+ break;
+ case AndroidCommand::kNativeWindowDestroyed:
+ env->CallStarboardVoidMethodOrAbort("beforeSuspend", "()V");
+ {
+ ScopedLock lock(android_command_mutex_);
+ // Cobalt can't keep running without a window, even if the Activity
+ // hasn't stopped yet. DispatchAndDelete() will inject events as needed
+ // if we're not already paused.
+ DispatchAndDelete(new Event(kSbEventTypeSuspend, NULL, NULL));
+ if (window_) {
+ window_->native_window = NULL;
+ }
+ native_window_ = NULL;
+ // Now that we've suspended the Starboard app, and let go of the window,
+ // signal that the Android UI thread can continue.
+ android_command_condition_.Signal();
+ }
+ break;
+
+ case AndroidCommand::kWindowFocusLost:
+ break;
+ case AndroidCommand::kWindowFocusGained: {
+ // Android does not have a publicly-exposed way to
+ // register for high-contrast text settings changed events.
+ // We assume that it can only change when our focus changes
+ // (because the user exits and enters the app) so we check
+ // for changes here.
+ SbAccessibilityDisplaySettings settings;
+ SbMemorySet(&settings, 0, sizeof(settings));
+ if (!SbAccessibilityGetDisplaySettings(&settings)) {
+ break;
+ }
+
+ bool enabled = settings.has_high_contrast_text_setting &&
+ settings.is_high_contrast_text_enabled;
+
+ if (enabled != last_is_accessibility_high_contrast_text_enabled_) {
+ DispatchAndDelete(new Event(
+ kSbEventTypeAccessiblitySettingsChanged, NULL, NULL));
+ }
+ last_is_accessibility_high_contrast_text_enabled_ = enabled;
+ break;
+ }
+
+ // Remember the Android activity state to sync to when we have a window.
+ case AndroidCommand::kStart:
+ case AndroidCommand::kResume:
+ case AndroidCommand::kPause:
+ case AndroidCommand::kStop:
+ sync_state = activity_state_ = cmd.type;
+ break;
+ }
+
+ // If there's a window, sync the app state to the Activity lifecycle, letting
+ // DispatchAndDelete() inject events as needed if we missed a state.
+ if (native_window_) {
+ switch (sync_state) {
+ case AndroidCommand::kStart:
+ DispatchAndDelete(new Event(kSbEventTypeResume, NULL, NULL));
+ break;
+ case AndroidCommand::kResume:
+ DispatchAndDelete(new Event(kSbEventTypeUnpause, NULL, NULL));
+ break;
+ case AndroidCommand::kPause:
+ DispatchAndDelete(new Event(kSbEventTypePause, NULL, NULL));
+ break;
+ case AndroidCommand::kStop:
+ if (state() != kStateSuspended) {
+ // We usually suspend when losing the window above, but if the window
+ // wasn't destroyed (e.g. when Daydream starts) then we still have to
+ // suspend when the Activity is stopped.
+ env->CallStarboardVoidMethodOrAbort("beforeSuspend", "()V");
+ DispatchAndDelete(new Event(kSbEventTypeSuspend, NULL, NULL));
+ }
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void ApplicationAndroid::SendAndroidCommand(AndroidCommand::CommandType type,
+ void* data) {
+ SB_LOG(INFO) << "Send Android command: " << AndroidCommandName(type);
+ AndroidCommand cmd {type, data};
+ ScopedLock lock(android_command_mutex_);
+ write(android_command_writefd_, &cmd, sizeof(cmd));
+ // Synchronization only necessary when managing resources.
+ switch (type) {
+ case AndroidCommand::kInputQueueChanged:
+ while (input_queue_ != data) {
+ android_command_condition_.Wait();
+ }
+ break;
+ case AndroidCommand::kNativeWindowCreated:
+ case AndroidCommand::kNativeWindowDestroyed:
+ while (native_window_ != data) {
+ android_command_condition_.Wait();
+ }
+ break;
+ default:
+ break;
+ }
+}
+
+void ApplicationAndroid::ProcessAndroidInput() {
+ SB_DCHECK(input_events_generator_);
+ AInputEvent* android_event = NULL;
+ while (AInputQueue_getEvent(input_queue_, &android_event) >= 0) {
+ SB_LOG(INFO) << "Android input: type="
+ << AInputEvent_getType(android_event);
+ if (AInputQueue_preDispatchEvent(input_queue_, android_event)) {
+ continue;
+ }
+ InputEventsGenerator::Events app_events;
+ bool handled = input_events_generator_->CreateInputEventsFromAndroidEvent(
+ android_event, &app_events);
+ for (int i = 0; i < app_events.size(); ++i) {
+ DispatchAndDelete(app_events[i].release());
+ }
+ AInputQueue_finishEvent(input_queue_, android_event, handled);
+ }
+}
+
+void ApplicationAndroid::ProcessKeyboardInject() {
+ SbKey key;
+ int err = read(keyboard_inject_readfd_, &key, sizeof(key));
+ SB_DCHECK(err >= 0) << "Keyboard inject read failed: errno=" << errno;
+ SB_LOG(INFO) << "Keyboard inject: " << key;
+
+ InputEventsGenerator::Events app_events;
+ input_events_generator_->CreateInputEventsFromSbKey(key, &app_events);
+ for (int i = 0; i < app_events.size(); ++i) {
+ DispatchAndDelete(app_events[i].release());
+ }
+}
+
+void ApplicationAndroid::SendKeyboardInject(SbKey key) {
+ write(keyboard_inject_writefd_, &key, sizeof(key));
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_CobaltA11yHelper_nativeInjectKeyEvent(JNIEnv* env,
+ jobject unused_clazz,
+ jint key) {
+ ApplicationAndroid::Get()->SendKeyboardInject(static_cast<SbKey>(key));
+}
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+
+void ApplicationAndroid::SbWindowShowOnScreenKeyboard(SbWindow window,
+ const char* input_text,
+ int ticket) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject j_keyboard_editor = env->CallStarboardObjectMethodOrAbort(
+ "getKeyboardEditor", "()Ldev/cobalt/coat/KeyboardEditor;");
+ env->CallVoidMethodOrAbort(j_keyboard_editor, "showKeyboard", "()V");
+ // TODO: Fire kSbEventTypeWindowSizeChange and
+ // kSbEventTypeOnScreenKeyboardShown if necessary.
+ return;
+}
+
+void ApplicationAndroid::SbWindowHideOnScreenKeyboard(SbWindow window,
+ int ticket) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject j_keyboard_editor = env->CallStarboardObjectMethodOrAbort(
+ "getKeyboardEditor", "()Ldev/cobalt/coat/KeyboardEditor;");
+ env->CallVoidMethodOrAbort(j_keyboard_editor, "hideKeyboard", "()V");
+ // TODO: Fire kSbEventTypeWindowSizeChange and
+ // kSbEventTypeOnScreenKeyboardHidden if necessary.
+ return;
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_KeyboardInputConnection_nativeSendText(
+ JniEnvExt* env,
+ jobject unused_clazz,
+ jstring text) {
+ if (text) {
+ std::string utf_str = env->GetStringStandardUTFOrAbort(text);
+ ApplicationAndroid::Get()->SbWindowSendInputEvent(utf_str.c_str());
+ }
+}
+
+void DeleteSbInputDataWithText(void* ptr) {
+ SbInputData* data = static_cast<SbInputData*>(ptr);
+ const char* input_text = data->input_text;
+ data->input_text = NULL;
+ delete input_text;
+ ApplicationAndroid::DeleteDestructor<SbInputData>(ptr);
+}
+
+void ApplicationAndroid::SbWindowSendInputEvent(const char* input_text) {
+ char* text = SbStringDuplicate(input_text);
+ SbInputData* data = new SbInputData();
+ SbMemorySet(data, 0, sizeof(*data));
+ data->window = window_;
+ data->type = kSbInputEventTypeInput;
+ data->device_type = kSbInputDeviceTypeOnScreenKeyboard;
+ data->input_text = text;
+ Inject(new Event(kSbEventTypeInput, data, &DeleteSbInputDataWithText));
+ return;
+}
+
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
+
+bool ApplicationAndroid::OnSearchRequested() {
+ for (int i = 0; i < 2; i++) {
+ SbInputData* data = new SbInputData();
+ SbMemorySet(data, 0, sizeof(*data));
+ data->window = window_;
+ data->key = kSbKeyBrowserSearch;
+ data->type = (i == 0) ? kSbInputEventTypePress : kSbInputEventTypeUnpress;
+ Inject(new Event(kSbEventTypeInput, data, &DeleteDestructor<SbInputData>));
+ }
+ return true;
+}
+
+extern "C" SB_EXPORT_PLATFORM
+jboolean Java_dev_cobalt_coat_StarboardBridge_nativeOnSearchRequested(
+ JniEnvExt* env, jobject unused_this) {
+ return ApplicationAndroid::Get()->OnSearchRequested();
+}
+
+void ApplicationAndroid::HandleDeepLink(const char* link_url) {
+ if (link_url == NULL || link_url[0] == '\0') {
+ return;
+ }
+ char* deep_link = SbStringDuplicate(link_url);
+ SB_DCHECK(deep_link);
+ Inject(new Event(kSbEventTypeLink, deep_link, SbMemoryDeallocate));
+}
+
+extern "C" SB_EXPORT_PLATFORM
+void Java_dev_cobalt_coat_StarboardBridge_nativeHandleDeepLink(
+ JniEnvExt* env, jobject unused_this, jstring j_url) {
+ if (j_url) {
+ std::string utf_str = env->GetStringStandardUTFOrAbort(j_url);
+ ApplicationAndroid::Get()->HandleDeepLink(utf_str.c_str());
+ }
+}
+
+extern "C" SB_EXPORT_PLATFORM
+void Java_dev_cobalt_coat_StarboardBridge_nativeStopApp(
+ JniEnvExt* env, jobject unused_this, jint error_level) {
+ ApplicationAndroid::Get()->Stop(error_level);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/application_android.h b/src/starboard/android/shared/application_android.h
new file mode 100644
index 0000000..3631e03
--- /dev/null
+++ b/src/starboard/android/shared/application_android.h
@@ -0,0 +1,131 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_APPLICATION_ANDROID_H_
+#define STARBOARD_ANDROID_SHARED_APPLICATION_ANDROID_H_
+
+#include <android/looper.h>
+#include <android/native_window.h>
+
+#include "starboard/android/shared/input_events_generator.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/condition_variable.h"
+#include "starboard/configuration.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/shared/starboard/queue_application.h"
+#include "starboard/types.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// Android application receiving commands and input through ALooper.
+class ApplicationAndroid
+ : public ::starboard::shared::starboard::QueueApplication {
+ public:
+ struct AndroidCommand {
+ typedef enum {
+ kUndefined,
+ kStart,
+ kResume,
+ kPause,
+ kStop,
+ kInputQueueChanged,
+ kNativeWindowCreated,
+ kNativeWindowDestroyed,
+ kWindowFocusGained,
+ kWindowFocusLost,
+ } CommandType;
+
+ CommandType type;
+ void* data;
+ };
+
+ explicit ApplicationAndroid(ALooper* looper);
+ ~ApplicationAndroid() override;
+
+ static ApplicationAndroid* Get() {
+ return static_cast<ApplicationAndroid*>(
+ ::starboard::shared::starboard::Application::Get());
+ }
+
+ SbWindow CreateWindow(const SbWindowOptions* options);
+ bool DestroyWindow(SbWindow window);
+ bool OnSearchRequested();
+ void HandleDeepLink(const char* link_url);
+
+ void SendAndroidCommand(AndroidCommand::CommandType type, void* data);
+ void SendAndroidCommand(AndroidCommand::CommandType type) {
+ SendAndroidCommand(type, NULL);
+ }
+ void SendKeyboardInject(SbKey key);
+
+ void SbWindowShowOnScreenKeyboard(SbWindow window,
+ const char* input_text,
+ int ticket);
+ void SbWindowHideOnScreenKeyboard(SbWindow window, int ticket);
+ void SbWindowSendInputEvent(const char* input_text);
+
+ protected:
+ // --- Application overrides ---
+ void Initialize() override;
+ void Teardown() override;
+ bool IsStartImmediate() override { return false; }
+ void OnResume() override;
+
+ // --- QueueApplication overrides ---
+ bool MayHaveSystemEvents() override { return true; }
+ Event* WaitForSystemEventWithTimeout(SbTime time) override;
+ void WakeSystemEventWait() override;
+
+ private:
+ ALooper* looper_;
+ ANativeWindow* native_window_;
+ AInputQueue* input_queue_;
+
+ // Pipes attached to the looper.
+ int android_command_readfd_;
+ int android_command_writefd_;
+ int keyboard_inject_readfd_;
+ int keyboard_inject_writefd_;
+
+ // Synchronization for commands that change availability of Android resources
+ // such as the input_queue_ and/or native_window_.
+ Mutex android_command_mutex_;
+ ConditionVariable android_command_condition_;
+
+ // The last Activity lifecycle state command received.
+ AndroidCommand::CommandType activity_state_;
+
+ // The single open window, if any.
+ SbWindow window_;
+
+ scoped_ptr<InputEventsGenerator> input_events_generator_;
+
+ bool last_is_accessibility_high_contrast_text_enabled_;
+
+ // Methods to process pipes attached to the Looper.
+ void ProcessAndroidCommand();
+ void ProcessAndroidInput();
+ void ProcessKeyboardInject();
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_APPLICATION_ANDROID_H_
diff --git a/src/starboard/android/shared/atomic_public.h b/src/starboard/android/shared/atomic_public.h
new file mode 100644
index 0000000..5bd90ca
--- /dev/null
+++ b/src/starboard/android/shared/atomic_public.h
@@ -0,0 +1,26 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_ATOMIC_PUBLIC_H_
+#define STARBOARD_ANDROID_SHARED_ATOMIC_PUBLIC_H_
+
+#include "starboard/atomic.h"
+
+#if SB_IS(COMPILER_GCC)
+#include "starboard/shared/gcc/atomic_gcc_public.h"
+#else
+#error "Unknown Android compiler."
+#endif
+
+#endif // STARBOARD_ANDROID_SHARED_ATOMIC_PUBLIC_H_
diff --git a/src/starboard/android/shared/audio_decoder.cc b/src/starboard/android/shared/audio_decoder.cc
new file mode 100644
index 0000000..5bd0905
--- /dev/null
+++ b/src/starboard/android/shared/audio_decoder.cc
@@ -0,0 +1,250 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/audio_decoder.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/audio_sink.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+
+// Can be locally set to |1| for verbose audio decoding. Verbose audio
+// decoding will log the following transitions that take place for each audio
+// unit:
+// T1: Our client passes an |InputBuffer| of audio data into us.
+// T2: We receive a corresponding media codec output buffer back from our
+// |MediaCodecBridge|.
+// T3: Our client reads a corresponding |DecodedAudio| out of us.
+//
+// Example usage for debugging audio playback:
+// $ adb logcat -c
+// $ adb logcat | tee log.txt
+// # Play video and get to frozen point.
+// $ CTRL-C
+// $ cat log.txt | grep -P 'T2: pts \d+' | wc -l
+// 523
+// $ cat log.txt | grep -P 'T3: pts \d+' | wc -l
+// 522
+// # Oh no, why isn't our client reading the audio we have ready to go?
+// # Time to go find out...
+#define STARBOARD_ANDROID_SHARED_AUDIO_DECODER_VERBOSE 0
+#if STARBOARD_ANDROID_SHARED_AUDIO_DECODER_VERBOSE
+#define VERBOSE_MEDIA_LOG() SB_LOG(INFO)
+#else
+#define VERBOSE_MEDIA_LOG() SB_EAT_STREAM_PARAMETERS
+#endif
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+SbMediaAudioSampleType GetSupportedSampleType() {
+ SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(
+ kSbMediaAudioSampleTypeInt16Deprecated));
+ return kSbMediaAudioSampleTypeInt16Deprecated;
+}
+
+void* IncrementPointerByBytes(void* pointer, int offset) {
+ return static_cast<uint8_t*>(pointer) + offset;
+}
+
+} // namespace
+
+AudioDecoder::AudioDecoder(SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system)
+ : audio_codec_(audio_codec),
+ audio_header_(audio_header),
+ sample_type_(GetSupportedSampleType()),
+ output_sample_rate_(audio_header.samples_per_second),
+ output_channel_count_(audio_header.number_of_channels),
+ drm_system_(static_cast<DrmSystem*>(drm_system)) {
+ if (!InitializeCodec()) {
+ SB_LOG(ERROR) << "Failed to initialize audio decoder.";
+ }
+}
+
+AudioDecoder::~AudioDecoder() {}
+
+void AudioDecoder::Initialize(const OutputCB& output_cb,
+ const ErrorCB& error_cb) {
+ SB_DCHECK(BelongsToCurrentThread());
+ SB_DCHECK(output_cb);
+ SB_DCHECK(!output_cb_);
+ SB_DCHECK(error_cb);
+ SB_DCHECK(!error_cb_);
+ SB_DCHECK(media_decoder_);
+
+ output_cb_ = output_cb;
+ error_cb_ = error_cb;
+
+ media_decoder_->Initialize(error_cb_);
+}
+
+void AudioDecoder::Decode(const scoped_refptr<InputBuffer>& input_buffer,
+ const ConsumedCB& consumed_cb) {
+ SB_DCHECK(BelongsToCurrentThread());
+ SB_DCHECK(input_buffer);
+ SB_DCHECK(output_cb_);
+ SB_DCHECK(media_decoder_);
+
+ VERBOSE_MEDIA_LOG() << "T1: timestamp " << input_buffer->timestamp();
+
+ media_decoder_->WriteInputBuffer(input_buffer);
+
+ ScopedLock lock(decoded_audios_mutex_);
+ if (media_decoder_->GetNumberOfPendingTasks() + decoded_audios_.size() <=
+ kMaxPendingWorkSize) {
+ Schedule(consumed_cb);
+ } else {
+ consumed_cb_ = consumed_cb;
+ }
+}
+
+void AudioDecoder::WriteEndOfStream() {
+ SB_DCHECK(BelongsToCurrentThread());
+ SB_DCHECK(output_cb_);
+ SB_DCHECK(media_decoder_);
+
+ media_decoder_->WriteEndOfStream();
+}
+
+scoped_refptr<AudioDecoder::DecodedAudio> AudioDecoder::Read() {
+ SB_DCHECK(BelongsToCurrentThread());
+ SB_DCHECK(output_cb_);
+
+ scoped_refptr<DecodedAudio> result;
+ {
+ starboard::ScopedLock lock(decoded_audios_mutex_);
+ SB_DCHECK(!decoded_audios_.empty());
+ if (!decoded_audios_.empty()) {
+ result = decoded_audios_.front();
+ VERBOSE_MEDIA_LOG() << "T3: timestamp " << result->timestamp();
+ decoded_audios_.pop();
+ }
+ }
+
+ if (consumed_cb_) {
+ Schedule(consumed_cb_);
+ consumed_cb_ = nullptr;
+ }
+ return result;
+}
+
+void AudioDecoder::Reset() {
+ SB_DCHECK(BelongsToCurrentThread());
+ SB_DCHECK(output_cb_);
+
+ media_decoder_.reset();
+
+ if (!InitializeCodec()) {
+ // TODO: Communicate this failure to our clients somehow.
+ SB_LOG(ERROR) << "Failed to initialize codec after reset.";
+ }
+
+ consumed_cb_ = nullptr;
+
+ while (!decoded_audios_.empty()) {
+ decoded_audios_.pop();
+ }
+
+ CancelPendingJobs();
+}
+
+bool AudioDecoder::InitializeCodec() {
+ SB_DCHECK(!media_decoder_);
+ media_decoder_.reset(
+ new MediaDecoder(this, audio_codec_, audio_header_, drm_system_));
+ if (media_decoder_->is_valid()) {
+ if (error_cb_) {
+ media_decoder_->Initialize(error_cb_);
+ }
+ return true;
+ }
+ media_decoder_.reset();
+ return false;
+}
+
+void AudioDecoder::ProcessOutputBuffer(
+ MediaCodecBridge* media_codec_bridge,
+ const DequeueOutputResult& dequeue_output_result) {
+ SB_DCHECK(media_codec_bridge);
+ SB_DCHECK(output_cb_);
+ SB_DCHECK(dequeue_output_result.index >= 0);
+
+ if (dequeue_output_result.flags & BUFFER_FLAG_END_OF_STREAM) {
+ media_codec_bridge->ReleaseOutputBuffer(dequeue_output_result.index, false);
+ {
+ starboard::ScopedLock lock(decoded_audios_mutex_);
+ decoded_audios_.push(new DecodedAudio());
+ }
+
+ Schedule(output_cb_);
+ return;
+ }
+
+ ScopedJavaByteBuffer byte_buffer(
+ media_codec_bridge->GetOutputBuffer(dequeue_output_result.index));
+ SB_DCHECK(!byte_buffer.IsNull());
+
+ if (dequeue_output_result.num_bytes > 0) {
+ int16_t* data = static_cast<int16_t*>(IncrementPointerByBytes(
+ byte_buffer.address(), dequeue_output_result.offset));
+ int size = dequeue_output_result.num_bytes;
+ if (2 * audio_header_.samples_per_second == output_sample_rate_) {
+ // The audio is encoded using implicit HE-AAC. As the audio sink has
+ // been created already we try to down-mix the decoded data to half of
+ // its channels so the audio sink can play it with the correct pitch.
+ for (int i = 0; i < size / sizeof(int16_t); i++) {
+ data[i / 2] = (static_cast<int32_t>(data[i]) +
+ static_cast<int32_t>(data[i + 1]) / 2);
+ }
+ size /= 2;
+ }
+
+ scoped_refptr<DecodedAudio> decoded_audio = new DecodedAudio(
+ audio_header_.number_of_channels, GetSampleType(), GetStorageType(),
+ dequeue_output_result.presentation_time_microseconds, size);
+
+ SbMemoryCopy(decoded_audio->buffer(), data, size);
+ {
+ starboard::ScopedLock lock(decoded_audios_mutex_);
+ decoded_audios_.push(decoded_audio);
+ VERBOSE_MEDIA_LOG() << "T2: timestamp "
+ << decoded_audios_.front()->timestamp();
+ }
+ Schedule(output_cb_);
+ }
+
+ media_codec_bridge->ReleaseOutputBuffer(dequeue_output_result.index, false);
+}
+
+void AudioDecoder::RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) {
+ AudioOutputFormatResult output_format =
+ media_codec_bridge->GetAudioOutputFormat();
+ if (output_format.status == MEDIA_CODEC_ERROR) {
+ SB_LOG(ERROR) << "|getOutputFormat| failed";
+ return;
+ }
+ output_sample_rate_ = output_format.sample_rate;
+ output_channel_count_ = output_format.channel_count;
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/audio_decoder.h b/src/starboard/android/shared/audio_decoder.h
new file mode 100644
index 0000000..c18e4a4
--- /dev/null
+++ b/src/starboard/android/shared/audio_decoder.h
@@ -0,0 +1,100 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_AUDIO_DECODER_H_
+#define STARBOARD_ANDROID_SHARED_AUDIO_DECODER_H_
+
+#include <jni.h>
+
+#include <queue>
+
+#include "starboard/android/shared/drm_system.h"
+#include "starboard/android/shared/media_codec_bridge.h"
+#include "starboard/android/shared/media_decoder.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/decoded_audio_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/job_queue.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class AudioDecoder
+ : public ::starboard::shared::starboard::player::filter::AudioDecoder,
+ private ::starboard::shared::starboard::player::JobQueue::JobOwner,
+ private MediaDecoder::Host {
+ public:
+ AudioDecoder(SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system);
+ ~AudioDecoder() override;
+
+ void Initialize(const OutputCB& output_cb, const ErrorCB& error_cb) override;
+ void Decode(const scoped_refptr<InputBuffer>& input_buffer,
+ const ConsumedCB& consumed_cb) override;
+ void WriteEndOfStream() override;
+ scoped_refptr<DecodedAudio> Read() override;
+ void Reset() override;
+
+ SbMediaAudioSampleType GetSampleType() const override {
+ return sample_type_;
+ }
+ SbMediaAudioFrameStorageType GetStorageType() const override {
+ return kSbMediaAudioFrameStorageTypeInterleaved;
+ }
+ int GetSamplesPerSecond() const override {
+ return audio_header_.samples_per_second;
+ }
+
+ bool is_valid() const { return media_decoder_ != NULL; }
+
+ private:
+ // The maximum amount of work that can exist in the union of |EventQueue|,
+ // |pending_work| and |decoded_audios_|.
+ static const int kMaxPendingWorkSize = 64;
+
+ bool InitializeCodec();
+ void ProcessOutputBuffer(MediaCodecBridge* media_codec_bridge,
+ const DequeueOutputResult& output) override;
+ void RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) override;
+ bool Tick(MediaCodecBridge* media_codec_bridge) override { return false; }
+ void OnFlushing() override {}
+
+ SbMediaAudioCodec audio_codec_;
+ SbMediaAudioHeader audio_header_;
+ SbMediaAudioSampleType sample_type_;
+
+ jint output_sample_rate_;
+ jint output_channel_count_;
+
+ DrmSystem* drm_system_;
+
+ OutputCB output_cb_;
+ ErrorCB error_cb_;
+ ConsumedCB consumed_cb_;
+
+ starboard::Mutex decoded_audios_mutex_;
+ std::queue<scoped_refptr<DecodedAudio> > decoded_audios_;
+
+ scoped_ptr<MediaDecoder> media_decoder_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_AUDIO_DECODER_H_
diff --git a/src/starboard/android/shared/audio_sink_get_max_channels.cc b/src/starboard/android/shared/audio_sink_get_max_channels.cc
new file mode 100644
index 0000000..979f62f
--- /dev/null
+++ b/src/starboard/android/shared/audio_sink_get_max_channels.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/audio_sink.h"
+
+int SbAudioSinkGetMaxChannels() {
+ return 6;
+}
diff --git a/src/starboard/android/shared/audio_sink_get_nearest_supported_sample_frequency.cc b/src/starboard/android/shared/audio_sink_get_nearest_supported_sample_frequency.cc
new file mode 100644
index 0000000..bc753b9
--- /dev/null
+++ b/src/starboard/android/shared/audio_sink_get_nearest_supported_sample_frequency.cc
@@ -0,0 +1,27 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/audio_sink.h"
+
+#include "starboard/log.h"
+
+int SbAudioSinkGetNearestSupportedSampleFrequency(int sampling_frequency_hz) {
+ if (sampling_frequency_hz <= 0) {
+ SB_LOG(ERROR) << "Invalid audio sampling frequency "
+ << sampling_frequency_hz;
+ return 1;
+ }
+
+ return sampling_frequency_hz;
+}
diff --git a/src/starboard/android/shared/audio_sink_is_audio_frame_storage_type_supported.cc b/src/starboard/android/shared/audio_sink_is_audio_frame_storage_type_supported.cc
new file mode 100644
index 0000000..771410e
--- /dev/null
+++ b/src/starboard/android/shared/audio_sink_is_audio_frame_storage_type_supported.cc
@@ -0,0 +1,20 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/audio_sink.h"
+
+bool SbAudioSinkIsAudioFrameStorageTypeSupported(
+ SbMediaAudioFrameStorageType audio_frame_storage_type) {
+ return audio_frame_storage_type == kSbMediaAudioFrameStorageTypeInterleaved;
+}
diff --git a/src/starboard/android/shared/audio_sink_is_audio_sample_type_supported.cc b/src/starboard/android/shared/audio_sink_is_audio_sample_type_supported.cc
new file mode 100644
index 0000000..1de6614
--- /dev/null
+++ b/src/starboard/android/shared/audio_sink_is_audio_sample_type_supported.cc
@@ -0,0 +1,21 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/audio_sink.h"
+
+bool SbAudioSinkIsAudioSampleTypeSupported(
+ SbMediaAudioSampleType audio_sample_type) {
+ return audio_sample_type == kSbMediaAudioSampleTypeInt16Deprecated ||
+ audio_sample_type == kSbMediaAudioSampleTypeFloat32;
+}
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.cc b/src/starboard/android/shared/audio_track_audio_sink_type.cc
new file mode 100644
index 0000000..43cf47d
--- /dev/null
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.cc
@@ -0,0 +1,414 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/audio_track_audio_sink_type.h"
+
+#include <algorithm>
+#include <deque>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/mutex.h"
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+// The maximum number of frames that can be written to android audio track per
+// write request. If we don't set this cap for writing frames to audio track,
+// we will repeatedly allocate a large byte array which cannot be consumed by
+// audio track completely.
+const int kMaxFramesPerRequest = 65536;
+
+const jint kNoOffset = 0;
+
+// Helper function to compute the size of the two valid starboard audio sample
+// types.
+size_t GetSampleSize(SbMediaAudioSampleType sample_type) {
+ switch (sample_type) {
+ case kSbMediaAudioSampleTypeFloat32:
+ return sizeof(float);
+ case kSbMediaAudioSampleTypeInt16Deprecated:
+ return sizeof(int16_t);
+ }
+ SB_NOTREACHED();
+ return 0u;
+}
+
+int GetAudioFormatSampleType(SbMediaAudioSampleType sample_type) {
+ switch (sample_type) {
+ case kSbMediaAudioSampleTypeFloat32:
+ // Android AudioFormat.ENCODING_PCM_FLOAT.
+ return 4;
+ case kSbMediaAudioSampleTypeInt16Deprecated:
+ // Android AudioFormat.ENCODING_PCM_16BIT.
+ return 2;
+ }
+ SB_NOTREACHED();
+ return 0u;
+}
+
+void* IncrementPointerByBytes(void* pointer, size_t offset) {
+ return static_cast<uint8_t*>(pointer) + offset;
+}
+
+class AudioTrackAudioSink : public SbAudioSinkPrivate {
+ public:
+ AudioTrackAudioSink(
+ Type* type,
+ int channels,
+ int sampling_frequency_hz,
+ SbMediaAudioSampleType sample_type,
+ SbAudioSinkFrameBuffers frame_buffers,
+ int frames_per_channel,
+ SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+ SbAudioSinkConsumeFramesFunc consume_frame_func,
+ void* context);
+ ~AudioTrackAudioSink() override;
+
+ bool IsAudioTrackValid() const { return j_audio_track_bridge_; }
+ bool IsType(Type* type) override { return type_ == type; }
+ void SetPlaybackRate(double playback_rate) override {
+ SB_DCHECK(playback_rate >= 0.0);
+ if (playback_rate != 0.0 && playback_rate != 1.0) {
+ SB_NOTIMPLEMENTED() << "TODO: Only playback rates of 0.0 and 1.0 are "
+ "currently supported.";
+ playback_rate = (playback_rate > 0.0) ? 1.0 : 0.0;
+ }
+ ScopedLock lock(mutex_);
+ playback_rate_ = playback_rate;
+ }
+
+ void SetVolume(double volume) override;
+
+ private:
+ static void* ThreadEntryPoint(void* context);
+ void AudioThreadFunc();
+
+ Type* type_;
+ int channels_;
+ int sampling_frequency_hz_;
+ SbMediaAudioSampleType sample_type_;
+ void* frame_buffer_;
+ int frames_per_channel_;
+ SbAudioSinkUpdateSourceStatusFunc update_source_status_func_;
+ SbAudioSinkConsumeFramesFunc consume_frame_func_;
+ void* context_;
+ int last_playback_head_position_;
+ jobject j_audio_track_bridge_;
+ jobject j_audio_data_;
+
+ volatile bool quit_;
+ SbThread audio_out_thread_;
+
+ starboard::Mutex mutex_;
+ double playback_rate_;
+
+ int written_frames_;
+};
+
+AudioTrackAudioSink::AudioTrackAudioSink(
+ Type* type,
+ int channels,
+ int sampling_frequency_hz,
+ SbMediaAudioSampleType sample_type,
+ SbAudioSinkFrameBuffers frame_buffers,
+ int frames_per_channel,
+ SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+ SbAudioSinkConsumeFramesFunc consume_frame_func,
+ void* context)
+ : type_(type),
+ channels_(channels),
+ sampling_frequency_hz_(sampling_frequency_hz),
+ sample_type_(sample_type),
+ frame_buffer_(frame_buffers[0]),
+ frames_per_channel_(frames_per_channel),
+ update_source_status_func_(update_source_status_func),
+ consume_frame_func_(consume_frame_func),
+ context_(context),
+ last_playback_head_position_(0),
+ j_audio_track_bridge_(NULL),
+ j_audio_data_(NULL),
+ quit_(false),
+ audio_out_thread_(kSbThreadInvalid),
+ playback_rate_(1.0f),
+ written_frames_(0) {
+ SB_DCHECK(update_source_status_func_);
+ SB_DCHECK(consume_frame_func_);
+ SB_DCHECK(frame_buffer_);
+ SB_DCHECK(SbAudioSinkIsAudioSampleTypeSupported(sample_type));
+
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_audio_output_manager(
+ env->CallStarboardObjectMethodOrAbort(
+ "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+ jobject j_audio_track_bridge = env->CallObjectMethodOrAbort(
+ j_audio_output_manager.Get(), "createAudioTrackBridge",
+ "(IIII)Ldev/cobalt/media/AudioTrackBridge;",
+ GetAudioFormatSampleType(sample_type_), sampling_frequency_hz_, channels_,
+ frames_per_channel);
+ if (!j_audio_track_bridge) {
+ return;
+ }
+ j_audio_track_bridge_ = env->ConvertLocalRefToGlobalRef(j_audio_track_bridge);
+ if (sample_type_ == kSbMediaAudioSampleTypeFloat32) {
+ j_audio_data_ = env->NewFloatArray(channels_ * kMaxFramesPerRequest);
+ } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
+ j_audio_data_ = env->NewByteArray(channels_ * GetSampleSize(sample_type_) *
+ kMaxFramesPerRequest);
+ } else {
+ SB_NOTREACHED();
+ }
+ j_audio_data_ = env->ConvertLocalRefToGlobalRef(j_audio_data_);
+
+ audio_out_thread_ = SbThreadCreate(
+ 0, kSbThreadPriorityRealTime, kSbThreadNoAffinity, true,
+ "audio_track_audio_out", &AudioTrackAudioSink::ThreadEntryPoint, this);
+ SB_DCHECK(SbThreadIsValid(audio_out_thread_));
+}
+
+AudioTrackAudioSink::~AudioTrackAudioSink() {
+ quit_ = true;
+
+ if (SbThreadIsValid(audio_out_thread_)) {
+ SbThreadJoin(audio_out_thread_, NULL);
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ if (j_audio_track_bridge_) {
+ ScopedLocalJavaRef<jobject> j_audio_output_manager(
+ env->CallStarboardObjectMethodOrAbort(
+ "getAudioOutputManager",
+ "()Ldev/cobalt/media/AudioOutputManager;"));
+ env->CallVoidMethodOrAbort(
+ j_audio_output_manager.Get(), "destroyAudioTrackBridge",
+ "(Ldev/cobalt/media/AudioTrackBridge;)V", j_audio_track_bridge_);
+ env->DeleteGlobalRef(j_audio_track_bridge_);
+ j_audio_track_bridge_ = NULL;
+ }
+
+ if (j_audio_data_) {
+ env->DeleteGlobalRef(j_audio_data_);
+ j_audio_data_ = NULL;
+ }
+}
+
+// static
+void* AudioTrackAudioSink::ThreadEntryPoint(void* context) {
+ SB_DCHECK(context);
+ AudioTrackAudioSink* sink = reinterpret_cast<AudioTrackAudioSink*>(context);
+ sink->AudioThreadFunc();
+
+ return NULL;
+}
+
+void AudioTrackAudioSink::AudioThreadFunc() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V");
+
+ bool was_playing = true;
+
+ while (!quit_) {
+ ScopedLocalJavaRef<jobject> j_audio_timestamp(
+ env->CallObjectMethodOrAbort(j_audio_track_bridge_, "getAudioTimestamp",
+ "()Landroid/media/AudioTimestamp;"));
+
+ int playback_head_position =
+ env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "framePosition", "J");
+ SbTime frames_consumed_at =
+ env->GetLongFieldOrAbort(j_audio_timestamp.Get(), "nanoTime", "J") /
+ 1000;
+
+ SB_DCHECK(playback_head_position >= last_playback_head_position_);
+
+ playback_head_position =
+ std::max(playback_head_position, last_playback_head_position_);
+ int frames_consumed = playback_head_position - last_playback_head_position_;
+ last_playback_head_position_ = playback_head_position;
+ frames_consumed = std::min(frames_consumed, written_frames_);
+
+ if (frames_consumed != 0) {
+ SB_DCHECK(frames_consumed >= 0);
+ consume_frame_func_(frames_consumed, frames_consumed_at, context_);
+ written_frames_ -= frames_consumed;
+ }
+
+ int frames_in_buffer;
+ int offset_in_frames;
+ bool is_playing;
+ bool is_eos_reached;
+ update_source_status_func_(&frames_in_buffer, &offset_in_frames,
+ &is_playing, &is_eos_reached, context_);
+
+ {
+ ScopedLock lock(mutex_);
+ if (playback_rate_ == 0.0) {
+ is_playing = false;
+ }
+ }
+
+ if (was_playing && !is_playing) {
+ was_playing = false;
+ env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
+ } else if (!was_playing && is_playing) {
+ was_playing = true;
+ env->CallVoidMethodOrAbort(j_audio_track_bridge_, "play", "()V");
+ }
+
+ if (!is_playing || frames_in_buffer == 0) {
+ SbThreadSleep(10 * kSbTimeMillisecond);
+ continue;
+ }
+
+ int start_position =
+ (offset_in_frames + written_frames_) % frames_per_channel_;
+ int expected_written_frames = 0;
+ if (frames_per_channel_ > offset_in_frames + written_frames_) {
+ expected_written_frames =
+ std::min(frames_per_channel_ - (offset_in_frames + written_frames_),
+ frames_in_buffer - written_frames_);
+ } else {
+ expected_written_frames = frames_in_buffer - written_frames_;
+ }
+
+ expected_written_frames =
+ std::min(expected_written_frames, kMaxFramesPerRequest);
+ if (expected_written_frames == 0) {
+ // It is possible that all the frames in buffer are written to the
+ // soundcard, but those are not being consumed.
+ continue;
+ }
+ SB_DCHECK(expected_written_frames > 0);
+ bool written_fully = false;
+
+ if (sample_type_ == kSbMediaAudioSampleTypeFloat32) {
+ int expected_written_size = expected_written_frames * channels_;
+ env->SetFloatArrayRegion(
+ static_cast<jfloatArray>(j_audio_data_), kNoOffset,
+ expected_written_size,
+ static_cast<const float*>(IncrementPointerByBytes(
+ frame_buffer_,
+ start_position * channels_ * GetSampleSize(sample_type_))));
+ int written =
+ env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([FI)I",
+ j_audio_data_, expected_written_size);
+ SB_DCHECK(written >= 0);
+ SB_DCHECK(written % channels_ == 0);
+ written_frames_ += written / channels_;
+ written_fully = (written == expected_written_frames);
+ } else if (sample_type_ == kSbMediaAudioSampleTypeInt16Deprecated) {
+ int expected_written_size =
+ expected_written_frames * channels_ * GetSampleSize(sample_type_);
+ env->SetByteArrayRegion(
+ static_cast<jbyteArray>(j_audio_data_), kNoOffset,
+ expected_written_size,
+ static_cast<const jbyte*>(IncrementPointerByBytes(
+ frame_buffer_,
+ start_position * channels_ * GetSampleSize(sample_type_))));
+ int written =
+ env->CallIntMethodOrAbort(j_audio_track_bridge_, "write", "([BI)I",
+ j_audio_data_, expected_written_size);
+ SB_DCHECK(written >= 0);
+ SB_DCHECK(written % (channels_ * GetSampleSize(sample_type_)) == 0);
+ written_frames_ += written / (channels_ * GetSampleSize(sample_type_));
+ written_fully = (written == expected_written_frames);
+ } else {
+ SB_NOTREACHED();
+ }
+
+ auto unplayed_frames_in_time =
+ written_frames_ * kSbTimeSecond / sampling_frequency_hz_ -
+ (SbTimeGetMonotonicNow() - frames_consumed_at);
+ // As long as there is enough data in the buffer, run the loop in lower
+ // frequency to avoid taking too much CPU. Note that the threshold should
+ // be big enough to account for the unstable playback head reported at the
+ // beginning of the playback and during underrun.
+ if (playback_head_position > 0 &&
+ unplayed_frames_in_time > 500 * kSbTimeMillisecond) {
+ SbThreadSleep(40 * kSbTimeMillisecond);
+ } else if (!written_fully) {
+ // Only sleep if the buffer is nearly full and the last write is partial.
+ SbThreadSleep(1 * kSbTimeMillisecond);
+ }
+ }
+
+ // For an immediate stop, use pause(), followed by flush() to discard audio
+ // data that hasn't been played back yet.
+ env->CallVoidMethodOrAbort(j_audio_track_bridge_, "pause", "()V");
+ // Flushes the audio data currently queued for playback. Any data that has
+ // been written but not yet presented will be discarded.
+ env->CallVoidMethodOrAbort(j_audio_track_bridge_, "flush", "()V");
+}
+
+void AudioTrackAudioSink::SetVolume(double volume) {
+ auto* env = JniEnvExt::Get();
+ jint status = env->CallIntMethodOrAbort(j_audio_track_bridge_, "setVolume",
+ "(F)I", static_cast<float>(volume));
+ if (status != 0) {
+ SB_LOG(ERROR) << "Failed to set volume";
+ }
+}
+
+} // namespace
+
+SbAudioSink AudioTrackAudioSinkType::Create(
+ int channels,
+ int sampling_frequency_hz,
+ SbMediaAudioSampleType audio_sample_type,
+ SbMediaAudioFrameStorageType audio_frame_storage_type,
+ SbAudioSinkFrameBuffers frame_buffers,
+ int frames_per_channel,
+ SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+ SbAudioSinkConsumeFramesFunc consume_frames_func,
+ void* context) {
+ AudioTrackAudioSink* audio_sink = new AudioTrackAudioSink(
+ this, channels, sampling_frequency_hz, audio_sample_type, frame_buffers,
+ frames_per_channel, update_source_status_func, consume_frames_func,
+ context);
+ if (!audio_sink->IsAudioTrackValid()) {
+ SB_DLOG(ERROR)
+ << "AudioTrackAudioSinkType::Create failed to create audio track";
+ Destroy(audio_sink);
+ return kSbAudioSinkInvalid;
+ }
+ return audio_sink;
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+namespace {
+SbAudioSinkPrivate::Type* audio_track_audio_sink_type_;
+} // namespace
+
+// static
+void SbAudioSinkPrivate::PlatformInitialize() {
+ SB_DCHECK(!audio_track_audio_sink_type_);
+ audio_track_audio_sink_type_ =
+ new starboard::android::shared::AudioTrackAudioSinkType;
+ SetPrimaryType(audio_track_audio_sink_type_);
+ EnableFallbackToStub();
+}
+
+// static
+void SbAudioSinkPrivate::PlatformTearDown() {
+ SB_DCHECK(audio_track_audio_sink_type_ == GetPrimaryType());
+ SetPrimaryType(NULL);
+ delete audio_track_audio_sink_type_;
+ audio_track_audio_sink_type_ = NULL;
+}
diff --git a/src/starboard/android/shared/audio_track_audio_sink_type.h b/src/starboard/android/shared/audio_track_audio_sink_type.h
new file mode 100644
index 0000000..6dda67e
--- /dev/null
+++ b/src/starboard/android/shared/audio_track_audio_sink_type.h
@@ -0,0 +1,58 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_AUDIO_TRACK_AUDIO_SINK_TYPE_H_
+#define STARBOARD_ANDROID_SHARED_AUDIO_TRACK_AUDIO_SINK_TYPE_H_
+
+#include "starboard/audio_sink.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class AudioTrackAudioSinkType : public SbAudioSinkPrivate::Type {
+ public:
+ SbAudioSink Create(
+ int channels,
+ int sampling_frequency_hz,
+ SbMediaAudioSampleType audio_sample_type,
+ SbMediaAudioFrameStorageType audio_frame_storage_type,
+ SbAudioSinkFrameBuffers frame_buffers,
+ int frames_per_channel,
+ SbAudioSinkUpdateSourceStatusFunc update_source_status_func,
+ SbAudioSinkConsumeFramesFunc consume_frames_func,
+ void* context);
+
+ bool IsValid(SbAudioSink audio_sink) override {
+ return audio_sink != kSbAudioSinkInvalid && audio_sink->IsType(this);
+ }
+
+ void Destroy(SbAudioSink audio_sink) override {
+ if (audio_sink != kSbAudioSinkInvalid && !IsValid(audio_sink)) {
+ SB_LOG(WARNING) << "audio_sink is invalid.";
+ return;
+ }
+ delete audio_sink;
+ }
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_AUDIO_TRACK_AUDIO_SINK_TYPE_H_
diff --git a/src/starboard/android/shared/cobalt/android.cc b/src/starboard/android/shared/cobalt/android.cc
new file mode 100644
index 0000000..ab1baa1
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android.cc
@@ -0,0 +1,33 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/cobalt/android.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+Android::Android(const scoped_refptr<::cobalt::dom::Window>& window) {
+ UNREFERENCED_PARAMETER(window);
+
+ feedback_service_ = new FeedbackService();
+}
+
+void Android::TraceMembers(script::Tracer* tracer) {
+ tracer->Trace(feedback_service_);
+}
+
+Android::~Android() {}
+
+} // namespace webapi_extension
+} // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/android.h b/src/starboard/android/shared/cobalt/android.h
new file mode 100644
index 0000000..b21d533
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android.h
@@ -0,0 +1,47 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_COBALT_ANDROID_H_
+#define STARBOARD_ANDROID_SHARED_COBALT_ANDROID_H_
+
+#include "cobalt/dom/window.h"
+#include "cobalt/script/wrappable.h"
+#include "starboard/android/shared/cobalt/feedback_service.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+class Android : public ::cobalt::script::Wrappable {
+ public:
+ explicit Android(const scoped_refptr<::cobalt::dom::Window>& window);
+
+ const scoped_refptr<FeedbackService>& feedback_service() const {
+ return feedback_service_;
+ }
+
+ DEFINE_WRAPPABLE_TYPE(Android);
+ void TraceMembers(script::Tracer* tracer) override;
+
+ private:
+ ~Android() override;
+
+ scoped_refptr<FeedbackService> feedback_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(Android);
+};
+
+} // namespace webapi_extension
+} // namespace cobalt
+
+#endif // STARBOARD_ANDROID_SHARED_COBALT_ANDROID_H_
diff --git a/src/starboard/android/shared/cobalt/android.idl b/src/starboard/android/shared/cobalt/android.idl
new file mode 100644
index 0000000..18e71e7
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android.idl
@@ -0,0 +1,18 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The Cobalt Web Extension for Android.
+interface Android {
+ readonly attribute FeedbackService feedbackService;
+};
diff --git a/src/starboard/android/shared/cobalt/android_media_session_client.cc b/src/starboard/android/shared/cobalt/android_media_session_client.cc
new file mode 100644
index 0000000..25b9323
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_media_session_client.cc
@@ -0,0 +1,355 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/cobalt/android_media_session_client.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/time.h"
+#include "cobalt/media_session/media_session_action_details.h"
+#include "cobalt/media_session/media_session_client.h"
+#include "cobalt/script/sequence.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/once.h"
+#include "starboard/player.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace cobalt {
+
+using ::cobalt::media_session::MediaImage;
+using ::cobalt::media_session::MediaMetadata;
+using ::cobalt::media_session::MediaSession;
+using ::cobalt::media_session::MediaSessionAction;
+using ::cobalt::media_session::MediaSessionActionDetails;
+using ::cobalt::media_session::MediaSessionClient;
+using ::cobalt::media_session::MediaSessionPlaybackState;
+using ::cobalt::media_session::kMediaSessionActionPause;
+using ::cobalt::media_session::kMediaSessionActionPlay;
+using ::cobalt::media_session::kMediaSessionActionSeek;
+using ::cobalt::media_session::kMediaSessionActionSeekbackward;
+using ::cobalt::media_session::kMediaSessionActionSeekforward;
+using ::cobalt::media_session::kMediaSessionActionPrevioustrack;
+using ::cobalt::media_session::kMediaSessionActionNexttrack;
+using ::cobalt::media_session::kMediaSessionPlaybackStateNone;
+using ::cobalt::media_session::kMediaSessionPlaybackStatePaused;
+using ::cobalt::media_session::kMediaSessionPlaybackStatePlaying;
+
+using MediaImageSequence = ::cobalt::script::Sequence<MediaImage>;
+
+using ::starboard::android::shared::JniEnvExt;
+using ::starboard::android::shared::ScopedLocalJavaRef;
+
+namespace {
+
+// These constants are from android.media.session.PlaybackState
+const jlong kPlaybackStateActionStop = 1 << 0; // not supported
+const jlong kPlaybackStateActionPause = 1 << 1;
+const jlong kPlaybackStateActionPlay = 1 << 2;
+const jlong kPlaybackStateActionRewind = 1 << 3;
+const jlong kPlaybackStateActionSkipToPrevious = 1 << 4;
+const jlong kPlaybackStateActionSkipToNext = 1 << 5;
+const jlong kPlaybackStateActionFastForward = 1 << 6;
+const jlong kPlaybackStateActionSetRating = 1 << 7; // not supported
+const jlong kPlaybackStateActionSeekTo = 1 << 8;
+
+// Converts a MediaSessionClient::AvailableActions bitset into
+// a android.media.session.PlaybackState jlong bitset.
+jlong MediaSessionActionsToPlaybackStateActions(
+ const MediaSessionClient::AvailableActionsSet& actions) {
+ jlong result = 0;
+ if (actions[kMediaSessionActionPause]) {
+ result |= kPlaybackStateActionPause;
+ }
+ if (actions[kMediaSessionActionPlay]) {
+ result |= kPlaybackStateActionPlay;
+ }
+ if (actions[kMediaSessionActionSeekbackward]) {
+ result |= kPlaybackStateActionRewind;
+ }
+ if (actions[kMediaSessionActionPrevioustrack]) {
+ result |= kPlaybackStateActionSkipToPrevious;
+ }
+ if (actions[kMediaSessionActionNexttrack]) {
+ result |= kPlaybackStateActionSkipToNext;
+ }
+ if (actions[kMediaSessionActionSeekforward]) {
+ result |= kPlaybackStateActionFastForward;
+ }
+ if (actions[kMediaSessionActionSeek]) {
+ result |= kPlaybackStateActionSeekTo;
+ }
+ return result;
+}
+
+PlaybackState MediaSessionPlaybackStateToPlaybackState(
+ MediaSessionPlaybackState in_state) {
+ switch (in_state) {
+ case kMediaSessionPlaybackStatePlaying:
+ return kPlaying;
+ case kMediaSessionPlaybackStatePaused:
+ return kPaused;
+ case kMediaSessionPlaybackStateNone:
+ return kNone;
+ }
+}
+
+MediaSessionAction PlaybackStateActionToMediaSessionAction(jlong action) {
+ MediaSessionAction result;
+ switch (action) {
+ case kPlaybackStateActionPause:
+ result = kMediaSessionActionPause;
+ break;
+ case kPlaybackStateActionPlay:
+ result = kMediaSessionActionPlay;
+ break;
+ case kPlaybackStateActionRewind:
+ result = kMediaSessionActionSeekbackward;
+ break;
+ case kPlaybackStateActionSkipToPrevious:
+ result = kMediaSessionActionPrevioustrack;
+ break;
+ case kPlaybackStateActionSkipToNext:
+ result = kMediaSessionActionNexttrack;
+ break;
+ case kPlaybackStateActionFastForward:
+ result = kMediaSessionActionSeekforward;
+ break;
+ case kPlaybackStateActionSeekTo:
+ result = kMediaSessionActionSeek;
+ break;
+ default:
+ SB_NOTREACHED() << "Unsupported MediaSessionAction 0x"
+ << std::hex << action;
+ result = static_cast<MediaSessionAction>(-1);
+ }
+ return result;
+}
+
+MediaSessionPlaybackState PlaybackStateToMediaSessionPlaybackState(
+ PlaybackState state) {
+ MediaSessionPlaybackState result;
+ switch (state) {
+ case kPlaying:
+ result = kMediaSessionPlaybackStatePlaying;
+ break;
+ case kPaused:
+ result = kMediaSessionPlaybackStatePaused;
+ break;
+ case kNone:
+ result = kMediaSessionPlaybackStateNone;
+ break;
+ default:
+ SB_NOTREACHED() << "Unsupported PlaybackState " << state;
+ result = static_cast<MediaSessionPlaybackState>(-1);
+ }
+ return result;
+}
+
+} // namespace
+
+class AndroidMediaSessionClient : public MediaSessionClient {
+ static SbOnceControl once_flag;
+ static SbMutex mutex;
+ // The last MediaSessionClient to become active, or null.
+ // Used to route Java callbacks.
+ // In practice, only one MediaSessionClient will become active at a time.
+ // Protected by "mutex"
+ static AndroidMediaSessionClient* active_client;
+
+ // TODO: Pass the necessary info through web MediaSession so we don't need to
+ // short-circuit to the player implementation to get info about the playback.
+ static SbPlayer active_player;
+
+ static void OnceInit() { SbMutexCreate(&mutex); }
+
+ public:
+ static void NativeInvokeAction(jlong action, jlong seek_ms) {
+ SbOnce(&once_flag, OnceInit);
+ SbMutexAcquire(&mutex);
+
+ if (active_client != NULL) {
+ MediaSessionAction cobalt_action =
+ PlaybackStateActionToMediaSessionAction(action);
+ active_client->InvokeAction(scoped_ptr<MediaSessionActionDetails::Data>(
+ new MediaSessionActionDetails::Data(cobalt_action,
+ seek_ms / 1000.0)));
+ }
+
+ SbMutexRelease(&mutex);
+ }
+
+ static void UpdateActiveSessionPlatformPlaybackState(
+ MediaSessionPlaybackState state) {
+ SbOnce(&once_flag, OnceInit);
+ SbMutexAcquire(&mutex);
+
+ if (active_client != NULL) {
+ active_client->UpdatePlatformPlaybackState(state);
+ }
+
+ SbMutexRelease(&mutex);
+ }
+
+ static void UpdateActiveSessionPlatformPlayer(SbPlayer player) {
+ SbOnce(&once_flag, OnceInit);
+ SbMutexAcquire(&mutex);
+
+ active_player = player;
+
+ SbMutexRelease(&mutex);
+ }
+
+ AndroidMediaSessionClient() {}
+
+ virtual ~AndroidMediaSessionClient() {
+ SbOnce(&once_flag, OnceInit);
+ SbMutexAcquire(&mutex);
+ if (active_client == this) {
+ active_client = NULL;
+ }
+ SbMutexRelease(&mutex);
+ }
+
+ void OnMediaSessionChanged() override {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ jint playback_state =
+ MediaSessionPlaybackStateToPlaybackState(GetActualPlaybackState());
+
+ SbOnce(&once_flag, OnceInit);
+ SbMutexAcquire(&mutex);
+ if (playback_state != kNone) {
+ active_client = this;
+ } else if (active_client == this) {
+ active_client = NULL;
+ }
+
+ SbPlayerInfo2 player_info;
+ SbMemorySet(&player_info, 0, sizeof(player_info));
+ if (active_player != kSbPlayerInvalid) {
+ SbPlayerGetInfo2(active_player, &player_info);
+ }
+ SbMutexRelease(&mutex);
+
+ jlong playback_state_actions =
+ MediaSessionActionsToPlaybackStateActions(GetAvailableActions());
+
+ scoped_refptr<MediaSession> media_session(GetMediaSession());
+ scoped_refptr<MediaMetadata> media_metadata(media_session->metadata());
+
+ ScopedLocalJavaRef<jstring> j_title;
+ ScopedLocalJavaRef<jstring> j_artist;
+ ScopedLocalJavaRef<jstring> j_album;
+ ScopedLocalJavaRef<jobjectArray> j_artwork;
+
+ if (media_metadata) {
+ j_title.Reset(
+ env->NewStringStandardUTFOrAbort(media_metadata->title().c_str()));
+ j_artist.Reset(
+ env->NewStringStandardUTFOrAbort(media_metadata->artist().c_str()));
+ j_album.Reset(
+ env->NewStringStandardUTFOrAbort(media_metadata->album().c_str()));
+
+ const MediaImageSequence& artwork(media_metadata->artwork());
+ if (!artwork.empty()) {
+ ScopedLocalJavaRef<jclass> media_image_class(
+ env->FindClassExtOrAbort("dev/cobalt/media/MediaImage"));
+ jmethodID media_image_constructor = env->GetMethodID(
+ media_image_class.Get(), "<init>",
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
+ env->AbortOnException();
+
+ j_artwork.Reset(static_cast<jobjectArray>(env->NewObjectArray(
+ artwork.size(), media_image_class.Get(), NULL)));
+ env->AbortOnException();
+
+ ScopedLocalJavaRef<jstring> j_src;
+ ScopedLocalJavaRef<jstring> j_sizes;
+ ScopedLocalJavaRef<jstring> j_type;
+ for (MediaImageSequence::size_type i = 0; i < artwork.size(); i++) {
+ const MediaImage& media_image(artwork.at(i));
+ j_src.Reset(
+ env->NewStringStandardUTFOrAbort(media_image.src().c_str()));
+ j_sizes.Reset(
+ env->NewStringStandardUTFOrAbort(media_image.sizes().c_str()));
+ j_type.Reset(
+ env->NewStringStandardUTFOrAbort(media_image.type().c_str()));
+
+ ScopedLocalJavaRef<jobject> j_media_image(
+ env->NewObject(media_image_class.Get(), media_image_constructor,
+ j_src.Get(), j_sizes.Get(), j_type.Get()));
+
+ env->SetObjectArrayElement(j_artwork.Get(), i, j_media_image.Get());
+ }
+ }
+ }
+
+ env->CallStarboardVoidMethodOrAbort(
+ "updateMediaSession",
+ "(IJJFLjava/lang/String;Ljava/lang/String;Ljava/lang/String;"
+ "[Ldev/cobalt/media/MediaImage;)V",
+ playback_state, playback_state_actions,
+ player_info.current_media_timestamp / kSbTimeMillisecond,
+ static_cast<jfloat>(player_info.playback_rate),
+ j_title.Get(), j_artist.Get(), j_album.Get(), j_artwork.Get());
+ }
+};
+
+SbOnceControl AndroidMediaSessionClient::once_flag = SB_ONCE_INITIALIZER;
+SbMutex AndroidMediaSessionClient::mutex;
+AndroidMediaSessionClient* AndroidMediaSessionClient::active_client = NULL;
+SbPlayer AndroidMediaSessionClient::active_player = kSbPlayerInvalid;
+
+void UpdateActiveSessionPlatformPlaybackState(PlaybackState state) {
+ MediaSessionPlaybackState media_session_state =
+ PlaybackStateToMediaSessionPlaybackState(state);
+
+ AndroidMediaSessionClient::UpdateActiveSessionPlatformPlaybackState(
+ media_session_state);
+}
+
+void UpdateActiveSessionPlatformPlayer(SbPlayer player) {
+ AndroidMediaSessionClient::UpdateActiveSessionPlatformPlayer(player);
+}
+
+} // namespace cobalt
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+using starboard::android::shared::cobalt::AndroidMediaSessionClient;
+
+extern "C" SB_EXPORT_PLATFORM
+void Java_dev_cobalt_media_CobaltMediaSession_nativeInvokeAction(
+ JNIEnv* env,
+ jclass unused_clazz,
+ jlong action,
+ jlong seek_ms) {
+ AndroidMediaSessionClient::NativeInvokeAction(action, seek_ms);
+}
+
+namespace cobalt {
+namespace media_session {
+
+// static
+scoped_ptr<MediaSessionClient> MediaSessionClient::Create() {
+ return make_scoped_ptr<MediaSessionClient>(new AndroidMediaSessionClient());
+}
+
+} // namespace media_session
+} // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/android_media_session_client.h b/src/starboard/android/shared/cobalt/android_media_session_client.h
new file mode 100644
index 0000000..284e165
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_media_session_client.h
@@ -0,0 +1,39 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_COBALT_ANDROID_MEDIA_SESSION_CLIENT_H_
+#define STARBOARD_ANDROID_SHARED_COBALT_ANDROID_MEDIA_SESSION_CLIENT_H_
+
+#include "starboard/player.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace cobalt {
+
+// Duplicated in CobaltMediaSession.java
+enum PlaybackState { kPlaying = 0, kPaused = 1, kNone = 2 };
+
+void UpdateActiveSessionPlatformPlaybackState(PlaybackState state);
+
+// TODO: Pass the necessary info through web MediaSession so we don't need to
+// short-circuit to the player implementation to get info about the playback.
+void UpdateActiveSessionPlatformPlayer(SbPlayer player);
+
+} // namespace cobalt
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_COBALT_ANDROID_MEDIA_SESSION_CLIENT_H_
diff --git a/src/starboard/android/shared/cobalt/android_user_authorizer.cc b/src/starboard/android/shared/cobalt/android_user_authorizer.cc
new file mode 100644
index 0000000..a84b653
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_user_authorizer.cc
@@ -0,0 +1,127 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/cobalt/android_user_authorizer.h"
+
+#include "base/time.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/log.h"
+
+// We're in starboard source, but compiled with Cobalt.
+#define STARBOARD_IMPLEMENTATION
+#include "starboard/shared/nouser/user_internal.h"
+#undef STARBOARD_IMPLEMENTATION
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace cobalt {
+
+AndroidUserAuthorizer::AndroidUserAuthorizer() : shutdown_(false) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject local_ref = env->CallStarboardObjectMethodOrAbort(
+ "getUserAuthorizer", "()Ldev/cobalt/account/UserAuthorizer;");
+ j_user_authorizer_ = env->ConvertLocalRefToGlobalRef(local_ref);
+}
+
+AndroidUserAuthorizer::~AndroidUserAuthorizer() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->DeleteGlobalRef(j_user_authorizer_);
+}
+
+void AndroidUserAuthorizer::Shutdown() {
+ shutdown_ = true;
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(j_user_authorizer_, "interrupt", "()V");
+}
+
+scoped_ptr<AccessToken> AndroidUserAuthorizer::AuthorizeUser(SbUser user) {
+ SB_DCHECK(user == &::starboard::shared::nouser::g_user);
+ if (shutdown_) {
+ DLOG(WARNING) << "No-op AuthorizeUser after shutdown";
+ return scoped_ptr<AccessToken>(NULL);
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_token(
+ env->CallObjectMethodOrAbort(j_user_authorizer_, "authorizeUser",
+ "()Ldev/cobalt/account/AccessToken;"));
+ return CreateAccessToken(j_token.Get());
+}
+
+bool AndroidUserAuthorizer::DeauthorizeUser(SbUser user) {
+ SB_DCHECK(user == &::starboard::shared::nouser::g_user);
+ if (shutdown_) {
+ DLOG(WARNING) << "No-op DeauthorizeUser after shutdown";
+ return false;
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ return env->CallBooleanMethodOrAbort(j_user_authorizer_, "deauthorizeUser",
+ "()Z");
+}
+
+scoped_ptr<AccessToken>
+AndroidUserAuthorizer::RefreshAuthorization(SbUser user) {
+ SB_DCHECK(user == &::starboard::shared::nouser::g_user);
+ if (shutdown_) {
+ DLOG(WARNING) << "No-op RefreshAuthorization after shutdown";
+ return scoped_ptr<AccessToken>(NULL);
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_token(
+ env->CallObjectMethodOrAbort(j_user_authorizer_, "refreshAuthorization",
+ "()Ldev/cobalt/account/AccessToken;"));
+ return CreateAccessToken(j_token.Get());
+}
+
+scoped_ptr<AccessToken>
+AndroidUserAuthorizer::CreateAccessToken(jobject j_token) {
+ if (!j_token) {
+ return scoped_ptr<AccessToken>(NULL);
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ scoped_ptr<AccessToken> access_token(new AccessToken());
+
+ ScopedLocalJavaRef<jstring> j_token_string(env->CallObjectMethodOrAbort(
+ j_token, "getTokenValue", "()Ljava/lang/String;"));
+ if (j_token_string) {
+ access_token->token_value =
+ env->GetStringStandardUTFOrAbort(j_token_string.Get());
+ }
+
+ jlong j_expiry = env->CallLongMethodOrAbort(
+ j_token, "getExpirySeconds", "()J");
+ if (j_expiry > 0) {
+ access_token->expiry =
+ base::Time::UnixEpoch() + base::TimeDelta::FromSeconds(j_expiry);
+ }
+
+ return access_token.Pass();
+}
+
+} // namespace cobalt
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+namespace cobalt {
+namespace account {
+
+UserAuthorizer* UserAuthorizer::Create() {
+ return new ::starboard::android::shared::cobalt::AndroidUserAuthorizer();
+}
+
+} // namespace account
+} // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/android_user_authorizer.h b/src/starboard/android/shared/cobalt/android_user_authorizer.h
new file mode 100644
index 0000000..6e9cadc
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_user_authorizer.h
@@ -0,0 +1,63 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_COBALT_ANDROID_USER_AUTHORIZER_H_
+#define STARBOARD_ANDROID_SHARED_COBALT_ANDROID_USER_AUTHORIZER_H_
+
+#include "cobalt/account/user_authorizer.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace cobalt {
+
+using ::cobalt::account::AccessToken;
+
+// Android implementation of UserAuthorizer, using the single 'nouser' SbUser to
+// represent the Android platform running our app. Unlike game consoles which
+// launch the app as a particular platform user, Android always launches the app
+// as the same platform "user" no matter what accounts may be on the device.
+//
+// Signing-in is a higher-level concept that is implemented by the Android app
+// using the Android AccountManager and/or Google Play Services to select an
+// account and get auth tokens for the selected account to make "signed-in"
+// requests. Account selection is accomplished by prompting the user as needed
+// when getting a token with AuthorizeUser(), and remembering the selected
+// account to be used without prompting in RefreshAuthorization().
+class AndroidUserAuthorizer : public ::cobalt::account::UserAuthorizer {
+ public:
+ AndroidUserAuthorizer();
+ ~AndroidUserAuthorizer() override;
+
+ scoped_ptr<AccessToken> AuthorizeUser(SbUser user) override;
+ bool DeauthorizeUser(SbUser user) override;
+ scoped_ptr<AccessToken> RefreshAuthorization(SbUser user) override;
+ void Shutdown() override;
+
+ private:
+ scoped_ptr<AccessToken> CreateAccessToken(jobject j_token);
+
+ jobject j_user_authorizer_;
+
+ bool shutdown_;
+};
+
+} // namespace cobalt
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_COBALT_ANDROID_USER_AUTHORIZER_H_
diff --git a/src/starboard/android/shared/cobalt/android_webapi_extension.cc b/src/starboard/android/shared/cobalt/android_webapi_extension.cc
new file mode 100644
index 0000000..827c702
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_webapi_extension.cc
@@ -0,0 +1,38 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "cobalt/browser/webapi_extension.h"
+
+#include "base/compiler_specific.h"
+#include "cobalt/script/global_environment.h"
+#include "starboard/android/shared/cobalt/android.h"
+
+namespace cobalt {
+namespace browser {
+
+base::optional<std::string> GetWebAPIExtensionObjectPropertyName() {
+ return std::string("android");
+}
+
+scoped_refptr<::cobalt::script::Wrappable> CreateWebAPIExtensionObject(
+ const scoped_refptr<::cobalt::dom::Window>& window,
+ ::cobalt::script::GlobalEnvironment* global_environment) {
+ UNREFERENCED_PARAMETER(global_environment);
+
+ return scoped_refptr<::cobalt::script::Wrappable>(
+ new webapi_extension::Android(window));
+}
+
+} // namespace browser
+} // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/android_webapi_extension.gyp b/src/starboard/android/shared/cobalt/android_webapi_extension.gyp
new file mode 100644
index 0000000..0c9511e
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/android_webapi_extension.gyp
@@ -0,0 +1,33 @@
+# Copyright 2018 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+ 'targets': [
+ {
+ 'target_name': 'android_webapi_extension',
+ 'type': 'static_library',
+ 'sources': [
+ 'android.h',
+ 'android.cc',
+ 'android_webapi_extension.cc',
+ 'feedback_service.h',
+ 'feedback_service.cc',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/cobalt/dom/dom.gyp:dom',
+ '<(DEPTH)/cobalt/script/script.gyp:script',
+ '<(DEPTH)/base/base.gyp:base',
+ ],
+ },
+ ],
+}
diff --git a/src/starboard/android/shared/cobalt/cobalt_platform.gyp b/src/starboard/android/shared/cobalt/cobalt_platform.gyp
new file mode 100644
index 0000000..6f6379a
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/cobalt_platform.gyp
@@ -0,0 +1,30 @@
+# Copyright 2017 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'cobalt_platform',
+ 'type': 'static_library',
+ 'sources': [
+ 'android_user_authorizer.h',
+ 'android_user_authorizer.cc',
+ 'android_media_session_client.cc',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/cobalt/media_session/media_session.gyp:media_session'
+ ],
+ },
+ ],
+}
diff --git a/src/starboard/android/shared/cobalt/configuration.gypi b/src/starboard/android/shared/cobalt/configuration.gypi
new file mode 100644
index 0000000..35647ce
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/configuration.gypi
@@ -0,0 +1,62 @@
+# Copyright 2017 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Cobalt-on-Android-specific configuration for Starboard.
+
+{
+ 'variables': {
+ 'in_app_dial': 0,
+
+ 'custom_media_session_client': 1,
+ 'enable_account_manager': 1,
+ 'enable_map_to_mesh': 1,
+
+ # Some Android devices do not advertise their support for
+ # EGL_SWAP_BEHAVIOR_PRESERVED_BIT properly, so, we play it safe and disable
+ # relying on it for Android.
+ 'render_dirty_region_only': 0,
+
+ # The 'android_system' font package installs only minimal fonts, with a
+ # fonts.xml referencing the superset of font files we expect to find on any
+ # Android platform. The Android SbFileOpen implementation falls back to
+ # system fonts when it can't find the font file in the cobalt content.
+ 'cobalt_font_package': 'android_system',
+
+ # On Android, we almost never want to actually terminate the process, so
+ # instead indicate that we would instead like to be suspended when users
+ # initiate an "exit".
+ 'cobalt_user_on_exit_strategy': 'suspend',
+
+ # Switch Android's SurfaceFlinger queue to "async mode" so that we don't
+ # queue up rendered frames which would interfere with frame timing and
+ # more importantly lead to input latency.
+ 'cobalt_egl_swap_interval': 0,
+
+ # Platform-specific implementations to compile into cobalt.
+ 'cobalt_platform_dependencies': [
+ '<(DEPTH)/starboard/android/shared/cobalt/cobalt_platform.gyp:cobalt_platform',
+ ],
+
+ # Platform-specific interfaces to inject into Cobalt's JavaScript 'window'
+ # global object.
+ 'cobalt_webapi_extension_source_idl_files': [
+ 'android.idl',
+ 'feedback_service.idl',
+ ],
+
+ # Platform-specific IDL interface implementations.
+ 'cobalt_webapi_extension_gyp_target':
+ '<(DEPTH)/starboard/android/shared/cobalt/android_webapi_extension.gyp:android_webapi_extension',
+ },
+}
diff --git a/src/starboard/android/shared/cobalt/configuration.py b/src/starboard/android/shared/cobalt/configuration.py
new file mode 100644
index 0000000..8bf09e2
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/configuration.py
@@ -0,0 +1,78 @@
+# Copyright 2017 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android Cobalt shared configuration."""
+
+import os
+
+from cobalt.build import cobalt_configuration
+from starboard.tools.testing import test_filter
+
+
+class CobaltAndroidConfiguration(cobalt_configuration.CobaltConfiguration):
+ """Starboard Android Cobalt shared configuration."""
+
+ def __init__(self, platform_configuration, application_name,
+ application_directory):
+ super(CobaltAndroidConfiguration, self).__init__(
+ platform_configuration, application_name, application_directory)
+
+ def GetPostIncludes(self):
+ # If there isn't a configuration.gypi found in the usual place, we'll
+ # supplement with our shared implementation.
+ includes = super(CobaltAndroidConfiguration, self).GetPostIncludes()
+ for include in includes:
+ if os.path.basename(include) == 'configuration.gypi':
+ return includes
+
+ shared_gypi_path = os.path.join(os.path.dirname(__file__),
+ 'configuration.gypi')
+ if os.path.isfile(shared_gypi_path):
+ includes.append(shared_gypi_path)
+ return includes
+
+ def WebdriverBenchmarksEnabled(self):
+ return True
+
+ def GetTestFilters(self):
+ filters = super(CobaltAndroidConfiguration, self).GetTestFilters()
+ for target, tests in self._FAILING_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ def GetTestEnvVariables(self):
+ return {
+ 'base_unittests': {'ASAN_OPTIONS': 'detect_leaks=0'},
+ 'crypto_unittests': {'ASAN_OPTIONS': 'detect_leaks=0'},
+ 'net_unittests': {'ASAN_OPTIONS': 'detect_leaks=0'}
+ }
+
+ # A map of failing or crashing tests per target.
+ _FAILING_TESTS = {
+ 'net_unittests': [
+ 'DialHttpServerTest.AllOtherRequests',
+ 'DialHttpServerTest.CallbackExceptionInServiceHandler',
+ 'DialHttpServerTest.CallbackHandleRequestReturnsFalse',
+ 'DialHttpServerTest.CallbackNormalTest',
+ 'DialHttpServerTest.CurrentRunningAppRedirect',
+ 'DialHttpServerTest.SendManifest',
+ 'HostResolverImplDnsTest.DnsTaskUnspec',
+ ],
+ 'renderer_test': [
+ # Instead of returning an error when allocating too much texture
+ # memory, Android instead just terminates the process. Since this
+ # test explicitly tries to allocate too much texture memory, we cannot
+ # run it on Android platforms.
+ 'StressTest.TooManyTextures',
+ ],
+ }
diff --git a/src/starboard/android/shared/cobalt/feedback_service.cc b/src/starboard/android/shared/cobalt/feedback_service.cc
new file mode 100644
index 0000000..c17307d
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/feedback_service.cc
@@ -0,0 +1,83 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/cobalt/feedback_service.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+void FeedbackService::SendFeedback(
+ const script::ValueHandleHolder& product_specific_data,
+ const std::string& category_tag,
+ script::ExceptionState* exception_state) {
+ const script::Handle<script::ArrayBuffer> empty_array_buffer;
+ SendFeedback(product_specific_data, category_tag, empty_array_buffer,
+ exception_state);
+}
+
+void FeedbackService::SendFeedback(
+ const script::ValueHandleHolder& product_specific_data,
+ const std::string& category_tag,
+ const script::Handle<script::ArrayBuffer>& screenshot_data,
+ script::ExceptionState* exception_state) {
+ using starboard::android::shared::ScopedLocalJavaRef;
+ using starboard::android::shared::JniEnvExt;
+
+ std::unordered_map<std::string, std::string> product_specific_data_map =
+ script::ConvertSimpleObjectToMap(product_specific_data, exception_state);
+
+ JniEnvExt* env = JniEnvExt::Get();
+
+ // Convert the unordered map of product specific data to a hashmap in JNI.
+ ScopedLocalJavaRef<jobject> product_specific_data_hash_map(
+ env->NewObjectOrAbort("java/util/HashMap", "(I)V",
+ product_specific_data_map.size()));
+
+ ScopedLocalJavaRef<jstring> key;
+ ScopedLocalJavaRef<jstring> value;
+
+ for (const auto& data : product_specific_data_map) {
+ key.Reset(env->NewStringStandardUTFOrAbort(data.first.c_str()));
+ value.Reset(env->NewStringStandardUTFOrAbort(data.second.c_str()));
+
+ env->CallObjectMethodOrAbort(
+ product_specific_data_hash_map.Get(), "put",
+ "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", key.Get(),
+ value.Get());
+ }
+
+ ScopedLocalJavaRef<jstring> category_tag_string;
+ category_tag_string.Reset(
+ env->NewStringStandardUTFOrAbort(category_tag.c_str()));
+
+ // Convert the screenshot to a byte array in JNI.
+ ScopedLocalJavaRef<jbyteArray> screenshot_byte_array;
+ if (!screenshot_data.IsEmpty()) {
+ screenshot_byte_array.Reset(env->NewByteArrayFromRaw(
+ reinterpret_cast<const jbyte*>(screenshot_data->Data()),
+ screenshot_data->ByteLength()));
+ env->AbortOnException();
+ }
+
+ env->CallStarboardVoidMethodOrAbort(
+ "sendFeedback", "(Ljava/util/HashMap;Ljava/lang/String;[B)V",
+ product_specific_data_hash_map.Get(), category_tag_string.Get(),
+ screenshot_byte_array.Get());
+}
+
+} // namespace webapi_extension
+} // namespace cobalt
diff --git a/src/starboard/android/shared/cobalt/feedback_service.h b/src/starboard/android/shared/cobalt/feedback_service.h
new file mode 100644
index 0000000..270cf16
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/feedback_service.h
@@ -0,0 +1,49 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_COBALT_FEEDBACK_SERVICE_H_
+#define STARBOARD_ANDROID_SHARED_COBALT_FEEDBACK_SERVICE_H_
+
+#include <string>
+
+#include "cobalt/script/array_buffer.h"
+#include "cobalt/script/exception_state.h"
+#include "cobalt/script/value_handle.h"
+#include "cobalt/script/wrappable.h"
+
+namespace cobalt {
+namespace webapi_extension {
+
+class FeedbackService : public ::cobalt::script::Wrappable {
+ public:
+ void SendFeedback(const script::ValueHandleHolder& product_specific_data,
+ const std::string& category_tag,
+ script::ExceptionState* exception_state);
+
+ void SendFeedback(const script::ValueHandleHolder& product_specific_data,
+ const std::string& category_tag,
+ const script::Handle<script::ArrayBuffer>& screenshot_data,
+ script::ExceptionState* exception_state);
+
+ FeedbackService() = default;
+ FeedbackService(const FeedbackService&) = delete;
+ FeedbackService& operator=(const FeedbackService&) = delete;
+
+ DEFINE_WRAPPABLE_TYPE(FeedbackService);
+};
+
+} // namespace webapi_extension
+} // namespace cobalt
+
+#endif // STARBOARD_ANDROID_SHARED_COBALT_FEEDBACK_SERVICE_H_
diff --git a/src/starboard/android/shared/cobalt/feedback_service.idl b/src/starboard/android/shared/cobalt/feedback_service.idl
new file mode 100644
index 0000000..b4c722a
--- /dev/null
+++ b/src/starboard/android/shared/cobalt/feedback_service.idl
@@ -0,0 +1,21 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The send feedback report service.
+interface FeedbackService {
+ // Send feedback.
+ [RaisesException] void sendFeedback(any productSpecificData,
+ optional DOMString categoryTag = "",
+ optional ArrayBuffer screenshotData);
+};
diff --git a/src/starboard/android/shared/configuration_public.h b/src/starboard/android/shared/configuration_public.h
new file mode 100644
index 0000000..890d939
--- /dev/null
+++ b/src/starboard/android/shared/configuration_public.h
@@ -0,0 +1,454 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// The Starboard configuration for generic Android. Other devices will have
+// specific Starboard implementations, even if they ultimately are running some
+// version of Android -- but they may base their configuration on the generic
+// Android configuration headers.
+
+// Other source files should never include this header directly, but should
+// include the generic "starboard/configuration.h" instead.
+
+#ifndef STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_
+#define STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_
+
+// The API version implemented by this platform.
+#define SB_API_VERSION SB_EXPERIMENTAL_API_VERSION
+
+// --- Architecture Configuration --------------------------------------------
+
+// Whether the current platform is big endian. SB_IS_LITTLE_ENDIAN will be
+// automatically set based on this.
+#define SB_IS_BIG_ENDIAN 0
+
+// Whether the current platform is a MIPS architecture.
+#define SB_IS_ARCH_MIPS 0
+
+// Whether the current platform is a PPC architecture.
+#define SB_IS_ARCH_PPC 0
+
+// The current platform CPU architecture architecture.
+#if defined(__arm__) || defined(__aarch64__)
+#define SB_IS_ARCH_ARM 1
+#define SB_IS_ARCH_X86 0
+#elif defined(__i386__) || defined(__x86_64__)
+#define SB_IS_ARCH_ARM 0
+#define SB_IS_ARCH_X86 1
+#endif
+
+// Whether the current platform is 32-bit or 64-bit architecture.
+#if defined(__aarch64__) || defined(__x86_64__)
+#define SB_IS_32_BIT 0
+#define SB_IS_64_BIT 1
+#else
+#define SB_IS_32_BIT 1
+#define SB_IS_64_BIT 0
+#endif
+
+// Whether the current platform's pointers are 32-bit.
+// Whether the current platform's longs are 32-bit.
+#if SB_IS(32_BIT)
+#define SB_HAS_32_BIT_POINTERS 1
+#define SB_HAS_32_BIT_LONG 1
+#else
+#define SB_HAS_32_BIT_POINTERS 0
+#define SB_HAS_32_BIT_LONG 0
+#endif
+
+// Whether the current platform's pointers are 64-bit.
+// Whether the current platform's longs are 64-bit.
+#if SB_IS(64_BIT)
+#define SB_HAS_64_BIT_POINTERS 1
+#define SB_HAS_64_BIT_LONG 1
+#else
+#define SB_HAS_64_BIT_POINTERS 0
+#define SB_HAS_64_BIT_LONG 0
+#endif
+
+// Configuration parameters that allow the application to make some general
+// compile-time decisions with respect to the the number of cores likely to be
+// available on this platform. For a definitive measure, the application should
+// still call SbSystemGetNumberOfProcessors at runtime.
+
+// Whether the current platform is expected to have many cores (> 6), or a
+// wildly varying number of cores.
+#define SB_HAS_MANY_CORES 1
+
+// Whether the current platform is expected to have exactly 1 core.
+#define SB_HAS_1_CORE 0
+
+// Whether the current platform is expected to have exactly 2 cores.
+#define SB_HAS_2_CORES 0
+
+// Whether the current platform is expected to have exactly 4 cores.
+#define SB_HAS_4_CORES 0
+
+// Whether the current platform is expected to have exactly 6 cores.
+#define SB_HAS_6_CORES 0
+
+// Whether the current platform's thread scheduler will automatically balance
+// threads between cores, as opposed to systems where threads will only ever run
+// on the specifically pinned core.
+#define SB_HAS_CROSS_CORE_SCHEDULER 1
+
+// Indicates that there is no support for alignment at greater than 16 bytes for
+// items on the stack.
+#define SB_HAS_QUIRK_DOES_NOT_STACK_ALIGN_OVER_16_BYTES 1
+
+// This quirk is used to fix an issue caused by the rewriting of memset to
+// SbMemorySet in third_party/protobuf/src/google/protobuf/stubs/port.h.
+#define SB_HAS_QUIRK_MEMSET_IN_SYSTEM_HEADERS 1
+
+// --- System Header Configuration -------------------------------------------
+
+// Any system headers listed here that are not provided by the platform will be
+// emulated in starboard/types.h.
+
+// Whether the current platform provides the standard header stdarg.h.
+#define SB_HAS_STDARG_H 1
+
+// Whether the current platform provides the standard header stdbool.h.
+#define SB_HAS_STDBOOL_H 1
+
+// Whether the current platform provides the standard header stddef.h.
+#define SB_HAS_STDDEF_H 1
+
+// Whether the current platform provides the standard header stdint.h.
+#define SB_HAS_STDINT_H 1
+
+// Whether the current platform provides the standard header inttypes.h.
+#define SB_HAS_INTTYPES_H 1
+
+// Whether the current platform provides the standard header wchar.h.
+#define SB_HAS_WCHAR_H 1
+
+// Whether the current platform provides the standard header limits.h.
+#define SB_HAS_LIMITS_H 1
+
+// Whether the current platform provides the standard header float.h.
+#define SB_HAS_FLOAT_H 1
+
+// Whether the current platform provides ssize_t.
+#define SB_HAS_SSIZE_T 1
+
+// Type detection for wchar_t.
+#if defined(__WCHAR_MAX__) && \
+ (__WCHAR_MAX__ == 0x7fffffff || __WCHAR_MAX__ == 0xffffffff)
+#define SB_IS_WCHAR_T_UTF32 1
+#elif defined(__WCHAR_MAX__) && \
+ (__WCHAR_MAX__ == 0x7fff || __WCHAR_MAX__ == 0xffff)
+#define SB_IS_WCHAR_T_UTF16 1
+#endif
+
+// Chrome only defines these two if ARMEL or MIPSEL are defined.
+#if defined(__ARMEL__)
+// Chrome has an exclusion for iOS here, we should too when we support iOS.
+#define SB_IS_WCHAR_T_UNSIGNED 1
+#elif defined(__MIPSEL__)
+#define SB_IS_WCHAR_T_SIGNED 1
+#endif
+
+// --- Compiler Configuration ------------------------------------------------
+
+// The platform's annotation for forcing a C function to be inlined.
+#define SB_C_FORCE_INLINE __inline__ __attribute__((always_inline))
+
+// The platform's annotation for marking a C function as suggested to be
+// inlined.
+#define SB_C_INLINE inline
+
+// The platform's annotation for marking a C function as forcibly not
+// inlined.
+#define SB_C_NOINLINE __attribute__((noinline))
+
+// The platform's annotation for marking a symbol as exported outside of the
+// current shared library.
+#define SB_EXPORT_PLATFORM __attribute__((visibility("default")))
+
+// The platform's annotation for marking a symbol as imported from outside of
+// the current linking unit.
+#define SB_IMPORT_PLATFORM
+
+// --- Extensions Configuration ----------------------------------------------
+
+// Do not use <unordered_map> and <unordered_set> for the hash table types.
+#define SB_HAS_STD_UNORDERED_HASH 0
+
+// GCC/Clang doesn't define a long long hash function, except for Android and
+// Game consoles.
+#define SB_HAS_LONG_LONG_HASH 0
+
+// GCC/Clang doesn't define a string hash function, except for Game Consoles.
+#define SB_HAS_STRING_HASH 0
+
+// Desktop Linux needs a using statement for the hash functions.
+#define SB_HAS_HASH_USING 0
+
+// Set this to 1 if hash functions for custom types can be defined as a
+// hash_value() function. Otherwise, they need to be placed inside a
+// partially-specified hash struct template with an operator().
+#define SB_HAS_HASH_VALUE 0
+
+// Set this to 1 if use of hash_map or hash_set causes a deprecation warning
+// (which then breaks the build).
+#define SB_HAS_HASH_WARNING 1
+
+// The location to include hash_map on this platform.
+#define SB_HASH_MAP_INCLUDE <ext/hash_map>
+
+// C++'s hash_map and hash_set are often found in different namespaces depending
+// on the compiler.
+#define SB_HASH_NAMESPACE __gnu_cxx
+
+// The location to include hash_set on this platform.
+#define SB_HASH_SET_INCLUDE <ext/hash_set>
+
+// Define this to how this platform copies varargs blocks.
+#define SB_VA_COPY(dest, source) va_copy(dest, source)
+
+// --- Filesystem Configuration ----------------------------------------------
+
+// The current platform's maximum length of the name of a single directory
+// entry, not including the absolute path.
+#define SB_FILE_MAX_NAME 64
+
+// The current platform's maximum length of an absolute path.
+#define SB_FILE_MAX_PATH 4096
+
+// The current platform's maximum number of files that can be opened at the
+// same time by one process.
+#define SB_FILE_MAX_OPEN 256
+
+// The current platform's file path component separator character. This is the
+// character that appears after a directory in a file path. For example, the
+// absolute canonical path of the file "/path/to/a/file.txt" uses '/' as a path
+// component separator character.
+#define SB_FILE_SEP_CHAR '/'
+
+// The current platform's alternate file path component separator character.
+// This is like SB_FILE_SEP_CHAR, except if your platform supports an alternate
+// character, then you can place that here. For example, on windows machines,
+// the primary separator character is probably '\', but the alternate is '/'.
+#define SB_FILE_ALT_SEP_CHAR '/'
+
+// The current platform's search path component separator character. When
+// specifying an ordered list of absolute paths of directories to search for a
+// given reason, this is the character that appears between entries. For
+// example, the search path of "/etc/search/first:/etc/search/second" uses ':'
+// as a search path component separator character.
+#define SB_PATH_SEP_CHAR ':'
+
+// The string form of SB_FILE_SEP_CHAR.
+#define SB_FILE_SEP_STRING "/"
+
+// The string form of SB_FILE_ALT_SEP_CHAR.
+#define SB_FILE_ALT_SEP_STRING "/"
+
+// The string form of SB_PATH_SEP_CHAR.
+#define SB_PATH_SEP_STRING ":"
+
+// --- Graphics Configuration ------------------------------------------------
+
+// Specifies whether this platform supports a performant accelerated blitter
+// API. The basic requirement is a scaled, clipped, alpha-blended blit.
+#define SB_HAS_BLITTER 0
+
+// Specifies the preferred byte order of color channels in a pixel. Refer to
+// starboard/configuration.h for the possible values. EGL/GLES platforms should
+// generally prefer a byte order of RGBA, regardless of endianness.
+#define SB_PREFERRED_RGBA_BYTE_ORDER SB_PREFERRED_RGBA_BYTE_ORDER_RGBA
+
+// Indicates whether or not the given platform supports bilinear filtering.
+// This can be checked to enable/disable renderer tests that verify that this is
+// working properly.
+#define SB_HAS_BILINEAR_FILTERING_SUPPORT 1
+
+// Indicates whether or not the given platform supports rendering of NV12
+// textures. These textures typically originate from video decoders.
+#define SB_HAS_NV12_TEXTURE_SUPPORT 1
+
+// Whether the current platform should frequently flip their display buffer.
+// If this is not required (e.g. SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER is set
+// to 0), then optimizations where the display buffer is not flipped if the
+// scene hasn't changed are enabled.
+#define SB_MUST_FREQUENTLY_FLIP_DISPLAY_BUFFER 0
+
+// --- I/O Configuration -----------------------------------------------------
+
+// Whether the current platform has microphone supported.
+#define SB_HAS_MICROPHONE 1
+
+// Whether the current platform implements the on screen keyboard interface.
+#define SB_HAS_ON_SCREEN_KEYBOARD 0
+
+// Whether the current platform has speech recognizer.
+#define SB_HAS_SPEECH_RECOGNIZER 1
+
+// Whether the current platform has speech synthesis.
+#define SB_HAS_SPEECH_SYNTHESIS 1
+
+// --- Media Configuration ---------------------------------------------------
+
+// Specifies whether this platform supports retrieving system-level closed
+// caption settings
+#define SB_HAS_CAPTIONS 1
+
+// Whether the current platform uses a media player that relies on a URL.
+#define SB_HAS_PLAYER_WITH_URL 0
+
+// The maximum audio bitrate the platform can decode. The following value
+// equals to 5M bytes per seconds which is more than enough for compressed
+// audio.
+#define SB_MEDIA_MAX_AUDIO_BITRATE_IN_BITS_PER_SECOND (40 * 1024 * 1024)
+
+// The maximum video bitrate the platform can decode. The following value
+// equals to 25M bytes per seconds which is more than enough for compressed
+// video.
+#define SB_MEDIA_MAX_VIDEO_BITRATE_IN_BITS_PER_SECOND (200 * 1024 * 1024)
+
+// Specifies whether this platform has webm/vp9 support. This should be set to
+// non-zero on platforms with webm/vp9 support.
+#define SB_HAS_MEDIA_WEBM_VP9_SUPPORT 1
+
+// Specifies whether this platform updates audio frames asynchronously. In such
+// case an extra parameter will be added to |SbAudioSinkConsumeFramesFunc| to
+// indicate the absolute time that the consumed audio frames are reported.
+// Check document for |SbAudioSinkConsumeFramesFunc| in audio_sink.h for more
+// details.
+#define SB_HAS_ASYNC_AUDIO_FRAMES_REPORTING 1
+
+// Specifies the stack size for threads created inside media stack. Set to 0 to
+// use the default thread stack size. Set to non-zero to explicitly set the
+// stack size for media stack threads.
+#define SB_MEDIA_THREAD_STACK_SIZE 0U
+
+// --- Decoder-only Params ---
+
+// Specifies how media buffers must be aligned on this platform as some
+// decoders may have special requirement on the alignment of buffers being
+// decoded.
+#define SB_MEDIA_BUFFER_ALIGNMENT 128U
+
+// Specifies how video frame buffers must be aligned on this platform.
+#define SB_MEDIA_VIDEO_FRAME_ALIGNMENT 256U
+
+// The encoded video frames are compressed in different ways, their decoding
+// time can vary a lot. Occasionally a single frame can take longer time to
+// decode than the average time per frame. The player has to cache some frames
+// to account for such inconsistency. The number of frames being cached are
+// controlled by the following two macros.
+//
+// Specify the number of video frames to be cached before the playback starts.
+// Note that set this value too large may increase the playback start delay.
+#define SB_MEDIA_MAXIMUM_VIDEO_PREROLL_FRAMES 4
+
+// Specify the number of video frames to be cached during playback. A large
+// value leads to more stable fps but also causes the app to use more memory.
+#define SB_MEDIA_MAXIMUM_VIDEO_FRAMES 12
+
+// --- Memory Configuration --------------------------------------------------
+
+// The memory page size, which controls the size of chunks on memory that
+// allocators deal with, and the alignment of those chunks. This doesn't have to
+// be the hardware-defined physical page size, but it should be a multiple of
+// it.
+#define SB_MEMORY_PAGE_SIZE 4096
+
+// Whether this platform has and should use an MMAP function to map physical
+// memory to the virtual address space.
+#define SB_HAS_MMAP 1
+
+// Whether this platform can map executable memory. Implies SB_HAS_MMAP. This is
+// required for platforms that want to JIT.
+#define SB_CAN_MAP_EXECUTABLE_MEMORY 1
+
+// Whether this platform has and should use an growable heap (e.g. with sbrk())
+// to map physical memory to the virtual address space.
+#define SB_HAS_VIRTUAL_REGIONS 0
+
+// Specifies the alignment for IO Buffers, in bytes. Some low-level network APIs
+// may require buffers to have a specific alignment, and this is the place to
+// specify that.
+#define SB_NETWORK_IO_BUFFER_ALIGNMENT 16
+
+// Determines the alignment that allocations should have on this platform.
+#define SB_MALLOC_ALIGNMENT ((size_t)16U)
+
+// Determines the threshhold of allocation size that should be done with mmap
+// (if available), rather than allocated within the core heap.
+#define SB_DEFAULT_MMAP_THRESHOLD ((size_t)(256 * 1024U))
+
+// Defines the path where memory debugging logs should be written to.
+#define SB_MEMORY_LOG_PATH "/tmp/starboard"
+
+// --- Network Configuration -------------------------------------------------
+
+// Specifies whether this platform supports IPV6.
+#define SB_HAS_IPV6 1
+
+// Specifies whether this platform supports pipe.
+#define SB_HAS_PIPE 1
+
+// --- Timing API ------------------------------------------------------------
+
+// Whether this platform has an API to retrieve how long the current thread
+// has spent in the executing state.
+#define SB_HAS_TIME_THREAD_NOW 1
+
+// --- Thread Configuration --------------------------------------------------
+
+// Whether the current platform supports thread priorities.
+#define SB_HAS_THREAD_PRIORITY_SUPPORT 1
+
+// Defines the maximum number of simultaneous threads for this platform. Some
+// platforms require sharing thread handles with other kinds of system handles,
+// like mutexes, so we want to keep this managable.
+#define SB_MAX_THREADS 90
+
+// The maximum number of thread local storage keys supported by this platform.
+// This comes from bionic PTHREAD_KEYS_MAX in limits.h, which we've decided
+// to not include here to decrease symbol pollution.
+#define SB_MAX_THREAD_LOCAL_KEYS 128
+
+// The maximum length of the name for a thread, including the NULL-terminator.
+#define SB_MAX_THREAD_NAME_LENGTH 16
+
+// --- Tuneable Parameters ---------------------------------------------------
+
+// Specifies the network receive buffer size in bytes, set via
+// SbSocketSetReceiveBufferSize().
+//
+// Setting this to 0 indicates that SbSocketSetReceiveBufferSize() should
+// not be called. Use this for OSs (such as Linux) where receive buffer
+// auto-tuning is better.
+//
+// On some platforms, this may affect max TCP window size which may
+// dramatically affect throughput in the presence of latency.
+//
+// If your platform does not have a good TCP auto-tuning mechanism,
+// a setting of (128 * 1024) here is recommended.
+#define SB_NETWORK_RECEIVE_BUFFER_SIZE (0)
+
+// --- User Configuration ----------------------------------------------------
+
+// The maximum number of users that can be signed in at the same time.
+#define SB_USER_MAX_SIGNED_IN 1
+
+// --- Platform Specific Audits ----------------------------------------------
+
+#if !defined(__GNUC__)
+#error "Android builds need a GCC-like compiler (for the moment)."
+#endif
+
+#endif // STARBOARD_ANDROID_SHARED_CONFIGURATION_PUBLIC_H_
diff --git a/src/starboard/android/shared/decode_target_create.cc b/src/starboard/android/shared/decode_target_create.cc
new file mode 100644
index 0000000..9b6f900
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_create.cc
@@ -0,0 +1,148 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/decode_target_create.h"
+
+#include <android/native_window_jni.h>
+#include <jni.h>
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <GLES2/gl2ext.h>
+
+#include "starboard/android/shared/decode_target_internal.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/decode_target.h"
+#include "starboard/shared/gles/gl_call.h"
+
+using starboard::android::shared::JniEnvExt;
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+jobject CreateSurfaceTexture(int gl_texture_id) {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ jobject local_surface_texture = env->NewObjectOrAbort(
+ "android/graphics/SurfaceTexture", "(I)V", gl_texture_id);
+
+ jobject global_surface_texture =
+ env->ConvertLocalRefToGlobalRef(local_surface_texture);
+
+ return global_surface_texture;
+}
+
+jobject CreateSurfaceFromSurfaceTexture(jobject surface_texture) {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ jobject local_surface = env->NewObjectOrAbort(
+ "android/view/Surface", "(Landroid/graphics/SurfaceTexture;)V",
+ surface_texture);
+
+ jobject global_surface = env->ConvertLocalRefToGlobalRef(local_surface);
+
+ return global_surface;
+}
+
+struct CreateParams {
+ int width;
+ int height;
+ SbDecodeTargetFormat format;
+
+ SbDecodeTarget decode_target_out;
+};
+
+void CreateWithContextRunner(void* context) {
+ CreateParams* params = static_cast<CreateParams*>(context);
+
+ // Setup the GL texture that Android's MediaCodec library will target with
+ // the decoder. We don't call glTexImage2d() on it, Android will handle
+ // the creation of the content when SurfaceTexture::updateTexImage() is
+ // called.
+ GLuint texture;
+ GL_CALL(glGenTextures(1, &texture));
+ GL_CALL(glBindTexture(GL_TEXTURE_EXTERNAL_OES, texture));
+ GL_CALL(glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
+ GL_LINEAR));
+ GL_CALL(glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
+ GL_LINEAR));
+ GL_CALL(glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
+ GL_CLAMP_TO_EDGE));
+ GL_CALL(glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
+ GL_CLAMP_TO_EDGE));
+
+ SbDecodeTarget decode_target = new SbDecodeTargetPrivate;
+ decode_target->data = new SbDecodeTargetPrivate::Data;
+
+ // Wrap the GL texture in an Android SurfaceTexture object.
+ decode_target->data->surface_texture = CreateSurfaceTexture(texture);
+
+ // We will also need an Android Surface object in order to obtain a
+ // ANativeWindow object that we can pass into the AMediaCodec library.
+ decode_target->data->surface =
+ CreateSurfaceFromSurfaceTexture(decode_target->data->surface_texture);
+
+ decode_target->data->native_window =
+ ANativeWindow_fromSurface(JniEnvExt::Get(), decode_target->data->surface);
+
+ // Setup our publicly accessible decode target information.
+ decode_target->data->info.format = params->format;
+ decode_target->data->info.is_opaque = true;
+ decode_target->data->info.width = params->width;
+ decode_target->data->info.height = params->height;
+ decode_target->data->info.planes[0].texture = texture;
+ decode_target->data->info.planes[0].gl_texture_target =
+ GL_TEXTURE_EXTERNAL_OES;
+ decode_target->data->info.planes[0].width = params->width;
+ decode_target->data->info.planes[0].height = params->height;
+
+ // These values will be initialized when SbPlayerGetCurrentFrame() is called.
+ decode_target->data->info.planes[0].content_region.left = 0;
+ decode_target->data->info.planes[0].content_region.right = 0;
+ decode_target->data->info.planes[0].content_region.top = 0;
+ decode_target->data->info.planes[0].content_region.bottom = 0;
+
+ GL_CALL(glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0));
+
+ params->decode_target_out = decode_target;
+}
+
+} // namespace
+
+SbDecodeTarget DecodeTargetCreate(
+ SbDecodeTargetGraphicsContextProvider* provider,
+ SbDecodeTargetFormat format,
+ int width,
+ int height) {
+ SB_DCHECK(format == kSbDecodeTargetFormat1PlaneRGBA);
+ if (format != kSbDecodeTargetFormat1PlaneRGBA) {
+ return kSbDecodeTargetInvalid;
+ }
+
+ CreateParams params;
+ params.width = width;
+ params.height = height;
+ params.format = format;
+ params.decode_target_out = kSbDecodeTargetInvalid;
+
+ SbDecodeTargetRunInGlesContext(
+ provider, &CreateWithContextRunner, ¶ms);
+ return params.decode_target_out;
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/decode_target_create.h b/src/starboard/android/shared/decode_target_create.h
new file mode 100644
index 0000000..3f32621
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_create.h
@@ -0,0 +1,34 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_DECODE_TARGET_CREATE_H_
+#define STARBOARD_ANDROID_SHARED_DECODE_TARGET_CREATE_H_
+
+#include "starboard/decode_target.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+SbDecodeTarget DecodeTargetCreate(
+ SbDecodeTargetGraphicsContextProvider* provider,
+ SbDecodeTargetFormat format,
+ int width,
+ int height);
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_DECODE_TARGET_CREATE_H_
diff --git a/src/starboard/android/shared/decode_target_get_info.cc b/src/starboard/android/shared/decode_target_get_info.cc
new file mode 100644
index 0000000..38ded1a
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_get_info.cc
@@ -0,0 +1,29 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/decode_target_internal.h"
+#include "starboard/decode_target.h"
+#include "starboard/memory.h"
+
+bool SbDecodeTargetGetInfo(SbDecodeTarget decode_target,
+ SbDecodeTargetInfo* out_info) {
+ if (!SbMemoryIsZero(out_info, sizeof(*out_info))) {
+ SB_DCHECK(false) << "out_info must be zeroed out.";
+ return false;
+ }
+
+ SbMemoryCopy(out_info, &decode_target->data->info, sizeof(*out_info));
+
+ return true;
+}
diff --git a/src/starboard/android/shared/decode_target_internal.cc b/src/starboard/android/shared/decode_target_internal.cc
new file mode 100644
index 0000000..957fb1d
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_internal.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/decode_target_internal.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+
+using starboard::android::shared::JniEnvExt;
+
+SbDecodeTargetPrivate::Data::~Data() {
+ ANativeWindow_release(native_window);
+
+ JniEnvExt* env = JniEnvExt::Get();
+ env->DeleteGlobalRef(surface);
+ env->DeleteGlobalRef(surface_texture);
+
+ glDeleteTextures(1, &info.planes[0].texture);
+ SB_DCHECK(glGetError() == GL_NO_ERROR);
+}
diff --git a/src/starboard/android/shared/decode_target_internal.h b/src/starboard/android/shared/decode_target_internal.h
new file mode 100644
index 0000000..953840a
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_internal.h
@@ -0,0 +1,47 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_DECODE_TARGET_INTERNAL_H_
+#define STARBOARD_ANDROID_SHARED_DECODE_TARGET_INTERNAL_H_
+
+#include <android/native_window.h>
+#include <GLES2/gl2.h>
+#include <jni.h>
+
+#include "starboard/common/ref_counted.h"
+#include "starboard/decode_target.h"
+
+struct SbDecodeTargetPrivate {
+ class Data : public starboard::RefCounted<Data> {
+ public:
+ Data() {}
+
+ // Java objects which wrap the texture. We hold on to global references
+ // to these objects.
+ jobject surface_texture;
+ jobject surface;
+ ANativeWindow* native_window;
+
+ // Publicly accessible information about the decode target.
+ SbDecodeTargetInfo info;
+
+ private:
+ friend class starboard::RefCounted<Data>;
+ ~Data();
+ };
+
+ starboard::scoped_refptr<Data> data;
+};
+
+#endif // STARBOARD_ANDROID_SHARED_DECODE_TARGET_INTERNAL_H_
diff --git a/src/starboard/android/shared/decode_target_release.cc b/src/starboard/android/shared/decode_target_release.cc
new file mode 100644
index 0000000..29a3f4c
--- /dev/null
+++ b/src/starboard/android/shared/decode_target_release.cc
@@ -0,0 +1,28 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <GLES2/gl2.h>
+
+#include "starboard/android/shared/decode_target_internal.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/decode_target.h"
+#include "starboard/log.h"
+
+void SbDecodeTargetRelease(SbDecodeTarget decode_target) {
+ // Most of the actual data within |decode_target| is stored in the reference
+ // counted decode_target->data, so deleting |decode_target| here may not
+ // actually release any resources, if there are other references to
+ // decode_target->data.
+ delete decode_target;
+}
diff --git a/src/starboard/android/shared/directory_close.cc b/src/starboard/android/shared/directory_close.cc
new file mode 100644
index 0000000..30d52ec
--- /dev/null
+++ b/src/starboard/android/shared/directory_close.cc
@@ -0,0 +1,32 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/directory.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+
+#include "starboard/android/shared/directory_internal.h"
+#include "starboard/shared/iso/impl/directory_close.h"
+
+bool SbDirectoryClose(SbDirectory directory) {
+ if (directory && directory->asset_dir) {
+ AAssetDir_close(directory->asset_dir);
+ delete directory;
+ return true;
+ }
+
+ return ::starboard::shared::iso::impl::SbDirectoryClose(directory);
+}
diff --git a/src/starboard/android/shared/directory_get_next.cc b/src/starboard/android/shared/directory_get_next.cc
new file mode 100644
index 0000000..f1e5b02
--- /dev/null
+++ b/src/starboard/android/shared/directory_get_next.cc
@@ -0,0 +1,35 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/directory.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/directory_internal.h"
+#include "starboard/shared/iso/impl/directory_get_next.h"
+
+bool SbDirectoryGetNext(SbDirectory directory, SbDirectoryEntry* out_entry) {
+ if (directory && directory->asset_dir && out_entry) {
+ const char* file_name = AAssetDir_getNextFileName(directory->asset_dir);
+ if (file_name == NULL) {
+ return false;
+ }
+ size_t size = SB_ARRAY_SIZE_INT(out_entry->name);
+ SbStringCopy(out_entry->name, file_name, size);
+ return true;
+ }
+
+ return ::starboard::shared::iso::impl::SbDirectoryGetNext(directory,
+ out_entry);
+}
diff --git a/src/starboard/android/shared/directory_internal.h b/src/starboard/android/shared/directory_internal.h
new file mode 100644
index 0000000..1e2108b
--- /dev/null
+++ b/src/starboard/android/shared/directory_internal.h
@@ -0,0 +1,37 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_DIRECTORY_INTERNAL_H_
+#define STARBOARD_ANDROID_SHARED_DIRECTORY_INTERNAL_H_
+
+#include <android/asset_manager.h>
+
+#include <dirent.h>
+
+#include "starboard/directory.h"
+#include "starboard/shared/internal_only.h"
+
+struct SbDirectoryPrivate {
+ // Note: Only one of these two fields will be valid for any given file.
+
+ // The ISO C directory stream handle, or NULL if it's an asset directory.
+ DIR* directory;
+
+ // If not NULL this is an Android asset directory.
+ AAssetDir* asset_dir;
+
+ SbDirectoryPrivate() : directory(NULL), asset_dir(NULL) {}
+};
+
+#endif // STARBOARD_ANDROID_SHARED_DIRECTORY_INTERNAL_H_
diff --git a/src/starboard/android/shared/directory_open.cc b/src/starboard/android/shared/directory_open.cc
new file mode 100644
index 0000000..9c0f7a8
--- /dev/null
+++ b/src/starboard/android/shared/directory_open.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/directory.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+
+#include "starboard/android/shared/directory_internal.h"
+#include "starboard/shared/iso/impl/directory_open.h"
+
+using starboard::android::shared::IsAndroidAssetPath;
+using starboard::android::shared::OpenAndroidAssetDir;
+
+SbDirectory SbDirectoryOpen(const char* path, SbFileError* out_error) {
+ if (!IsAndroidAssetPath(path)) {
+ return ::starboard::shared::iso::impl::SbDirectoryOpen(path, out_error);
+ }
+
+ AAssetDir* asset_dir = OpenAndroidAssetDir(path);
+ if (asset_dir) {
+ SbDirectory result = new SbDirectoryPrivate();
+ result->asset_dir = asset_dir;
+ return result;
+ }
+
+ if (out_error) {
+ *out_error = kSbFileErrorFailed;
+ }
+ return kSbDirectoryInvalid;
+}
diff --git a/src/starboard/android/shared/drm_create_system.cc b/src/starboard/android/shared/drm_create_system.cc
new file mode 100644
index 0000000..e92d6a1
--- /dev/null
+++ b/src/starboard/android/shared/drm_create_system.cc
@@ -0,0 +1,48 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/drm_system.h"
+
+#include "starboard/android/shared/media_common.h"
+
+SbDrmSystem SbDrmCreateSystem(
+ const char* key_system,
+ void* context,
+ SbDrmSessionUpdateRequestFunc update_request_callback,
+ SbDrmSessionUpdatedFunc session_updated_callback,
+ SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback,
+ SbDrmServerCertificateUpdatedFunc server_certificate_updated_callback,
+ SbDrmSessionClosedFunc session_closed_callback) {
+ using starboard::android::shared::DrmSystem;
+ using starboard::android::shared::IsWidevine;
+
+ if (!update_request_callback || !session_updated_callback ||
+ !key_statuses_changed_callback || !server_certificate_updated_callback ||
+ !session_closed_callback) {
+ return kSbDrmSystemInvalid;
+ }
+
+ if (!IsWidevine(key_system)) {
+ return kSbDrmSystemInvalid;
+ }
+
+ DrmSystem* drm_system =
+ new DrmSystem(context, update_request_callback, session_updated_callback,
+ key_statuses_changed_callback);
+ if (!drm_system->is_valid()) {
+ delete drm_system;
+ return kSbDrmSystemInvalid;
+ }
+ return drm_system;
+}
diff --git a/src/starboard/android/shared/drm_system.cc b/src/starboard/android/shared/drm_system.cc
new file mode 100644
index 0000000..e01c8f7
--- /dev/null
+++ b/src/starboard/android/shared/drm_system.cc
@@ -0,0 +1,307 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/drm_system.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+
+namespace {
+
+using starboard::android::shared::DrmSystem;
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+const char kNoUrl[] = "";
+
+// Using all capital names to be consistent with other Android media statuses.
+// They are defined in the same order as in their Java counterparts. Their
+// values should be kept in consistent with their Java counterparts defined in
+// android.media.MediaDrm.KeyStatus.
+const jint MEDIA_DRM_KEY_STATUS_EXPIRED = 1;
+const jint MEDIA_DRM_KEY_STATUS_INTERNAL_ERROR = 4;
+const jint MEDIA_DRM_KEY_STATUS_OUTPUT_NOT_ALLOWED = 2;
+const jint MEDIA_DRM_KEY_STATUS_PENDING = 3;
+const jint MEDIA_DRM_KEY_STATUS_USABLE = 0;
+
+} // namespace
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_MediaDrmBridge_nativeOnSessionMessage(
+ JNIEnv* env,
+ jobject unused_this,
+ jlong native_media_drm_bridge,
+ jint ticket,
+ jbyteArray j_session_id,
+ jint request_type,
+ jbyteArray j_message) {
+ jbyte* session_id_elements = env->GetByteArrayElements(j_session_id, NULL);
+ jsize session_id_size = env->GetArrayLength(j_session_id);
+
+ jbyte* message_elements = env->GetByteArrayElements(j_message, NULL);
+ jsize message_size = env->GetArrayLength(j_message);
+
+ SB_DCHECK(session_id_elements);
+ SB_DCHECK(message_elements);
+
+ DrmSystem* drm_system = reinterpret_cast<DrmSystem*>(native_media_drm_bridge);
+ SB_DCHECK(drm_system);
+ drm_system->CallUpdateRequestCallback(ticket, session_id_elements,
+ session_id_size, message_elements,
+ message_size, kNoUrl);
+ env->ReleaseByteArrayElements(j_session_id, session_id_elements, JNI_ABORT);
+ env->ReleaseByteArrayElements(j_message, message_elements, JNI_ABORT);
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_MediaDrmBridge_nativeOnKeyStatusChange(
+ JniEnvExt* env,
+ jobject unused_this,
+ jlong native_media_drm_bridge,
+ jbyteArray j_session_id,
+ jobjectArray j_key_status_array) {
+ jbyte* session_id_elements = env->GetByteArrayElements(j_session_id, NULL);
+ jsize session_id_size = env->GetArrayLength(j_session_id);
+
+ SB_DCHECK(session_id_elements);
+
+ // NULL array indicates key status isn't supported (i.e. Android API < 23)
+ jsize length = (j_key_status_array == NULL) ? 0
+ : env->GetArrayLength(j_key_status_array);
+ std::vector<SbDrmKeyId> drm_key_ids(length);
+ std::vector<SbDrmKeyStatus> drm_key_statuses(length);
+
+ for (jsize i = 0; i < length; ++i) {
+ jobject j_key_status =
+ env->GetObjectArrayElementOrAbort(j_key_status_array, i);
+ jbyteArray j_key_id = static_cast<jbyteArray>(
+ env->CallObjectMethodOrAbort(j_key_status, "getKeyId", "()[B"));
+
+ jbyte* key_id_elements = env->GetByteArrayElements(j_key_id, NULL);
+ jsize key_id_size = env->GetArrayLength(j_key_id);
+ SB_DCHECK(key_id_elements);
+
+ SB_DCHECK(key_id_size <= sizeof(drm_key_ids[i].identifier));
+ SbMemoryCopy(drm_key_ids[i].identifier, key_id_elements, key_id_size);
+ env->ReleaseByteArrayElements(j_key_id, key_id_elements, JNI_ABORT);
+ drm_key_ids[i].identifier_size = key_id_size;
+
+ jint j_status_code =
+ env->CallIntMethodOrAbort(j_key_status, "getStatusCode", "()I");
+ if (j_status_code == MEDIA_DRM_KEY_STATUS_EXPIRED) {
+ drm_key_statuses[i] = kSbDrmKeyStatusExpired;
+ } else if (j_status_code == MEDIA_DRM_KEY_STATUS_INTERNAL_ERROR) {
+ drm_key_statuses[i] = kSbDrmKeyStatusError;
+ } else if (j_status_code == MEDIA_DRM_KEY_STATUS_OUTPUT_NOT_ALLOWED) {
+ drm_key_statuses[i] = kSbDrmKeyStatusRestricted;
+ } else if (j_status_code == MEDIA_DRM_KEY_STATUS_PENDING) {
+ drm_key_statuses[i] = kSbDrmKeyStatusPending;
+ } else if (j_status_code == MEDIA_DRM_KEY_STATUS_USABLE) {
+ drm_key_statuses[i] = kSbDrmKeyStatusUsable;
+ } else {
+ SB_NOTREACHED();
+ drm_key_statuses[i] = kSbDrmKeyStatusError;
+ }
+ }
+
+ DrmSystem* drm_system = reinterpret_cast<DrmSystem*>(native_media_drm_bridge);
+ SB_DCHECK(drm_system);
+ drm_system->CallDrmSessionKeyStatusesChangedCallback(
+ session_id_elements, session_id_size, drm_key_ids, drm_key_statuses);
+
+ env->ReleaseByteArrayElements(j_session_id, session_id_elements, JNI_ABORT);
+}
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+jbyteArray ByteArrayFromRaw(const void* data, int size) {
+ return JniEnvExt::Get()->NewByteArrayFromRaw(static_cast<const jbyte*>(data),
+ size);
+}
+
+} // namespace
+
+DrmSystem::DrmSystem(
+ void* context,
+ SbDrmSessionUpdateRequestFunc update_request_callback,
+ SbDrmSessionUpdatedFunc session_updated_callback,
+ SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback)
+ : context_(context),
+ update_request_callback_(update_request_callback),
+ session_updated_callback_(session_updated_callback),
+ key_statuses_changed_callback_(key_statuses_changed_callback),
+ j_media_drm_bridge_(NULL),
+ j_media_crypto_(NULL),
+ hdcp_lost_(false) {
+ JniEnvExt* env = JniEnvExt::Get();
+ j_media_drm_bridge_ = env->CallStaticObjectMethodOrAbort(
+ "dev/cobalt/media/MediaDrmBridge", "create",
+ "(J)Ldev/cobalt/media/MediaDrmBridge;", reinterpret_cast<jlong>(this));
+ if (!j_media_drm_bridge_) {
+ SB_LOG(ERROR) << "Failed to create MediaDrmBridge.";
+ return;
+ }
+ j_media_drm_bridge_ = env->ConvertLocalRefToGlobalRef(j_media_drm_bridge_);
+ j_media_crypto_ = env->CallObjectMethodOrAbort(
+ j_media_drm_bridge_, "getMediaCrypto", "()Landroid/media/MediaCrypto;");
+ if (!j_media_crypto_) {
+ SB_LOG(ERROR) << "Failed to create MediaCrypto.";
+ return;
+ }
+ j_media_crypto_ = env->ConvertLocalRefToGlobalRef(j_media_crypto_);
+}
+
+DrmSystem::~DrmSystem() {
+ JniEnvExt* env = JniEnvExt::Get();
+ if (j_media_crypto_) {
+ env->DeleteGlobalRef(j_media_crypto_);
+ j_media_crypto_ = NULL;
+ }
+ if (j_media_drm_bridge_) {
+ env->CallVoidMethodOrAbort(j_media_drm_bridge_, "destroy", "()V");
+ env->DeleteGlobalRef(j_media_drm_bridge_);
+ j_media_drm_bridge_ = NULL;
+ }
+}
+
+void DrmSystem::GenerateSessionUpdateRequest(int ticket,
+ const char* type,
+ const void* initialization_data,
+ int initialization_data_size) {
+ ScopedLocalJavaRef<jbyteArray> j_init_data(
+ ByteArrayFromRaw(initialization_data, initialization_data_size));
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(type));
+ env->CallVoidMethodOrAbort(
+ j_media_drm_bridge_, "createSession", "(I[BLjava/lang/String;)V",
+ static_cast<jint>(ticket), j_init_data.Get(), j_mime.Get());
+ // |update_request_callback_| will be called by Java calling into
+ // |onSessionMessage|.
+}
+
+void DrmSystem::UpdateSession(int ticket,
+ const void* key,
+ int key_size,
+ const void* session_id,
+ int session_id_size) {
+ ScopedLocalJavaRef<jbyteArray> j_session_id(
+ ByteArrayFromRaw(session_id, session_id_size));
+ ScopedLocalJavaRef<jbyteArray> j_response(ByteArrayFromRaw(key, key_size));
+
+ jboolean status = JniEnvExt::Get()->CallBooleanMethodOrAbort(
+ j_media_drm_bridge_, "updateSession", "([B[B)Z", j_session_id.Get(),
+ j_response.Get());
+ session_updated_callback_(
+ this, context_, ticket,
+ status == JNI_TRUE ? kSbDrmStatusSuccess : kSbDrmStatusUnknownError, NULL,
+ session_id, session_id_size);
+}
+
+void DrmSystem::CloseSession(const void* session_id, int session_id_size) {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jbyteArray> j_session_id(
+ ByteArrayFromRaw(session_id, session_id_size));
+ std::string session_id_as_string(
+ static_cast<const char*>(session_id),
+ static_cast<const char*>(session_id) + session_id_size);
+
+ {
+ ScopedLock scoped_lock(mutex_);
+ auto iter = cached_drm_key_ids_.find(session_id_as_string);
+ if (iter != cached_drm_key_ids_.end()) {
+ cached_drm_key_ids_.erase(iter);
+ }
+ }
+ env->CallVoidMethodOrAbort(j_media_drm_bridge_, "closeSession", "([B)V",
+ j_session_id.Get());
+}
+
+DrmSystem::DecryptStatus DrmSystem::Decrypt(InputBuffer* buffer) {
+ SB_DCHECK(buffer);
+ SB_DCHECK(buffer->drm_info());
+ SB_DCHECK(j_media_crypto_);
+ // The actual decryption will take place by calling |queueSecureInputBuffer|
+ // in the decoders. Our existence implies that there is enough information
+ // to perform the decryption.
+ // TODO: Returns kRetry when |UpdateSession| is not called at all to allow the
+ // player worker to handle the retry logic.
+ return kSuccess;
+}
+
+void DrmSystem::CallUpdateRequestCallback(int ticket,
+ const void* session_id,
+ int session_id_size,
+ const void* content,
+ int content_size,
+ const char* url) {
+ update_request_callback_(this, context_, ticket, kSbDrmStatusSuccess,
+ kSbDrmSessionRequestTypeLicenseRequest, NULL,
+ session_id, session_id_size, content, content_size,
+ url);
+}
+
+void DrmSystem::CallDrmSessionKeyStatusesChangedCallback(
+ const void* session_id,
+ int session_id_size,
+ const std::vector<SbDrmKeyId>& drm_key_ids,
+ const std::vector<SbDrmKeyStatus>& drm_key_statuses) {
+ SB_DCHECK(drm_key_ids.size() == drm_key_statuses.size());
+
+ std::string session_id_as_string(
+ static_cast<const char*>(session_id),
+ static_cast<const char*>(session_id) + session_id_size);
+
+ bool hdcp_lost = false;
+ {
+ ScopedLock scoped_lock(mutex_);
+ cached_drm_key_ids_[session_id_as_string] = drm_key_ids;
+ hdcp_lost = hdcp_lost_;
+ }
+
+ if (hdcp_lost) {
+ OnInsufficientOutputProtection();
+ return;
+ }
+
+ key_statuses_changed_callback_(this, context_, session_id, session_id_size,
+ static_cast<int>(drm_key_ids.size()),
+ drm_key_ids.data(), drm_key_statuses.data());
+}
+
+void DrmSystem::OnInsufficientOutputProtection() {
+ // HDCP has lost, update the statuses of all keys in all known sessions to be
+ // restricted.
+ ScopedLock scoped_lock(mutex_);
+ hdcp_lost_ = true;
+ for (auto& iter : cached_drm_key_ids_) {
+ const std::string& session_id = iter.first;
+ const std::vector<SbDrmKeyId>& drm_key_ids = iter.second;
+ std::vector<SbDrmKeyStatus> drm_key_statuses(drm_key_ids.size(),
+ kSbDrmKeyStatusRestricted);
+
+ key_statuses_changed_callback_(this, context_, session_id.data(),
+ session_id.size(),
+ static_cast<int>(drm_key_ids.size()),
+ drm_key_ids.data(), drm_key_statuses.data());
+ }
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/drm_system.h b/src/starboard/android/shared/drm_system.h
new file mode 100644
index 0000000..cec052b
--- /dev/null
+++ b/src/starboard/android/shared/drm_system.h
@@ -0,0 +1,99 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_DRM_SYSTEM_H_
+#define STARBOARD_ANDROID_SHARED_DRM_SYSTEM_H_
+
+#include "starboard/shared/starboard/drm/drm_system_internal.h"
+
+#include <jni.h>
+
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class DrmSystem : public ::SbDrmSystemPrivate {
+ public:
+ DrmSystem(void* context,
+ SbDrmSessionUpdateRequestFunc update_request_callback,
+ SbDrmSessionUpdatedFunc session_updated_callback,
+ SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback);
+
+ ~DrmSystem() override;
+ void GenerateSessionUpdateRequest(int ticket,
+ const char* type,
+ const void* initialization_data,
+ int initialization_data_size) override;
+ void UpdateSession(int ticket,
+ const void* key,
+ int key_size,
+ const void* session_id,
+ int session_id_size);
+ void CloseSession(const void* session_id, int session_id_size) override;
+ DecryptStatus Decrypt(InputBuffer* buffer) override;
+#if SB_API_VERSION >= 10
+ void UpdateServerCertificate(int ticket,
+ const void* certificate,
+ int certificate_size) override {
+ SB_UNREFERENCED_PARAMETER(ticket);
+ SB_UNREFERENCED_PARAMETER(certificate);
+ SB_UNREFERENCED_PARAMETER(certificate_size);
+ }
+#endif // SB_API_VERSION >= 10
+
+ jobject GetMediaCrypto() const { return j_media_crypto_; }
+ void CallUpdateRequestCallback(int ticket,
+ const void* session_id,
+ int session_id_size,
+ const void* content,
+ int content_size,
+ const char* url);
+ void CallDrmSessionKeyStatusesChangedCallback(
+ const void* session_id,
+ int session_id_size,
+ const std::vector<SbDrmKeyId>& drm_key_ids,
+ const std::vector<SbDrmKeyStatus>& drm_key_statuses);
+ void OnInsufficientOutputProtection();
+
+ bool is_valid() const {
+ return j_media_drm_bridge_ != NULL && j_media_crypto_ != NULL;
+ }
+
+ private:
+ void* context_;
+ SbDrmSessionUpdateRequestFunc update_request_callback_;
+ SbDrmSessionUpdatedFunc session_updated_callback_;
+ // TODO: Update key statuses to Cobalt.
+ SbDrmSessionKeyStatusesChangedFunc key_statuses_changed_callback_;
+
+ jobject j_media_drm_bridge_;
+ jobject j_media_crypto_;
+
+ Mutex mutex_;
+ std::unordered_map<std::string, std::vector<SbDrmKeyId> > cached_drm_key_ids_;
+ bool hdcp_lost_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_DRM_SYSTEM_H_
diff --git a/src/starboard/android/shared/egl_swap_buffers.cc b/src/starboard/android/shared/egl_swap_buffers.cc
new file mode 100644
index 0000000..e1567f8
--- /dev/null
+++ b/src/starboard/android/shared/egl_swap_buffers.cc
@@ -0,0 +1,44 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+
+#include "starboard/android/shared/video_window.h"
+#include "starboard/shared/gles/gl_call.h"
+
+extern "C" {
+EGLBoolean __real_eglSwapBuffers(EGLDisplay dpy, EGLSurface surface);
+
+// This needs to be exported to ensure shared_library targets include it.
+SB_EXPORT_PLATFORM EGLBoolean __wrap_eglSwapBuffers(
+ EGLDisplay dpy, EGLSurface surface) {
+ // Kick off the GPU while waiting for new player bounds to take effect.
+ GL_CALL(glFlush());
+
+ // Wait for player bounds to take effect before presenting the UI frame which
+ // uses those player bounds. This helps to keep punch-out videos in sync with
+ // the UI frame.
+ // TODO: It is possible for the new UI frame to be displayed too late
+ // (especially if there's a lot to render), so this case still needs to
+ // be handled.
+
+ // Note, we're no longer calling WaitForVideoBoundsUpdate because it does
+ // not work properly without calling SurfaceHolder setFixedSize.
+ // starboard::android::shared::WaitForVideoBoundsUpdate();
+
+ return __real_eglSwapBuffers(dpy, surface);
+}
+
+}
diff --git a/src/starboard/android/shared/file_can_open.cc b/src/starboard/android/shared/file_can_open.cc
new file mode 100644
index 0000000..5aeb5fd
--- /dev/null
+++ b/src/starboard/android/shared/file_can_open.cc
@@ -0,0 +1,43 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/directory.h"
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_can_open.h"
+
+using starboard::android::shared::IsAndroidAssetPath;
+using starboard::android::shared::OpenAndroidAsset;
+
+bool SbFileCanOpen(const char* path, int flags) {
+ if (!IsAndroidAssetPath(path)) {
+ return ::starboard::shared::posix::impl::FileCanOpen(path, flags);
+ }
+
+ SbFile file = SbFileOpen(path, flags | kSbFileOpenOnly, NULL, NULL);
+ bool result = SbFileIsValid(file);
+ SbFileClose(file);
+
+ if (!result) {
+ SbDirectory directory = SbDirectoryOpen(path, NULL);
+ result = SbDirectoryIsValid(directory);
+ SbDirectoryClose(directory);
+ }
+
+ return result;
+}
diff --git a/src/starboard/android/shared/file_close.cc b/src/starboard/android/shared/file_close.cc
new file mode 100644
index 0000000..192aede
--- /dev/null
+++ b/src/starboard/android/shared/file_close.cc
@@ -0,0 +1,30 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_close.h"
+
+bool SbFileClose(SbFile file) {
+ if (file && file->asset) {
+ AAsset_close(file->asset);
+ delete file;
+ return true;
+ }
+
+ return ::starboard::shared::posix::impl::FileClose(file);
+}
diff --git a/src/starboard/android/shared/file_delete.cc b/src/starboard/android/shared/file_delete.cc
new file mode 100644
index 0000000..9a0ab8b
--- /dev/null
+++ b/src/starboard/android/shared/file_delete.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_delete.h"
+
+bool SbFileDelete(const char* path) {
+ return ::starboard::shared::posix::impl::FileDelete(path);
+}
diff --git a/src/starboard/android/shared/file_exists.cc b/src/starboard/android/shared/file_exists.cc
new file mode 100644
index 0000000..cfe253c
--- /dev/null
+++ b/src/starboard/android/shared/file_exists.cc
@@ -0,0 +1,19 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+bool SbFileExists(const char* path) {
+ return SbFileCanOpen(path, kSbFileRead);
+}
diff --git a/src/starboard/android/shared/file_flush.cc b/src/starboard/android/shared/file_flush.cc
new file mode 100644
index 0000000..25634ca
--- /dev/null
+++ b/src/starboard/android/shared/file_flush.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_flush.h"
+
+bool SbFileFlush(SbFile file) {
+ return ::starboard::shared::posix::impl::FileFlush(file);
+}
diff --git a/src/starboard/android/shared/file_get_info.cc b/src/starboard/android/shared/file_get_info.cc
new file mode 100644
index 0000000..d7e7559
--- /dev/null
+++ b/src/starboard/android/shared/file_get_info.cc
@@ -0,0 +1,34 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_get_info.h"
+
+bool SbFileGetInfo(SbFile file, SbFileInfo* out_info) {
+ if (file && file->asset && out_info) {
+ out_info->creation_time = 0;
+ out_info->is_directory = 0;
+ out_info->is_symbolic_link = 0;
+ out_info->last_accessed = 0;
+ out_info->last_modified = 0;
+ out_info->size = AAsset_getLength(file->asset);
+ return true;
+ }
+
+ return ::starboard::shared::posix::impl::FileGetInfo(file, out_info);
+}
diff --git a/src/starboard/android/shared/file_get_path_info.cc b/src/starboard/android/shared/file_get_path_info.cc
new file mode 100644
index 0000000..9bc05dc
--- /dev/null
+++ b/src/starboard/android/shared/file_get_path_info.cc
@@ -0,0 +1,51 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include "starboard/directory.h"
+
+#include "starboard/android/shared/directory_internal.h"
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_get_path_info.h"
+
+using starboard::android::shared::IsAndroidAssetPath;
+using starboard::android::shared::OpenAndroidAsset;
+
+bool SbFileGetPathInfo(const char* path, SbFileInfo* out_info) {
+ if (!IsAndroidAssetPath(path)) {
+ return ::starboard::shared::posix::impl::FileGetPathInfo(path, out_info);
+ }
+
+ SbFile file = SbFileOpen(path, kSbFileRead, NULL, NULL);
+ if (file) {
+ bool result = SbFileGetInfo(file, out_info);
+ SbFileClose(file);
+ return result;
+ }
+
+ SbDirectory directory = SbDirectoryOpen(path, NULL);
+ if (directory && directory->asset_dir) {
+ out_info->creation_time = 0;
+ out_info->is_directory = 1;
+ out_info->is_symbolic_link = 0;
+ out_info->last_accessed = 0;
+ out_info->last_modified = 0;
+ out_info->size = 0;
+ SbDirectoryClose(directory);
+ return true;
+ }
+
+ return false;
+}
diff --git a/src/starboard/android/shared/file_internal.cc b/src/starboard/android/shared/file_internal.cc
new file mode 100644
index 0000000..fd7ae03
--- /dev/null
+++ b/src/starboard/android/shared/file_internal.cc
@@ -0,0 +1,138 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/file_internal.h"
+
+#include <android/asset_manager.h>
+#include <android/asset_manager_jni.h>
+#include <android/log.h>
+#include <jni.h>
+#include <string>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+const char* g_app_assets_dir = "/cobalt/assets";
+const char* g_app_files_dir = NULL;
+const char* g_app_cache_dir = NULL;
+const char* g_app_lib_dir = NULL;
+
+namespace {
+jobject g_java_asset_manager;
+AAssetManager* g_asset_manager;
+
+// Copies the characters from a jstring and returns a newly allocated buffer
+// with the result.
+const char* DuplicateJavaString(JniEnvExt* env, jstring j_string) {
+ SB_DCHECK(j_string);
+ std::string utf_str = env->GetStringStandardUTFOrAbort(j_string);
+ const char* result = SbStringDuplicate(utf_str.c_str());
+ return result;
+}
+
+} // namespace
+
+void SbFileAndroidInitialize() {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ SB_DCHECK(g_java_asset_manager == NULL);
+ SB_DCHECK(g_asset_manager == NULL);
+ ScopedLocalJavaRef<jstring> j_app(
+ env->CallStarboardObjectMethodOrAbort(
+ "getApplicationContext", "()Landroid/content/Context;"));
+ g_java_asset_manager = env->ConvertLocalRefToGlobalRef(
+ env->CallObjectMethodOrAbort(j_app.Get(),
+ "getAssets", "()Landroid/content/res/AssetManager;"));
+ g_asset_manager = AAssetManager_fromJava(env, g_java_asset_manager);
+
+ SB_DCHECK(g_app_files_dir == NULL);
+ ScopedLocalJavaRef<jstring> j_string(
+ env->CallStarboardObjectMethodOrAbort("getFilesAbsolutePath",
+ "()Ljava/lang/String;"));
+ g_app_files_dir = DuplicateJavaString(env, j_string.Get());
+ SB_DLOG(INFO) << "Files dir: " << g_app_files_dir;
+
+ SB_DCHECK(g_app_cache_dir == NULL);
+ j_string.Reset(
+ env->CallStarboardObjectMethodOrAbort("getCacheAbsolutePath",
+ "()Ljava/lang/String;"));
+ g_app_cache_dir = DuplicateJavaString(env, j_string.Get());
+ SB_DLOG(INFO) << "Cache dir: " << g_app_cache_dir;
+
+ SB_DCHECK(g_app_lib_dir == NULL);
+ ScopedLocalJavaRef<jobject> j_app_info(
+ env->CallObjectMethodOrAbort(j_app.Get(), "getApplicationInfo",
+ "()Landroid/content/pm/ApplicationInfo;"));
+ j_string.Reset(env->GetStringFieldOrAbort(j_app_info.Get(),
+ "nativeLibraryDir"));
+ g_app_lib_dir = DuplicateJavaString(env, j_string.Get());
+ SB_DLOG(INFO) << "Lib dir: " << g_app_lib_dir;
+}
+
+void SbFileAndroidTeardown() {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ if (g_java_asset_manager) {
+ env->DeleteGlobalRef(g_java_asset_manager);
+ g_java_asset_manager = NULL;
+ g_asset_manager = NULL;
+ }
+
+ if (g_app_files_dir) {
+ SbMemoryDeallocate(const_cast<char*>(g_app_files_dir));
+ g_app_files_dir = NULL;
+ }
+
+ if (g_app_cache_dir) {
+ SbMemoryDeallocate(const_cast<char*>(g_app_cache_dir));
+ g_app_cache_dir = NULL;
+ }
+}
+
+bool IsAndroidAssetPath(const char* path) {
+ size_t prefix_len = SbStringGetLength(g_app_assets_dir);
+ return path != NULL
+ && SbStringCompare(g_app_assets_dir, path, prefix_len) == 0
+ && (path[prefix_len] == '/' || path[prefix_len] == '\0');
+}
+
+AAsset* OpenAndroidAsset(const char* path) {
+ if (!IsAndroidAssetPath(path) || g_asset_manager == NULL) {
+ return NULL;
+ }
+ const char* asset_path = path + SbStringGetLength(g_app_assets_dir) + 1;
+ return AAssetManager_open(g_asset_manager, asset_path, AASSET_MODE_RANDOM);
+}
+
+AAssetDir* OpenAndroidAssetDir(const char* path) {
+ if (!IsAndroidAssetPath(path) || g_asset_manager == NULL) {
+ return NULL;
+ }
+ const char* asset_path = path + SbStringGetLength(g_app_assets_dir);
+ if (*asset_path == '/') {
+ asset_path++;
+ }
+ return AAssetManager_openDir(g_asset_manager, asset_path);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/file_internal.h b/src/starboard/android/shared/file_internal.h
new file mode 100644
index 0000000..7d33227
--- /dev/null
+++ b/src/starboard/android/shared/file_internal.h
@@ -0,0 +1,57 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_FILE_INTERNAL_H_
+#define STARBOARD_ANDROID_SHARED_FILE_INTERNAL_H_
+
+#include <errno.h>
+
+#include <android/asset_manager.h>
+
+#include "starboard/file.h"
+#include "starboard/shared/internal_only.h"
+
+struct SbFilePrivate {
+ // Note: Only one of these two fields will be valid for any given file.
+
+ // The POSIX file descriptor of this file, or -1 if it's a read-only asset.
+ int descriptor;
+
+ // If not NULL this is an Android asset.
+ AAsset* asset;
+
+ SbFilePrivate() : descriptor(-1), asset(NULL) {}
+};
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+extern const char* g_app_assets_dir;
+extern const char* g_app_files_dir;
+extern const char* g_app_cache_dir;
+extern const char* g_app_lib_dir;
+
+void SbFileAndroidInitialize();
+void SbFileAndroidTeardown();
+
+bool IsAndroidAssetPath(const char* path);
+AAsset* OpenAndroidAsset(const char* path);
+AAssetDir* OpenAndroidAssetDir(const char* path);
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_FILE_INTERNAL_H_
diff --git a/src/starboard/android/shared/file_open.cc b/src/starboard/android/shared/file_open.cc
new file mode 100644
index 0000000..83d924a
--- /dev/null
+++ b/src/starboard/android/shared/file_open.cc
@@ -0,0 +1,106 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include <string>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_open.h"
+
+using starboard::android::shared::IsAndroidAssetPath;
+using starboard::android::shared::OpenAndroidAsset;
+
+namespace {
+
+// We don't package most font files in Cobalt content and fallback to the system
+// font file of the same name.
+const std::string kFontsXml("fonts.xml");
+const std::string kSystemFontsDir("/system/fonts/");
+const std::string kCobaltFontsDir("/cobalt/assets/fonts/");
+
+// Returns the fallback for the given asset path, or an empty string if none.
+// NOTE: While Cobalt now provides a mechanism for loading system fonts through
+// SbSystemGetPath(), using the fallback logic within SbFileOpen() is
+// still preferred for Android's fonts. The reason for this is that the
+// Android OS actually allows fonts to be loaded from two locations: one
+// that it provides; and one that the devices running its OS, which it
+// calls vendors, can provide. Rather than including the full Android font
+// package, vendors have the option of using a smaller Android font
+// package and supplementing it with their own fonts.
+//
+// If Android were to use SbSystemGetPath() for its fonts, vendors would
+// have no way of providing those supplemental fonts to Cobalt, which
+// could result in a limited selection of fonts being available. By
+// treating Android's fonts as Cobalt's fonts, Cobalt can still offer a
+// straightforward mechanism for including vendor fonts via
+// SbSystemGetPath().
+std::string FallbackPath(const std::string& path) {
+ // Fonts fallback to the system fonts.
+ if (path.compare(0, kCobaltFontsDir.length(), kCobaltFontsDir) == 0) {
+ std::string file_name = path.substr(kCobaltFontsDir.length());
+ // fonts.xml doesn't fallback.
+ if (file_name != kFontsXml) {
+ return kSystemFontsDir + file_name;
+ }
+ }
+ return std::string();
+}
+
+} // namespace
+
+SbFile SbFileOpen(const char* path,
+ int flags,
+ bool* out_created,
+ SbFileError* out_error) {
+ if (!IsAndroidAssetPath(path)) {
+ return ::starboard::shared::posix::impl::FileOpen(
+ path, flags, out_created, out_error);
+ }
+
+ // Assets are never created and are always read-only, whether it's actually an
+ // asset or we end up opening a fallback path.
+ if (out_created) {
+ *out_created = false;
+ }
+ bool can_read = flags & kSbFileRead;
+ bool can_write = flags & kSbFileWrite;
+ if (!can_read || can_write) {
+ if (out_error) {
+ *out_error = kSbFileErrorAccessDenied;
+ }
+ return kSbFileInvalid;
+ }
+
+ AAsset* asset = OpenAndroidAsset(path);
+ if (asset) {
+ SbFile result = new SbFilePrivate();
+ result->asset = asset;
+ return result;
+ }
+
+ std::string fallback_path = FallbackPath(path);
+ if (!fallback_path.empty()) {
+ SbFile result = ::starboard::shared::posix::impl::FileOpen(
+ fallback_path.c_str(), flags, out_created, out_error);
+ return result;
+ }
+
+ if (out_error) {
+ *out_error = kSbFileErrorFailed;
+ }
+ return kSbFileInvalid;
+}
diff --git a/src/starboard/android/shared/file_read.cc b/src/starboard/android/shared/file_read.cc
new file mode 100644
index 0000000..d283019
--- /dev/null
+++ b/src/starboard/android/shared/file_read.cc
@@ -0,0 +1,32 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_read.h"
+
+int SbFileRead(SbFile file, char* data, int size) {
+ if (!file || size < 0) {
+ return -1;
+ }
+
+ if (file->asset) {
+ return AAsset_read(file->asset, data, size);
+ } else {
+ return ::starboard::shared::posix::impl::FileRead(file, data, size);
+ }
+}
diff --git a/src/starboard/android/shared/file_seek.cc b/src/starboard/android/shared/file_seek.cc
new file mode 100644
index 0000000..590446b
--- /dev/null
+++ b/src/starboard/android/shared/file_seek.cc
@@ -0,0 +1,28 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include <android/asset_manager.h>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_seek.h"
+
+int64_t SbFileSeek(SbFile file, SbFileWhence whence, int64_t offset) {
+ if (file && file->asset) {
+ return AAsset_seek64(file->asset, offset, whence);
+ } else {
+ return ::starboard::shared::posix::impl::FileSeek(file, whence, offset);
+ }
+}
diff --git a/src/starboard/android/shared/file_truncate.cc b/src/starboard/android/shared/file_truncate.cc
new file mode 100644
index 0000000..c1075e7
--- /dev/null
+++ b/src/starboard/android/shared/file_truncate.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_truncate.h"
+
+bool SbFileTruncate(SbFile file, int64_t length) {
+ return ::starboard::shared::posix::impl::FileTruncate(file, length);
+}
diff --git a/src/starboard/android/shared/file_write.cc b/src/starboard/android/shared/file_write.cc
new file mode 100644
index 0000000..7705435
--- /dev/null
+++ b/src/starboard/android/shared/file_write.cc
@@ -0,0 +1,22 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/file.h"
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/shared/posix/impl/file_write.h"
+
+int SbFileWrite(SbFile file, const char* data, int size) {
+ return ::starboard::shared::posix::impl::FileWrite(file, data, size);
+}
diff --git a/src/starboard/android/shared/get_home_directory.cc b/src/starboard/android/shared/get_home_directory.cc
new file mode 100644
index 0000000..0d75716
--- /dev/null
+++ b/src/starboard/android/shared/get_home_directory.cc
@@ -0,0 +1,43 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <pwd.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/log.h"
+#include "starboard/shared/nouser/user_internal.h"
+#include "starboard/string.h"
+
+using ::starboard::android::shared::g_app_files_dir;
+
+namespace starboard {
+namespace shared {
+namespace nouser {
+
+// With a single SbUser representing the Android platform, the 'file_storage'
+// SbStorage implementation writes a single SbStorageRecord for all accounts in
+// the home directory we return here. This is analogous to a web browser
+// providing a single cookie store no matter what any particular web app does to
+// present signing in as different users.
+bool GetHomeDirectory(SbUser user, char* out_path, int path_size) {
+ int len = SbStringCopy(out_path, g_app_files_dir, path_size);
+ return len < path_size;
+}
+
+} // namespace nouser
+} // namespace shared
+} // namespace starboard
diff --git a/src/starboard/android/shared/gyp_configuration.gypi b/src/starboard/android/shared/gyp_configuration.gypi
new file mode 100644
index 0000000..10f6c0c
--- /dev/null
+++ b/src/starboard/android/shared/gyp_configuration.gypi
@@ -0,0 +1,133 @@
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Platform specific configuration for Android on Starboard. Automatically
+# included by gyp_cobalt in all .gyp files by Cobalt together with base.gypi.
+#
+{
+ 'variables': {
+ 'target_os': 'android',
+ 'final_executable_type': 'shared_library',
+ 'gtest_target_type': 'shared_library',
+
+ 'gl_type': 'system_gles2',
+ 'enable_remote_debugging': 0,
+
+ # Define platform specific compiler and linker flags.
+ # Refer to base.gypi for a list of all available variables.
+ 'compiler_flags_host': [
+ '-O2',
+ ],
+ 'compiler_flags_debug': [
+ '-frtti',
+ '-O0',
+ ],
+ 'compiler_flags_devel': [
+ '-frtti',
+ '-O2',
+ ],
+ 'compiler_flags_qa': [
+ '-fno-rtti',
+ '-O2',
+ '-gline-tables-only',
+ ],
+ 'compiler_flags_gold': [
+ '-fno-rtti',
+ '-O2',
+ '-gline-tables-only',
+ ],
+ 'platform_libraries': [
+ '-lEGL',
+ '-lGLESv2',
+ '-lOpenSLES',
+ '-landroid',
+ '-llog',
+ '-lmediandk',
+ ],
+ 'conditions': [
+ ['cobalt_fastbuild==0', {
+ 'compiler_flags_debug': [
+ '-g',
+ ],
+ 'compiler_flags_devel': [
+ '-g',
+ ],
+ 'compiler_flags_qa': [
+ '-gline-tables-only',
+ ],
+ 'compiler_flags_gold': [
+ '-gline-tables-only',
+ ],
+ }],
+ ],
+ },
+
+ 'target_defaults': {
+ 'target_conditions': [
+ ['sb_pedantic_warnings==1', {
+ 'cflags': [
+ '-Wall',
+ '-Wextra',
+ '-Wunreachable-code',
+ # Don't get pedantic about warnings from base macros. These must be
+ # disabled after the -Wall above, so this has to be done here rather
+ # than in the platform's target toolchain.
+ # TODO: Rebase base and use static_assert instead of COMPILE_ASSERT
+ '-Wno-unused-local-typedef', # COMPILE_ASSERT
+ '-Wno-missing-field-initializers', # LAZY_INSTANCE_INITIALIZER
+ ],
+ }],
+ ['_type=="executable"', {
+ # Android Lollipop+ requires relocatable executables.
+ 'cflags': [
+ '-fPIE',
+ ],
+ 'ldflags': [
+ '-pie',
+ ],
+ },{
+ # Android requires relocatable shared libraries.
+ 'cflags': [
+ '-fPIC',
+ ],
+ }],
+ ['use_asan==1', {
+ 'cflags': [
+ '-fsanitize=address',
+ '-fno-omit-frame-pointer',
+ ],
+ 'ldflags': [
+ '-fsanitize=address',
+ # Force linking of the helpers in sanitizer_options.cc
+ '-Wl,-u_sanitizer_options_link_helper',
+ ],
+ 'defines': [
+ 'ADDRESS_SANITIZER',
+ ],
+ }],
+ ['use_tsan==1', {
+ 'cflags': [
+ '-fsanitize=thread',
+ '-fno-omit-frame-pointer',
+ ],
+ 'ldflags': [
+ '-fsanitize=thread',
+ ],
+ 'defines': [
+ 'THREAD_SANITIZER',
+ ],
+ }],
+ ],
+ }, # end of target_defaults
+}
diff --git a/src/starboard/android/shared/gyp_configuration.py b/src/starboard/android/shared/gyp_configuration.py
new file mode 100644
index 0000000..5c78367
--- /dev/null
+++ b/src/starboard/android/shared/gyp_configuration.py
@@ -0,0 +1,302 @@
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Starboard Android shared platform configuration for gyp_cobalt."""
+
+from __future__ import print_function
+
+import imp
+import os
+from subprocess import call
+
+import gyp_utils
+import starboard.android.shared.sdk_utils as sdk_utils
+from starboard.build.platform_configuration import PlatformConfiguration
+from starboard.tools.testing import test_filter
+from starboard.tools.toolchain import ar
+from starboard.tools.toolchain import bash
+from starboard.tools.toolchain import clang
+from starboard.tools.toolchain import clangxx
+from starboard.tools.toolchain import cp
+from starboard.tools.toolchain import touch
+
+_APK_DIR = os.path.join(os.path.dirname(__file__), os.path.pardir, 'apk')
+_APK_BUILD_ID_FILE = os.path.join(_APK_DIR, 'build.id')
+_COBALT_GRADLE = os.path.join(_APK_DIR, 'cobalt-gradle.sh')
+
+# Maps the Android ABI to the name of the toolchain.
+_ABI_TOOLCHAIN_NAME = {
+ 'x86': 'i686-linux-android',
+ 'armeabi': 'arm-linux-androideabi',
+ 'armeabi-v7a': 'arm-linux-androideabi',
+ 'arm64-v8a': 'aarch64-linux-android',
+}
+
+
+class AndroidConfiguration(PlatformConfiguration):
+ """Starboard Android platform configuration."""
+
+ # TODO: make ASAN work with NDK tools and enable it by default
+ def __init__(self, platform, android_abi, asan_enabled_by_default=False):
+ super(AndroidConfiguration, self).__init__(platform,
+ asan_enabled_by_default)
+ self._target_toolchain = None
+ self._host_toolchain = None
+
+ self.AppendApplicationConfigurationPath(os.path.dirname(__file__))
+
+ self.android_abi = android_abi
+
+ self.host_compiler_environment = gyp_utils.GetHostCompilerEnvironment()
+ self.android_home = sdk_utils.GetSdkPath()
+ self.android_ndk_home = sdk_utils.GetNdkPath()
+
+ print('Using Android SDK at {}'.format(self.android_home))
+ print('Using Android NDK at {}'.format(self.android_ndk_home))
+
+ def GetBuildFormat(self):
+ """Returns the desired build format."""
+ # The comma means that ninja and qtcreator_ninja will be chained and use the
+ # same input information so that .gyp files will only have to be parsed
+ # once.
+ return 'ninja,qtcreator_ninja'
+
+ def GetVariables(self, configuration):
+ variables = super(AndroidConfiguration, self).GetVariables(
+ configuration, use_clang=1)
+ variables.update({
+ 'ANDROID_HOME':
+ self.android_home,
+ 'NDK_HOME':
+ self.android_ndk_home,
+ 'ANDROID_ABI':
+ self.android_abi,
+ 'include_path_platform_deploy_gypi':
+ 'starboard/android/shared/platform_deploy.gypi',
+ 'javascript_engine':
+ 'v8',
+ 'cobalt_enable_jit':
+ 1,
+ })
+ return variables
+
+ def GetGeneratorVariables(self, configuration):
+ _ = configuration
+ generator_variables = {
+ 'qtcreator_session_name_prefix': 'cobalt',
+ }
+ return generator_variables
+
+ def GetEnvironmentVariables(self):
+ sdk_utils.InstallSdkIfNeeded(self.android_abi)
+ call([_COBALT_GRADLE, '--reset'])
+ with open(_APK_BUILD_ID_FILE, 'w') as build_id_file:
+ build_id_file.write('{}'.format(gyp_utils.GetBuildNumber()))
+
+ env_variables = {}
+
+ # Android builds tend to consume significantly more memory than the
+ # default settings permit, so cap this at 1 in order to avoid build
+ # issues. Without this, 32GB machines end up getting automatically
+ # configured to run 5 at a time, which can be too much for at least
+ # android-arm64_debug.
+ # TODO: Eventually replace this with something more robust, like an
+ # implementation of the abstract toolchain for Android.
+ env_variables.update({'GYP_LINK_CONCURRENCY': '1'})
+
+ return env_variables
+
+ def GetTargetToolchain(self):
+ if not self._target_toolchain:
+ tool_prefix = os.path.join(
+ sdk_utils.GetToolsPath(self.android_abi), 'bin',
+ _ABI_TOOLCHAIN_NAME[self.android_abi] + '-')
+ cc_path = tool_prefix + 'clang'
+ cxx_path = tool_prefix + 'clang++'
+ ar_path = tool_prefix + 'ar'
+ clang_flags = [
+ # We'll pretend not to be Linux, but Starboard instead.
+ '-U__linux__',
+
+ # libwebp uses the cpufeatures library to detect ARM NEON support
+ '-I{}/sources/android/cpufeatures'.format(self.android_ndk_home),
+
+ # Mimic build/cmake/android.toolchain.cmake in the Android NDK.
+ '-ffunction-sections',
+ '-funwind-tables',
+ '-fstack-protector-strong',
+ '-no-canonical-prefixes',
+
+ # Other flags
+ '-fsigned-char',
+ '-fno-limit-debug-info',
+ '-fno-exceptions',
+ '-fcolor-diagnostics',
+ '-fno-strict-aliasing', # See http://crbug.com/32204
+
+ # Default visibility is hidden to enable dead stripping.
+ '-fvisibility=hidden',
+ # Any warning will stop the build.
+ '-Werror',
+
+ # Disable errors for the warning till the Android NDK r19 is fixed.
+ # The warning is trigger when compiling .c files and complains for
+ # '-stdlib=libc++' which is added by the NDK.
+ '-Wno-error=unused-command-line-argument',
+
+ # Don't warn about register variables (in base and net)
+ '-Wno-deprecated-register',
+ # Don't warn about deprecated ICU methods (in googleurl and net)
+ '-Wno-deprecated-declarations',
+ # Skia doesn't use overrides.
+ '-Wno-inconsistent-missing-override',
+ # shifting a negative signed value is undefined
+ '-Wno-shift-negative-value',
+ # Don't warn for implicit sign conversions. (in v8 and protobuf)
+ '-Wno-sign-conversion',
+ ]
+ clang_defines = [
+ # Enable compile-time decisions based on the ABI
+ 'ANDROID_ABI={}'.format(self.android_abi),
+ # -DANDROID is an argument to some ifdefs in the NDK's eglplatform.h
+ 'ANDROID',
+ # Cobalt on Linux flag
+ 'COBALT_LINUX',
+ # So that we get the PRI* macros from inttypes.h
+ '__STDC_FORMAT_MACROS',
+ # Enable GNU extensions to get prototypes like ffsl.
+ '_GNU_SOURCE=1',
+ # Undefining __linux__ causes the system headers to make wrong
+ # assumptions about which C-library is used on the platform.
+ '__BIONIC__',
+ # Undefining __linux__ leaves libc++ without a threads implementation.
+ # TODO: See if there's a way to make libcpp threading use Starboard.
+ '_LIBCPP_HAS_THREAD_API_PTHREAD',
+ ]
+ linker_flags = [
+ # Use the static LLVM libc++.
+ '-static-libstdc++',
+
+ # Mimic build/cmake/android.toolchain.cmake in the Android NDK.
+ '-Wl,--build-id',
+ '-Wl,--warn-shared-textrel',
+ '-Wl,--fatal-warnings',
+ '-Wl,--gc-sections',
+ '-Wl,-z,nocopyreloc',
+
+ # Wrapper synchronizes punch-out video bounds with the UI frame.
+ '-Wl,--wrap=eglSwapBuffers',
+ ]
+ self._target_toolchain = [
+ clang.CCompiler(
+ path=cc_path, defines=clang_defines, extra_flags=clang_flags),
+ clang.CxxCompiler(
+ path=cxx_path,
+ defines=clang_defines,
+ extra_flags=clang_flags + [
+ '-std=c++11',
+ ]),
+ clang.AssemblerWithCPreprocessor(
+ path=cc_path, defines=clang_defines, extra_flags=clang_flags),
+ ar.StaticThinLinker(path=ar_path),
+ ar.StaticLinker(path=ar_path),
+ clangxx.SharedLibraryLinker(path=cxx_path, extra_flags=linker_flags),
+ clangxx.ExecutableLinker(path=cxx_path, extra_flags=linker_flags),
+ ]
+ return self._target_toolchain
+
+ def GetHostToolchain(self):
+ if not self._host_toolchain:
+ cc_path = self.host_compiler_environment['CC_host'],
+ cxx_path = self.host_compiler_environment['CXX_host']
+ self._host_toolchain = [
+ clang.CCompiler(path=cc_path),
+ clang.CxxCompiler(path=cxx_path),
+ clang.AssemblerWithCPreprocessor(path=cc_path),
+ ar.StaticThinLinker(),
+ ar.StaticLinker(),
+ clangxx.ExecutableLinker(path=cxx_path),
+ clangxx.SharedLibraryLinker(path=cxx_path),
+ cp.Copy(),
+ touch.Stamp(),
+ bash.Shell(),
+ ]
+ return self._host_toolchain
+
+ def GetLauncher(self):
+ """Gets the module used to launch applications on this platform."""
+ module_path = os.path.abspath(
+ os.path.join(os.path.dirname(__file__), 'launcher.py'))
+ launcher_module = imp.load_source('launcher', module_path)
+ return launcher_module
+
+ def GetTestFilters(self):
+ filters = super(AndroidConfiguration, self).GetTestFilters()
+ for target, tests in self._FILTERED_TESTS.iteritems():
+ filters.extend(test_filter.TestFilter(target, test) for test in tests)
+ return filters
+
+ # A map of failing or crashing tests per target.
+ _FILTERED_TESTS = {
+ 'nplb': [
+ 'SbAudioSinkTest.AllFramesConsumed',
+ 'SbAudioSinkTest.SomeFramesConsumed',
+ 'SbAudioSinkTest.Underflow',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest'
+ '.SunnyDayDestination/0',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest'
+ '.SunnyDaySourceForDestination/0',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest'
+ '.SunnyDaySourceForDestination/1',
+ 'SbSocketAddressTypes/SbSocketGetInterfaceAddressTest'
+ '.SunnyDaySourceNotLoopback/0',
+ 'SbSocketBindTest.SunnyDayLocalInterface',
+ 'SbSocketGetLocalAddressTest.SunnyDayBoundSpecified',
+ 'SpeechRecognizerTest.StartIsCalledMultipleTimes',
+ 'SpeechRecognizerTest.StartRecognizerWith10MaxAlternatives',
+ 'SpeechRecognizerTest.StartRecognizerWithContinuousRecognition',
+ 'SpeechRecognizerTest.StartRecognizerWithInterimResults',
+ 'SpeechRecognizerTest.StartTestSunnyDay',
+ ],
+ 'player_filter_tests': [
+ 'AudioDecoderTests/AudioDecoderTest.EndOfStreamWithoutAnyInput/0',
+ 'AudioDecoderTests/AudioDecoderTest.ResetBeforeInput/0',
+ 'AudioDecoderTests/AudioDecoderTest.SingleInput/0',
+ 'VideoDecoderTests/VideoDecoderTest.DecodeFullGOP/0',
+ 'VideoDecoderTests/VideoDecoderTest.DecodeFullGOP/1',
+ 'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/0',
+ 'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/1',
+ 'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/2',
+ 'VideoDecoderTests/VideoDecoderTest.EndOfStreamWithoutAnyInput/3',
+ 'VideoDecoderTests/VideoDecoderTest'
+ '.GetCurrentDecodeTargetBeforeWriteInputBuffer/0',
+ 'VideoDecoderTests/VideoDecoderTest'
+ '.GetCurrentDecodeTargetBeforeWriteInputBuffer/2',
+ 'VideoDecoderTests/VideoDecoderTest.HoldFramesUntilFull/0',
+ 'VideoDecoderTests/VideoDecoderTest.HoldFramesUntilFull/1',
+
+ # On some platforms, and for some decoders (such as AVC), Android
+ # returns MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER for the test's
+ # invalid input frame instead of signaling an error, which the test is
+ # looking for.
+ 'VideoDecoderTests/VideoDecoderTest.SingleInvalidInput/0',
+ 'VideoDecoderTests/VideoDecoderTest.SingleInvalidInput/1',
+
+ # Android currently does not support multi-video playback, which
+ # the following tests depend upon.
+ 'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/0',
+ 'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/1',
+ 'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/2',
+ 'VideoDecoderTests/VideoDecoderTest.ThreeMoreDecoders/3',
+ ],
+ }
diff --git a/src/starboard/android/shared/input_events_generator.cc b/src/starboard/android/shared/input_events_generator.cc
new file mode 100644
index 0000000..f307963
--- /dev/null
+++ b/src/starboard/android/shared/input_events_generator.cc
@@ -0,0 +1,970 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/input_events_generator.h"
+
+#include <android/input.h>
+#include <android/keycodes.h>
+#include <jni.h>
+#include <math.h>
+
+#include "starboard/android/shared/application_android.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/double.h"
+#include "starboard/key.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+using ::starboard::shared::starboard::Application;
+typedef ::starboard::android::shared::InputEventsGenerator::Event Event;
+typedef ::starboard::android::shared::InputEventsGenerator::Events Events;
+
+namespace {
+
+SbKeyLocation AInputEventToSbKeyLocation(AInputEvent *event) {
+ int32_t keycode = AKeyEvent_getKeyCode(event);
+ switch (keycode) {
+ case AKEYCODE_ALT_LEFT:
+ case AKEYCODE_CTRL_LEFT:
+ case AKEYCODE_META_LEFT:
+ case AKEYCODE_SHIFT_LEFT:
+ return kSbKeyLocationLeft;
+ case AKEYCODE_ALT_RIGHT:
+ case AKEYCODE_CTRL_RIGHT:
+ case AKEYCODE_META_RIGHT:
+ case AKEYCODE_SHIFT_RIGHT:
+ return kSbKeyLocationRight;
+ }
+ return kSbKeyLocationUnspecified;
+}
+
+unsigned int AInputEventToSbModifiers(AInputEvent *event) {
+ int32_t meta = AKeyEvent_getMetaState(event);
+ unsigned int modifiers = kSbKeyModifiersNone;
+ if (meta & AMETA_ALT_ON) {
+ modifiers |= kSbKeyModifiersAlt;
+ }
+ if (meta & AMETA_CTRL_ON) {
+ modifiers |= kSbKeyModifiersCtrl;
+ }
+ if (meta & AMETA_META_ON) {
+ modifiers |= kSbKeyModifiersMeta;
+ }
+ if (meta & AMETA_SHIFT_ON) {
+ modifiers |= kSbKeyModifiersShift;
+ }
+ return modifiers;
+}
+
+std::unique_ptr<Event> CreateMoveEventWithKey(
+ int32_t device_id,
+ SbWindow window,
+ SbKey key,
+ SbKeyLocation location,
+ const SbInputVector& input_vector) {
+ std::unique_ptr<SbInputData> data(new SbInputData());
+ SbMemorySet(data.get(), 0, sizeof(*data));
+
+ // window
+ data->window = window;
+ data->type = kSbInputEventTypeMove;
+ data->device_type = kSbInputDeviceTypeGamepad;
+ data->device_id = device_id;
+
+ // key
+ data->key = key;
+ data->key_location = location;
+ data->key_modifiers = kSbKeyModifiersNone;
+ data->position = input_vector;
+
+ return std::unique_ptr<Event>(
+ new Application::Event(kSbEventTypeInput, data.release(),
+ &Application::DeleteDestructor<SbInputData>));
+}
+
+float GetFlat(jobject input_device, int axis) {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> motion_range(env->CallObjectMethodOrAbort(
+ input_device, "getMotionRange",
+ "(I)Landroid/view/InputDevice$MotionRange;", axis));
+
+ float flat = env->CallFloatMethodOrAbort(
+ motion_range.Get(), "getFlat", "()F");
+
+ SB_DCHECK(flat < 1.0f);
+ return flat;
+}
+
+bool IsDPadKey(SbKey key) {
+ return key == kSbKeyGamepadDPadUp || key == kSbKeyGamepadDPadDown ||
+ key == kSbKeyGamepadDPadLeft || key == kSbKeyGamepadDPadRight;
+}
+
+SbKey AInputEventToSbKey(AInputEvent* event) {
+ int32_t keycode = AKeyEvent_getKeyCode(event);
+ switch (keycode) {
+ // Modifiers
+ case AKEYCODE_ALT_LEFT:
+ case AKEYCODE_ALT_RIGHT:
+ case AKEYCODE_MENU:
+ return kSbKeyMenu;
+ case AKEYCODE_CTRL_LEFT:
+ case AKEYCODE_CTRL_RIGHT:
+ return kSbKeyControl;
+ case AKEYCODE_META_LEFT:
+ return kSbKeyLwin;
+ case AKEYCODE_META_RIGHT:
+ return kSbKeyRwin;
+ case AKEYCODE_SHIFT_LEFT:
+ case AKEYCODE_SHIFT_RIGHT:
+ return kSbKeyShift;
+ case AKEYCODE_CAPS_LOCK:
+ return kSbKeyCapital;
+ case AKEYCODE_NUM_LOCK:
+ return kSbKeyNumlock;
+ case AKEYCODE_SCROLL_LOCK:
+ return kSbKeyScroll;
+
+ // System functions
+ case AKEYCODE_SLEEP:
+ return kSbKeySleep;
+ case AKEYCODE_HELP:
+ return kSbKeyHelp;
+
+ // Navigation
+ case AKEYCODE_BACK: // Android back button, not backspace
+ case AKEYCODE_ESCAPE:
+ return kSbKeyEscape;
+
+ // Enter/Select
+ case AKEYCODE_ENTER:
+ case AKEYCODE_NUMPAD_ENTER:
+ return kSbKeyReturn;
+
+ // Focus movement
+ case AKEYCODE_PAGE_UP:
+ return kSbKeyPrior;
+ case AKEYCODE_PAGE_DOWN:
+ return kSbKeyNext;
+ case AKEYCODE_MOVE_HOME:
+ return kSbKeyHome;
+ case AKEYCODE_MOVE_END:
+ return kSbKeyEnd;
+
+ // D-pad
+ case AKEYCODE_DPAD_UP:
+ return kSbKeyGamepadDPadUp;
+ case AKEYCODE_DPAD_DOWN:
+ return kSbKeyGamepadDPadDown;
+ case AKEYCODE_DPAD_LEFT:
+ return kSbKeyGamepadDPadLeft;
+ case AKEYCODE_DPAD_RIGHT:
+ return kSbKeyGamepadDPadRight;
+ case AKEYCODE_DPAD_CENTER:
+ return kSbKeyGamepad1;
+
+ // Game controller
+ case AKEYCODE_BUTTON_A:
+ return kSbKeyGamepad1;
+ case AKEYCODE_BUTTON_B:
+ return kSbKeyGamepad2;
+ case AKEYCODE_BUTTON_C:
+ return kSbKeyUnknown;
+ case AKEYCODE_BUTTON_X:
+ return kSbKeyGamepad3;
+ case AKEYCODE_BUTTON_Y:
+ return kSbKeyGamepad4;
+ case AKEYCODE_BUTTON_L1:
+ return kSbKeyGamepadLeftBumper;
+ case AKEYCODE_BUTTON_R1:
+ return kSbKeyGamepadRightBumper;
+ case AKEYCODE_BUTTON_L2:
+ return kSbKeyGamepadLeftTrigger;
+ case AKEYCODE_BUTTON_R2:
+ return kSbKeyGamepadRightTrigger;
+ case AKEYCODE_BUTTON_THUMBL:
+ return kSbKeyGamepadLeftStick;
+ case AKEYCODE_BUTTON_THUMBR:
+ return kSbKeyGamepadRightStick;
+ case AKEYCODE_BUTTON_START:
+ return kSbKeyGamepad6;
+ case AKEYCODE_BUTTON_SELECT:
+ return kSbKeyGamepad5;
+ case AKEYCODE_BUTTON_MODE:
+ return kSbKeyModechange;
+
+ // Media transport
+ case AKEYCODE_MEDIA_PLAY_PAUSE:
+ return kSbKeyMediaPlayPause;
+ case AKEYCODE_MEDIA_PLAY:
+ return kSbKeyPlay;
+ case AKEYCODE_MEDIA_PAUSE:
+ return kSbKeyPause;
+ case AKEYCODE_MEDIA_STOP:
+ return kSbKeyMediaStop;
+ case AKEYCODE_MEDIA_NEXT:
+ return kSbKeyMediaNextTrack;
+ case AKEYCODE_MEDIA_PREVIOUS:
+ return kSbKeyMediaPrevTrack;
+ case AKEYCODE_MEDIA_REWIND:
+ return kSbKeyMediaRewind;
+ case AKEYCODE_MEDIA_FAST_FORWARD:
+ return kSbKeyMediaFastForward;
+
+#if SB_API_VERSION >= 6
+ // TV Remote specific
+ case AKEYCODE_CHANNEL_UP:
+ return kSbKeyChannelUp;
+ case AKEYCODE_CHANNEL_DOWN:
+ return kSbKeyChannelDown;
+ case AKEYCODE_CAPTIONS:
+ return kSbKeyClosedCaption;
+ case AKEYCODE_INFO:
+ return kSbKeyInfo;
+ case AKEYCODE_GUIDE:
+ return kSbKeyGuide;
+ case AKEYCODE_LAST_CHANNEL:
+ return kSbKeyLast;
+ case AKEYCODE_MEDIA_AUDIO_TRACK:
+ return kSbKeyMediaAudioTrack;
+
+ case AKEYCODE_PROG_RED:
+ return kSbKeyRed;
+ case AKEYCODE_PROG_GREEN:
+ return kSbKeyGreen;
+ case AKEYCODE_PROG_YELLOW:
+ return kSbKeyYellow;
+ case AKEYCODE_PROG_BLUE:
+ return kSbKeyBlue;
+#endif // SB_API_VERSION >= 6
+
+ // Whitespace
+ case AKEYCODE_TAB:
+ return kSbKeyTab;
+ case AKEYCODE_SPACE:
+ return kSbKeySpace;
+
+ // Deletion
+ case AKEYCODE_DEL: // Backspace
+ return kSbKeyBack;
+ case AKEYCODE_FORWARD_DEL:
+ return kSbKeyDelete;
+ case AKEYCODE_CLEAR:
+ return kSbKeyClear;
+
+ // Insert
+ case AKEYCODE_INSERT:
+ return kSbKeyInsert;
+
+ // Symbols
+ case AKEYCODE_NUMPAD_ADD:
+ return kSbKeyAdd;
+ case AKEYCODE_PLUS:
+ case AKEYCODE_EQUALS:
+ case AKEYCODE_NUMPAD_EQUALS:
+ return kSbKeyOemPlus;
+ case AKEYCODE_NUMPAD_SUBTRACT:
+ return kSbKeySubtract;
+ case AKEYCODE_MINUS:
+ return kSbKeyOemMinus;
+ case AKEYCODE_NUMPAD_MULTIPLY:
+ return kSbKeyMultiply;
+ case AKEYCODE_NUMPAD_DIVIDE:
+ return kSbKeyDivide;
+ case AKEYCODE_COMMA:
+ case AKEYCODE_NUMPAD_COMMA:
+ return kSbKeyOemComma;
+ case AKEYCODE_NUMPAD_DOT:
+ return kSbKeyDecimal;
+ case AKEYCODE_PERIOD:
+ return kSbKeyOemPeriod;
+ case AKEYCODE_SEMICOLON:
+ return kSbKeyOem1;
+ case AKEYCODE_SLASH:
+ return kSbKeyOem2;
+ case AKEYCODE_GRAVE:
+ return kSbKeyOem3;
+ case AKEYCODE_LEFT_BRACKET:
+ return kSbKeyOem4;
+ case AKEYCODE_BACKSLASH:
+ return kSbKeyOem5;
+ case AKEYCODE_RIGHT_BRACKET:
+ return kSbKeyOem6;
+ case AKEYCODE_APOSTROPHE:
+ return kSbKeyOem7;
+
+ // Function keys
+ case AKEYCODE_F1:
+ case AKEYCODE_F2:
+ case AKEYCODE_F3:
+ case AKEYCODE_F4:
+ case AKEYCODE_F5:
+ case AKEYCODE_F6:
+ case AKEYCODE_F7:
+ case AKEYCODE_F8:
+ case AKEYCODE_F9:
+ case AKEYCODE_F10:
+ case AKEYCODE_F11:
+ case AKEYCODE_F12:
+ return static_cast<SbKey>(kSbKeyF1 + (keycode - AKEYCODE_F1));
+
+ // Digits
+ case AKEYCODE_0:
+ case AKEYCODE_1:
+ case AKEYCODE_2:
+ case AKEYCODE_3:
+ case AKEYCODE_4:
+ case AKEYCODE_5:
+ case AKEYCODE_6:
+ case AKEYCODE_7:
+ case AKEYCODE_8:
+ case AKEYCODE_9:
+ return static_cast<SbKey>(kSbKey0 + (keycode - AKEYCODE_0));
+
+ // Numpad digits
+ case AKEYCODE_NUMPAD_0:
+ case AKEYCODE_NUMPAD_1:
+ case AKEYCODE_NUMPAD_2:
+ case AKEYCODE_NUMPAD_3:
+ case AKEYCODE_NUMPAD_4:
+ case AKEYCODE_NUMPAD_5:
+ case AKEYCODE_NUMPAD_6:
+ case AKEYCODE_NUMPAD_7:
+ case AKEYCODE_NUMPAD_8:
+ case AKEYCODE_NUMPAD_9:
+ return static_cast<SbKey>(kSbKeyNumpad0 + (keycode - AKEYCODE_NUMPAD_0));
+
+ // Alphabetic
+ case AKEYCODE_A:
+ case AKEYCODE_B:
+ case AKEYCODE_C:
+ case AKEYCODE_D:
+ case AKEYCODE_E:
+ case AKEYCODE_F:
+ case AKEYCODE_G:
+ case AKEYCODE_H:
+ case AKEYCODE_I:
+ case AKEYCODE_J:
+ case AKEYCODE_K:
+ case AKEYCODE_L:
+ case AKEYCODE_M:
+ case AKEYCODE_N:
+ case AKEYCODE_O:
+ case AKEYCODE_P:
+ case AKEYCODE_Q:
+ case AKEYCODE_R:
+ case AKEYCODE_S:
+ case AKEYCODE_T:
+ case AKEYCODE_U:
+ case AKEYCODE_V:
+ case AKEYCODE_W:
+ case AKEYCODE_X:
+ case AKEYCODE_Y:
+ case AKEYCODE_Z:
+ return static_cast<SbKey>(kSbKeyA + (keycode - AKEYCODE_A));
+
+ // Don't handle these keys so the OS can in a uniform manner.
+ case AKEYCODE_VOLUME_UP:
+ case AKEYCODE_VOLUME_DOWN:
+ case AKEYCODE_MUTE:
+ case AKEYCODE_BRIGHTNESS_UP:
+ case AKEYCODE_BRIGHTNESS_DOWN:
+ case AKEYCODE_SEARCH:
+ default:
+ return kSbKeyUnknown;
+ }
+}
+
+} // namespace
+
+InputEventsGenerator::InputEventsGenerator(SbWindow window)
+ : window_(window),
+ hat_value_(),
+ left_thumbstick_key_pressed_{kSbKeyUnknown, kSbKeyUnknown} {
+ SB_DCHECK(SbWindowIsValid(window_));
+}
+
+InputEventsGenerator::~InputEventsGenerator() {}
+
+// For a left joystick, AMOTION_EVENT_AXIS_X reports the absolute X position of
+// the joystick. The value is normalized to a range from -1.0 (left) to 1.0
+// (right).
+//
+// For a left joystick, AMOTION_EVENT_AXIS_Y reports the absolute Y position of
+// the joystick. The value is normalized to a range from -1.0 (up or far) to 1.0
+// (down or near).
+//
+// On game pads with two analog joysticks, AMOTION_EVENT_AXIS_Z is often
+// reinterpreted to report the absolute X position of the second joystick.
+//
+// On game pads with two analog joysticks, AMOTION_EVENT_AXIS_RZ is often
+// reinterpreted to report the absolute Y position of the second joystick.
+void InputEventsGenerator::ProcessJoyStickEvent(FlatAxis axis,
+ int32_t motion_axis,
+ AInputEvent* android_event,
+ Events* events) {
+ SB_DCHECK(AMotionEvent_getPointerCount(android_event) > 0);
+
+ int32_t device_id = AInputEvent_getDeviceId(android_event);
+ SB_DCHECK(device_flat_.find(device_id) != device_flat_.end());
+
+ float flat = device_flat_[device_id][axis];
+ float offset = AMotionEvent_getAxisValue(android_event, motion_axis, 0);
+ int sign = offset < 0.0f ? -1 : 1;
+
+ if (SbDoubleAbsolute(offset) < flat) {
+ offset = sign * flat;
+ }
+ // Rescaled the range:
+ // [-1.0f, -flat] to [-1.0f, 0.0f] and [flat, 1.0f] to [0.0f, 1.0f]
+ offset = (offset - sign * flat) / (1 - flat);
+
+ // Report up and left as negative values.
+ SbInputVector input_vector;
+ SbKey key = kSbKeyUnknown;
+ SbKeyLocation location = kSbKeyLocationUnspecified;
+ switch (axis) {
+ case kLeftX: {
+ input_vector.x = offset;
+ input_vector.y = 0.0f;
+ key = kSbKeyGamepadLeftStickLeft;
+ location = kSbKeyLocationLeft;
+ break;
+ }
+ case kLeftY: {
+ input_vector.x = 0.0f;
+ input_vector.y = offset;
+ key = kSbKeyGamepadLeftStickUp;
+ location = kSbKeyLocationLeft;
+ break;
+ }
+ case kRightX: {
+ input_vector.x = offset;
+ input_vector.y = 0.0f;
+ key = kSbKeyGamepadRightStickLeft;
+ location = kSbKeyLocationRight;
+ break;
+ }
+ case kRightY: {
+ input_vector.x = 0.0f;
+ input_vector.y = offset;
+ key = kSbKeyGamepadRightStickUp;
+ location = kSbKeyLocationRight;
+ break;
+ }
+ default:
+ SB_NOTREACHED();
+ }
+
+ events->push_back(
+ CreateMoveEventWithKey(device_id, window_, key, location, input_vector));
+}
+
+namespace {
+
+// Generate a Starboard event from an Android event, with the SbKey and
+// SbInputEventType pre-specified (so that it can be used by event
+// synthesization as well.)
+void PushKeyEvent(SbKey key,
+ SbInputEventType type,
+ SbWindow window,
+ AInputEvent* android_event,
+ Events* events) {
+ if (key == kSbKeyUnknown) {
+ SB_NOTREACHED();
+ return;
+ }
+
+ std::unique_ptr<SbInputData> data(new SbInputData());
+ SbMemorySet(data.get(), 0, sizeof(*data));
+
+ // window
+ data->window = window;
+ data->type = type;
+
+ // device
+ // TODO: differentiate gamepad, remote, etc.
+ data->device_type = kSbInputDeviceTypeKeyboard;
+ data->device_id = AInputEvent_getDeviceId(android_event);
+
+ // key
+ data->key = key;
+ data->key_location = AInputEventToSbKeyLocation(android_event);
+ data->key_modifiers = AInputEventToSbModifiers(android_event);
+
+ std::unique_ptr<Event> event(
+ new Event(kSbEventTypeInput, data.release(),
+ &Application::DeleteDestructor<SbInputData>));
+ events->push_back(std::move(event));
+}
+
+// Some helper enumerations to index into the InputEventsGenerator::hat_value_
+// array.
+enum HatAxis {
+ kHatX,
+ kHatY,
+};
+
+struct HatValue {
+ HatAxis axis;
+ float value;
+};
+
+// Converts Starboard DPad direction keys to Starboard left thumbstick
+// direction keys.
+SbKey ConvertDPadKeyToThumbstickKey(SbKey key) {
+ switch (key) {
+ case kSbKeyGamepadDPadUp:
+ return kSbKeyGamepadLeftStickUp;
+ case kSbKeyGamepadDPadDown:
+ return kSbKeyGamepadLeftStickDown;
+ case kSbKeyGamepadDPadLeft:
+ return kSbKeyGamepadLeftStickLeft;
+ case kSbKeyGamepadDPadRight:
+ return kSbKeyGamepadLeftStickRight;
+ default: {
+ SB_NOTREACHED();
+ return kSbKeyUnknown;
+ }
+ }
+}
+
+// Convert a Starboard DPad direction key to a (axis, direction) pair.
+HatValue HatValueForDPadKey(SbKey key) {
+ SB_DCHECK(IsDPadKey(key));
+
+ switch (key) {
+ case kSbKeyGamepadDPadUp:
+ return HatValue({kHatY, -1.0f});
+ case kSbKeyGamepadDPadDown:
+ return HatValue({kHatY, 1.0f});
+ case kSbKeyGamepadDPadLeft:
+ return HatValue({kHatX, -1.0f});
+ case kSbKeyGamepadDPadRight:
+ return HatValue({kHatX, 1.0f});
+ default: {
+ SB_NOTREACHED();
+ return HatValue({kHatX, 0.0f});
+ }
+ }
+}
+
+// The inverse of HatValueForDPadKey().
+SbKey KeyForHatValue(const HatValue& hat_value) {
+ SB_DCHECK(hat_value.value > 0.5f || hat_value.value < -0.5f);
+ if (hat_value.axis == kHatX) {
+ if (hat_value.value > 0.5f) {
+ return kSbKeyGamepadDPadRight;
+ } else {
+ return kSbKeyGamepadDPadLeft;
+ }
+ } else if (hat_value.axis == kHatY) {
+ if (hat_value.value > 0.5f) {
+ return kSbKeyGamepadDPadDown;
+ } else {
+ return kSbKeyGamepadDPadUp;
+ }
+ } else {
+ SB_NOTREACHED();
+ return kSbKeyUnknown;
+ }
+}
+
+// Analyzes old axis values and new axis values and fire off any synthesized
+// key press/unpress events as necessary.
+void PossiblySynthesizeHatKeyEvents(HatAxis axis,
+ float old_value,
+ float new_value,
+ SbWindow window,
+ AInputEvent* android_event,
+ Events* events) {
+ if (old_value == new_value) {
+ // No events to generate if the hat motion value did not change.
+ return;
+ }
+
+ if (old_value > 0.5f || old_value < -0.5f) {
+ PushKeyEvent(KeyForHatValue(HatValue({axis, old_value})),
+ kSbInputEventTypeUnpress, window, android_event, events);
+ }
+ if (new_value > 0.5f || new_value < -0.5f) {
+ PushKeyEvent(KeyForHatValue(HatValue({axis, new_value})),
+ kSbInputEventTypePress, window, android_event, events);
+ }
+}
+
+} // namespace
+
+bool InputEventsGenerator::ProcessKeyEvent(AInputEvent* android_event,
+ Events* events) {
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+ if (!input_events_filter_.ShouldProcessKeyEvent(android_event)) {
+ return false;
+ }
+#endif
+
+ SbInputEventType type;
+ switch (AKeyEvent_getAction(android_event)) {
+ case AKEY_EVENT_ACTION_DOWN:
+ type = kSbInputEventTypePress;
+ break;
+ case AKEY_EVENT_ACTION_UP:
+ type = kSbInputEventTypeUnpress;
+ break;
+ default:
+ // TODO: send multiple events for AKEY_EVENT_ACTION_MULTIPLE
+ return false;
+ }
+
+ SbKey key = AInputEventToSbKey(android_event);
+ if (key == kSbKeyUnknown) {
+ return false;
+ }
+
+ if (AKeyEvent_getFlags(android_event) & AKEY_EVENT_FLAG_FALLBACK &&
+ IsDPadKey(key)) {
+ // For fallback DPad keys, we flow into special processing to manage the
+ // differentiation between the actual DPad and the left thumbstick, since
+ // Android conflates the key down/up events for these inputs.
+ ProcessFallbackDPadEvent(type, key, android_event, events);
+ } else {
+ PushKeyEvent(key, type, window_, android_event, events);
+ }
+ return true;
+}
+
+namespace {
+
+SbKey ButtonStateToSbKey(int32_t button_state) {
+ if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
+ return kSbKeyMouse1;
+ } else if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
+ return kSbKeyMouse2;
+ } else if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) {
+ return kSbKeyMouse3;
+ } else if (button_state & AMOTION_EVENT_BUTTON_BACK) {
+ return kSbKeyBrowserBack;
+ } else if (button_state & AMOTION_EVENT_BUTTON_FORWARD) {
+ return kSbKeyBrowserForward;
+ }
+ return kSbKeyUnknown;
+}
+
+// Get an SbKeyModifiers from a button state
+unsigned int ButtonStateToSbModifiers(unsigned int button_state) {
+ unsigned int key_modifiers = kSbKeyModifiersNone;
+#if SB_API_VERSION >= 6
+ if (button_state & AMOTION_EVENT_BUTTON_PRIMARY) {
+ key_modifiers |= kSbKeyModifiersPointerButtonLeft;
+ }
+ if (button_state & AMOTION_EVENT_BUTTON_SECONDARY) {
+ key_modifiers |= kSbKeyModifiersPointerButtonMiddle;
+ }
+ if (button_state & AMOTION_EVENT_BUTTON_TERTIARY) {
+ key_modifiers |= kSbKeyModifiersPointerButtonRight;
+ }
+ if (button_state & AMOTION_EVENT_BUTTON_BACK) {
+ key_modifiers |= kSbKeyModifiersPointerButtonBack;
+ }
+ if (button_state & AMOTION_EVENT_BUTTON_FORWARD) {
+ key_modifiers |= kSbKeyModifiersPointerButtonForward;
+ }
+#endif
+ return key_modifiers;
+}
+
+#if SB_API_VERSION < 6
+SbKey ScrollAxisToKey(float hscroll, float vscroll) {
+ if (vscroll != 0) {
+ return vscroll < 0 ? kSbKeyDown : kSbKeyUp;
+ } else if (hscroll != 0) {
+ return hscroll > 0 ? kSbKeyLeft : kSbKeyRight;
+ }
+ return kSbKeyUnknown;
+}
+#endif
+
+} // namespace
+
+bool InputEventsGenerator::ProcessPointerEvent(AInputEvent* android_event,
+ Events* events) {
+ float offset_x =
+ AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_X, 0);
+ float offset_y =
+ AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_Y, 0);
+
+ std::unique_ptr<SbInputData> data(new SbInputData());
+ SbMemorySet(data.get(), 0, sizeof(*data));
+
+ data->window = window_;
+ SB_DCHECK(SbWindowIsValid(data->window));
+#if SB_API_VERSION >= 6
+ data->pressure = NAN;
+ data->size = {NAN, NAN};
+ data->tilt = {NAN, NAN};
+#endif
+ unsigned int button_state = AMotionEvent_getButtonState(android_event);
+ unsigned int button_modifiers = ButtonStateToSbModifiers(button_state);
+
+ // Default to reporting pointer events as mouse events.
+ data->device_type = kSbInputDeviceTypeMouse;
+
+ // Report both stylus and touchscreen events as touchscreen device events.
+ int32_t event_source = AInputEvent_getSource(android_event);
+ if (((event_source & AINPUT_SOURCE_TOUCHSCREEN) != 0) ||
+ ((event_source & AINPUT_SOURCE_STYLUS) != 0)) {
+ data->device_type = kSbInputDeviceTypeTouchScreen;
+ }
+
+ data->device_id = AInputEvent_getDeviceId(android_event);
+ data->key_modifiers =
+ button_modifiers | AInputEventToSbModifiers(android_event);
+ data->position.x = offset_x;
+ data->position.y = offset_y;
+ data->key = ButtonStateToSbKey(button_state);
+
+ switch (AKeyEvent_getAction(android_event) & AMOTION_EVENT_ACTION_MASK) {
+ case AMOTION_EVENT_ACTION_UP:
+ data->type = kSbInputEventTypeUnpress;
+ break;
+ case AMOTION_EVENT_ACTION_DOWN:
+ data->type = kSbInputEventTypePress;
+ break;
+ case AMOTION_EVENT_ACTION_MOVE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
+ data->type = kSbInputEventTypeMove;
+ break;
+ case AMOTION_EVENT_ACTION_SCROLL: {
+ float hscroll = AMotionEvent_getAxisValue(
+ android_event, AMOTION_EVENT_AXIS_HSCROLL, 0); // left is -1
+ float vscroll = AMotionEvent_getAxisValue(
+ android_event, AMOTION_EVENT_AXIS_VSCROLL, 0); // down is -1
+ float wheel =
+ AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_WHEEL, 0);
+#if SB_API_VERSION >= 6
+ data->type = kSbInputEventTypeWheel;
+ data->key = kSbKeyUnknown;
+ data->delta.y = -vscroll;
+ data->delta.x = hscroll;
+#else
+ // This version of Starboard does not support wheel event types, send
+ // keyboard event types instead.
+ data->device_type = kSbInputDeviceTypeKeyboard;
+ data->key = ScrollAxisToKey(hscroll, vscroll);
+
+ std::unique_ptr<SbInputData> data_press(new SbInputData());
+ SbMemoryCopy(data_press.get(), data.get(), sizeof(*data_press));
+
+ // Send a press and unpress event.
+ data_press->type = kSbInputEventTypePress;
+ events->push_back(std::unique_ptr<Event>(
+ new Application::Event(kSbEventTypeInput, data_press.release(),
+ &Application::DeleteDestructor<SbInputData>)));
+
+ data->type = kSbInputEventTypeUnpress;
+#endif
+ break;
+ }
+ default:
+ return false;
+ }
+
+ events->push_back(std::unique_ptr<Event>(
+ new Application::Event(kSbEventTypeInput, data.release(),
+ &Application::DeleteDestructor<SbInputData>)));
+ return true;
+}
+
+bool InputEventsGenerator::ProcessMotionEvent(AInputEvent* android_event,
+ Events* events) {
+ int32_t event_source = AInputEvent_getSource(android_event);
+ if ((event_source & AINPUT_SOURCE_CLASS_POINTER) != 0) {
+ return ProcessPointerEvent(android_event, events);
+ }
+ if ((event_source & AINPUT_SOURCE_JOYSTICK) == 0) {
+ // Only handles joystick events in the code below.
+ return false;
+ }
+
+ UpdateDeviceFlatMapIfNecessary(android_event);
+ ProcessJoyStickEvent(kLeftX, AMOTION_EVENT_AXIS_X, android_event, events);
+ ProcessJoyStickEvent(kLeftY, AMOTION_EVENT_AXIS_Y, android_event, events);
+ ProcessJoyStickEvent(kRightX, AMOTION_EVENT_AXIS_Z, android_event, events);
+ ProcessJoyStickEvent(kRightY, AMOTION_EVENT_AXIS_RZ, android_event, events);
+
+ // Remember the "hat" input values (dpad on the game controller) to help
+ // differentiate hat vs. stick fallback events.
+ UpdateHatValuesAndPossiblySynthesizeKeyEvents(android_event, events);
+
+ // Lie to Android and tell it that we did not process the motion event,
+ // causing Android to synthesize dpad key events for us. When we handle
+ // those synthesized key events we'll enqueue kSbKeyGamepadLeft rather
+ // than kSbKeyGamepadDPad events if they're from the joystick.
+ return false;
+}
+
+// Special processing to disambiguate between DPad events and left-thumbstick
+// direction key events.
+void InputEventsGenerator::ProcessFallbackDPadEvent(SbInputEventType type,
+ SbKey key,
+ AInputEvent* android_event,
+ Events* events) {
+ SB_DCHECK(AKeyEvent_getFlags(android_event) & AKEY_EVENT_FLAG_FALLBACK);
+ SB_DCHECK(IsDPadKey(key));
+
+ HatAxis hat_axis = HatValueForDPadKey(key).axis;
+
+ if (hat_value_[hat_axis] != 0.0f && type == kSbInputEventTypePress) {
+ // Direction pad events are all assumed to be coming from the hat controls
+ // if motion events for that hat DPAD is active, but we do still handle
+ // repeat keys here.
+ if (AKeyEvent_getRepeatCount(android_event) > 0) {
+ SB_LOG(INFO) << AKeyEvent_getRepeatCount(android_event);
+ PushKeyEvent(key, kSbInputEventTypePress, window_, android_event, events);
+ }
+ return;
+ }
+
+ // If we get this far, then we are exclusively dealing with thumbstick events,
+ // as actual DPad events are processed in motion events by checking the
+ // hat axis representing the DPad.
+ SbKey thumbstick_key = ConvertDPadKeyToThumbstickKey(key);
+
+ if (left_thumbstick_key_pressed_[hat_axis] != kSbKeyUnknown &&
+ (type == kSbInputEventTypeUnpress ||
+ left_thumbstick_key_pressed_[hat_axis] != thumbstick_key)) {
+ // Fire an unpressed event if our current key differs from the last seen
+ // key.
+ PushKeyEvent(left_thumbstick_key_pressed_[hat_axis],
+ kSbInputEventTypeUnpress, window_, android_event, events);
+ }
+
+ if (type == kSbInputEventTypePress) {
+ PushKeyEvent(thumbstick_key, kSbInputEventTypePress, window_,
+ android_event, events);
+ left_thumbstick_key_pressed_[hat_axis] = thumbstick_key;
+ } else if (type == kSbInputEventTypeUnpress) {
+ left_thumbstick_key_pressed_[hat_axis] = kSbKeyUnknown;
+ } else {
+ SB_NOTREACHED();
+ }
+}
+
+// Update |InputEventsGenerator::hat_value_| according to the incoming motion
+// event's data. Possibly generate DPad events based on any changes in value
+// here.
+void InputEventsGenerator::UpdateHatValuesAndPossiblySynthesizeKeyEvents(
+ AInputEvent* android_event,
+ Events* events) {
+ float new_hat_x =
+ AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_HAT_X, 0);
+ PossiblySynthesizeHatKeyEvents(kHatX, hat_value_[kHatX], new_hat_x, window_,
+ android_event, events);
+ hat_value_[kHatX] = new_hat_x;
+
+ float new_hat_y =
+ AMotionEvent_getAxisValue(android_event, AMOTION_EVENT_AXIS_HAT_Y, 0);
+ PossiblySynthesizeHatKeyEvents(kHatY, hat_value_[kHatY], new_hat_y, window_,
+ android_event, events);
+ hat_value_[kHatY] = new_hat_y;
+}
+
+void InputEventsGenerator::UpdateDeviceFlatMapIfNecessary(
+ AInputEvent* android_event) {
+ int32_t device_id = AInputEvent_getDeviceId(android_event);
+ if (device_flat_.find(device_id) != device_flat_.end()) {
+ // |device_flat_| is already contains the device flat information.
+ return;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> input_device(env->CallStaticObjectMethodOrAbort(
+ "android/view/InputDevice", "getDevice", "(I)Landroid/view/InputDevice;",
+ device_id));
+ float flats[kNumAxes] = {GetFlat(input_device.Get(), AMOTION_EVENT_AXIS_X),
+ GetFlat(input_device.Get(), AMOTION_EVENT_AXIS_Y),
+ GetFlat(input_device.Get(), AMOTION_EVENT_AXIS_Z),
+ GetFlat(input_device.Get(), AMOTION_EVENT_AXIS_RZ)};
+ device_flat_[device_id] = std::vector<float>(flats, flats + kNumAxes);
+}
+
+bool InputEventsGenerator::CreateInputEventsFromAndroidEvent(
+ AInputEvent* android_event,
+ Events* events) {
+ if (android_event == NULL ||
+ (AInputEvent_getType(android_event) != AINPUT_EVENT_TYPE_KEY &&
+ AInputEvent_getType(android_event) != AINPUT_EVENT_TYPE_MOTION)) {
+ return false;
+ }
+
+ switch (AInputEvent_getType(android_event)) {
+ case AINPUT_EVENT_TYPE_KEY:
+ return ProcessKeyEvent(android_event, events);
+ case AINPUT_EVENT_TYPE_MOTION: {
+ return ProcessMotionEvent(android_event, events);
+ }
+ default:
+ SB_NOTREACHED();
+ }
+
+ return false;
+}
+
+void InputEventsGenerator::CreateInputEventsFromSbKey(SbKey key,
+ Events* events) {
+ events->clear();
+
+ // Press event
+ std::unique_ptr<SbInputData> data(new SbInputData());
+ SbMemorySet(data.get(), 0, sizeof(*data));
+
+ data->window = window_;
+ data->type = kSbInputEventTypePress;
+
+ data->device_type = kSbInputDeviceTypeKeyboard;
+ data->device_id = 0;
+
+ data->key = key;
+ data->key_location = kSbKeyLocationUnspecified;
+ data->key_modifiers = kSbKeyModifiersNone;
+
+ events->push_back(std::unique_ptr<Event>(
+ new Application::Event(kSbEventTypeInput, data.release(),
+ &Application::DeleteDestructor<SbInputData>)));
+
+ // Unpress event
+ data.reset(new SbInputData());
+ SbMemorySet(data.get(), 0, sizeof(*data));
+
+ data->window = window_;
+ data->type = kSbInputEventTypeUnpress;
+
+ data->device_type = kSbInputDeviceTypeKeyboard;
+ data->device_id = 0;
+
+ data->key = key;
+ data->key_location = kSbKeyLocationUnspecified;
+ data->key_modifiers = kSbKeyModifiersNone;
+
+ events->push_back(std::unique_ptr<Event>(
+ new Application::Event(kSbEventTypeInput, data.release(),
+ &Application::DeleteDestructor<SbInputData>)));
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/input_events_generator.h b/src/starboard/android/shared/input_events_generator.h
new file mode 100644
index 0000000..a3f6aef
--- /dev/null
+++ b/src/starboard/android/shared/input_events_generator.h
@@ -0,0 +1,101 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_INPUT_EVENTS_GENERATOR_H_
+#define STARBOARD_ANDROID_SHARED_INPUT_EVENTS_GENERATOR_H_
+
+#include <android/input.h>
+#include <map>
+#include <memory>
+#include <vector>
+
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+#include "starboard/android/shared/input_events_filter.h"
+#endif
+
+#include "starboard/input.h"
+#include "starboard/shared/starboard/application.h"
+#include "starboard/window.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class InputEventsGenerator {
+ public:
+ typedef ::starboard::shared::starboard::Application::Event Event;
+ typedef std::vector<std::unique_ptr<Event> > Events;
+
+ explicit InputEventsGenerator(SbWindow window);
+ virtual ~InputEventsGenerator();
+
+ // Translates an Android input event into a series of Starboard application
+ // events. The caller owns the new events and must delete them when done with
+ // them.
+ bool CreateInputEventsFromAndroidEvent(AInputEvent* android_event,
+ Events* events);
+
+ // Create press/unpress events from SbKey
+ // (for use with CobaltA11yHelper injection)
+ void CreateInputEventsFromSbKey(SbKey key, Events* events);
+
+ private:
+ enum FlatAxis {
+ kLeftX,
+ kLeftY,
+ kRightX,
+ kRightY,
+ kNumAxes,
+ };
+
+ bool ProcessKeyEvent(AInputEvent* android_event, Events* events);
+ bool ProcessPointerEvent(AInputEvent* android_event, Events* events);
+ bool ProcessMotionEvent(AInputEvent* android_event, Events* events);
+ void ProcessJoyStickEvent(FlatAxis axis,
+ int32_t motion_axis,
+ AInputEvent* android_event,
+ Events* events);
+ void UpdateDeviceFlatMapIfNecessary(AInputEvent* android_event);
+
+ void ProcessFallbackDPadEvent(SbInputEventType type,
+ SbKey key,
+ AInputEvent* android_event,
+ Events* events);
+ void UpdateHatValuesAndPossiblySynthesizeKeyEvents(AInputEvent* android_event,
+ Events* events);
+
+ SbWindow window_;
+
+#ifdef STARBOARD_INPUT_EVENTS_FILTER
+ InputEventsFilter input_events_filter_;
+#endif
+
+ // Map the device id with joystick flat position.
+ // Cache the flat area of joystick to avoid calling jni functions frequently.
+ std::map<int32_t, std::vector<float> > device_flat_;
+
+ // The curent X/Y analog values of the "hat" (dpad on the game controller).
+ float hat_value_[2];
+
+ // The last known value of the left thumbstick, used to track when we should
+ // generate key unpressed events for it. We store values for horizontal and
+ // vertical directions independently.
+ SbKey left_thumbstick_key_pressed_[2];
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_INPUT_EVENTS_GENERATOR_H_
diff --git a/src/starboard/android/shared/jni_env_ext.cc b/src/starboard/android/shared/jni_env_ext.cc
new file mode 100644
index 0000000..a42359e
--- /dev/null
+++ b/src/starboard/android/shared/jni_env_ext.cc
@@ -0,0 +1,107 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/jni_env_ext.h"
+
+#include <android/native_activity.h>
+#include <jni.h>
+
+#include <algorithm>
+#include <string>
+
+#include "starboard/thread.h"
+
+namespace {
+
+SbThreadLocalKey g_tls_key = kSbThreadLocalKeyInvalid;
+JavaVM* g_vm = NULL;
+jobject g_application_class_loader = NULL;
+jobject g_starboard_bridge = NULL;
+
+void Destroy(void* value) {
+ // OnThreadShutdown() must be called on each thread before it is destroyed.
+ SB_DCHECK(value == NULL);
+}
+
+} // namespace
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// static
+void JniEnvExt::Initialize(JniEnvExt* env, jobject starboard_bridge) {
+ SB_DCHECK(g_tls_key == kSbThreadLocalKeyInvalid);
+ g_tls_key = SbThreadCreateLocalKey(Destroy);
+
+ SB_DCHECK(g_vm == NULL);
+ env->GetJavaVM(&g_vm);
+
+ SB_DCHECK(g_application_class_loader == NULL);
+ g_application_class_loader = env->ConvertLocalRefToGlobalRef(
+ env->CallObjectMethodOrAbort(env->GetObjectClass(starboard_bridge),
+ "getClassLoader",
+ "()Ljava/lang/ClassLoader;"));
+
+ SB_DCHECK(g_starboard_bridge == NULL);
+ g_starboard_bridge = env->NewGlobalRef(starboard_bridge);
+}
+
+// static
+void JniEnvExt::OnThreadShutdown() {
+ // We must call DetachCurrentThread() before exiting, if we have ever
+ // previously called AttachCurrentThread() on it.
+ // http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/invocation.html
+ if (SbThreadGetLocalValue(g_tls_key)) {
+ g_vm->DetachCurrentThread();
+ SbThreadSetLocalValue(g_tls_key, NULL);
+ }
+}
+
+JniEnvExt* JniEnvExt::Get() {
+ JNIEnv* env;
+ if (JNI_OK != g_vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6)) {
+ // Tell the JVM our thread name so it doesn't change it.
+ char thread_name[16];
+ SbThreadGetName(thread_name, sizeof(thread_name));
+ JavaVMAttachArgs args { JNI_VERSION_1_6, thread_name, NULL };
+ g_vm->AttachCurrentThread(&env, &args);
+ // We don't use the value, but any non-NULL means we have to detach.
+ SbThreadSetLocalValue(g_tls_key, env);
+ }
+ // The downcast is safe since we only add methods, not fields.
+ return static_cast<JniEnvExt*>(env);
+}
+
+jobject JniEnvExt::GetStarboardBridge() {
+ return g_starboard_bridge;
+}
+
+jclass JniEnvExt::FindClassExtOrAbort(const char* name) {
+ // Convert the JNI FindClass name with slashes to the "binary name" with dots
+ // for ClassLoader.loadClass().
+ ::std::string dot_name = name;
+ ::std::replace(dot_name.begin(), dot_name.end(), '/', '.');
+ jstring jname = NewStringUTF(dot_name.c_str());
+ AbortOnException();
+ jobject clazz_obj =
+ CallObjectMethodOrAbort(g_application_class_loader, "loadClass",
+ "(Ljava/lang/String;)Ljava/lang/Class;", jname);
+ DeleteLocalRef(jname);
+ return static_cast<jclass>(clazz_obj);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/jni_env_ext.h b/src/starboard/android/shared/jni_env_ext.h
new file mode 100644
index 0000000..3797c08
--- /dev/null
+++ b/src/starboard/android/shared/jni_env_ext.h
@@ -0,0 +1,387 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_
+#define STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_
+
+#include <android/native_activity.h>
+#include <jni.h>
+
+#include <cstdarg>
+#include <cstring>
+#include <string>
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// An extension to JNIEnv to simplify making JNI calls.
+//
+// Call the static Get() method to get an instance that is already attached to
+// the JVM in the current thread.
+//
+// This extends the JNIEnv structure, which already has a C++ interface for
+// calling JNI methods, so any JNIEnv method can be called directly on this.
+//
+// There are convenience methods to lookup and call Java methods on object
+// instances in a single step, with even simpler methods to call Java methods on
+// the StarboardBridge.
+struct JniEnvExt : public JNIEnv {
+ // One-time initialization to be called before starting the application.
+ static void Initialize(JniEnvExt* jni_env, jobject starboard_bridge);
+
+ // Called right before each native thread is about to be shutdown.
+ static void OnThreadShutdown();
+
+ // Returns the thread-specific instance of JniEnvExt.
+ static JniEnvExt* Get();
+
+ // Returns the StarboardBridge object.
+ jobject GetStarboardBridge();
+
+ // Lookup the class of an object and find a field in it.
+ jfieldID GetStaticFieldIDOrAbort(jclass clazz,
+ const char* name,
+ const char* sig) {
+ jfieldID field = GetStaticFieldID(clazz, name, sig);
+ AbortOnException();
+ return field;
+ }
+
+ jfieldID GetFieldIDOrAbort(jobject obj,
+ const char* name,
+ const char* sig) {
+ jclass clazz = GetObjectClass(obj);
+ AbortOnException();
+ jfieldID field = GetFieldID(clazz, name, sig);
+ AbortOnException();
+ DeleteLocalRef(clazz);
+ return field;
+ }
+
+ jint GetEnumValueOrAbort(jclass clazz, const char* name) {
+ jfieldID field = GetStaticFieldIDOrAbort(clazz, name, "I");
+ jint enum_value = GetStaticIntField(clazz, field);
+ AbortOnException();
+ return enum_value;
+ }
+
+ // Lookup the class of an object and find a method in it.
+ jmethodID GetObjectMethodIDOrAbort(jobject obj,
+ const char* name,
+ const char* sig) {
+ jclass clazz = GetObjectClass(obj);
+ AbortOnException();
+ jmethodID method_id = GetMethodID(clazz, name, sig);
+ AbortOnException();
+ DeleteLocalRef(clazz);
+ return method_id;
+ }
+
+ jmethodID GetStaticMethodIDOrAbort(jclass clazz,
+ const char* name,
+ const char* sig) {
+ jmethodID method = GetStaticMethodID(clazz, name, sig);
+ AbortOnException();
+ return method;
+ }
+
+ jobject GetObjectArrayElementOrAbort(jobjectArray array, jsize index) {
+ jobject result = GetObjectArrayElement(array, index);
+ AbortOnException();
+ return result;
+ }
+
+ // Find a class by name using the application's class loader. This can load
+ // both system classes and application classes, even when not in a JNI
+ // stack frame (e.g. in a native thread that was attached the the JVM).
+ // https://developer.android.com/training/articles/perf-jni.html#faq_FindClass
+ jclass FindClassExtOrAbort(const char* name);
+
+ jclass FindClassOrAbort(const char* name) {
+ jclass result = FindClass(name);
+ AbortOnException();
+ return result;
+ }
+
+ // Convenience method to lookup and call a constructor.
+ jobject NewObjectOrAbort(const char* class_name, const char* sig, ...) {
+ va_list argp;
+ va_start(argp, sig);
+ jclass clazz = FindClassExtOrAbort(class_name);
+ jmethodID methodID = GetMethodID(clazz, "<init>", sig);
+ AbortOnException();
+ jobject result = NewObjectV(clazz, methodID, argp);
+ AbortOnException();
+ DeleteLocalRef(clazz);
+ va_end(argp);
+ return result;
+ }
+
+ // Constructs a new java.lang.String object from an array of characters in
+ // standard UTF-8 encoding. This differs from JNIEnv::NewStringUTF() which
+ // takes JNI modified UTF-8.
+ jstring NewStringStandardUTFOrAbort(const char* bytes) {
+ const jstring charset = NewStringUTF("UTF-8");
+ AbortOnException();
+ const jbyteArray byte_array = NewByteArrayFromRaw(
+ reinterpret_cast<const jbyte*>(bytes), strlen(bytes));
+ AbortOnException();
+ jstring result = static_cast<jstring>(NewObjectOrAbort(
+ "java/lang/String", "([BLjava/lang/String;)V", byte_array, charset));
+ DeleteLocalRef(byte_array);
+ DeleteLocalRef(charset);
+ return result;
+ }
+
+ // Returns a std::string representing the jstring in standard UTF-8 encoding.
+ // This differs from JNIEnv::GetStringUTFChars() which returns modified UTF-8.
+ // Also, the buffer of the returned bytes is managed by the std::string object
+ // so it is not necessary to release it with JNIEnv::ReleaseStringUTFChars().
+ std::string GetStringStandardUTFOrAbort(jstring str) {
+ if (str == NULL) {
+ return std::string();
+ }
+ const jstring charset = NewStringUTF("UTF-8");
+ AbortOnException();
+ const jbyteArray byte_array = static_cast<jbyteArray>(
+ CallObjectMethodOrAbort(str, "getBytes", "(Ljava/lang/String;)[B",
+ charset));
+ jsize array_length = GetArrayLength(byte_array);
+ AbortOnException();
+ void* bytes = GetPrimitiveArrayCritical(byte_array, NULL);
+ AbortOnException();
+ std::string result(static_cast<const char*>(bytes), array_length);
+ ReleasePrimitiveArrayCritical(byte_array, bytes, JNI_ABORT);
+ AbortOnException();
+ DeleteLocalRef(byte_array);
+ DeleteLocalRef(charset);
+ return result;
+ }
+
+// Convenience methods to lookup and read a field or call a method all at once:
+// Get[Type]FieldOrAbort() takes a jobject of an instance.
+// Call[Type]MethodOrAbort() takes a jobject of an instance.
+// CallStarboard[Type]MethodOrAbort() to call methods on the StarboardBridge.
+#define X(_jtype, _jname) \
+ _jtype Get##_jname##FieldOrAbort(jobject obj, const char* name, \
+ const char* sig) { \
+ _jtype result = Get##_jname##Field(obj, GetFieldIDOrAbort(obj, name, sig));\
+ AbortOnException(); \
+ return result; \
+ } \
+ \
+ _jtype GetStatic##_jname##FieldOrAbort(const char* class_name, \
+ const char* name, const char* sig) { \
+ jclass clazz = FindClassExtOrAbort(class_name); \
+ return GetStatic##_jname##FieldOrAbort(clazz, name, sig); \
+ } \
+ \
+ _jtype GetStatic##_jname##FieldOrAbort(jclass clazz, const char* name, \
+ const char* sig) { \
+ _jtype result = GetStatic##_jname##Field( \
+ clazz, GetStaticFieldIDOrAbort(clazz, name, sig)); \
+ AbortOnException(); \
+ return result; \
+ } \
+ \
+ _jtype Call##_jname##MethodOrAbort(jobject obj, const char* name, \
+ const char* sig, ...) { \
+ va_list argp; \
+ va_start(argp, sig); \
+ _jtype result = Call##_jname##MethodVOrAbort( \
+ obj, GetObjectMethodIDOrAbort(obj, name, sig), argp); \
+ va_end(argp); \
+ return result; \
+ } \
+ \
+ _jtype CallStarboard##_jname##MethodOrAbort(const char* name, \
+ const char* sig, ...) { \
+ va_list argp; \
+ va_start(argp, sig); \
+ jobject obj = GetStarboardBridge(); \
+ _jtype result = Call##_jname##MethodVOrAbort( \
+ obj, GetObjectMethodIDOrAbort(obj, name, sig), argp); \
+ va_end(argp); \
+ return result; \
+ } \
+ \
+ _jtype CallStatic##_jname##MethodOrAbort( \
+ const char* class_name, const char* method_name, const char* sig, ...) { \
+ va_list argp; \
+ va_start(argp, sig); \
+ jclass clazz = FindClassExtOrAbort(class_name); \
+ _jtype result = CallStatic##_jname##MethodVOrAbort( \
+ clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp); \
+ DeleteLocalRef(clazz); \
+ va_end(argp); \
+ return result; \
+ } \
+ \
+ _jtype Call##_jname##MethodVOrAbort(jobject obj, jmethodID methodID, \
+ va_list args) { \
+ _jtype result = Call##_jname##MethodV(obj, methodID, args); \
+ AbortOnException(); \
+ return result; \
+ } \
+ \
+ _jtype CallStatic##_jname##MethodVOrAbort(jclass clazz, jmethodID methodID, \
+ va_list args) { \
+ _jtype result = CallStatic##_jname##MethodV(clazz, methodID, args); \
+ AbortOnException(); \
+ return result; \
+ }
+
+ X(jobject, Object)
+ X(jboolean, Boolean)
+ X(jbyte, Byte)
+ X(jchar, Char)
+ X(jshort, Short)
+ X(jint, Int)
+ X(jlong, Long)
+ X(jfloat, Float)
+ X(jdouble, Double)
+
+#undef X
+
+ void CallVoidMethod(jobject obj, const char* name, const char* sig, ...) {
+ va_list argp;
+ va_start(argp, sig);
+ CallVoidMethodV(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
+ va_end(argp);
+ }
+
+ void CallVoidMethodOrAbort(jobject obj,
+ const char* name,
+ const char* sig,
+ ...) {
+ va_list argp;
+ va_start(argp, sig);
+ CallVoidMethodVOrAbort(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
+ va_end(argp);
+ }
+
+ void CallVoidMethodVOrAbort(jobject obj, jmethodID methodID, va_list args) {
+ CallVoidMethodV(obj, methodID, args);
+ AbortOnException();
+ }
+
+ void CallStarboardVoidMethod(const char* name, const char* sig, ...) {
+ va_list argp;
+ va_start(argp, sig);
+ jobject obj = GetStarboardBridge();
+ CallVoidMethodV(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
+ va_end(argp);
+ }
+
+ void CallStarboardVoidMethodOrAbort(const char* name, const char* sig, ...) {
+ va_list argp;
+ va_start(argp, sig);
+ jobject obj = GetStarboardBridge();
+ CallVoidMethodVOrAbort(obj, GetObjectMethodIDOrAbort(obj, name, sig), argp);
+ va_end(argp);
+ }
+
+ void CallStaticVoidMethod(const char* class_name,
+ const char* method_name,
+ const char* sig,
+ ...) {
+ va_list argp;
+ va_start(argp, sig);
+ jclass clazz = FindClassExtOrAbort(class_name);
+ CallStaticVoidMethodV(
+ clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp);
+ DeleteLocalRef(clazz);
+ va_end(argp);
+ }
+
+ void CallStaticVoidMethodOrAbort(const char* class_name,
+ const char* method_name,
+ const char* sig,
+ ...) {
+ va_list argp;
+ va_start(argp, sig);
+ jclass clazz = FindClassExtOrAbort(class_name);
+ CallStaticVoidMethodV(
+ clazz, GetStaticMethodIDOrAbort(clazz, method_name, sig), argp);
+ AbortOnException();
+ DeleteLocalRef(clazz);
+ va_end(argp);
+ }
+
+ jstring GetStringFieldOrAbort(jobject obj, const char* name) {
+ return static_cast<jstring>(
+ GetObjectFieldOrAbort(obj, name, "Ljava/lang/String;"));
+ }
+
+ jstring GetStaticStringFieldOrAbort(const char* class_name,
+ const char* name) {
+ return static_cast<jstring>(
+ GetStaticObjectFieldOrAbort(class_name, name, "Ljava/lang/String;"));
+ }
+
+ jstring GetStaticStringFieldOrAbort(jclass clazz, const char* name) {
+ return static_cast<jstring>(
+ GetStaticObjectFieldOrAbort(clazz, name, "Ljava/lang/String;"));
+ }
+
+// Convenience method to create a j[Type]Array from raw, native data. It is
+// the responsibility of clients to free the returned array when done with it
+// by manually calling |DeleteLocalRef| on it.
+#define X(_jtype, _jname) \
+ _jtype##Array New##_jname##ArrayFromRaw(const _jtype* data, jsize size) { \
+ SB_DCHECK(data); \
+ SB_DCHECK(size >= 0); \
+ _jtype##Array j_array = New##_jname##Array(size); \
+ SB_CHECK(j_array) << "Out of memory making new array"; \
+ Set##_jname##ArrayRegion(j_array, 0, size, data); \
+ return j_array; \
+ }
+
+ X(jboolean, Boolean)
+ X(jbyte, Byte)
+ X(jchar, Char)
+ X(jshort, Short)
+ X(jint, Int)
+ X(jlong, Long)
+ X(jfloat, Float)
+ X(jdouble, Double)
+
+#undef X
+
+ jobject ConvertLocalRefToGlobalRef(jobject local) {
+ jobject global = NewGlobalRef(local);
+ DeleteLocalRef(local);
+ return global;
+ }
+
+ void AbortOnException() {
+ if (!ExceptionCheck()) {
+ return;
+ }
+ ExceptionDescribe();
+ SbSystemBreakIntoDebugger();
+ }
+};
+
+SB_COMPILE_ASSERT(sizeof(JNIEnv) == sizeof(JniEnvExt),
+ JniEnvExt_must_not_add_fields);
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_JNI_ENV_EXT_H_
diff --git a/src/starboard/android/shared/jni_env_ext_test.cc b/src/starboard/android/shared/jni_env_ext_test.cc
new file mode 100644
index 0000000..4c69b95
--- /dev/null
+++ b/src/starboard/android/shared/jni_env_ext_test.cc
@@ -0,0 +1,102 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <string>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/configuration.h"
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+// UTF-16, UTF-8, and Modified UTF-8 test strings, all "𐆖€£$"
+// 𐆖 U+10196 -> U16: D800 DD96 U8: F0 90 86 96 MU8: ED A0 80 ED B6 96
+// € U+020AC -> U16: 20AC U8: E2 82 AC MU8: E2 82 AC
+// £ U+000A3 -> U16: 00A3 U8: C2 A3 MU8: C2 A3
+// $ U+00024 -> U16: 0024 U8: 24 MU8: 24
+const char16_t kU16[] = u"\U00010196\u20AC\u00A3\u0024";
+const char kU8[] = "\xF0\x90\x86\x96\xE2\x82\xAC\xC2\xA3\x24";
+const char kMU8[] = "\xED\xA0\x80\xED\xB6\x96\xE2\x82\xAC\xC2\xA3\x24";
+
+// Subtract one from the array size to not count the null terminator.
+const int kU16Length = SB_ARRAY_SIZE(kU16) - 1;
+const int kU8Length = SB_ARRAY_SIZE(kU8) - 1;
+const int kMU8Length = SB_ARRAY_SIZE(kMU8) - 1;
+
+// Note: there is no test for getting the string back as modified UTF-8 since
+// on some Android devices GetStringUTFChars() may return standard UTF-8.
+// (e.g. Nexus Player returns modified UTF-8, but Shield returns standard UTF-8)
+// see: https://github.com/android-ndk/ndk/issues/283
+
+TEST(JniEnvExtTest, NewStringStandardUTF) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jstring j_str = env->NewStringStandardUTFOrAbort(kU8);
+
+ EXPECT_EQ(kU16Length, env->GetStringLength(j_str));
+ const jchar* u16_chars = env->GetStringChars(j_str, NULL);
+ std::u16string u16_string(
+ reinterpret_cast<const char16_t*>(u16_chars), kU16Length);
+ EXPECT_EQ(std::u16string(kU16), u16_string);
+ env->ReleaseStringChars(j_str, u16_chars);
+}
+
+TEST(JniEnvExtTest, NewStringModifiedUTF) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jstring j_str = env->NewStringUTF(kMU8);
+
+ EXPECT_EQ(kU16Length, env->GetStringLength(j_str));
+ const jchar* u16_chars = env->GetStringChars(j_str, NULL);
+ std::u16string u16_string(
+ reinterpret_cast<const char16_t*>(u16_chars), kU16Length);
+ EXPECT_EQ(std::u16string(kU16), u16_string);
+ env->ReleaseStringChars(j_str, u16_chars);
+}
+
+TEST(JniEnvExtTest, EmptyNewStringStandardUTF) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jstring j_str = env->NewStringStandardUTFOrAbort("");
+
+ EXPECT_EQ(0, env->GetStringLength(j_str));
+}
+
+TEST(JniEnvExtTest, GetStringStandardUTF) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jstring j_str =
+ env->NewString(reinterpret_cast<const jchar*>(kU16), kU16Length);
+
+ std::string str = env->GetStringStandardUTFOrAbort(j_str);
+ EXPECT_EQ(kU8Length, str.length());
+ EXPECT_EQ(std::string(kU8), str);
+ env->DeleteLocalRef(j_str);
+}
+
+TEST(JniEnvExtTest, EmptyGetStringStandardUTF) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jchar empty[] = {};
+ jstring j_str = env->NewString(empty, 0);
+
+ std::string str = env->GetStringStandardUTFOrAbort(j_str);
+ EXPECT_EQ(0, str.length());
+ EXPECT_EQ(std::string(), str);
+ env->DeleteLocalRef(j_str);
+}
+
+} // namespace
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/jni_utils.h b/src/starboard/android/shared/jni_utils.h
new file mode 100644
index 0000000..0722504
--- /dev/null
+++ b/src/starboard/android/shared/jni_utils.h
@@ -0,0 +1,88 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_JNI_UTILS_H_
+#define STARBOARD_ANDROID_SHARED_JNI_UTILS_H_
+
+#include <jni.h>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// Wrapper class to manage the lifetime of a local reference to Java type
+// |JT|. This is necessary for local references to |JT|s that are obtained in
+// native code that was not called into from Java, since they will otherwise
+// not be cleaned up.
+template <typename JT>
+class ScopedLocalJavaRef {
+ public:
+ explicit ScopedLocalJavaRef(jobject j_object = NULL)
+ : jt_(static_cast<JT>(j_object)) {}
+ ~ScopedLocalJavaRef() {
+ if (jt_) {
+ JniEnvExt::Get()->DeleteLocalRef(jt_);
+ jt_ = NULL;
+ }
+ }
+ JT Get() const { return jt_; }
+ void Reset(jobject j_object) {
+ if (jt_) {
+ JniEnvExt::Get()->DeleteLocalRef(jt_);
+ }
+ jt_ = static_cast<JT>(j_object);
+ }
+ operator bool() const { return jt_; }
+
+ private:
+ JT jt_;
+
+ SB_DISALLOW_COPY_AND_ASSIGN(ScopedLocalJavaRef);
+};
+
+// Convenience class to manage the lifetime of a local Java ByteBuffer
+// reference, and provide accessors to its properties.
+class ScopedJavaByteBuffer {
+ public:
+ explicit ScopedJavaByteBuffer(jobject j_byte_buffer)
+ : j_byte_buffer_(j_byte_buffer) {}
+ void* address() const {
+ return JniEnvExt::Get()->GetDirectBufferAddress(j_byte_buffer_.Get());
+ }
+ jint capacity() const {
+ return JniEnvExt::Get()->GetDirectBufferCapacity(j_byte_buffer_.Get());
+ }
+ bool IsNull() const { return !j_byte_buffer_ || !address(); }
+ void CopyInto(const void* source, jint count) {
+ SB_DCHECK(!IsNull());
+ SB_DCHECK(count >= 0 && count <= capacity());
+ SbMemoryCopy(address(), source, count);
+ }
+
+ private:
+ ScopedLocalJavaRef<jobject> j_byte_buffer_;
+
+ SB_DISALLOW_COPY_AND_ASSIGN(ScopedJavaByteBuffer);
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_JNI_UTILS_H_
diff --git a/src/starboard/android/shared/launcher.py b/src/starboard/android/shared/launcher.py
new file mode 100644
index 0000000..bd43320
--- /dev/null
+++ b/src/starboard/android/shared/launcher.py
@@ -0,0 +1,396 @@
+#
+# Copyright 2017 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+"""Android implementation of Starboard launcher abstraction."""
+
+import os
+import Queue
+import re
+import socket
+import subprocess
+import sys
+import threading
+import time
+
+import _env # pylint: disable=unused-import
+
+from starboard.android.shared import sdk_utils
+from starboard.tools import abstract_launcher
+
+_APP_PACKAGE_NAME = 'dev.cobalt.coat'
+
+_APP_START_INTENT = 'dev.cobalt.coat/dev.cobalt.app.MainActivity'
+
+# Matches an "adb shell am monitor" error line.
+_RE_ADB_AM_MONITOR_ERROR = re.compile(r'\*\* ERROR')
+
+# String added to queue to indicate process has crashed
+_QUEUE_CODE_CRASHED = 'crashed'
+
+# Args to go/crow, which is started if no other device is attached.
+_CROW_COMMANDLINE = ['/google/data/ro/teams/mobile_eng_prod/crow/crow.par',
+ '--api_level', '24', '--device', 'tv',
+ '--open_gl_driver', 'host',
+ '--noenable_g3_monitor']
+
+# How long to keep logging after a crash in order to emit the stack trace.
+_CRASH_LOG_SECONDS = 1.0
+
+_DEV_NULL = open('/dev/null')
+
+_ADB = os.path.join(sdk_utils.GetSdkPath(), 'platform-tools', 'adb')
+
+_RUNTIME_PERMISSIONS = [
+ 'android.permission.GET_ACCOUNTS',
+ 'android.permission.RECORD_AUDIO',
+]
+
+
+def TargetOsPathJoin(*path_elements):
+ """os.path.join for the target (Android)."""
+ return '/'.join(path_elements)
+
+
+def CleanLine(line):
+ """Removes trailing carriages returns from ADB output."""
+ return line.replace('\r', '')
+
+
+class StepTimer(object):
+ """Class for timing how long install/run steps take."""
+
+ def __init__(self, step_name):
+ self.step_name = step_name
+ self.start_time = time.time()
+ self.end_time = None
+
+ def Stop(self):
+ if self.start_time is None:
+ sys.stderr.write('Cannot stop timer; not started\n')
+ else:
+ self.end_time = time.time()
+ total_time = self.end_time - self.start_time
+ sys.stderr.write('Step \"{}\" took {} seconds.\n'.format(
+ self.step_name, total_time))
+
+
+class AdbCommandBuilder(object):
+ """Builder for 'adb' commands."""
+
+ def __init__(self, device_id):
+ self.device_id = device_id
+
+ def Build(self, *args):
+ """Builds an 'adb' commandline with the given args."""
+ result = [_ADB]
+ if self.device_id:
+ result.append('-s')
+ result.append(self.device_id)
+ result += list(args)
+ return result
+
+
+class AdbAmMonitorWatcher(object):
+ """Watches an "adb shell am monitor" process to detect crashes."""
+
+ def __init__(self, adb_builder, done_queue):
+ self.adb_builder = adb_builder
+ self.process = subprocess.Popen(
+ adb_builder.Build('shell', 'am', 'monitor'),
+ stdout=subprocess.PIPE,
+ stderr=_DEV_NULL,
+ close_fds=True)
+ self.thread = threading.Thread(target=self._Run)
+ self.thread.start()
+ self.done_queue = done_queue
+
+ def Shutdown(self):
+ self.process.kill()
+ self.thread.join()
+
+ def _Run(self):
+ while True:
+ line = CleanLine(self.process.stdout.readline())
+ if not line:
+ return
+ if re.search(_RE_ADB_AM_MONITOR_ERROR, line):
+ self.done_queue.put(_QUEUE_CODE_CRASHED)
+ # This log line will wake up the main thread
+ subprocess.call(
+ self.adb_builder.Build('shell', 'log', '-t', 'starboard',
+ 'am monitor detected crash'),
+ close_fds=True)
+
+
+class Launcher(abstract_launcher.AbstractLauncher):
+ """Run an application on Android."""
+
+ def __init__(self, platform, target_name, config, device_id, **kwargs):
+
+ super(Launcher, self).__init__(platform, target_name, config, device_id,
+ **kwargs)
+
+ if not self.device_id:
+ self.device_id = self._IdentifyDevice()
+ else:
+ self._ConnectIfNecessary()
+
+ self.adb_builder = AdbCommandBuilder(self.device_id)
+
+ out_directory = os.path.split(self.GetTargetPath())[0]
+ self.apk_path = os.path.join(out_directory, '{}.apk'.format(target_name))
+ if not os.path.exists(self.apk_path):
+ raise Exception("Can't find APK {}".format(self.apk_path))
+
+ # This flag is set when the main Run() loop exits. If Kill() is called
+ # after this flag is set, it will not do anything.
+ self.killed = threading.Event()
+
+ # Keep track of the port used by ADB forward in order to remove it later
+ # on.
+ self.local_port = None
+
+ def _IsValidIPv4Address(self, address):
+ """Returns True if address is a valid IPv4 address, False otherwise."""
+ try:
+ # inet_aton throws an exception if the address is not a valid IPv4
+ # address. However addresses such as '127.1' might still be considered
+ # valid, hence the check for 3 '.' in the address.
+ if socket.inet_aton(address) and address.count('.') == 3:
+ return True
+ except:
+ pass
+ return False
+
+ def _GetAdbDevices(self):
+ """Returns a list of names of connected devices, or empty list if none."""
+
+ # Does not use the ADBCommandBuilder class because this command should be
+ # run without targeting a specific device.
+ p = subprocess.Popen([_ADB, 'devices'], stderr=_DEV_NULL,
+ stdout=subprocess.PIPE, close_fds=True)
+ result = p.stdout.readlines()[1:-1]
+ p.wait()
+
+ names = []
+ for device in result:
+ name_info = device.split('\t')
+ # Some devices may not have authorization for USB debugging.
+ try:
+ if 'unauthorized' not in name_info[1]:
+ names.append(name_info[0])
+ # Sometimes happens when device is found, even though none are connected.
+ except IndexError:
+ continue
+ return names
+
+ def _IdentifyDevice(self):
+ """Picks a device to be used to run the executable.
+
+ In the event that no device_id is provided, but multiple
+ devices are connected, this method chooses the first device
+ listed.
+
+ Returns:
+ The name of an attached device, or None if no devices are present.
+ """
+ device_name = None
+
+ devices = self._GetAdbDevices()
+ if devices:
+ device_name = devices[0]
+
+ return device_name
+
+ def _ConnectIfNecessary(self):
+ """Run ADB connect if needed for devices connected over IP."""
+ if not self._IsValidIPv4Address(self.device_id):
+ return
+ for device in self._GetAdbDevices():
+ # Devices returned by _GetAdbDevices might include port number, so cannot
+ # simply check if self.device_id is in the returned list.
+ if self.device_id in device:
+ return
+
+ # Device isn't connected. Run ADB connect.
+ # Does not use the ADBCommandBuilder class because this command should be
+ # run without targeting a specific device.
+ p = subprocess.Popen([_ADB, 'connect', self.device_id], stderr=_DEV_NULL,
+ stdout=subprocess.PIPE, close_fds=True)
+ result = p.stdout.readlines()[0]
+ p.wait()
+
+ if 'connected to' not in result:
+ sys.stderr.write('Failed to connect to {}\n'.format(self.device_id))
+
+ def _LaunchCrowIfNecessary(self):
+ if self.device_id:
+ return
+
+ # Note that we just leave Crow running, since we uninstall/reinstall
+ # each time anyway.
+ self._CheckCall(*_CROW_COMMANDLINE)
+
+ def _Call(self, *args):
+ sys.stderr.write('{}\n'.format(' '.join(args)))
+ subprocess.call(args, stdout=_DEV_NULL, stderr=_DEV_NULL,
+ close_fds=True)
+
+ def _CallAdb(self, *in_args):
+ args = self.adb_builder.Build(*in_args)
+ self._Call(*args)
+
+ def _CheckCall(self, *args):
+ sys.stderr.write('{}\n'.format(' '.join(args)))
+ subprocess.check_call(args, stdout=_DEV_NULL, stderr=_DEV_NULL,
+ close_fds=True)
+
+ def _CheckCallAdb(self, *in_args):
+ args = self.adb_builder.Build(*in_args)
+ self._CheckCall(*args)
+
+ def _PopenAdb(self, *args, **kwargs):
+ return subprocess.Popen(self.adb_builder.Build(*args), close_fds=True,
+ **kwargs)
+
+ def Run(self):
+ # The return code for binaries run on Android is read from a log line that
+ # it emitted in android_main.cc. This return_code variable will be assigned
+ # the value read when we see that line, or left at 1 in the event of a crash
+ # or early exit.
+ return_code = 1
+
+ # Setup for running executable
+ self._LaunchCrowIfNecessary()
+ self._CheckCallAdb('wait-for-device')
+ self._Shutdown()
+
+ # Clear logcat
+ self._CheckCallAdb('logcat', '-c')
+
+ # Install the APK.
+ install_timer = StepTimer('install')
+ self._CheckCallAdb('install', '-r', self.apk_path)
+ install_timer.Stop()
+
+ # Send the wakeup key to ensure daydream isn't running, otherwise Activity
+ # Manager may get in a loop running the test over and over again.
+ self._CheckCallAdb('shell', 'input', 'keyevent', 'KEY_WAKEUP')
+
+ # Grant runtime permissions to avoid prompts during testing.
+ for permission in _RUNTIME_PERMISSIONS:
+ self._CheckCallAdb('shell', 'pm', 'grant', _APP_PACKAGE_NAME, permission)
+
+ done_queue = Queue.Queue()
+ am_monitor = AdbAmMonitorWatcher(self.adb_builder, done_queue)
+
+ # Increases the size of the logcat buffer. Without this, the log buffer
+ # will not flush quickly enough and output will be cut off.
+ self._CheckCallAdb('logcat', '-G', '2M')
+
+ # Ctrl + C will kill this process
+ logcat_process = self._PopenAdb(
+ 'logcat', '-v', 'raw', '-s', '*:F', 'DEBUG:*', 'System.err:*',
+ 'starboard:*', 'starboard_media:*',
+ stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
+
+ # Actually running executable
+ run_timer = StepTimer('running executable')
+ try:
+ args = ['shell', 'am', 'start']
+ command_line_params = [
+ '--android_log_sleep_time=1000',
+ '--disable_sign_in',
+ ]
+ if self.target_command_line_params:
+ command_line_params += self.target_command_line_params
+ args += ['--esa', 'args', "'{}'".format(','.join(command_line_params))]
+ args += [_APP_START_INTENT]
+
+ self._CheckCallAdb(*args)
+
+ app_crashed = False
+ while True:
+ if not done_queue.empty():
+ done_queue_code = done_queue.get_nowait()
+ if done_queue_code == _QUEUE_CODE_CRASHED:
+ app_crashed = True
+ threading.Timer(_CRASH_LOG_SECONDS, logcat_process.kill).start()
+
+ # Note we cannot use "for line in logcat_process.stdout" because
+ # that uses a large buffer which will cause us to deadlock.
+ line = CleanLine(logcat_process.stdout.readline())
+
+ # Some crashes are not caught by the am_monitor thread, but they do
+ # produce the following string in logcat before they exit.
+ if 'beginning of crash' in line:
+ app_crashed = True
+ threading.Timer(_CRASH_LOG_SECONDS, logcat_process.kill).start()
+
+ if not line: # Logcat exited, or was killed
+ break
+ else:
+ self._WriteLine(line)
+ # Don't break until we see the below text in logcat, which should be
+ # written when the Starboard application event loop finishes.
+ if '***Application Stopped***' in line:
+ try:
+ return_code = int(line.split(' ')[-1])
+ except ValueError: # Error message was printed to stdout
+ pass
+ logcat_process.kill()
+ break
+
+ finally:
+ if app_crashed:
+ self._WriteLine('***Application Crashed***\n')
+ else:
+ self._Shutdown()
+ if self.local_port is not None:
+ self._CallAdb('forward', '--remove', 'tcp:{}'.format(self.local_port))
+ am_monitor.Shutdown()
+ self.killed.set()
+ run_timer.Stop()
+
+ return return_code
+
+ def _Shutdown(self):
+ self._CallAdb('shell', 'am', 'force-stop', _APP_PACKAGE_NAME)
+
+ def Kill(self):
+ if not self.killed.is_set():
+ sys.stderr.write('***Killing Launcher***\n')
+ self._CheckCallAdb('shell', 'log', '-t', 'starboard',
+ '***Application Stopped*** 1')
+ self._Shutdown()
+ else:
+ sys.stderr.write('Cannot kill launcher: already dead.\n')
+
+ def _WriteLine(self, line):
+ """Write log output to stdout."""
+ self.output_file.write(line)
+ self.output_file.flush()
+
+ def GetHostAndPortGivenPort(self, port):
+ forward_p = self._PopenAdb(
+ 'forward', 'tcp:0', 'tcp:{}'.format(port),
+ stdout=subprocess.PIPE,
+ stderr=_DEV_NULL)
+ forward_p.wait()
+
+ self.local_port = CleanLine(forward_p.stdout.readline()).rstrip('\n')
+ sys.stderr.write('ADB forward local port {} '
+ '=> device port {}\n'.format(self.local_port, port))
+ return socket.gethostbyname('localhost'), self.local_port
diff --git a/src/starboard/android/shared/log.cc b/src/starboard/android/shared/log.cc
new file mode 100644
index 0000000..a202f71
--- /dev/null
+++ b/src/starboard/android/shared/log.cc
@@ -0,0 +1,103 @@
+// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/log.h>
+#include <stdio.h>
+#include <unistd.h>
+
+#include <jni.h>
+#include <string>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/log_internal.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/command_line.h"
+#include "starboard/string.h"
+#include "starboard/thread.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+namespace {
+ const char kLogSleepTimeSwitch[] = "android_log_sleep_time";
+ SbTime g_log_sleep_time = 0;
+}
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+void LogInit(const starboard::shared::starboard::CommandLine& command_line) {
+ if (command_line.HasSwitch(kLogSleepTimeSwitch)) {
+ g_log_sleep_time =
+ SbStringAToL(command_line.GetSwitchValue(kLogSleepTimeSwitch).c_str());
+ SB_LOG(INFO) << "Android log sleep time: " << g_log_sleep_time;
+ }
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+void SbLog(SbLogPriority priority, const char* message) {
+ int android_priority;
+ switch (priority) {
+ case kSbLogPriorityUnknown:
+ android_priority = ANDROID_LOG_UNKNOWN;
+ break;
+ case kSbLogPriorityInfo:
+ android_priority = ANDROID_LOG_INFO;
+ break;
+ case kSbLogPriorityWarning:
+ android_priority = ANDROID_LOG_WARN;
+ break;
+ case kSbLogPriorityError:
+ android_priority = ANDROID_LOG_ERROR;
+ break;
+ case kSbLogPriorityFatal:
+ android_priority = ANDROID_LOG_FATAL;
+ break;
+ default:
+ android_priority = ANDROID_LOG_INFO;
+ break;
+ }
+ __android_log_write(android_priority, "starboard", message);
+
+ // In unit tests the logging is too fast for the android log to be read out
+ // and we end up losing crucial logs. The test runner specifies a sleep time.
+ SbThreadSleep(g_log_sleep_time);
+}
+
+// Helper to write messages to logcat even when Android non-warning/non-error
+// logging is stripped from the app with Proguard.
+extern "C" SB_EXPORT_PLATFORM jint
+Java_dev_cobalt_util_Log_nativeWrite(JniEnvExt* env,
+ jobject unused_clazz,
+ jchar priority,
+ jstring tag,
+ jstring msg,
+ jobject throwable) {
+ char log_method_name[2] = {static_cast<char>(priority), '\0'};
+ if (throwable == nullptr) {
+ return env->CallStaticIntMethodOrAbort(
+ "android.util.Log", log_method_name,
+ "(Ljava/lang/String;Ljava/lang/String;)I", tag, msg);
+ } else {
+ return env->CallStaticIntMethodOrAbort(
+ "android.util.Log", log_method_name,
+ "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I",
+ tag, msg, throwable);
+ }
+}
diff --git a/src/starboard/android/shared/log_flush.cc b/src/starboard/android/shared/log_flush.cc
new file mode 100644
index 0000000..09d54e2
--- /dev/null
+++ b/src/starboard/android/shared/log_flush.cc
@@ -0,0 +1,21 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/log.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+void SbLogFlush() {
+}
diff --git a/src/starboard/android/shared/log_format.cc b/src/starboard/android/shared/log_format.cc
new file mode 100644
index 0000000..734d22f
--- /dev/null
+++ b/src/starboard/android/shared/log_format.cc
@@ -0,0 +1,54 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <stdio.h>
+#include <string>
+
+#include "starboard/log.h"
+#include "starboard/mutex.h"
+#include "starboard/string.h"
+
+namespace {
+SbMutex log_line_mutex = SB_MUTEX_INITIALIZER;
+std::stringstream log_line;
+const int kFormatBufferSizeBytes = 16 * 1024;
+
+} // namespace
+
+void SbLogFormat(const char* format, va_list arguments) {
+ // __android_log_vprint() cannot be used here because each call to it
+ // will produce a new line in the log, whereas a call to a function in
+ // the C vprintf family only produces a new line if fed "\n", etc. The logic
+ // below ensures that a log line is written only when a newline is detected,
+ // making Android SbLogFormat() behavior consistent with the expectations of
+ // the code that uses it, such as unit test suites.
+
+ char formatted_buffer[kFormatBufferSizeBytes];
+ vsprintf(formatted_buffer, format, arguments);
+
+ const char* newline = SbStringFindCharacter(formatted_buffer, '\n');
+
+ SbMutexAcquire(&log_line_mutex);
+ std::string buffer_string(formatted_buffer);
+ log_line << buffer_string;
+ if (newline != NULL) {
+ log_line.flush();
+
+ SbLogRaw(log_line.str().c_str());
+
+ log_line.str("");
+ log_line.clear();
+ }
+ SbMutexRelease(&log_line_mutex);
+}
diff --git a/src/starboard/android/shared/log_internal.h b/src/starboard/android/shared/log_internal.h
new file mode 100644
index 0000000..5fb5169
--- /dev/null
+++ b/src/starboard/android/shared/log_internal.h
@@ -0,0 +1,33 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// This header provides a mechanism for multiple Android logging
+// formats to share a single log file handle.
+
+#ifndef STARBOARD_ANDROID_SHARED_LOG_INTERNAL_H_
+#define STARBOARD_ANDROID_SHARED_LOG_INTERNAL_H_
+
+#include "starboard/shared/starboard/command_line.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+void LogInit(const starboard::shared::starboard::CommandLine& command_line);
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_LOG_INTERNAL_H_
diff --git a/src/starboard/android/shared/log_is_tty.cc b/src/starboard/android/shared/log_is_tty.cc
new file mode 100644
index 0000000..fba57de
--- /dev/null
+++ b/src/starboard/android/shared/log_is_tty.cc
@@ -0,0 +1,21 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/log.h"
+
+#include <unistd.h>
+
+bool SbLogIsTty() {
+ return false;
+}
diff --git a/src/starboard/android/shared/log_raw.cc b/src/starboard/android/shared/log_raw.cc
new file mode 100644
index 0000000..79d63ad
--- /dev/null
+++ b/src/starboard/android/shared/log_raw.cc
@@ -0,0 +1,19 @@
+// Copyright 2015 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/log.h"
+
+void SbLogRaw(const char* message) {
+ SbLog(kSbLogPriorityInfo, message);
+}
diff --git a/src/starboard/android/shared/main.cc b/src/starboard/android/shared/main.cc
new file mode 100644
index 0000000..5769390
--- /dev/null
+++ b/src/starboard/android/shared/main.cc
@@ -0,0 +1,23 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/export.h"
+#include "starboard/log.h"
+
+extern "C" SB_EXPORT_PLATFORM int main(int argc, char** argv) {
+ // main() is never called on Android. However, the cobalt_bin
+ // target requires it to be there.
+ SB_NOTREACHED();
+ return 0;
+}
diff --git a/src/starboard/android/shared/media_codec_bridge.cc b/src/starboard/android/shared/media_codec_bridge.cc
new file mode 100644
index 0000000..4ac70c0
--- /dev/null
+++ b/src/starboard/android/shared/media_codec_bridge.cc
@@ -0,0 +1,358 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/media_codec_bridge.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+// See
+// https://developer.android.com/reference/android/media/MediaFormat.html#COLOR_RANGE_FULL.
+const jint COLOR_RANGE_FULL = 1;
+const jint COLOR_RANGE_LIMITED = 2;
+
+const jint COLOR_STANDARD_BT2020 = 6;
+const jint COLOR_STANDARD_BT601_NTSC = 4;
+const jint COLOR_STANDARD_BT601_PAL = 2;
+const jint COLOR_STANDARD_BT709 = 1;
+
+const jint COLOR_TRANSFER_HLG = 7;
+const jint COLOR_TRANSFER_LINEAR = 1;
+const jint COLOR_TRANSFER_SDR_VIDEO = 3;
+const jint COLOR_TRANSFER_ST2084 = 6;
+
+// A special value to represent that no mapping between an SbMedia* HDR
+// metadata value and Android HDR metadata value is possible. This value
+// implies that HDR playback should not be attempted.
+const jint COLOR_VALUE_UNKNOWN = -1;
+
+jint SbMediaPrimaryIdToColorStandard(SbMediaPrimaryId primary_id) {
+ switch (primary_id) {
+ case kSbMediaPrimaryIdBt709:
+ return COLOR_STANDARD_BT709;
+ case kSbMediaPrimaryIdBt2020:
+ return COLOR_STANDARD_BT2020;
+ default:
+ return COLOR_VALUE_UNKNOWN;
+ }
+}
+
+jint SbMediaTransferIdToColorTransfer(SbMediaTransferId transfer_id) {
+ switch (transfer_id) {
+ case kSbMediaTransferIdBt709:
+ return COLOR_TRANSFER_SDR_VIDEO;
+ case kSbMediaTransferIdSmpteSt2084:
+ return COLOR_TRANSFER_ST2084;
+ case kSbMediaTransferIdAribStdB67:
+ return COLOR_TRANSFER_HLG;
+ default:
+ return COLOR_VALUE_UNKNOWN;
+ }
+}
+
+jint SbMediaRangeIdToColorRange(SbMediaRangeId range_id) {
+ switch (range_id) {
+ case kSbMediaRangeIdLimited:
+ return COLOR_RANGE_LIMITED;
+ case kSbMediaRangeIdFull:
+ return COLOR_RANGE_FULL;
+ default:
+ return COLOR_VALUE_UNKNOWN;
+ }
+}
+
+} // namespace
+
+// static
+scoped_ptr<MediaCodecBridge> MediaCodecBridge::CreateAudioMediaCodecBridge(
+ SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ jobject j_media_crypto) {
+ const char* mime = SupportedAudioCodecToMimeType(audio_codec);
+ if (!mime) {
+ return scoped_ptr<MediaCodecBridge>(NULL);
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+ jobject j_media_codec_bridge = env->CallStaticObjectMethodOrAbort(
+ "dev/cobalt/media/MediaCodecBridge", "createAudioMediaCodecBridge",
+ "(Ljava/lang/String;ZZIILandroid/media/MediaCrypto;)Ldev/cobalt/media/"
+ "MediaCodecBridge;",
+ j_mime.Get(), !!j_media_crypto, false, audio_header.samples_per_second,
+ audio_header.number_of_channels, j_media_crypto);
+
+ if (!j_media_codec_bridge) {
+ return scoped_ptr<MediaCodecBridge>(NULL);
+ }
+
+ j_media_codec_bridge = env->ConvertLocalRefToGlobalRef(j_media_codec_bridge);
+ return scoped_ptr<MediaCodecBridge>(
+ new MediaCodecBridge(j_media_codec_bridge));
+}
+
+// static
+scoped_ptr<MediaCodecBridge> MediaCodecBridge::CreateVideoMediaCodecBridge(
+ SbMediaVideoCodec video_codec,
+ int width,
+ int height,
+ jobject j_surface,
+ jobject j_media_crypto,
+ const SbMediaColorMetadata* color_metadata) {
+ const char* mime = SupportedVideoCodecToMimeType(video_codec);
+ if (!mime) {
+ return scoped_ptr<MediaCodecBridge>(NULL);
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+
+ ScopedLocalJavaRef<jobject> j_color_info(nullptr);
+ if (color_metadata) {
+ jint color_standard =
+ SbMediaPrimaryIdToColorStandard(color_metadata->primaries);
+ jint color_transfer =
+ SbMediaTransferIdToColorTransfer(color_metadata->transfer);
+ jint color_range = SbMediaRangeIdToColorRange(color_metadata->range);
+
+ if (color_standard != COLOR_VALUE_UNKNOWN &&
+ color_transfer != COLOR_VALUE_UNKNOWN &&
+ color_range != COLOR_VALUE_UNKNOWN) {
+ const auto& mastering_metadata = color_metadata->mastering_metadata;
+ j_color_info.Reset(env->NewObjectOrAbort(
+ "dev/cobalt/media/MediaCodecBridge$ColorInfo", "(IIIFFFFFFFFFF)V",
+ color_range, color_standard, color_transfer,
+ mastering_metadata.primary_r_chromaticity_x,
+ mastering_metadata.primary_r_chromaticity_y,
+ mastering_metadata.primary_g_chromaticity_x,
+ mastering_metadata.primary_g_chromaticity_y,
+ mastering_metadata.primary_b_chromaticity_x,
+ mastering_metadata.primary_b_chromaticity_y,
+ mastering_metadata.white_point_chromaticity_x,
+ mastering_metadata.white_point_chromaticity_y,
+ mastering_metadata.luminance_max, mastering_metadata.luminance_min));
+ }
+ }
+
+ jobject j_media_codec_bridge = env->CallStaticObjectMethodOrAbort(
+ "dev/cobalt/media/MediaCodecBridge", "createVideoMediaCodecBridge",
+ "(Ljava/lang/String;ZZIILandroid/view/Surface;"
+ "Landroid/media/MediaCrypto;"
+ "Ldev/cobalt/media/MediaCodecBridge$ColorInfo;)"
+ "Ldev/cobalt/media/MediaCodecBridge;",
+ j_mime.Get(), !!j_media_crypto, false, width, height, j_surface,
+ j_media_crypto, j_color_info.Get());
+
+ if (!j_media_codec_bridge) {
+ return scoped_ptr<MediaCodecBridge>(NULL);
+ }
+
+ j_media_codec_bridge = env->ConvertLocalRefToGlobalRef(j_media_codec_bridge);
+ return scoped_ptr<MediaCodecBridge>(
+ new MediaCodecBridge(j_media_codec_bridge));
+}
+
+MediaCodecBridge::~MediaCodecBridge() {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ SB_DCHECK(j_media_codec_bridge_);
+ env->CallVoidMethodOrAbort(j_media_codec_bridge_, "release", "()V");
+ env->DeleteGlobalRef(j_media_codec_bridge_);
+ j_media_codec_bridge_ = NULL;
+
+ SB_DCHECK(j_reused_dequeue_input_result_);
+ env->DeleteGlobalRef(j_reused_dequeue_input_result_);
+ j_reused_dequeue_input_result_ = NULL;
+
+ SB_DCHECK(j_reused_dequeue_output_result_);
+ env->DeleteGlobalRef(j_reused_dequeue_output_result_);
+ j_reused_dequeue_output_result_ = NULL;
+
+ SB_DCHECK(j_reused_get_output_format_result_);
+ env->DeleteGlobalRef(j_reused_get_output_format_result_);
+ j_reused_get_output_format_result_ = NULL;
+}
+
+DequeueInputResult MediaCodecBridge::DequeueInputBuffer(jlong timeout_us) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(
+ j_media_codec_bridge_, "dequeueInputBuffer",
+ "(JLdev/cobalt/media/MediaCodecBridge$DequeueInputResult;)V", timeout_us,
+ j_reused_dequeue_input_result_);
+ return {env->CallIntMethodOrAbort(j_reused_dequeue_input_result_, "status",
+ "()I"),
+ env->CallIntMethodOrAbort(j_reused_dequeue_input_result_, "index",
+ "()I")};
+}
+
+jobject MediaCodecBridge::GetInputBuffer(jint index) {
+ SB_DCHECK(index >= 0);
+ return JniEnvExt::Get()->CallObjectMethodOrAbort(
+ j_media_codec_bridge_, "getInputBuffer", "(I)Ljava/nio/ByteBuffer;",
+ index);
+}
+
+jint MediaCodecBridge::QueueInputBuffer(jint index,
+ jint offset,
+ jint size,
+ jlong presentation_time_microseconds,
+ jint flags) {
+ return JniEnvExt::Get()->CallIntMethodOrAbort(
+ j_media_codec_bridge_, "queueInputBuffer", "(IIIJI)I", index, offset,
+ size, presentation_time_microseconds, flags);
+}
+
+jint MediaCodecBridge::QueueSecureInputBuffer(
+ jint index,
+ jint offset,
+ const SbDrmSampleInfo& drm_sample_info,
+ jlong presentation_time_microseconds) {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jbyteArray> j_iv(env->NewByteArrayFromRaw(
+ reinterpret_cast<const jbyte*>(drm_sample_info.initialization_vector),
+ drm_sample_info.initialization_vector_size));
+ ScopedLocalJavaRef<jbyteArray> j_key_id(env->NewByteArrayFromRaw(
+ reinterpret_cast<const jbyte*>(drm_sample_info.identifier),
+ drm_sample_info.identifier_size));
+
+ // Reshape the sub sample mapping like this:
+ // [(c0, e0), (c1, e1), ...] -> [c0, c1, ...] and [e0, e1, ...]
+ int32_t subsample_count = drm_sample_info.subsample_count;
+ scoped_array<jint> clear_bytes(new jint[subsample_count]);
+ scoped_array<jint> encrypted_bytes(new jint[subsample_count]);
+ for (int i = 0; i < subsample_count; ++i) {
+ clear_bytes[i] = drm_sample_info.subsample_mapping[i].clear_byte_count;
+ encrypted_bytes[i] =
+ drm_sample_info.subsample_mapping[i].encrypted_byte_count;
+ }
+ ScopedLocalJavaRef<jintArray> j_clear_bytes(
+ env->NewIntArrayFromRaw(clear_bytes.get(), subsample_count));
+ ScopedLocalJavaRef<jintArray> j_encrypted_bytes(
+ env->NewIntArrayFromRaw(encrypted_bytes.get(), subsample_count));
+
+ return env->CallIntMethodOrAbort(
+ j_media_codec_bridge_, "queueSecureInputBuffer", "(II[B[B[I[IIIIIJ)I",
+ index, offset, j_iv.Get(), j_key_id.Get(), j_clear_bytes.Get(),
+ j_encrypted_bytes.Get(), subsample_count, CRYPTO_MODE_AES_CTR, 0, 0,
+ presentation_time_microseconds);
+}
+
+DequeueOutputResult MediaCodecBridge::DequeueOutputBuffer(jlong timeout_us) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(
+ j_media_codec_bridge_, "dequeueOutputBuffer",
+ "(JLdev/cobalt/media/MediaCodecBridge$DequeueOutputResult;)V", timeout_us,
+ j_reused_dequeue_output_result_);
+ return {env->CallIntMethodOrAbort(j_reused_dequeue_output_result_, "status",
+ "()I"),
+ env->CallIntMethodOrAbort(j_reused_dequeue_output_result_, "index",
+ "()I"),
+ env->CallIntMethodOrAbort(j_reused_dequeue_output_result_, "flags",
+ "()I"),
+ env->CallIntMethodOrAbort(j_reused_dequeue_output_result_, "offset",
+ "()I"),
+ env->CallLongMethodOrAbort(j_reused_dequeue_output_result_,
+ "presentationTimeMicroseconds", "()J"),
+ env->CallIntMethodOrAbort(j_reused_dequeue_output_result_, "numBytes",
+ "()I")};
+}
+
+jobject MediaCodecBridge::GetOutputBuffer(jint index) {
+ SB_DCHECK(index >= 0);
+ return JniEnvExt::Get()->CallObjectMethodOrAbort(
+ j_media_codec_bridge_, "getOutputBuffer", "(I)Ljava/nio/ByteBuffer;",
+ index);
+}
+
+void MediaCodecBridge::ReleaseOutputBuffer(jint index, jboolean render) {
+ JniEnvExt::Get()->CallVoidMethodOrAbort(
+ j_media_codec_bridge_, "releaseOutputBuffer", "(IZ)V", index, render);
+}
+
+void MediaCodecBridge::ReleaseOutputBufferAtTimestamp(
+ jint index,
+ jlong render_timestamp_ns) {
+ JniEnvExt::Get()->CallVoidMethodOrAbort(j_media_codec_bridge_,
+ "releaseOutputBuffer", "(IJ)V", index,
+ render_timestamp_ns);
+}
+
+jint MediaCodecBridge::Flush() {
+ return JniEnvExt::Get()->CallIntMethodOrAbort(j_media_codec_bridge_, "flush",
+ "()I");
+}
+
+SurfaceDimensions MediaCodecBridge::GetOutputDimensions() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(
+ j_media_codec_bridge_, "getOutputFormat",
+ "(Ldev/cobalt/media/MediaCodecBridge$GetOutputFormatResult;)V",
+ j_reused_get_output_format_result_);
+ return {env->CallIntMethodOrAbort(j_reused_get_output_format_result_, "width",
+ "()I"),
+ env->CallIntMethodOrAbort(j_reused_get_output_format_result_,
+ "height", "()I")};
+}
+
+AudioOutputFormatResult MediaCodecBridge::GetAudioOutputFormat() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(
+ j_media_codec_bridge_, "getOutputFormat",
+ "(Ldev/cobalt/media/MediaCodecBridge$GetOutputFormatResult;)V",
+ j_reused_get_output_format_result_);
+
+ jint status = env->CallIntMethodOrAbort(j_reused_get_output_format_result_,
+ "status", "()I");
+ if (status == MEDIA_CODEC_ERROR) {
+ return {status, 0, 0};
+ }
+
+ return {status, env->CallIntMethodOrAbort(j_reused_get_output_format_result_,
+ "sampleRate", "()I"),
+ env->CallIntMethodOrAbort(j_reused_get_output_format_result_,
+ "channelCount", "()I")};
+}
+
+MediaCodecBridge::MediaCodecBridge(jobject j_media_codec_bridge)
+ : j_media_codec_bridge_(j_media_codec_bridge),
+ j_reused_dequeue_input_result_(NULL),
+ j_reused_dequeue_output_result_(NULL),
+ j_reused_get_output_format_result_(NULL) {
+ SB_DCHECK(j_media_codec_bridge_);
+ JniEnvExt* env = JniEnvExt::Get();
+ SB_DCHECK(env->GetObjectRefType(j_media_codec_bridge_) == JNIGlobalRefType);
+
+ j_reused_dequeue_input_result_ = env->NewObjectOrAbort(
+ "dev/cobalt/media/MediaCodecBridge$DequeueInputResult", "()V");
+ SB_DCHECK(j_reused_dequeue_input_result_);
+ j_reused_dequeue_input_result_ =
+ env->ConvertLocalRefToGlobalRef(j_reused_dequeue_input_result_);
+
+ j_reused_dequeue_output_result_ = env->NewObjectOrAbort(
+ "dev/cobalt/media/MediaCodecBridge$DequeueOutputResult", "()V");
+ SB_DCHECK(j_reused_dequeue_output_result_);
+ j_reused_dequeue_output_result_ =
+ env->ConvertLocalRefToGlobalRef(j_reused_dequeue_output_result_);
+
+ j_reused_get_output_format_result_ = env->NewObjectOrAbort(
+ "dev/cobalt/media/MediaCodecBridge$GetOutputFormatResult", "()V");
+ SB_DCHECK(j_reused_get_output_format_result_);
+ j_reused_get_output_format_result_ =
+ env->ConvertLocalRefToGlobalRef(j_reused_get_output_format_result_);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/media_codec_bridge.h b/src/starboard/android/shared/media_codec_bridge.h
new file mode 100644
index 0000000..3172e33
--- /dev/null
+++ b/src/starboard/android/shared/media_codec_bridge.h
@@ -0,0 +1,139 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_MEDIA_CODEC_BRIDGE_H_
+#define STARBOARD_ANDROID_SHARED_MEDIA_CODEC_BRIDGE_H_
+
+#include <string>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/common/scoped_ptr.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// These must be in sync with MediaCodecWrapper.MEDIA_CODEC_XXX constants in
+// MediaCodecBridge.java.
+const jint MEDIA_CODEC_OK = 0;
+const jint MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER = 1;
+const jint MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER = 2;
+const jint MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED = 3;
+const jint MEDIA_CODEC_OUTPUT_FORMAT_CHANGED = 4;
+const jint MEDIA_CODEC_INPUT_END_OF_STREAM = 5;
+const jint MEDIA_CODEC_OUTPUT_END_OF_STREAM = 6;
+const jint MEDIA_CODEC_NO_KEY = 7;
+const jint MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION = 8;
+const jint MEDIA_CODEC_ABORT = 9;
+const jint MEDIA_CODEC_ERROR = 10;
+
+const jint BUFFER_FLAG_CODEC_CONFIG = 2;
+const jint BUFFER_FLAG_END_OF_STREAM = 4;
+
+const jint CRYPTO_MODE_UNENCRYPTED = 0;
+const jint CRYPTO_MODE_AES_CTR = 1;
+const jint CRYPTO_MODE_AES_CBC = 2;
+
+struct DequeueInputResult {
+ jint status;
+ jint index;
+};
+
+struct DequeueOutputResult {
+ jint status;
+ jint index;
+ jint flags;
+ jint offset;
+ jlong presentation_time_microseconds;
+ jint num_bytes;
+};
+
+struct SurfaceDimensions {
+ jint width;
+ jint height;
+};
+
+struct AudioOutputFormatResult {
+ jint status;
+ jint sample_rate;
+ jint channel_count;
+};
+
+class MediaCodecBridge {
+ public:
+ static scoped_ptr<MediaCodecBridge> CreateAudioMediaCodecBridge(
+ SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ jobject j_media_crypto);
+
+ static scoped_ptr<MediaCodecBridge> CreateVideoMediaCodecBridge(
+ SbMediaVideoCodec video_codec,
+ int width,
+ int height,
+ jobject j_surface,
+ jobject j_media_crypto,
+ const SbMediaColorMetadata* color_metadata);
+
+ ~MediaCodecBridge();
+
+ DequeueInputResult DequeueInputBuffer(jlong timeout_us);
+ // It is the responsibility of the client to manage the lifetime of the
+ // jobject that |GetInputBuffer| returns.
+ jobject GetInputBuffer(jint index);
+ jint QueueInputBuffer(jint index,
+ jint offset,
+ jint size,
+ jlong presentation_time_microseconds,
+ jint flags);
+ jint QueueSecureInputBuffer(jint index,
+ jint offset,
+ const SbDrmSampleInfo& drm_sample_info,
+ jlong presentation_time_microseconds);
+
+ DequeueOutputResult DequeueOutputBuffer(jlong timeout_us);
+ // It is the responsibility of the client to manage the lifetime of the
+ // jobject that |GetOutputBuffer| returns.
+ jobject GetOutputBuffer(jint index);
+ void ReleaseOutputBuffer(jint index, jboolean render);
+ void ReleaseOutputBufferAtTimestamp(jint index, jlong render_timestamp_ns);
+
+ jint Flush();
+ SurfaceDimensions GetOutputDimensions();
+ AudioOutputFormatResult GetAudioOutputFormat();
+
+ private:
+ // |MediaCodecBridge|s must only be created through its factory methods.
+ explicit MediaCodecBridge(jobject j_media_codec_bridge);
+
+ jobject j_media_codec_bridge_;
+
+ // Profiling and allocation tracking has identified this area to be hot,
+ // and, capable of enough to cause GC times to raise high enough to impact
+ // playback. We mitigate this by reusing these output objects between calls
+ // to |DequeueInputBuffer|, |DequeueOutputBuffer|, and
+ // |GetOutputDimensions|.
+ jobject j_reused_dequeue_input_result_;
+ jobject j_reused_dequeue_output_result_;
+ jobject j_reused_get_output_format_result_;
+
+ SB_DISALLOW_COPY_AND_ASSIGN(MediaCodecBridge);
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_MEDIA_CODEC_BRIDGE_H_
diff --git a/src/starboard/android/shared/media_common.h b/src/starboard/android/shared/media_common.h
new file mode 100644
index 0000000..78ba783
--- /dev/null
+++ b/src/starboard/android/shared/media_common.h
@@ -0,0 +1,102 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
+#define STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
+
+#include <deque>
+#include <queue>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/media.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/player/filter/audio_frame_tracker.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+const int64_t kSecondInMicroseconds = 1000 * 1000;
+
+inline bool IsWidevine(const char* key_system) {
+ return SbStringCompareAll(key_system, "com.widevine") == 0 ||
+ SbStringCompareAll(key_system, "com.widevine.alpha") == 0;
+}
+
+// Map a supported |SbMediaAudioCodec| into its corresponding mime type
+// string. Returns |NULL| if |audio_codec| is not supported.
+inline const char* SupportedAudioCodecToMimeType(
+ const SbMediaAudioCodec audio_codec) {
+ if (audio_codec == kSbMediaAudioCodecAac) {
+ return "audio/mp4a-latm";
+ }
+ return NULL;
+}
+
+// Map a supported |SbMediaVideoCodec| into its corresponding mime type
+// string. Returns |NULL| if |video_codec| is not supported.
+inline const char* SupportedVideoCodecToMimeType(
+ const SbMediaVideoCodec video_codec) {
+ if (video_codec == kSbMediaVideoCodecVp9) {
+ return "video/x-vnd.on2.vp9";
+ } else if (video_codec == kSbMediaVideoCodecH264) {
+ return "video/avc";
+ }
+ return NULL;
+}
+
+// A simple thread-safe queue for events of type |E|, that supports polling
+// based access only.
+template <typename E>
+class EventQueue {
+ public:
+ E PollFront() {
+ ScopedLock lock(mutex_);
+ if (!deque_.empty()) {
+ E event = deque_.front();
+ deque_.pop_front();
+ return event;
+ }
+
+ return E();
+ }
+
+ void PushBack(const E& event) {
+ ScopedLock lock(mutex_);
+ deque_.push_back(event);
+ }
+
+ void Clear() {
+ ScopedLock lock(mutex_);
+ deque_.clear();
+ }
+
+ size_t size() const {
+ ScopedLock lock(mutex_);
+ return deque_.size();
+ }
+
+ private:
+ ::starboard::Mutex mutex_;
+ std::deque<E> deque_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_MEDIA_COMMON_H_
diff --git a/src/starboard/android/shared/media_decoder.cc b/src/starboard/android/shared/media_decoder.cc
new file mode 100644
index 0000000..507239e
--- /dev/null
+++ b/src/starboard/android/shared/media_decoder.cc
@@ -0,0 +1,393 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/media_decoder.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/audio_sink.h"
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+const jlong kDequeueTimeout = 0;
+
+const jint kNoOffset = 0;
+const jlong kNoPts = 0;
+const jint kNoSize = 0;
+const jint kNoBufferFlags = 0;
+
+const char* GetNameForMediaCodecStatus(jint status) {
+ switch (status) {
+ case MEDIA_CODEC_OK:
+ return "MEDIA_CODEC_OK";
+ case MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER:
+ return "MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER";
+ case MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER:
+ return "MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER";
+ case MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED:
+ return "MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED";
+ case MEDIA_CODEC_OUTPUT_FORMAT_CHANGED:
+ return "MEDIA_CODEC_OUTPUT_FORMAT_CHANGED";
+ case MEDIA_CODEC_INPUT_END_OF_STREAM:
+ return "MEDIA_CODEC_INPUT_END_OF_STREAM";
+ case MEDIA_CODEC_OUTPUT_END_OF_STREAM:
+ return "MEDIA_CODEC_OUTPUT_END_OF_STREAM";
+ case MEDIA_CODEC_NO_KEY:
+ return "MEDIA_CODEC_NO_KEY";
+ case MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION:
+ return "MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION";
+ case MEDIA_CODEC_ABORT:
+ return "MEDIA_CODEC_ABORT";
+ case MEDIA_CODEC_ERROR:
+ return "MEDIA_CODEC_ERROR";
+ default:
+ SB_NOTREACHED();
+ return "MEDIA_CODEC_ERROR_UNKNOWN";
+ }
+}
+
+} // namespace
+
+MediaDecoder::MediaDecoder(Host* host,
+ SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system)
+ : media_type_(kSbMediaTypeAudio),
+ host_(host),
+ decoder_thread_(kSbThreadInvalid),
+ media_codec_bridge_(NULL),
+ stream_ended_(false),
+ drm_system_(static_cast<DrmSystem*>(drm_system)) {
+ SB_DCHECK(host_);
+
+ jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL;
+ SB_DCHECK(!drm_system_ || j_media_crypto);
+ media_codec_bridge_ = MediaCodecBridge::CreateAudioMediaCodecBridge(
+ audio_codec, audio_header, j_media_crypto);
+ if (!media_codec_bridge_) {
+ SB_LOG(ERROR) << "Failed to create audio media codec bridge.";
+ return;
+ }
+ if (audio_header.audio_specific_config_size > 0) {
+ // |audio_header.audio_specific_config| is guaranteed to be outlived the
+ // decoder as it is stored in |FilterBasedPlayerWorkerHandler|.
+ event_queue_.PushBack(Event(
+ static_cast<const int8_t*>(audio_header.audio_specific_config),
+ audio_header.audio_specific_config_size));
+ }
+}
+
+MediaDecoder::MediaDecoder(Host* host,
+ SbMediaVideoCodec video_codec,
+ int width,
+ int height,
+ jobject j_output_surface,
+ SbDrmSystem drm_system,
+ const SbMediaColorMetadata* color_metadata)
+ : media_type_(kSbMediaTypeVideo),
+ host_(host),
+ stream_ended_(false),
+ drm_system_(static_cast<DrmSystem*>(drm_system)),
+ decoder_thread_(kSbThreadInvalid),
+ media_codec_bridge_(NULL) {
+ jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL;
+ SB_DCHECK(!drm_system_ || j_media_crypto);
+ media_codec_bridge_ = MediaCodecBridge::CreateVideoMediaCodecBridge(
+ video_codec, width, height, j_output_surface, j_media_crypto,
+ color_metadata);
+ if (!media_codec_bridge_) {
+ SB_LOG(ERROR) << "Failed to create video media codec bridge.";
+ }
+}
+
+MediaDecoder::~MediaDecoder() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ JoinOnDecoderThread();
+}
+
+void MediaDecoder::Initialize(const ErrorCB& error_cb) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ SB_DCHECK(error_cb);
+ SB_DCHECK(!error_cb_);
+
+ error_cb_ = error_cb;
+}
+
+void MediaDecoder::WriteInputBuffer(
+ const scoped_refptr<InputBuffer>& input_buffer) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ SB_DCHECK(input_buffer);
+
+ if (stream_ended_) {
+ SB_LOG(ERROR) << "Decode() is called after WriteEndOfStream() is called.";
+ return;
+ }
+
+ if (!SbThreadIsValid(decoder_thread_)) {
+ decoder_thread_ = SbThreadCreate(
+ 0, kSbThreadPriorityNormal, kSbThreadNoAffinity, true,
+ media_type_ == kSbMediaTypeAudio ? "audio_decoder" : "video_decoder",
+ &MediaDecoder::ThreadEntryPoint, this);
+ SB_DCHECK(SbThreadIsValid(decoder_thread_));
+ }
+
+ event_queue_.PushBack(Event(input_buffer));
+}
+
+void MediaDecoder::WriteEndOfStream() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ stream_ended_ = true;
+ event_queue_.PushBack(Event(Event::kWriteEndOfStream));
+}
+
+// static
+void* MediaDecoder::ThreadEntryPoint(void* context) {
+ SB_DCHECK(context);
+ MediaDecoder* decoder = static_cast<MediaDecoder*>(context);
+ decoder->DecoderThreadFunc();
+ return NULL;
+}
+
+void MediaDecoder::DecoderThreadFunc() {
+ SB_DCHECK(error_cb_);
+
+ // TODO: Replace |pending_work| with a single object instead of using a deque.
+ std::deque<Event> pending_work;
+
+ // TODO: Refactor the i/o logic using async based decoder.
+ while (!destroying_.load()) {
+ if (pending_work.empty()) {
+ Event event = event_queue_.PollFront();
+
+ if (event.type == Event::kWriteInputBuffer ||
+ event.type == Event::kWriteEndOfStream ||
+ event.type == Event::kWriteCodecConfig) {
+ pending_work.push_back(event);
+ }
+ }
+
+ if (media_type_ == kSbMediaTypeAudio) {
+ if (!ProcessOneInputBuffer(&pending_work) &&
+ !DequeueAndProcessOutputBuffer()) {
+ SbThreadSleep(kSbTimeMillisecond);
+ }
+ continue;
+ }
+
+ SB_DCHECK(media_type_ == kSbMediaTypeVideo);
+ // Call Tick() to give the video decoder a chance to release the frames
+ // after each input or output operations.
+ if (!ProcessOneInputBuffer(&pending_work) &&
+ !host_->Tick(media_codec_bridge_.get())) {
+ SbThreadSleep(kSbTimeMillisecond);
+ }
+ if (!DequeueAndProcessOutputBuffer() &&
+ !host_->Tick(media_codec_bridge_.get())) {
+ SbThreadSleep(kSbTimeMillisecond);
+ }
+ }
+
+ SB_LOG(INFO) << "Destroying decoder thread.";
+ host_->OnFlushing();
+ jint status = media_codec_bridge_->Flush();
+ if (status != MEDIA_CODEC_OK) {
+ SB_LOG(ERROR) << "Failed to flush media codec.";
+ }
+}
+
+void MediaDecoder::JoinOnDecoderThread() {
+ if (!SbThreadIsValid(decoder_thread_)) {
+ return;
+ }
+ destroying_.store(true);
+ SbThreadJoin(decoder_thread_, NULL);
+ event_queue_.Clear();
+ decoder_thread_ = kSbThreadInvalid;
+}
+
+bool MediaDecoder::ProcessOneInputBuffer(std::deque<Event>* pending_work) {
+ SB_DCHECK(pending_work);
+ if (pending_work->empty()) {
+ return false;
+ }
+
+ SB_CHECK(media_codec_bridge_);
+
+ // During secure playback, and only secure playback, is is possible that our
+ // attempt to enqueue an input buffer will be rejected by MediaCodec because
+ // we do not have a key yet. In this case, we hold on to the input buffer
+ // that we have already set up, and repeatedly attempt to enqueue it until
+ // it works. Ideally, we would just wait until MediaDrm was ready, however
+ // the shared starboard player framework assumes that it is possible to
+ // perform decryption and decoding as separate steps, so from its
+ // perspective, having made it to this point implies that we ready to
+ // decode. It is not possible to do them as separate steps on Android. From
+ // the perspective of user application, decryption and decoding are one
+ // atomic step.
+ DequeueInputResult dequeue_input_result;
+ Event event;
+ bool input_buffer_already_written = false;
+ if (pending_queue_input_buffer_task_) {
+ dequeue_input_result =
+ pending_queue_input_buffer_task_->dequeue_input_result;
+ SB_DCHECK(dequeue_input_result.index >= 0);
+ event = pending_queue_input_buffer_task_->event;
+ pending_queue_input_buffer_task_ = nullopt_t();
+ input_buffer_already_written = true;
+ } else {
+ dequeue_input_result =
+ media_codec_bridge_->DequeueInputBuffer(kDequeueTimeout);
+ event = pending_work->front();
+ if (dequeue_input_result.index < 0) {
+ HandleError("dequeueInputBuffer", dequeue_input_result.status);
+ return false;
+ }
+ pending_work->pop_front();
+ }
+
+ SB_DCHECK(event.type == Event::kWriteCodecConfig ||
+ event.type == Event::kWriteInputBuffer ||
+ event.type == Event::kWriteEndOfStream);
+ const scoped_refptr<InputBuffer>& input_buffer = event.input_buffer;
+ if (event.type == Event::kWriteEndOfStream) {
+ SB_DCHECK(pending_work->empty());
+ }
+ const void* data = NULL;
+ int size = 0;
+ if (event.type == Event::kWriteCodecConfig) {
+ SB_DCHECK(media_type_ == kSbMediaTypeAudio);
+ data = event.codec_config;
+ size = event.codec_config_size;
+ } else if (event.type == Event::kWriteInputBuffer) {
+ data = input_buffer->data();
+ size = input_buffer->size();
+ } else if (event.type == Event::kWriteEndOfStream) {
+ data = NULL;
+ size = 0;
+ }
+
+ // Don't bother rewriting the same data if we already did it last time we
+ // were called and had it stored in |pending_queue_input_buffer_task_|.
+ if (!input_buffer_already_written && event.type != Event::kWriteEndOfStream) {
+ ScopedJavaByteBuffer byte_buffer(
+ media_codec_bridge_->GetInputBuffer(dequeue_input_result.index));
+ if (byte_buffer.IsNull() || byte_buffer.capacity() < size) {
+ SB_LOG(ERROR) << "Unable to write to MediaCodec input buffer.";
+ return false;
+ }
+ byte_buffer.CopyInto(data, size);
+ }
+
+ jint status;
+ if (event.type == Event::kWriteCodecConfig) {
+ status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
+ kNoOffset, size, kNoPts,
+ BUFFER_FLAG_CODEC_CONFIG);
+ } else if (event.type == Event::kWriteInputBuffer) {
+ jlong pts_us = input_buffer->timestamp();
+ if (drm_system_ && input_buffer->drm_info()) {
+ status = media_codec_bridge_->QueueSecureInputBuffer(
+ dequeue_input_result.index, kNoOffset, *input_buffer->drm_info(),
+ pts_us);
+ } else {
+ status = media_codec_bridge_->QueueInputBuffer(
+ dequeue_input_result.index, kNoOffset, size, pts_us, kNoBufferFlags);
+ }
+ } else {
+ status = media_codec_bridge_->QueueInputBuffer(dequeue_input_result.index,
+ kNoOffset, size, kNoPts,
+ BUFFER_FLAG_END_OF_STREAM);
+ }
+
+ if (status != MEDIA_CODEC_OK) {
+ HandleError("queue(Secure)?InputBuffer", status);
+ // TODO: Stop the decoding loop on fatal error.
+ SB_DCHECK(!pending_queue_input_buffer_task_);
+ pending_queue_input_buffer_task_ = {dequeue_input_result, event};
+ return false;
+ }
+
+ return true;
+}
+
+bool MediaDecoder::DequeueAndProcessOutputBuffer() {
+ SB_CHECK(media_codec_bridge_);
+
+ DequeueOutputResult dequeue_output_result =
+ media_codec_bridge_->DequeueOutputBuffer(kDequeueTimeout);
+
+ // Note that if the |index| field of |DequeueOutputResult| is negative, then
+ // all fields other than |status| and |index| are invalid. This is
+ // especially important, as the Java side of |MediaCodecBridge| will reuse
+ // objects for returned results behind the scenes.
+ if (dequeue_output_result.index < 0) {
+ if (dequeue_output_result.status == MEDIA_CODEC_OUTPUT_FORMAT_CHANGED) {
+ host_->RefreshOutputFormat(media_codec_bridge_.get());
+ return true;
+ }
+
+ if (dequeue_output_result.status == MEDIA_CODEC_OUTPUT_BUFFERS_CHANGED) {
+ SB_DLOG(INFO) << "Output buffers changed, trying to dequeue again.";
+ return true;
+ }
+
+ HandleError("dequeueOutputBuffer", dequeue_output_result.status);
+ return false;
+ }
+
+ host_->ProcessOutputBuffer(media_codec_bridge_.get(), dequeue_output_result);
+ return true;
+}
+
+void MediaDecoder::HandleError(const char* action_name, jint status) {
+ SB_DCHECK(status != MEDIA_CODEC_OK);
+
+ bool retry = false;
+ if (status == MEDIA_CODEC_DEQUEUE_INPUT_AGAIN_LATER) {
+ // Don't bother logging a try again later status, it happens a lot.
+ return;
+ } else if (status == MEDIA_CODEC_DEQUEUE_OUTPUT_AGAIN_LATER) {
+ // Don't bother logging a try again later status, it will happen a lot.
+ return;
+ } else if (status == MEDIA_CODEC_NO_KEY) {
+ retry = true;
+ } else if (status == MEDIA_CODEC_INSUFFICIENT_OUTPUT_PROTECTION) {
+ drm_system_->OnInsufficientOutputProtection();
+ } else {
+ error_cb_(kSbPlayerErrorDecode,
+ FormatString("%s failed with status %d.", action_name, status));
+ }
+
+ if (retry) {
+ SB_LOG(INFO) << "|" << action_name << "| failed with status: "
+ << GetNameForMediaCodecStatus(status)
+ << ", will try again after a delay.";
+ } else {
+ SB_LOG(ERROR) << "|" << action_name << "| failed with status: "
+ << GetNameForMediaCodecStatus(status) << ".";
+ }
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/media_decoder.h b/src/starboard/android/shared/media_decoder.h
new file mode 100644
index 0000000..b504ae6
--- /dev/null
+++ b/src/starboard/android/shared/media_decoder.h
@@ -0,0 +1,160 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_MEDIA_DECODER_H_
+#define STARBOARD_ANDROID_SHARED_MEDIA_DECODER_H_
+
+#include <jni.h>
+
+#include <deque>
+#include <functional>
+#include <queue>
+
+#include "starboard/android/shared/drm_system.h"
+#include "starboard/android/shared/media_codec_bridge.h"
+#include "starboard/atomic.h"
+#include "starboard/common/optional.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/media.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/common.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// TODO: Better encapsulation the MediaCodecBridge so the decoders no longer
+// need to talk directly to the MediaCodecBridge.
+class MediaDecoder {
+ public:
+ typedef ::starboard::shared::starboard::player::filter::ErrorCB ErrorCB;
+ typedef ::starboard::shared::starboard::player::InputBuffer InputBuffer;
+
+ // This class should be implemented by the users of MediaDecoder to receive
+ // various notifications. Note that all such functions are called on the
+ // decoder thread.
+ class Host {
+ public:
+ virtual void ProcessOutputBuffer(MediaCodecBridge* media_codec_bridge,
+ const DequeueOutputResult& output) = 0;
+ virtual void RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) = 0;
+ // This function gets called frequently on the decoding thread to give the
+ // Host a chance to process when the MediaDecoder is decoding video.
+ // TODO: Revise the scheduling logic to give the host a chance to process in
+ // a more elegant way.
+ virtual bool Tick(MediaCodecBridge* media_codec_bridge) = 0;
+ // This function gets called before calling Flush() on the contained
+ // MediaCodecBridge so the host can have a chance to do necessary cleanups
+ // before the MediaCodecBridge is flushed.
+ virtual void OnFlushing() = 0;
+
+ protected:
+ ~Host() {}
+ };
+
+ MediaDecoder(Host* host,
+ SbMediaAudioCodec audio_codec,
+ const SbMediaAudioHeader& audio_header,
+ SbDrmSystem drm_system);
+ MediaDecoder(Host* host,
+ SbMediaVideoCodec video_codec,
+ int width,
+ int height,
+ jobject j_output_surface,
+ SbDrmSystem drm_system,
+ const SbMediaColorMetadata* color_metadata);
+ ~MediaDecoder();
+
+ void Initialize(const ErrorCB& error_cb);
+ void WriteInputBuffer(const scoped_refptr<InputBuffer>& input_buffer);
+ void WriteEndOfStream();
+
+ size_t GetNumberOfPendingTasks() const { return event_queue_.size(); }
+
+ bool is_valid() const { return media_codec_bridge_ != NULL; }
+
+ private:
+ struct Event {
+ enum Type {
+ kInvalid,
+ kWriteCodecConfig,
+ kWriteInputBuffer,
+ kWriteEndOfStream,
+ };
+
+ explicit Event(Type type = kInvalid) : type(type) {
+ SB_DCHECK(type != kWriteInputBuffer && type != kWriteCodecConfig);
+ }
+ Event(const int8_t* codec_config, int16_t codec_config_size)
+ : type(kWriteCodecConfig),
+ codec_config(codec_config),
+ codec_config_size(codec_config_size) {}
+ explicit Event(const scoped_refptr<InputBuffer>& input_buffer)
+ : type(kWriteInputBuffer), input_buffer(input_buffer) {}
+
+ Type type;
+ scoped_refptr<InputBuffer> input_buffer;
+ const int8_t* codec_config;
+ int16_t codec_config_size;
+ };
+
+ struct QueueInputBufferTask {
+ DequeueInputResult dequeue_input_result;
+ Event event;
+ };
+
+ static void* ThreadEntryPoint(void* context);
+ void DecoderThreadFunc();
+ void JoinOnDecoderThread();
+
+ void TeardownCodec();
+
+ bool ProcessOneInputBuffer(std::deque<Event>* pending_work);
+ // Attempt to dequeue a media codec output buffer. Returns whether the
+ // processing should continue. If a valid buffer is dequeued, it will call
+ // ProcessOutputBuffer() on host internally. It is the responsibility of
+ // ProcessOutputBuffer() to release the output buffer back to the system.
+ bool DequeueAndProcessOutputBuffer();
+
+ void HandleError(const char* action_name, jint status);
+
+ ::starboard::shared::starboard::ThreadChecker thread_checker_;
+
+ const SbMediaType media_type_;
+ Host* host_;
+ ErrorCB error_cb_;
+
+ // Working thread to avoid lengthy decoding work block the player thread.
+ SbThread decoder_thread_;
+ scoped_ptr<MediaCodecBridge> media_codec_bridge_;
+
+ bool stream_ended_;
+
+ DrmSystem* drm_system_;
+
+ // Events are processed in a queue, except for when handling events of type
+ // |kReset|, which are allowed to cut to the front.
+ EventQueue<Event> event_queue_;
+ atomic_bool destroying_;
+
+ optional<QueueInputBufferTask> pending_queue_input_buffer_task_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_MEDIA_DECODER_H_
diff --git a/src/starboard/android/shared/media_get_audio_configuration.cc b/src/starboard/android/shared/media_get_audio_configuration.cc
new file mode 100644
index 0000000..ef59d08
--- /dev/null
+++ b/src/starboard/android/shared/media_get_audio_configuration.cc
@@ -0,0 +1,50 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+bool SbMediaGetAudioConfiguration(
+ int output_index,
+ SbMediaAudioConfiguration* out_configuration) {
+ if (output_index != 0 || out_configuration == NULL) {
+ return false;
+ }
+
+ out_configuration->index = 0;
+ out_configuration->connector = kSbMediaAudioConnectorHdmi;
+ out_configuration->latency = 0;
+ out_configuration->coding_type = kSbMediaAudioCodingTypePcm;
+
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_audio_output_manager(
+ env->CallStarboardObjectMethodOrAbort(
+ "getAudioOutputManager", "()Ldev/cobalt/media/AudioOutputManager;"));
+ int channels = static_cast<int>(env->CallIntMethodOrAbort(
+ j_audio_output_manager.Get(), "getMaxChannels", "()I"));
+ if (channels < 2) {
+ SB_DLOG(WARNING)
+ << "The supported channels from output device is smaller than 2. "
+ "Fallback to 2 channels";
+ out_configuration->number_of_channels = 2;
+ } else {
+ out_configuration->number_of_channels = channels;
+ }
+ return true;
+}
diff --git a/src/starboard/android/shared/media_get_audio_output_count.cc b/src/starboard/android/shared/media_get_audio_output_count.cc
new file mode 100644
index 0000000..befefd3
--- /dev/null
+++ b/src/starboard/android/shared/media_get_audio_output_count.cc
@@ -0,0 +1,20 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+int SbMediaGetAudioOutputCount() {
+ // Only supports one HDMI output.
+ return 1;
+}
diff --git a/src/starboard/android/shared/media_get_initial_buffer_capacity.cc b/src/starboard/android/shared/media_get_initial_buffer_capacity.cc
new file mode 100644
index 0000000..1634373
--- /dev/null
+++ b/src/starboard/android/shared/media_get_initial_buffer_capacity.cc
@@ -0,0 +1,19 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+int SbMediaGetInitialBufferCapacity() {
+ return 80 * 1024 * 1024;
+}
diff --git a/src/starboard/android/shared/media_get_max_buffer_capacity.cc b/src/starboard/android/shared/media_get_max_buffer_capacity.cc
new file mode 100644
index 0000000..1d3899b
--- /dev/null
+++ b/src/starboard/android/shared/media_get_max_buffer_capacity.cc
@@ -0,0 +1,29 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+int SbMediaGetMaxBufferCapacity(SbMediaVideoCodec codec,
+ int resolution_width,
+ int resolution_height,
+ int bits_per_pixel) {
+ SB_UNREFERENCED_PARAMETER(codec);
+ SB_UNREFERENCED_PARAMETER(resolution_width);
+ SB_UNREFERENCED_PARAMETER(resolution_height);
+ SB_UNREFERENCED_PARAMETER(bits_per_pixel);
+ // TODO: refine this to a more reasonable value, taking into account
+ // resolution. On most platforms this is 36 * 1024 * 1024 for 1080p, and
+ // 65 * 1024 * 1024 for 4k.
+ return 500 * 1024 * 1024;
+}
diff --git a/src/starboard/android/shared/media_is_audio_supported.cc b/src/starboard/android/shared/media_is_audio_supported.cc
new file mode 100644
index 0000000..8994965
--- /dev/null
+++ b/src/starboard/android/shared/media_is_audio_supported.cc
@@ -0,0 +1,38 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/media_support_internal.h"
+
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+using starboard::android::shared::SupportedAudioCodecToMimeType;
+
+SB_EXPORT bool SbMediaIsAudioSupported(SbMediaAudioCodec audio_codec,
+ int64_t bitrate) {
+ const char* mime = SupportedAudioCodecToMimeType(audio_codec);
+ if (!mime) {
+ return false;
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+ return env->CallStaticBooleanMethodOrAbort(
+ "dev/cobalt/media/MediaCodecUtil", "hasAudioDecoderFor",
+ "(Ljava/lang/String;I)Z", j_mime.Get(),
+ static_cast<jint>(bitrate)) == JNI_TRUE;
+}
diff --git a/src/starboard/android/shared/media_is_output_protected.cc b/src/starboard/android/shared/media_is_output_protected.cc
new file mode 100644
index 0000000..141e75e
--- /dev/null
+++ b/src/starboard/android/shared/media_is_output_protected.cc
@@ -0,0 +1,26 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+#include "starboard/log.h"
+
+bool SbMediaIsOutputProtected() {
+ // Protected output is expected to be properly enforced by the system level
+ // DRM system, and also does not provide a convenient way to query its
+ // state. Thus, from a starboard application perspective, we always return
+ // |true|, and rely on playback of protected content failing at a later
+ // point if the output was not actually secure.
+ return true;
+}
diff --git a/src/starboard/android/shared/media_is_supported.cc b/src/starboard/android/shared/media_is_supported.cc
new file mode 100644
index 0000000..c7b150a
--- /dev/null
+++ b/src/starboard/android/shared/media_is_supported.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/media_common.h"
+
+SB_EXPORT bool SbMediaIsSupported(SbMediaVideoCodec video_codec,
+ SbMediaAudioCodec audio_codec,
+ const char* key_system) {
+ using starboard::android::shared::IsWidevine;
+ using starboard::android::shared::JniEnvExt;
+ if (!IsWidevine(key_system)) {
+ return false;
+ }
+ return JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
+ "dev/cobalt/media/MediaDrmBridge",
+ "isWidevineCryptoSchemeSupported", "()Z") == JNI_TRUE;
+}
diff --git a/src/starboard/android/shared/media_is_video_supported.cc b/src/starboard/android/shared/media_is_video_supported.cc
new file mode 100644
index 0000000..ed29410
--- /dev/null
+++ b/src/starboard/android/shared/media_is_video_supported.cc
@@ -0,0 +1,93 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/media/media_support_internal.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/configuration.h"
+#include "starboard/media.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+using starboard::android::shared::SupportedVideoCodecToMimeType;
+
+namespace {
+
+// https://developer.android.com/reference/android/view/Display.HdrCapabilities.html#HDR_TYPE_HDR10
+const jint HDR_TYPE_DOLBY_VISION = 1;
+const jint HDR_TYPE_HDR10 = 2;
+const jint HDR_TYPE_HLG = 3;
+
+bool IsTransferCharacteristicsSupported(SbMediaTransferId transfer_id) {
+ // Bt709 and unspecified are assumed to always be supported.
+ if (transfer_id == kSbMediaTransferIdBt709 ||
+ transfer_id == kSbMediaTransferIdUnspecified) {
+ return true;
+ }
+ // An HDR capable VP9 decoder is needed to handle HDR at all.
+ bool has_hdr_capable_vp9_decoder =
+ JniEnvExt::Get()->CallStaticBooleanMethodOrAbort(
+ "dev/cobalt/media/MediaCodecUtil", "hasHdrCapableVp9Decoder",
+ "()Z") == JNI_TRUE;
+ if (!has_hdr_capable_vp9_decoder) {
+ return false;
+ }
+
+ jint hdr_type;
+ if (transfer_id == kSbMediaTransferIdSmpteSt2084) {
+ hdr_type = HDR_TYPE_HDR10;
+ } else if (transfer_id == kSbMediaTransferIdAribStdB67) {
+ hdr_type = HDR_TYPE_HLG;
+ } else {
+ // No other transfer functions are supported, see
+ // https://source.android.com/devices/tech/display/hdr.
+ return false;
+ }
+
+ return JniEnvExt::Get()->CallStarboardBooleanMethodOrAbort(
+ "isHdrTypeSupported", "(I)Z", hdr_type) == JNI_TRUE;
+}
+
+} // namespace
+
+SB_EXPORT bool SbMediaIsVideoSupported(SbMediaVideoCodec video_codec,
+ int frame_width,
+ int frame_height,
+ int64_t bitrate,
+ int fps,
+ bool decode_to_texture_required,
+ SbMediaTransferId eotf) {
+ if (!IsTransferCharacteristicsSupported(eotf)) {
+ return false;
+ }
+ // While not necessarily true, for now we assume that all Android devices
+ // can play decode-to-texture video just as well as normal video.
+ SB_UNREFERENCED_PARAMETER(decode_to_texture_required);
+
+ const char* mime = SupportedVideoCodecToMimeType(video_codec);
+ if (!mime) {
+ return false;
+ }
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_mime(env->NewStringStandardUTFOrAbort(mime));
+ bool must_support_hdr = (eotf != kSbMediaTransferIdBt709 &&
+ eotf != kSbMediaTransferIdUnspecified);
+ return env->CallStaticBooleanMethodOrAbort(
+ "dev/cobalt/media/MediaCodecUtil", "hasVideoDecoderFor",
+ "(Ljava/lang/String;ZIIIIZ)Z", j_mime.Get(), false, frame_width,
+ frame_height, static_cast<jint>(bitrate), fps,
+ must_support_hdr) == JNI_TRUE;
+}
diff --git a/src/starboard/android/shared/media_set_output_protection.cc b/src/starboard/android/shared/media_set_output_protection.cc
new file mode 100644
index 0000000..a3de2d9
--- /dev/null
+++ b/src/starboard/android/shared/media_set_output_protection.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/media.h"
+
+#include "starboard/log.h"
+
+bool SbMediaSetOutputProtection(bool enabled) {
+ // Output is always protected from a starboard application perspective, so
+ // return a redundant true. See documentation in |SbMediaIsOutputProtected|
+ // for further details.
+ return true;
+}
diff --git a/src/starboard/android/shared/microphone_impl.cc b/src/starboard/android/shared/microphone_impl.cc
new file mode 100644
index 0000000..ffff860
--- /dev/null
+++ b/src/starboard/android/shared/microphone_impl.cc
@@ -0,0 +1,540 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/microphone/microphone_internal.h"
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include <algorithm>
+#include <queue>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/shared/starboard/thread_checker.h"
+
+using starboard::android::shared::JniEnvExt;
+
+namespace starboard {
+namespace android {
+namespace shared {
+namespace {
+
+const int kSampleRateInHz = 16000;
+const int kSampleRateInMillihertz = kSampleRateInHz * 1000;
+const int kNumOfOpenSLESBuffers = 2;
+const int kSamplesPerBuffer = 128;
+const int kBufferSizeInBytes = kSamplesPerBuffer * sizeof(int16_t);
+
+bool CheckReturnValue(SLresult result) {
+ return result == SL_RESULT_SUCCESS;
+}
+} // namespace
+
+class SbMicrophoneImpl : public SbMicrophonePrivate {
+ public:
+ SbMicrophoneImpl();
+ ~SbMicrophoneImpl() SB_OVERRIDE;
+
+ bool Open() SB_OVERRIDE;
+ bool Close() SB_OVERRIDE;
+ int Read(void* out_audio_data, int audio_data_size) SB_OVERRIDE;
+
+ void SetPermission(bool is_granted);
+ static bool IsMicrophoneDisconnected();
+ static bool IsMicrophoneMute();
+
+ private:
+ enum State { kWaitPermission, kPermissionGranted, kOpened, kClosed };
+
+ static void SwapAndPublishBuffer(SLAndroidSimpleBufferQueueItf buffer_object,
+ void* context);
+ void SwapAndPublishBuffer();
+
+ bool CreateAudioRecorder();
+ void DeleteAudioRecorder();
+
+ void ClearBuffer();
+
+ bool RequestAudioPermission();
+ bool StartRecording();
+ bool StopRecording();
+
+ SLObjectItf engine_object_;
+ SLEngineItf engine_;
+ SLObjectItf recorder_object_;
+ SLRecordItf recorder_;
+ SLAndroidSimpleBufferQueueItf buffer_object_;
+ SLAndroidConfigurationItf config_object_;
+
+ // Keeps track of the microphone's current state.
+ State state_;
+ // Audio data that has been delivered to the buffer queue.
+ std::queue<int16_t*> delivered_queue_;
+ // Audio data that is ready to be read.
+ std::queue<int16_t*> ready_queue_;
+};
+
+SbMicrophoneImpl::SbMicrophoneImpl()
+ : engine_object_(nullptr),
+ engine_(nullptr),
+ recorder_object_(nullptr),
+ recorder_(nullptr),
+ buffer_object_(nullptr),
+ config_object_(nullptr),
+ state_(kClosed) {}
+
+SbMicrophoneImpl::~SbMicrophoneImpl() {
+ Close();
+}
+
+bool SbMicrophoneImpl::RequestAudioPermission() {
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject j_audio_permission_requester =
+ static_cast<jobject>(env->CallStarboardObjectMethodOrAbort(
+ "getAudioPermissionRequester",
+ "()Ldev/cobalt/coat/AudioPermissionRequester;"));
+ jboolean j_permission = env->CallBooleanMethodOrAbort(
+ j_audio_permission_requester, "requestRecordAudioPermission", "(J)Z",
+ reinterpret_cast<intptr_t>(this));
+ return j_permission;
+}
+
+// static
+bool SbMicrophoneImpl::IsMicrophoneDisconnected() {
+ JniEnvExt* env = JniEnvExt::Get();
+ jboolean j_microphone =
+ env->CallStarboardBooleanMethodOrAbort("isMicrophoneDisconnected", "()Z");
+ return j_microphone;
+}
+
+// static
+bool SbMicrophoneImpl::IsMicrophoneMute() {
+ JniEnvExt* env = JniEnvExt::Get();
+ jboolean j_microphone =
+ env->CallStarboardBooleanMethodOrAbort("isMicrophoneMute", "()Z");
+ return j_microphone;
+}
+
+bool SbMicrophoneImpl::Open() {
+ if (state_ == kOpened) {
+ // The microphone has already been opened; clear the unread buffer. See
+ // starboard/microphone.h for more info.
+ ClearBuffer();
+ return true;
+ }
+
+ if (IsMicrophoneDisconnected()) {
+ SB_DLOG(WARNING) << "No microphone connected.";
+ return false;
+ } else if (!RequestAudioPermission()) {
+ state_ = kWaitPermission;
+ SB_DLOG(INFO) << "Waiting for audio permission.";
+ // The permission is not set; this causes the MicrophoneManager to call
+ // read() repeatedly and wait for the user's response.
+ return true;
+ } else if (!StartRecording()) {
+ SB_DLOG(WARNING) << "Error starting recording.";
+ return false;
+ }
+
+ // Successfully opened the microphone and started recording.
+ state_ = kOpened;
+ return true;
+}
+
+bool SbMicrophoneImpl::StartRecording() {
+ if (!CreateAudioRecorder()) {
+ SB_DLOG(WARNING) << "Create audio recorder failed.";
+ DeleteAudioRecorder();
+ return false;
+ }
+
+ // Enqueues kNumOfOpenSLESBuffers zero buffers to start.
+ // Adds buffers to the queue before changing state to ensure that recording
+ // starts as soon as the state is modified.
+ for (int i = 0; i < kNumOfOpenSLESBuffers; ++i) {
+ int16_t* buffer = new int16_t[kSamplesPerBuffer];
+ SbMemorySet(buffer, 0, kBufferSizeInBytes);
+ delivered_queue_.push(buffer);
+ SLresult result =
+ (*buffer_object_)->Enqueue(buffer_object_, buffer, kBufferSizeInBytes);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error adding buffers to the queue.";
+ return false;
+ }
+ }
+
+ // Start the recording by setting the state to |SL_RECORDSTATE_RECORDING|.
+ // When the object is in the SL_RECORDSTATE_RECORDING state, adding buffers
+ // will implicitly start the recording process.
+ SLresult result =
+ (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_RECORDING);
+ if (!CheckReturnValue(result)) {
+ return false;
+ }
+
+ return true;
+}
+
+bool SbMicrophoneImpl::Close() {
+ if (state_ == kClosed) {
+ // The microphone has already been closed.
+ return true;
+ }
+
+ if (state_ == kOpened && !StopRecording()) {
+ SB_DLOG(WARNING) << "Error closing the microphone.";
+ return false;
+ }
+
+ // Successfully closed the microphone and stopped recording.
+ state_ = kClosed;
+ return true;
+}
+
+bool SbMicrophoneImpl::StopRecording() {
+ // Stop recording by setting the record state to |SL_RECORDSTATE_STOPPED|.
+ SLresult result =
+ (*recorder_)->SetRecordState(recorder_, SL_RECORDSTATE_STOPPED);
+ if (!CheckReturnValue(result)) {
+ return false;
+ }
+
+ ClearBuffer();
+
+ DeleteAudioRecorder();
+
+ return true;
+}
+
+int SbMicrophoneImpl::Read(void* out_audio_data, int audio_data_size) {
+ if (state_ == kClosed || IsMicrophoneMute()) {
+ // No audio data is read from a stopped or muted microphone; return an
+ // error.
+ return -1;
+ }
+
+ if (!out_audio_data || audio_data_size == 0 || state_ == kWaitPermission) {
+ // No data to be read.
+ return 0;
+ }
+
+ if (state_ == kPermissionGranted) {
+ if (StartRecording()) {
+ state_ = kOpened;
+ } else {
+ // Could not start recording; return an error.
+ state_ = kClosed;
+ return -1;
+ }
+ }
+
+ int read_bytes = 0;
+ scoped_ptr<int16_t> buffer;
+ // Go through the ready queue, reading and sending audio data.
+ while (!ready_queue_.empty() &&
+ audio_data_size - read_bytes >= kBufferSizeInBytes) {
+ buffer.reset(ready_queue_.front());
+ SbMemoryCopy(static_cast<uint8_t*>(out_audio_data) + read_bytes,
+ buffer.get(), kBufferSizeInBytes);
+ ready_queue_.pop();
+ read_bytes += kBufferSizeInBytes;
+ }
+
+ buffer.reset();
+ return read_bytes;
+}
+
+void SbMicrophoneImpl::SetPermission(bool is_granted) {
+ state_ = is_granted ? kPermissionGranted : kClosed;
+}
+
+// static
+void SbMicrophoneImpl::SwapAndPublishBuffer(
+ SLAndroidSimpleBufferQueueItf buffer_object,
+ void* context) {
+ SbMicrophoneImpl* recorder = static_cast<SbMicrophoneImpl*>(context);
+ recorder->SwapAndPublishBuffer();
+}
+
+void SbMicrophoneImpl::SwapAndPublishBuffer() {
+ if (!delivered_queue_.empty()) {
+ // The front item in the delivered queue already has the buffered data, so
+ // move it from the delivered queue to the ready queue for future reads.
+ int16_t* buffer = delivered_queue_.front();
+ delivered_queue_.pop();
+ ready_queue_.push(buffer);
+ }
+
+ if (state_ == kOpened) {
+ int16_t* buffer = new int16_t[kSamplesPerBuffer];
+ SbMemorySet(buffer, 0, kBufferSizeInBytes);
+ delivered_queue_.push(buffer);
+ SLresult result =
+ (*buffer_object_)->Enqueue(buffer_object_, buffer, kBufferSizeInBytes);
+ CheckReturnValue(result);
+ }
+}
+
+bool SbMicrophoneImpl::CreateAudioRecorder() {
+ SLresult result;
+ // Initializes the SL engine object with specific options.
+ // OpenSL ES for Android is designed for multi-threaded applications and
+ // is thread-safe.
+ result = slCreateEngine(&engine_object_, 0, nullptr, 0, nullptr, nullptr);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error creating the SL engine object.";
+ return false;
+ }
+
+ // Realize the SL engine object in synchronous mode.
+ result = (*engine_object_)
+ ->Realize(engine_object_, /* async = */ SL_BOOLEAN_FALSE);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error realizing the SL engine object.";
+ return false;
+ }
+
+ // Get the SL engine interface.
+ result =
+ (*engine_object_)->GetInterface(engine_object_, SL_IID_ENGINE, &engine_);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error getting the SL engine interface.";
+ return false;
+ }
+
+ // Audio source configuration; the audio source is an I/O device data locator.
+ SLDataLocator_IODevice input_dev_locator = {
+ SL_DATALOCATOR_IODEVICE, SL_IODEVICE_AUDIOINPUT,
+ SL_DEFAULTDEVICEID_AUDIOINPUT, nullptr};
+
+ SLDataSource audio_source = {&input_dev_locator, nullptr};
+ SLDataLocator_AndroidSimpleBufferQueue simple_buffer_queue = {
+ SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, kNumOfOpenSLESBuffers};
+
+ // Audio sink configuration; the audio sink is a simple buffer queue. PCM is
+ // the only data format allowed with buffer queues.
+ SLAndroidDataFormat_PCM_EX format = {
+ SL_ANDROID_DATAFORMAT_PCM_EX, 1 /* numChannels */,
+ kSampleRateInMillihertz, SL_PCMSAMPLEFORMAT_FIXED_16,
+ SL_PCMSAMPLEFORMAT_FIXED_16, SL_SPEAKER_FRONT_CENTER,
+ SL_BYTEORDER_LITTLEENDIAN, SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT};
+ SLDataSink audio_sink = {&simple_buffer_queue, &format};
+
+ const int kCount = 2;
+ const SLInterfaceID kInterfaceId[kCount] = {SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ SL_IID_ANDROIDCONFIGURATION};
+ const SLboolean kInterfaceRequired[kCount] = {SL_BOOLEAN_TRUE,
+ SL_BOOLEAN_TRUE};
+ // Create the audio recorder.
+ result = (*engine_)->CreateAudioRecorder(engine_, &recorder_object_,
+ &audio_source, &audio_sink, kCount,
+ kInterfaceId, kInterfaceRequired);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error creating the audio recorder.";
+ return false;
+ }
+
+ // Configure the audio recorder (before it is realized); get the configuration
+ // interface.
+ result = (*recorder_object_)
+ ->GetInterface(recorder_object_, SL_IID_ANDROIDCONFIGURATION,
+ &config_object_);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error getting the audio recorder interface.";
+ return false;
+ }
+
+ // Use the main microphone tuned for voice recognition.
+ const SLuint32 kPresetValue = SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION;
+ result =
+ (*config_object_)
+ ->SetConfiguration(config_object_, SL_ANDROID_KEY_RECORDING_PRESET,
+ &kPresetValue, sizeof(SLuint32));
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error configuring the audio recorder.";
+ return false;
+ }
+
+ // Realize the recorder in synchronous mode.
+ result = (*recorder_object_)
+ ->Realize(recorder_object_, /* async = */ SL_BOOLEAN_FALSE);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error realizing the audio recorder. Double check that "
+ "the microphone is connected to the device.";
+ return false;
+ }
+
+ // Get the record interface (an implicit interface).
+ result = (*recorder_object_)
+ ->GetInterface(recorder_object_, SL_IID_RECORD, &recorder_);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error getting the audio recorder interface.";
+ return false;
+ }
+
+ // Get the buffer queue interface which was explicitly requested.
+ result = (*recorder_object_)
+ ->GetInterface(recorder_object_, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+ &buffer_object_);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error getting the buffer queue interface.";
+ return false;
+ }
+
+ // Setup to receive buffer queue event callbacks for when a buffer in the
+ // queue is completed.
+ result =
+ (*buffer_object_)
+ ->RegisterCallback(buffer_object_,
+ &SbMicrophoneImpl::SwapAndPublishBuffer, this);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error registering buffer queue callbacks.";
+ return false;
+ }
+
+ return true;
+}
+
+void SbMicrophoneImpl::DeleteAudioRecorder() {
+ if (recorder_object_) {
+ (*recorder_object_)->Destroy(recorder_object_);
+ }
+
+ config_object_ = nullptr;
+ buffer_object_ = nullptr;
+ recorder_ = nullptr;
+ recorder_object_ = nullptr;
+
+ // Destroy the engine object.
+ if (engine_object_) {
+ (*engine_object_)->Destroy(engine_object_);
+ }
+ engine_ = nullptr;
+ engine_object_ = nullptr;
+}
+
+void SbMicrophoneImpl::ClearBuffer() {
+ // Clear the buffer queue to get rid of old data.
+ if (buffer_object_) {
+ SLresult result = (*buffer_object_)->Clear(buffer_object_);
+ if (!CheckReturnValue(result)) {
+ SB_DLOG(WARNING) << "Error clearing the buffer.";
+ }
+ }
+
+ while (!delivered_queue_.empty()) {
+ delete[] delivered_queue_.front();
+ delivered_queue_.pop();
+ }
+
+ while (!ready_queue_.empty()) {
+ delete[] ready_queue_.front();
+ ready_queue_.pop();
+ }
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+int SbMicrophonePrivate::GetAvailableMicrophones(
+ SbMicrophoneInfo* out_info_array,
+ int info_array_size) {
+ // Note that there is no way of checking for a connected microphone/device
+ // before API 23, so GetAvailableMicrophones() will assume a microphone is
+ // connected and always return 1 on APIs < 23.
+ if (starboard::android::shared::SbMicrophoneImpl::
+ IsMicrophoneDisconnected()) {
+ SB_DLOG(WARNING) << "No microphone connected.";
+ return 0;
+ }
+ if (starboard::android::shared::SbMicrophoneImpl::IsMicrophoneMute()) {
+ SB_DLOG(WARNING) << "Microphone is muted.";
+ return 0;
+ }
+
+ if (out_info_array && info_array_size > 0) {
+ // Only support one microphone.
+ out_info_array[0].id = reinterpret_cast<SbMicrophoneId>(1);
+ out_info_array[0].type = kSbMicrophoneUnknown;
+ out_info_array[0].max_sample_rate_hz =
+ starboard::android::shared::kSampleRateInHz;
+ out_info_array[0].min_read_size =
+ starboard::android::shared::kSamplesPerBuffer;
+ }
+
+ return 1;
+}
+
+bool SbMicrophonePrivate::IsMicrophoneSampleRateSupported(
+ SbMicrophoneId id,
+ int sample_rate_in_hz) {
+ if (!SbMicrophoneIdIsValid(id)) {
+ return false;
+ }
+
+ return sample_rate_in_hz == starboard::android::shared::kSampleRateInHz;
+}
+
+namespace {
+const int kUnusedBufferSize = 32 * 1024;
+// Only a single microphone is supported.
+SbMicrophone s_microphone = kSbMicrophoneInvalid;
+
+} // namespace
+
+SbMicrophone SbMicrophonePrivate::CreateMicrophone(SbMicrophoneId id,
+ int sample_rate_in_hz,
+ int buffer_size_bytes) {
+ if (!SbMicrophoneIdIsValid(id) ||
+ !IsMicrophoneSampleRateSupported(id, sample_rate_in_hz) ||
+ buffer_size_bytes > kUnusedBufferSize || buffer_size_bytes <= 0) {
+ return kSbMicrophoneInvalid;
+ }
+
+ if (s_microphone != kSbMicrophoneInvalid) {
+ return kSbMicrophoneInvalid;
+ }
+
+ s_microphone = new starboard::android::shared::SbMicrophoneImpl();
+ return s_microphone;
+}
+
+void SbMicrophonePrivate::DestroyMicrophone(SbMicrophone microphone) {
+ if (!SbMicrophoneIsValid(microphone)) {
+ return;
+ }
+
+ SB_DCHECK(s_microphone == microphone);
+ s_microphone->Close();
+
+ delete s_microphone;
+ s_microphone = kSbMicrophoneInvalid;
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_AudioPermissionRequester_nativeHandlePermission(
+ JNIEnv* env,
+ jobject unused_this,
+ jlong nativeSbMicrophoneImpl,
+ jboolean is_granted) {
+ starboard::android::shared::SbMicrophoneImpl* native =
+ reinterpret_cast<starboard::android::shared::SbMicrophoneImpl*>(
+ nativeSbMicrophoneImpl);
+ native->SetPermission(is_granted);
+}
diff --git a/src/starboard/android/shared/platform_deploy.gypi b/src/starboard/android/shared/platform_deploy.gypi
new file mode 100644
index 0000000..3d6e60f
--- /dev/null
+++ b/src/starboard/android/shared/platform_deploy.gypi
@@ -0,0 +1,68 @@
+# Copyright 2017 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+ 'variables': {
+ 'GRADLE_BUILD_TYPE': '<(cobalt_config)',
+ 'GRADLE_FILES_DIR': '<(PRODUCT_DIR)/gradle/<(executable_name)',
+ 'executable_file': '<(PRODUCT_DIR)/lib/lib<(executable_name).so',
+ 'apk': '<(PRODUCT_DIR)/<(executable_name).apk',
+ },
+ 'conditions': [
+ [ 'cobalt_config == "gold"', {
+ 'variables': {
+ # Android Gradle wants a "release" build type, so use that for "gold".
+ 'GRADLE_BUILD_TYPE': 'release'
+ }
+ }]
+ ],
+ 'dependencies': [ '<(DEPTH)/starboard/android/apk/apk.gyp:apk_sources' ],
+ 'includes': [ '<(DEPTH)/starboard/build/collect_deploy_content.gypi' ],
+ 'actions': [
+ {
+ 'action_name': 'build_apk',
+ 'inputs': [
+ '<(executable_file)',
+ '<(PRODUCT_DIR)/gradle/apk_sources.stamp',
+ '<(content_deploy_stamp_file)',
+ ],
+ 'outputs': [ '<(apk)' ],
+ 'action': [
+ '<(DEPTH)/starboard/android/apk/cobalt-gradle.sh',
+ '--sdk', '<(ANDROID_HOME)',
+ '--ndk', '<(NDK_HOME)',
+ '--cache', '<(GRADLE_FILES_DIR)/cache',
+ '--project-dir', '<(DEPTH)/starboard/android/apk/',
+ '-P', 'cobaltBuildAbi=<(ANDROID_ABI)',
+ '-P', 'cobaltDeployApk=<(apk)',
+ '-P', 'cobaltContentDir=<(content_deploy_dir)',
+ '-P', 'cobaltGradleDir=<(GRADLE_FILES_DIR)',
+ '-P', 'cobaltProductDir=<(PRODUCT_DIR)',
+ '-P', 'cobaltTarget=<(executable_name)',
+ 'assembleCobalt_<(GRADLE_BUILD_TYPE)',
+ ],
+ 'message': 'Build APK: <(apk)',
+ },
+ {
+ # Clean the gradle directory to conserve space after we have the APK
+ 'action_name': 'delete_gradle_dir',
+ 'inputs': [ '<(apk)' ],
+ 'outputs': [ '<(GRADLE_FILES_DIR).deleted.stamp' ],
+ 'action': [
+ 'sh', '-c',
+ 'rm -rf <(GRADLE_FILES_DIR) && touch <(GRADLE_FILES_DIR).deleted.stamp',
+ ],
+ 'message': 'Cleanup Gradle: <(executable_name)',
+ },
+ ],
+}
diff --git a/src/starboard/android/shared/player_components_impl.cc b/src/starboard/android/shared/player_components_impl.cc
new file mode 100644
index 0000000..0d14113
--- /dev/null
+++ b/src/starboard/android/shared/player_components_impl.cc
@@ -0,0 +1,109 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/player/filter/player_components.h"
+
+#include "starboard/android/shared/audio_decoder.h"
+#include "starboard/android/shared/video_decoder.h"
+#include "starboard/android/shared/video_render_algorithm.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/common/scoped_ptr.h"
+#include "starboard/media.h"
+#include "starboard/shared/starboard/player/filter/audio_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/audio_renderer_sink.h"
+#include "starboard/shared/starboard/player/filter/audio_renderer_sink_impl.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_render_algorithm.h"
+#include "starboard/shared/starboard/player/filter/video_render_algorithm_impl.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_sink.h"
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+namespace {
+
+class PlayerComponentsImpl : public PlayerComponents {
+ void CreateAudioComponents(
+ const AudioParameters& audio_parameters,
+ scoped_ptr<AudioDecoder>* audio_decoder,
+ scoped_ptr<AudioRendererSink>* audio_renderer_sink) override {
+ using AudioDecoderImpl = ::starboard::android::shared::AudioDecoder;
+
+ SB_DCHECK(audio_decoder);
+ SB_DCHECK(audio_renderer_sink);
+
+ scoped_ptr<AudioDecoderImpl> audio_decoder_impl(new AudioDecoderImpl(
+ audio_parameters.audio_codec, audio_parameters.audio_header,
+ audio_parameters.drm_system));
+ if (audio_decoder_impl->is_valid()) {
+ audio_decoder->reset(audio_decoder_impl.release());
+ } else {
+ audio_decoder->reset();
+ }
+ audio_renderer_sink->reset(new AudioRendererSinkImpl);
+ }
+
+ void CreateVideoComponents(
+ const VideoParameters& video_parameters,
+ scoped_ptr<VideoDecoder>* video_decoder,
+ scoped_ptr<VideoRenderAlgorithm>* video_render_algorithm,
+ scoped_refptr<VideoRendererSink>* video_renderer_sink) override {
+ using VideoDecoderImpl = ::starboard::android::shared::VideoDecoder;
+ using VideoRenderAlgorithmImpl =
+ ::starboard::android::shared::VideoRenderAlgorithm;
+
+ SB_DCHECK(video_decoder);
+ SB_DCHECK(video_render_algorithm);
+ SB_DCHECK(video_renderer_sink);
+
+ scoped_ptr<VideoDecoderImpl> video_decoder_impl(new VideoDecoderImpl(
+ video_parameters.video_codec, video_parameters.drm_system,
+ video_parameters.output_mode,
+ video_parameters.decode_target_graphics_context_provider));
+ if (video_decoder_impl->is_valid()) {
+ *video_renderer_sink = video_decoder_impl->GetSink();
+ video_decoder->reset(video_decoder_impl.release());
+ } else {
+ video_decoder->reset();
+ *video_renderer_sink = NULL;
+ }
+
+ video_render_algorithm->reset(new VideoRenderAlgorithmImpl);
+ }
+
+ void GetAudioRendererParams(int* max_cached_frames,
+ int* max_frames_per_append) const override {
+ SB_DCHECK(max_cached_frames);
+ SB_DCHECK(max_frames_per_append);
+
+ *max_cached_frames = 256 * 1024;
+ *max_frames_per_append = 16384;
+ }
+};
+
+} // namespace
+
+// static
+scoped_ptr<PlayerComponents> PlayerComponents::Create() {
+ return make_scoped_ptr<PlayerComponents>(new PlayerComponentsImpl);
+}
+
+} // namespace filter
+} // namespace player
+} // namespace starboard
+} // namespace shared
+} // namespace starboard
diff --git a/src/starboard/android/shared/player_create.cc b/src/starboard/android/shared/player_create.cc
new file mode 100644
index 0000000..7db4b61
--- /dev/null
+++ b/src/starboard/android/shared/player_create.cc
@@ -0,0 +1,116 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/configuration.h"
+#include "starboard/decode_target.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/player/filter/filter_based_player_worker_handler.h"
+#include "starboard/shared/starboard/player/player_internal.h"
+#include "starboard/shared/starboard/player/player_worker.h"
+
+using starboard::shared::starboard::player::filter::
+ FilterBasedPlayerWorkerHandler;
+using starboard::shared::starboard::player::PlayerWorker;
+using starboard::android::shared::cobalt::kPlaying;
+using starboard::android::shared::cobalt::
+ UpdateActiveSessionPlatformPlaybackState;
+using starboard::android::shared::cobalt::UpdateActiveSessionPlatformPlayer;
+
+SbPlayer SbPlayerCreate(SbWindow window,
+ SbMediaVideoCodec video_codec,
+ SbMediaAudioCodec audio_codec,
+#if SB_API_VERSION < 10
+ SbMediaTime duration_pts,
+#endif // SB_API_VERSION < 10
+ SbDrmSystem drm_system,
+ const SbMediaAudioHeader* audio_header,
+ SbPlayerDeallocateSampleFunc sample_deallocate_func,
+ SbPlayerDecoderStatusFunc decoder_status_func,
+ SbPlayerStatusFunc player_status_func,
+ SbPlayerErrorFunc player_error_func,
+ void* context,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider* provider) {
+ SB_UNREFERENCED_PARAMETER(window);
+ SB_UNREFERENCED_PARAMETER(provider);
+#if SB_API_VERSION < 10
+ SB_UNREFERENCED_PARAMETER(duration_pts);
+#endif // SB_API_VERSION < 10
+
+ if (!sample_deallocate_func || !decoder_status_func || !player_status_func
+#if SB_HAS(PLAYER_ERROR_MESSAGE)
+ || !player_error_func
+#endif // SB_HAS(PLAYER_ERROR_MESSAGE)
+ ) {
+ return kSbPlayerInvalid;
+ }
+
+ if (audio_codec != kSbMediaAudioCodecNone &&
+ audio_codec != kSbMediaAudioCodecAac) {
+ SB_LOG(ERROR) << "Unsupported audio codec " << audio_codec;
+ return kSbPlayerInvalid;
+ }
+
+ if (audio_codec != kSbMediaAudioCodecNone && !audio_header) {
+ SB_LOG(ERROR) << "SbPlayerCreate() requires a non-NULL SbMediaAudioHeader "
+ << "when |audio_codec| is not kSbMediaAudioCodecNone";
+ return kSbPlayerInvalid;
+ }
+
+ if (video_codec != kSbMediaVideoCodecNone &&
+ video_codec != kSbMediaVideoCodecH264 &&
+ video_codec != kSbMediaVideoCodecVp9) {
+ SB_LOG(ERROR) << "Unsupported video codec " << video_codec;
+ return kSbPlayerInvalid;
+ }
+
+ if (audio_codec == kSbMediaAudioCodecNone &&
+ video_codec == kSbMediaVideoCodecNone) {
+ SB_LOG(ERROR) << "SbPlayerCreate() requires at least one audio track or"
+ << " one video track.";
+ return kSbPlayerInvalid;
+ }
+
+ if (!SbPlayerOutputModeSupported(output_mode, video_codec, drm_system)) {
+ SB_LOG(ERROR) << "Unsupported player output mode " << output_mode;
+ return kSbPlayerInvalid;
+ }
+
+ // TODO: increase this once we support multiple video windows.
+ const int kMaxNumberOfPlayers = 1;
+ if (SbPlayerPrivate::number_of_players() >= kMaxNumberOfPlayers) {
+ return kSbPlayerInvalid;
+ }
+
+ UpdateActiveSessionPlatformPlaybackState(kPlaying);
+
+ starboard::scoped_ptr<PlayerWorker::Handler> handler(
+ new FilterBasedPlayerWorkerHandler(video_codec, audio_codec, drm_system,
+ audio_header, output_mode, provider));
+ SbPlayer player = SbPlayerPrivate::CreateInstance(
+ audio_codec, video_codec, sample_deallocate_func, decoder_status_func,
+ player_status_func, player_error_func, context, handler.Pass());
+
+ // TODO: accomplish this through more direct means.
+ // Set the bounds to initialize the VideoSurfaceView. The initial values don't
+ // matter.
+ SbPlayerSetBounds(player, 0, 0, 0, 0, 0);
+
+ UpdateActiveSessionPlatformPlayer(player);
+
+ return player;
+}
diff --git a/src/starboard/android/shared/player_destroy.cc b/src/starboard/android/shared/player_destroy.cc
new file mode 100644
index 0000000..b1b5a96
--- /dev/null
+++ b/src/starboard/android/shared/player_destroy.cc
@@ -0,0 +1,32 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/shared/starboard/player/player_internal.h"
+
+using starboard::android::shared::cobalt::kNone;
+using starboard::android::shared::cobalt::
+ UpdateActiveSessionPlatformPlaybackState;
+using starboard::android::shared::cobalt::UpdateActiveSessionPlatformPlayer;
+
+void SbPlayerDestroy(SbPlayer player) {
+ if (!SbPlayerIsValid(player)) {
+ return;
+ }
+ UpdateActiveSessionPlatformPlaybackState(kNone);
+ UpdateActiveSessionPlatformPlayer(kSbPlayerInvalid);
+ delete player;
+}
diff --git a/src/starboard/android/shared/player_set_bounds.cc b/src/starboard/android/shared/player_set_bounds.cc
new file mode 100644
index 0000000..d783a66
--- /dev/null
+++ b/src/starboard/android/shared/player_set_bounds.cc
@@ -0,0 +1,34 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/player/player_internal.h"
+
+void SbPlayerSetBounds(SbPlayer player,
+ int z_index,
+ int x,
+ int y,
+ int width,
+ int height) {
+ if (!SbPlayerIsValid(player)) {
+ SB_DLOG(WARNING) << "player is invalid.";
+ return;
+ }
+ starboard::android::shared::JniEnvExt::Get()->CallStarboardVoidMethodOrAbort(
+ "setVideoSurfaceBounds", "(IIII)V", x, y, width, height);
+ player->SetBounds(z_index, x, y, width, height);
+}
diff --git a/src/starboard/android/shared/player_set_playback_rate.cc b/src/starboard/android/shared/player_set_playback_rate.cc
new file mode 100644
index 0000000..413c093
--- /dev/null
+++ b/src/starboard/android/shared/player_set_playback_rate.cc
@@ -0,0 +1,41 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/player.h"
+
+#include "starboard/android/shared/cobalt/android_media_session_client.h"
+#include "starboard/log.h"
+#include "starboard/shared/starboard/player/player_internal.h"
+
+using starboard::android::shared::cobalt::kPaused;
+using starboard::android::shared::cobalt::kPlaying;
+using starboard::android::shared::cobalt::
+ UpdateActiveSessionPlatformPlaybackState;
+
+bool SbPlayerSetPlaybackRate(SbPlayer player, double playback_rate) {
+ if (!SbPlayerIsValid(player)) {
+ SB_DLOG(WARNING) << "player is invalid.";
+ return false;
+ }
+ if (playback_rate < 0.0) {
+ SB_DLOG(WARNING) << "playback_rate cannot be negative but it is set to "
+ << playback_rate << '.';
+ return false;
+ }
+ bool paused = (playback_rate == 0.0);
+ UpdateActiveSessionPlatformPlaybackState(paused ? kPaused : kPlaying);
+
+ player->SetPlaybackRate(playback_rate);
+ return true;
+}
diff --git a/src/starboard/android/shared/sanitizer_options.cc b/src/starboard/android/shared/sanitizer_options.cc
new file mode 100644
index 0000000..aafbc54
--- /dev/null
+++ b/src/starboard/android/shared/sanitizer_options.cc
@@ -0,0 +1,42 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Removes gallium leak warnings from x11 GL code, but defines it as a weak
+// symbol, so other code can override it if they want to.
+
+#if defined(ADDRESS_SANITIZER)
+
+// Functions returning default options are declared weak in the tools' runtime
+// libraries. To make the linker pick the strong replacements for those
+// functions from this module, we explicitly force its inclusion by passing
+// -Wl,-u_sanitizer_options_link_helper
+extern "C" void _sanitizer_options_link_helper() { }
+
+#define SANITIZER_HOOK_ATTRIBUTE \
+ extern "C" \
+ __attribute__((no_sanitize_address)) \
+ __attribute__((no_sanitize_memory)) \
+ __attribute__((no_sanitize_thread)) \
+ __attribute__((visibility("default"))) \
+ __attribute__((weak)) \
+ __attribute__((used))
+
+// Newline separated list of issues to suppress, see
+// http://clang.llvm.org/docs/AddressSanitizer.html#issue-suppression
+// http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/sanitizer_common/sanitizer_suppressions.cc
+SANITIZER_HOOK_ATTRIBUTE const char* __lsan_default_suppressions() {
+ return "leak:egl_gallium.so\n";
+}
+
+#endif // defined(ADDRESS_SANITIZER)
diff --git a/src/starboard/android/shared/sdk_utils.py b/src/starboard/android/shared/sdk_utils.py
new file mode 100644
index 0000000..025e8df
--- /dev/null
+++ b/src/starboard/android/shared/sdk_utils.py
@@ -0,0 +1,348 @@
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""Utilities to use the toolchain from the Android NDK."""
+
+import ConfigParser
+import fcntl
+import hashlib
+import logging
+import os
+import re
+import shutil
+import StringIO
+import subprocess
+import sys
+import time
+import urllib
+import zipfile
+
+from starboard.tools import build
+
+# The API level of NDK standalone toolchain to install. This should be the
+# minimum API level on which the app is expected to run. If some feature from a
+# newer NDK level is needed, this may be increased with caution.
+# https://developer.android.com/ndk/guides/stable_apis.html
+#
+# Using 24 will lead to missing symbols on API 23 devices.
+# https://github.com/android-ndk/ndk/issues/126
+_ANDROID_NDK_API_LEVEL = '21'
+
+# Packages to install in the Android SDK.
+# Get available packages from "sdkmanager --list --verbose"
+_ANDROID_SDK_PACKAGES = [
+ 'build-tools;28.0.3',
+ 'cmake;3.6.4111459',
+ 'emulator',
+ 'extras;android;m2repository',
+ 'extras;google;m2repository',
+ 'lldb;3.1',
+ 'ndk-bundle',
+ 'patcher;v4',
+ 'platforms;android-28',
+ 'platform-tools',
+ 'tools',
+]
+
+# Seconds to sleep before writing "y" for android sdk update license prompt.
+_SDK_LICENSE_PROMPT_SLEEP_SECONDS = 5
+
+# Location from which to download the SDK command-line tools
+# see https://developer.android.com/studio/index.html#command-tools
+_SDK_URL = 'https://dl.google.com/android/repository/sdk-tools-linux-3859397.zip'
+
+_STARBOARD_TOOLCHAINS_DIR = build.GetToolchainsDir()
+
+# The path to the Android SDK, if placed inside of starboard-toolchains.
+_STARBOARD_TOOLCHAINS_SDK_DIR = os.path.join(_STARBOARD_TOOLCHAINS_DIR,
+ 'AndroidSdk')
+
+_ANDROID_HOME = os.environ.get('ANDROID_HOME')
+if _ANDROID_HOME:
+ _SDK_PATH = _ANDROID_HOME
+else:
+ _SDK_PATH = _STARBOARD_TOOLCHAINS_SDK_DIR
+
+_ANDROID_NDK_HOME = os.environ.get('ANDROID_NDK_HOME')
+if _ANDROID_NDK_HOME:
+ _NDK_PATH = _ANDROID_NDK_HOME
+else:
+ _NDK_PATH = os.path.join(_SDK_PATH, 'ndk-bundle')
+
+_SDKMANAGER_TOOL = os.path.join(_SDK_PATH, 'tools', 'bin', 'sdkmanager')
+
+# Maps the Android ABI to the architecture name of the toolchain.
+_TOOLS_ABI_ARCH_MAP = {
+ 'x86': 'x86',
+ 'armeabi': 'arm',
+ 'armeabi-v7a': 'arm',
+ 'arm64-v8a': 'arm64',
+}
+
+_SCRIPT_HASH_PROPERTY = 'SdkUtils.Hash'
+
+with open(__file__, 'rb') as script:
+ _SCRIPT_HASH = hashlib.md5(script.read()).hexdigest()
+
+
+def GetToolsPath(abi):
+ """Returns the path where the NDK standalone toolchain should be."""
+ tools_arch = _TOOLS_ABI_ARCH_MAP[abi]
+ tools_dir = 'android_toolchain_api{}_{}'.format(_ANDROID_NDK_API_LEVEL,
+ tools_arch)
+ return os.path.realpath(os.path.join(_STARBOARD_TOOLCHAINS_DIR, tools_dir))
+
+
+def _CheckStamp(dir_path):
+ """Checks that the specified directory is up-to-date with the NDK."""
+ stamp_path = os.path.join(dir_path, 'ndk.stamp')
+ return (os.path.exists(stamp_path) and
+ _ReadNdkRevision(stamp_path) == _GetInstalledNdkRevision() and
+ _ReadProperty(stamp_path, _SCRIPT_HASH_PROPERTY) == _SCRIPT_HASH)
+
+
+def _UpdateStamp(dir_path):
+ """Updates the stamp file in the specified directory to the NDK revision."""
+ path = GetNdkPath()
+ properties_path = os.path.join(path, 'source.properties')
+ stamp_path = os.path.join(dir_path, 'ndk.stamp')
+ shutil.copyfile(properties_path, stamp_path)
+ with open(stamp_path, 'a') as stamp:
+ stamp.write('{} = {}\n'.format(_SCRIPT_HASH_PROPERTY, _SCRIPT_HASH))
+
+
+def GetNdkPath():
+ return _NDK_PATH
+
+
+def GetSdkPath():
+ return _SDK_PATH
+
+
+def _ReadNdkRevision(properties_path):
+ return _ReadProperty(properties_path, 'pkg.revision')
+
+
+def _ReadProperty(properties_path, property_key):
+ with open(properties_path, 'r') as f:
+ ini_str = '[properties]\n' + f.read()
+ config = ConfigParser.RawConfigParser()
+ config.readfp(StringIO.StringIO(ini_str))
+ try:
+ return config.get('properties', property_key)
+ except ConfigParser.NoOptionError:
+ return None
+
+
+def _GetInstalledNdkRevision():
+ """Returns the installed NDK's revision."""
+ path = GetNdkPath()
+ properties_path = os.path.join(path, 'source.properties')
+ try:
+ return _ReadNdkRevision(properties_path)
+ except IOError:
+ logging.error("Error: Can't read NDK properties in %s", properties_path)
+ sys.exit(1)
+
+
+def InstallSdkIfNeeded(abi):
+ """Installs appropriate SDK/NDK and NDK standalone tools if needed."""
+ _MaybeDownloadAndInstallSdkAndNdk()
+ _MaybeMakeToolchain(abi)
+
+
+def _DownloadAndUnzipFile(url, destination_path):
+ dl_file, dummy_headers = urllib.urlretrieve(url)
+ _UnzipFile(dl_file, destination_path)
+
+
+def _UnzipFile(zip_path, dest_path):
+ """Extract all files and restore permissions from a zip file."""
+ zip_file = zipfile.ZipFile(zip_path)
+ for info in zip_file.infolist():
+ zip_file.extract(info.filename, dest_path)
+ os.chmod(os.path.join(dest_path, info.filename), info.external_attr >> 16L)
+
+
+def _MaybeDownloadAndInstallSdkAndNdk():
+ """Download the SDK and NDK if not already available."""
+ # Hold an exclusive advisory lock on the _STARBOARD_TOOLCHAINS_DIR, to
+ # prevent issues with modification for multiple variants.
+ try:
+ toolchains_dir_fd = os.open(_STARBOARD_TOOLCHAINS_DIR, os.O_RDONLY)
+ fcntl.flock(toolchains_dir_fd, fcntl.LOCK_EX)
+
+ if _ANDROID_HOME:
+ if not os.access(_SDKMANAGER_TOOL, os.X_OK):
+ logging.error('Error: ANDROID_HOME is set but SDK is not present!')
+ sys.exit(1)
+ logging.warning('Warning: Using Android SDK in ANDROID_HOME,'
+ ' which is not automatically updated.\n'
+ ' The following package versions are installed:')
+ installed_packages = _GetInstalledSdkPackages()
+ for package in _ANDROID_SDK_PACKAGES:
+ version = installed_packages.get(package, '< MISSING! >')
+ msg = ' {:30} : {}'.format(package, version)
+ logging.warning(msg)
+ else:
+ logging.warning('Checking Android SDK.')
+ _DownloadInstallOrUpdateSdk()
+
+ if _ANDROID_NDK_HOME:
+ logging.warning('Warning: Using Android NDK in ANDROID_NDK_HOME,'
+ ' which is not automatically updated')
+ ndk_revision = _GetInstalledNdkRevision()
+ logging.warning('Using Android NDK version %s', ndk_revision)
+
+ if _ANDROID_HOME or _ANDROID_NDK_HOME:
+ reply = raw_input(
+ 'Do you want to continue using your custom Android tools? [yN]')
+ if reply.upper() != 'Y':
+ sys.exit(1)
+
+ finally:
+ fcntl.flock(toolchains_dir_fd, fcntl.LOCK_UN)
+ os.close(toolchains_dir_fd)
+
+
+def _GetInstalledSdkPackages():
+ """Returns a map of installed package name to package version."""
+ # We detect and parse either new-style or old-style output from sdkmanager:
+ #
+ # [new-style]
+ # Installed packages:
+ # --------------------------------------
+ # build-tools;25.0.2
+ # Description: Android SDK Build-Tools 25.0.2
+ # Version: 25.0.2
+ # Installed Location: <abs path>
+ # ...
+ #
+ # [old-style]
+ # Installed packages:
+ # Path | Version | Description | Location
+ # ------- | ------- | ------- | -------
+ # build-tools;25.0.2 | 25.0.2 | Android SDK Build-Tools 25.0.2 | <rel path>
+ # ...
+ section_re = re.compile(r'^[A-Z][^:]*:$')
+ version_re = re.compile(r'^\s+Version:\s+(\S+)')
+
+ p = subprocess.Popen(
+ [_SDKMANAGER_TOOL, '--list', '--verbose'], stdout=subprocess.PIPE)
+
+ installed_package_versions = {}
+ new_style = False
+ old_style = False
+ for line in iter(p.stdout.readline, ''):
+
+ if section_re.match(line):
+ if new_style or old_style:
+ # We left the new/old style installed packages section
+ break
+ if line.startswith('Installed'):
+ # Read the header of this section to determine new/old style
+ line = p.stdout.readline().strip()
+ new_style = line.startswith('----')
+ old_style = line.startswith('Path')
+ if old_style:
+ line = p.stdout.readline().strip()
+ if not line.startswith('----'):
+ logging.error('Error: Unexpected SDK package listing format')
+ continue
+
+ # We're in the installed packages section, and it's new-style
+ if new_style:
+ if not line.startswith(' '):
+ pkg_name = line.strip()
+ else:
+ m = version_re.match(line)
+ if m:
+ installed_package_versions[pkg_name] = m.group(1)
+
+ # We're in the installed packages section, and it's old-style
+ elif old_style:
+ fields = [f.strip() for f in line.split('|', 2)]
+ if len(fields) >= 2:
+ installed_package_versions[fields[0]] = fields[1]
+
+ # Discard the rest of the output
+ p.communicate()
+ return installed_package_versions
+
+
+def _IsOnBuildbot():
+ return 'BUILDBOT_BUILDERNAME' in os.environ
+
+
+def _DownloadInstallOrUpdateSdk():
+ """Downloads (if necessary) and installs/updates the Android SDK."""
+
+ # If we can't access the "sdkmanager" tool, we need to download the SDK
+ if not os.access(_SDKMANAGER_TOOL, os.X_OK):
+ logging.warning('Downloading Android SDK to %s',
+ _STARBOARD_TOOLCHAINS_SDK_DIR)
+ if os.path.exists(_STARBOARD_TOOLCHAINS_SDK_DIR):
+ shutil.rmtree(_STARBOARD_TOOLCHAINS_SDK_DIR)
+ _DownloadAndUnzipFile(_SDK_URL, _STARBOARD_TOOLCHAINS_SDK_DIR)
+ if not os.access(_SDKMANAGER_TOOL, os.X_OK):
+ logging.error('SDK download failed.')
+ sys.exit(1)
+
+ # Run the "sdkmanager" command with the appropriate packages
+
+ if _IsOnBuildbot():
+ stdin = subprocess.PIPE
+ else:
+ stdin = sys.stdin
+
+ p = subprocess.Popen(
+ [_SDKMANAGER_TOOL, '--verbose'] + _ANDROID_SDK_PACKAGES, stdin=stdin)
+
+ if _IsOnBuildbot():
+ time.sleep(_SDK_LICENSE_PROMPT_SLEEP_SECONDS)
+ try:
+ p.stdin.write('y\n')
+ except IOError:
+ logging.warning('There were no SDK licenses to accept.')
+
+ p.wait()
+
+
+def _MaybeMakeToolchain(abi):
+ """Run the NDK's make_standalone_toolchain.py if necessary."""
+ tools_arch = _TOOLS_ABI_ARCH_MAP[abi]
+ tools_path = GetToolsPath(abi)
+ if _CheckStamp(tools_path):
+ logging.info('NDK %s toolchain already at %s', tools_arch,
+ _GetInstalledNdkRevision())
+ return
+
+ logging.warning('Installing NDK %s toolchain %s in %s', tools_arch,
+ _GetInstalledNdkRevision(), tools_path)
+
+ if os.path.exists(tools_path):
+ shutil.rmtree(tools_path)
+
+ # Run the NDK script to make the standalone toolchain
+ script_path = os.path.join(GetNdkPath(), 'build', 'tools',
+ 'make_standalone_toolchain.py')
+ args = [
+ script_path, '--arch', tools_arch, '--api', _ANDROID_NDK_API_LEVEL,
+ '--stl', 'libc++', '--install-dir', tools_path
+ ]
+ script_proc = subprocess.Popen(args)
+ rc = script_proc.wait()
+ if rc != 0:
+ raise RuntimeError('%s failed.' % script_path)
+
+ _UpdateStamp(tools_path)
diff --git a/src/starboard/android/shared/socket_get_interface_address.cc b/src/starboard/android/shared/socket_get_interface_address.cc
new file mode 100644
index 0000000..fe75115
--- /dev/null
+++ b/src/starboard/android/shared/socket_get_interface_address.cc
@@ -0,0 +1,93 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "net/base/net_util.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/log.h"
+#include "starboard/memory.h"
+#include "starboard/socket.h"
+
+using starboard::android::shared::JniEnvExt;
+
+namespace {
+
+const size_t kDefaultPrefixLength = 8;
+
+} // namespace
+
+// Note: The following is an incorrect implementation for
+// SbSocketGetInterfaceAddress. Specifically, things missing are:
+// We should see if the destination is NULL, or ANY address, or a regular
+// unicast address.
+// Also, currently, we assume netmask is 255.255.255.0 in IPv4, and
+// has a prefix length of 64 for IPv6.
+// For IPv6, there are a few rules about which IPs are valid (and prefered).
+// E.g. globally routable IP addresses are prefered over ULAs.
+bool SbSocketGetInterfaceAddress(const SbSocketAddress* const /*destination*/,
+ SbSocketAddress* out_source_address,
+ SbSocketAddress* out_netmask) {
+ if (out_source_address == NULL) {
+ return false;
+ }
+
+ SbMemorySet(out_source_address->address, 0,
+ sizeof(out_source_address->address));
+ out_source_address->port = 0;
+
+ JniEnvExt* env = JniEnvExt::Get();
+
+ jbyteArray s = (jbyteArray)env->CallStarboardObjectMethodOrAbort(
+ "getLocalInterfaceAddress", "()[B");
+ if (s == NULL) {
+ return false;
+ }
+
+ jint sz = env->GetArrayLength(s);
+ if (sz > sizeof(out_source_address->address)) {
+ // This should never happen
+ SB_LOG(ERROR) << "SbSocketGetInterfaceAddress address too big";
+ return false;
+ }
+ switch (sz) {
+ case 4:
+ out_source_address->type = kSbSocketAddressTypeIpv4;
+ if (out_netmask) {
+ out_netmask->address[0] = 255;
+ out_netmask->address[1] = 255;
+ out_netmask->address[2] = 255;
+ out_netmask->address[3] = 0;
+ out_netmask->type = kSbSocketAddressTypeIpv4;
+ }
+ break;
+ default:
+ out_source_address->type = kSbSocketAddressTypeIpv6;
+ if (out_netmask) {
+ for (int i = 0; i < kDefaultPrefixLength; ++i) {
+ out_netmask->address[i] = 0xff;
+ }
+ for (int i = kDefaultPrefixLength; i < net::kIPv6AddressSize; ++i) {
+ out_netmask->address[i] = 0;
+ }
+ out_netmask->type = kSbSocketAddressTypeIpv6;
+ }
+ break;
+ }
+
+ jbyte* bytes = env->GetByteArrayElements(s, NULL);
+ SB_CHECK(bytes) << "GetByteArrayElements failed";
+ SbMemoryCopy(out_source_address->address, bytes, sz);
+ env->ReleaseByteArrayElements(s, bytes, JNI_ABORT);
+
+ return true;
+}
diff --git a/src/starboard/android/shared/speech_recognizer_impl.cc b/src/starboard/android/shared/speech_recognizer_impl.cc
new file mode 100644
index 0000000..bea9e52
--- /dev/null
+++ b/src/starboard/android/shared/speech_recognizer_impl.cc
@@ -0,0 +1,331 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h"
+
+#include <android/native_activity.h>
+#include <jni.h>
+
+#include <limits>
+#include <vector>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/mutex.h"
+#include "starboard/shared/starboard/thread_checker.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+// Android speech recognizer error.
+// Reference:
+// https://developer.android.com/reference/android/speech/SpeechRecognizer.html
+enum SpeechRecognizerErrorCode {
+ kErrorNetworkTimeout = 1,
+ kErrorNetwork = 2,
+ kErrorAudio = 3,
+ kErrorService = 4,
+ kErrorClient = 5,
+ kErrorSpeechTimeout = 6,
+ kErrorNoMatch = 7,
+ kErrorRecognizerBusy = 8,
+ kErrorInsufficientPermissions = 9,
+};
+} // namespace
+
+class SbSpeechRecognizerImpl : public SbSpeechRecognizerPrivate {
+ public:
+ explicit SbSpeechRecognizerImpl(const SbSpeechRecognizerHandler* handler);
+ ~SbSpeechRecognizerImpl();
+
+ // Called from constructor's thread.
+ bool Start(const SbSpeechConfiguration* configuration) override;
+ void Stop() override;
+ void Cancel() override;
+
+ // Called from Android's UI thread.
+ void OnSpeechDetected(bool detected);
+ void OnError(int error);
+ void OnResults(const std::vector<std::string>& results,
+ const std::vector<float>& confidences,
+ bool is_final);
+
+ private:
+ SbSpeechRecognizerHandler handler_;
+ jobject j_voice_recognizer_;
+ bool is_started_;
+
+ ::starboard::shared::starboard::ThreadChecker thread_checker_;
+};
+
+SbSpeechRecognizerImpl::SbSpeechRecognizerImpl(
+ const SbSpeechRecognizerHandler* handler)
+ : handler_(*handler), is_started_(false) {
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject local_ref = env->CallStarboardObjectMethodOrAbort(
+ "getVoiceRecognizer", "()Ldev/cobalt/coat/VoiceRecognizer;");
+ j_voice_recognizer_ = env->ConvertLocalRefToGlobalRef(local_ref);
+}
+
+SbSpeechRecognizerImpl::~SbSpeechRecognizerImpl() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ Cancel();
+
+ JniEnvExt* env = JniEnvExt::Get();
+ env->DeleteGlobalRef(j_voice_recognizer_);
+}
+
+bool SbSpeechRecognizerImpl::Start(const SbSpeechConfiguration* configuration) {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+ SB_DCHECK(configuration);
+
+ if (is_started_) {
+ return false;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(
+ j_voice_recognizer_, "startRecognition", "(ZZIJ)V",
+ configuration->continuous, configuration->interim_results,
+ configuration->max_alternatives, reinterpret_cast<jlong>(this));
+
+ is_started_ = true;
+ return true;
+}
+
+void SbSpeechRecognizerImpl::Stop() {
+ SB_DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!is_started_) {
+ return;
+ }
+
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(j_voice_recognizer_, "stopRecognition", "()V");
+
+ is_started_ = false;
+}
+
+void SbSpeechRecognizerImpl::Cancel() {
+ Stop();
+}
+
+void SbSpeechRecognizerImpl::OnSpeechDetected(bool detected) {
+ // Called from Android's UI thread instead of constructor's thread.
+ SB_DCHECK(!thread_checker_.CalledOnValidThread());
+
+ handler_.on_speech_detected(handler_.context, detected);
+}
+
+void SbSpeechRecognizerImpl::OnError(int error) {
+ // Called from Android's UI thread instead of constructor's thread.
+ SB_DCHECK(!thread_checker_.CalledOnValidThread());
+
+ SbSpeechRecognizerError recognizer_error;
+ switch (error) {
+ case kErrorNetworkTimeout:
+ recognizer_error = kSbNetworkError;
+ break;
+ case kErrorNetwork:
+ recognizer_error = kSbNetworkError;
+ break;
+ case kErrorAudio:
+ recognizer_error = kSbAudioCaptureError;
+ break;
+ case kErrorService:
+ recognizer_error = kSbNetworkError;
+ break;
+ case kErrorClient:
+ recognizer_error = kSbAborted;
+ break;
+ case kErrorSpeechTimeout:
+ recognizer_error = kSbNoSpeechError;
+ break;
+ case kErrorRecognizerBusy:
+ case kErrorInsufficientPermissions:
+ recognizer_error = kSbNotAllowed;
+ break;
+ case kErrorNoMatch:
+ // Maybe keep listening until found a match or time-out triggered by
+ // client.
+ recognizer_error = kSbNoSpeechError;
+ break;
+ }
+ handler_.on_error(handler_.context, recognizer_error);
+}
+
+void SbSpeechRecognizerImpl::OnResults(const std::vector<std::string>& results,
+ const std::vector<float>& confidences,
+ bool is_final) {
+ // Called from Android's UI thread instead of constructor's thread.
+ SB_DCHECK(!thread_checker_.CalledOnValidThread());
+
+ bool has_confidence = (confidences.size() != 0);
+ if (has_confidence) {
+ SB_DCHECK(confidences.size() == results.size());
+ }
+ int kSpeechResultSize = results.size();
+ std::vector<SbSpeechResult> speech_results(kSpeechResultSize);
+ for (int i = 0; i < kSpeechResultSize; ++i) {
+ // The callback is responsible for freeing the buffer with
+ // SbMemoryDeallocate.
+ speech_results[i].transcript = SbStringDuplicate(results[i].c_str());
+ speech_results[i].confidence =
+ has_confidence ? confidences[i]
+ : std::numeric_limits<float>::quiet_NaN();
+ }
+ handler_.on_results(handler_.context, speech_results.data(),
+ kSpeechResultSize, is_final);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+namespace {
+
+using starboard::android::shared::JniEnvExt;
+
+starboard::Mutex s_speech_recognizer_mutex_;
+SbSpeechRecognizer s_speech_recognizer = kSbSpeechRecognizerInvalid;
+} // namespace
+
+// static
+SbSpeechRecognizer SbSpeechRecognizerPrivate::CreateSpeechRecognizer(
+ const SbSpeechRecognizerHandler* handler) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ SB_DCHECK(!SbSpeechRecognizerIsValid(s_speech_recognizer));
+ s_speech_recognizer =
+ new starboard::android::shared::SbSpeechRecognizerImpl(handler);
+ return s_speech_recognizer;
+}
+
+// static
+void SbSpeechRecognizerPrivate::DestroySpeechRecognizer(
+ SbSpeechRecognizer speech_recognizer) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ SB_DCHECK(s_speech_recognizer == speech_recognizer);
+ SB_DCHECK(SbSpeechRecognizerIsValid(s_speech_recognizer));
+ delete s_speech_recognizer;
+ s_speech_recognizer = kSbSpeechRecognizerInvalid;
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VoiceRecognizer_nativeOnSpeechDetected(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeSpeechRecognizerImpl,
+ jboolean detected) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ starboard::android::shared::SbSpeechRecognizerImpl* native =
+ reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
+ nativeSpeechRecognizerImpl);
+ // This is called by the Android UI thread and it is possible that the
+ // SbSpeechRecognizer is destroyed before this is called.
+ if (native != s_speech_recognizer) {
+ SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
+ return;
+ }
+
+ native->OnSpeechDetected(detected);
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VoiceRecognizer_nativeOnError(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeSpeechRecognizerImpl,
+ jint error) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ starboard::android::shared::SbSpeechRecognizerImpl* native =
+ reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
+ nativeSpeechRecognizerImpl);
+ // This is called by the Android UI thread and it is possible that the
+ // SbSpeechRecognizer is destroyed before this is called.
+ if (native != s_speech_recognizer) {
+ SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
+ return;
+ }
+
+ native->OnError(error);
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VoiceRecognizer_nativeHandlePermission(
+ JNIEnv* env,
+ jobject jcaller,
+ jlong nativeSpeechRecognizerImpl,
+ jboolean is_granted) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ starboard::android::shared::SbSpeechRecognizerImpl* native =
+ reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
+ nativeSpeechRecognizerImpl);
+ // This is called by the Android UI thread and it is possible that the
+ // SbSpeechRecognizer is destroyed before this is called.
+ if (native != s_speech_recognizer) {
+ SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
+ return;
+ }
+ if (!is_granted) {
+ native->OnError(starboard::android::shared::kErrorInsufficientPermissions);
+ }
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_coat_VoiceRecognizer_nativeOnResults(
+ JniEnvExt* env,
+ jobject unused_this,
+ jlong nativeSpeechRecognizerImpl,
+ jobjectArray results,
+ jfloatArray confidences,
+ jboolean is_final) {
+ starboard::ScopedLock lock(s_speech_recognizer_mutex_);
+
+ starboard::android::shared::SbSpeechRecognizerImpl* native =
+ reinterpret_cast<starboard::android::shared::SbSpeechRecognizerImpl*>(
+ nativeSpeechRecognizerImpl);
+ // This is called by the Android UI thread and it is possible that the
+ // SbSpeechRecognizer is destroyed before this is called.
+ if (native != s_speech_recognizer) {
+ SB_DLOG(WARNING) << "The speech recognizer is destroyed.";
+ return;
+ }
+
+ std::vector<std::string> options;
+ jint argc = env->GetArrayLength(results);
+ for (jint i = 0; i < argc; i++) {
+ starboard::android::shared::ScopedLocalJavaRef<jstring> element(
+ env->GetObjectArrayElement(results, i));
+ std::string utf_str = env->GetStringStandardUTFOrAbort(element.Get());
+ options.push_back(utf_str);
+ }
+
+ std::vector<float> scores(options.size(), 0.0);
+ if (confidences != NULL) {
+ SB_DCHECK(argc == env->GetArrayLength(results));
+ float* confidences_array = env->GetFloatArrayElements(confidences, NULL);
+ std::copy(confidences_array, confidences_array + argc, scores.begin());
+ env->ReleaseFloatArrayElements(confidences, confidences_array, 0);
+ }
+ native->OnResults(options, scores, is_final);
+}
diff --git a/src/starboard/android/shared/speech_synthesis_cancel.cc b/src/starboard/android/shared/speech_synthesis_cancel.cc
new file mode 100644
index 0000000..6eab70e
--- /dev/null
+++ b/src/starboard/android/shared/speech_synthesis_cancel.cc
@@ -0,0 +1,31 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/speech_synthesis.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+void SbSpeechSynthesisCancel() {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ ScopedLocalJavaRef<jobject> j_tts_helper(
+ env->CallStarboardObjectMethodOrAbort(
+ "getTextToSpeechHelper",
+ "()Ldev/cobalt/coat/CobaltTextToSpeechHelper;"));
+ env->CallVoidMethodOrAbort(j_tts_helper.Get(), "cancel", "()V");
+}
diff --git a/src/starboard/android/shared/speech_synthesis_speak.cc b/src/starboard/android/shared/speech_synthesis_speak.cc
new file mode 100644
index 0000000..b571951
--- /dev/null
+++ b/src/starboard/android/shared/speech_synthesis_speak.cc
@@ -0,0 +1,33 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/speech_synthesis.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+void SbSpeechSynthesisSpeak(const char* text) {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_tts_helper(
+ env->CallStarboardObjectMethodOrAbort(
+ "getTextToSpeechHelper",
+ "()Ldev/cobalt/coat/CobaltTextToSpeechHelper;"));
+ ScopedLocalJavaRef<jstring> j_text_string(
+ env->NewStringStandardUTFOrAbort(text));
+ env->CallVoidMethodOrAbort(j_tts_helper.Get(),
+ "speak", "(Ljava/lang/String;)V", j_text_string.Get());
+}
diff --git a/src/starboard/android/shared/starboard_platform.gypi b/src/starboard/android/shared/starboard_platform.gypi
new file mode 100644
index 0000000..64bcca9
--- /dev/null
+++ b/src/starboard/android/shared/starboard_platform.gypi
@@ -0,0 +1,486 @@
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+ 'variables': {
+ 'has_input_events_filter' : '<!(python ../../../build/file_exists.py <(DEPTH)/starboard/android/shared/input_events_filter.cc)',
+ },
+ 'includes': [
+ '<(DEPTH)/starboard/shared/starboard/player/filter/player_filter.gypi',
+ ],
+ 'targets': [
+ {
+ 'target_name': 'starboard_base_symbolize',
+ 'type': 'static_library',
+ 'sources': [
+ '<(DEPTH)/base/third_party/symbolize/demangle.cc',
+ '<(DEPTH)/base/third_party/symbolize/symbolize.cc',
+ ],
+ },
+ # Copy sources that we compile from the NDK so that we can reference them
+ # by a relative path. Otherwise, without GYP pathname relativization
+ # different configuration builds would clobber each other since they'd all
+ # generate their .o at the same path in the NDK sources directory.
+ {
+ 'target_name': 'ndk_sources',
+ 'type': 'none',
+ 'copies': [{
+ 'destination': '<(SHARED_INTERMEDIATE_DIR)/ndk-sources/',
+ 'files': [
+ '<(NDK_HOME)/sources/android/cpufeatures/cpu-features.c',
+ ],
+ }],
+ },
+ {
+ 'target_name': 'starboard_platform',
+ 'type': 'static_library',
+ 'sources': [
+ '<@(filter_based_player_sources)',
+ 'accessibility_get_caption_settings.cc',
+ 'accessibility_get_display_settings.cc',
+ 'accessibility_get_text_to_speech_settings.cc',
+ 'accessibility_set_captions_enabled.cc',
+ 'android_main.cc',
+ 'application_android.cc',
+ 'application_android.h',
+ 'atomic_public.h',
+ 'audio_decoder.cc',
+ 'audio_decoder.h',
+ 'audio_renderer.h',
+ 'audio_sink_get_max_channels.cc',
+ 'audio_sink_get_nearest_supported_sample_frequency.cc',
+ 'audio_sink_is_audio_frame_storage_type_supported.cc',
+ 'audio_sink_is_audio_sample_type_supported.cc',
+ 'audio_track_audio_sink_type.cc',
+ 'audio_track_audio_sink_type.h',
+ 'configuration_public.h',
+ 'decode_target_create.cc',
+ 'decode_target_create.h',
+ 'decode_target_get_info.cc',
+ 'decode_target_internal.cc',
+ 'decode_target_internal.h',
+ 'decode_target_release.cc',
+ 'directory_close.cc',
+ 'directory_get_next.cc',
+ 'directory_internal.h',
+ 'directory_open.cc',
+ 'drm_create_system.cc',
+ 'drm_system.cc',
+ 'drm_system.h',
+ 'egl_swap_buffers.cc',
+ 'file_can_open.cc',
+ 'file_close.cc',
+ 'file_delete.cc',
+ 'file_exists.cc',
+ 'file_flush.cc',
+ 'file_get_info.cc',
+ 'file_get_path_info.cc',
+ 'file_internal.cc',
+ 'file_internal.h',
+ 'file_open.cc',
+ 'file_read.cc',
+ 'file_seek.cc',
+ 'file_truncate.cc',
+ 'file_write.cc',
+ 'get_home_directory.cc',
+ 'input_events_generator.cc',
+ 'input_events_generator.h',
+ 'jni_env_ext.cc',
+ 'jni_env_ext.h',
+ 'jni_utils.h',
+ 'log.cc',
+ 'log_flush.cc',
+ 'log_format.cc',
+ 'log_internal.h',
+ 'log_is_tty.cc',
+ 'log_raw.cc',
+ 'main.cc',
+ 'media_codec_bridge.cc',
+ 'media_codec_bridge.h',
+ 'media_common.h',
+ 'media_decoder.cc',
+ 'media_decoder.h',
+ 'media_get_audio_configuration.cc',
+ 'media_get_audio_output_count.cc',
+ 'media_get_initial_buffer_capacity.cc',
+ 'media_get_max_buffer_capacity.cc',
+ 'media_is_audio_supported.cc',
+ 'media_is_output_protected.cc',
+ 'media_is_supported.cc',
+ 'media_is_video_supported.cc',
+ 'media_set_output_protection.cc',
+ 'microphone_impl.cc',
+ 'player_components_impl.cc',
+ 'player_create.cc',
+ 'player_destroy.cc',
+ 'player_set_bounds.cc',
+ 'player_set_playback_rate.cc',
+ 'sanitizer_options.cc',
+ 'socket_get_interface_address.cc',
+ 'speech_recognizer_impl.cc',
+ 'speech_synthesis_cancel.cc',
+ 'speech_synthesis_speak.cc',
+ 'system_get_connection_type.cc',
+ 'system_get_device_type.cc',
+ 'system_get_locale_id.cc',
+ 'system_get_path.cc',
+ 'system_get_property.cc',
+ 'system_get_stack.cc',
+ 'system_has_capability.cc',
+ 'system_platform_error.cc',
+ 'system_request_stop.cc',
+ 'system_request_suspend.cc',
+ 'thread_create.cc',
+ 'thread_create_priority.cc',
+ 'thread_get_name.cc',
+ 'thread_types_public.h',
+ 'time_zone_get_dst_name.cc',
+ 'time_zone_get_name.cc',
+ 'trace_util.h',
+ 'video_decoder.cc',
+ 'video_decoder.h',
+ 'video_render_algorithm.cc',
+ 'video_render_algorithm.h',
+ 'video_window.cc',
+ 'video_window.h',
+ 'window_create.cc',
+ 'window_destroy.cc',
+ 'window_get_platform_handle.cc',
+ 'window_get_size.cc',
+ 'window_internal.h',
+ '<(SHARED_INTERMEDIATE_DIR)/ndk-sources/cpu-features.c',
+ '<(DEPTH)/starboard/accessibility.h',
+ '<(DEPTH)/starboard/atomic.h',
+ '<(DEPTH)/starboard/audio_sink.h',
+ '<(DEPTH)/starboard/common/ref_counted.h',
+ '<(DEPTH)/starboard/common/scoped_ptr.h',
+ '<(DEPTH)/starboard/condition_variable.h',
+ '<(DEPTH)/starboard/configuration.h',
+ '<(DEPTH)/starboard/decode_target.h',
+ '<(DEPTH)/starboard/directory.h',
+ '<(DEPTH)/starboard/drm.h',
+ '<(DEPTH)/starboard/event.h',
+ '<(DEPTH)/starboard/export.h',
+ '<(DEPTH)/starboard/file.h',
+ '<(DEPTH)/starboard/input.h',
+ '<(DEPTH)/starboard/key.h',
+ '<(DEPTH)/starboard/log.h',
+ '<(DEPTH)/starboard/media.h',
+ '<(DEPTH)/starboard/memory.h',
+ '<(DEPTH)/starboard/mutex.h',
+ '<(DEPTH)/starboard/once.h',
+ '<(DEPTH)/starboard/player.h',
+ '<(DEPTH)/starboard/queue.h',
+ '<(DEPTH)/starboard/socket.h',
+ '<(DEPTH)/starboard/speech_synthesis.h',
+ '<(DEPTH)/starboard/string.h',
+ '<(DEPTH)/starboard/system.h',
+ '<(DEPTH)/starboard/thread.h',
+ '<(DEPTH)/starboard/time_zone.h',
+ '<(DEPTH)/starboard/types.h',
+ '<(DEPTH)/starboard/window.h',
+ '<(DEPTH)/starboard/android/shared/window_blur_on_screen_keyboard.cc',
+ '<(DEPTH)/starboard/android/shared//window_focus_on_screen_keyboard.cc',
+ '<(DEPTH)/starboard/android/shared/window_get_on_screen_keyboard_bounding_rect.cc',
+ '<(DEPTH)/starboard/android/shared/window_hide_on_screen_keyboard.cc',
+ '<(DEPTH)/starboard/android/shared/window_is_on_screen_keyboard_shown.cc',
+ '<(DEPTH)/starboard/android/shared/window_set_on_screen_keyboard_keep_focus.cc',
+ '<(DEPTH)/starboard/android/shared/window_show_on_screen_keyboard.cc',
+ '<(DEPTH)/starboard/shared/dlmalloc/memory_map.cc',
+ '<(DEPTH)/starboard/shared/dlmalloc/memory_protect.cc',
+ '<(DEPTH)/starboard/shared/dlmalloc/memory_unmap.cc',
+ '<(DEPTH)/starboard/shared/gcc/atomic_gcc_public.h',
+ '<(DEPTH)/starboard/shared/gles/gl_call.h',
+ '<(DEPTH)/starboard/shared/internal_only.h',
+ '<(DEPTH)/starboard/shared/iso/character_is_alphanumeric.cc',
+ '<(DEPTH)/starboard/shared/iso/character_is_digit.cc',
+ '<(DEPTH)/starboard/shared/iso/character_is_hex_digit.cc',
+ '<(DEPTH)/starboard/shared/iso/character_is_space.cc',
+ '<(DEPTH)/starboard/shared/iso/character_is_upper.cc',
+ '<(DEPTH)/starboard/shared/iso/character_to_lower.cc',
+ '<(DEPTH)/starboard/shared/iso/character_to_upper.cc',
+ '<(DEPTH)/starboard/shared/iso/double_absolute.cc',
+ '<(DEPTH)/starboard/shared/iso/double_exponent.cc',
+ '<(DEPTH)/starboard/shared/iso/double_floor.cc',
+ '<(DEPTH)/starboard/shared/iso/double_is_finite.cc',
+ '<(DEPTH)/starboard/shared/iso/double_is_nan.cc',
+ '<(DEPTH)/starboard/shared/iso/impl/directory_close.h',
+ '<(DEPTH)/starboard/shared/iso/impl/directory_get_next.h',
+ '<(DEPTH)/starboard/shared/iso/impl/directory_open.h',
+ '<(DEPTH)/starboard/shared/iso/memory_allocate_unchecked.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_compare.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_copy.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_find_byte.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_free.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_move.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_reallocate_unchecked.cc',
+ '<(DEPTH)/starboard/shared/iso/memory_set.cc',
+ '<(DEPTH)/starboard/shared/iso/string_compare.cc',
+ '<(DEPTH)/starboard/shared/iso/string_compare_all.cc',
+ '<(DEPTH)/starboard/shared/iso/string_find_character.cc',
+ '<(DEPTH)/starboard/shared/iso/string_find_last_character.cc',
+ '<(DEPTH)/starboard/shared/iso/string_find_string.cc',
+ '<(DEPTH)/starboard/shared/iso/string_get_length.cc',
+ '<(DEPTH)/starboard/shared/iso/string_get_length_wide.cc',
+ '<(DEPTH)/starboard/shared/iso/string_parse_double.cc',
+ '<(DEPTH)/starboard/shared/iso/string_parse_signed_integer.cc',
+ '<(DEPTH)/starboard/shared/iso/string_parse_uint64.cc',
+ '<(DEPTH)/starboard/shared/iso/string_parse_unsigned_integer.cc',
+ '<(DEPTH)/starboard/shared/iso/string_scan.cc',
+ '<(DEPTH)/starboard/shared/iso/system_binary_search.cc',
+ '<(DEPTH)/starboard/shared/iso/system_sort.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_add.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_create.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_destroy.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_internal.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_remove.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_wait_timed.cc',
+ '<(DEPTH)/starboard/shared/libevent/socket_waiter_wake_up.cc',
+ '<(DEPTH)/starboard/shared/linux/byte_swap.cc',
+ '<(DEPTH)/starboard/shared/linux/memory_get_stack_bounds.cc',
+ '<(DEPTH)/starboard/shared/linux/page_internal.cc',
+ '<(DEPTH)/starboard/shared/linux/system_get_random_data.cc',
+ '<(DEPTH)/starboard/shared/linux/system_get_total_cpu_memory.cc',
+ '<(DEPTH)/starboard/shared/linux/system_get_used_cpu_memory.cc',
+ '<(DEPTH)/starboard/shared/linux/system_is_debugger_attached.cc',
+ '<(DEPTH)/starboard/shared/linux/system_symbolize.cc',
+ '<(DEPTH)/starboard/shared/linux/thread_get_id.cc',
+ '<(DEPTH)/starboard/shared/linux/thread_set_name.cc',
+ '<(DEPTH)/starboard/shared/nouser/user_get_current.cc',
+ '<(DEPTH)/starboard/shared/nouser/user_get_property.cc',
+ '<(DEPTH)/starboard/shared/nouser/user_get_signed_in.cc',
+ '<(DEPTH)/starboard/shared/nouser/user_internal.cc',
+ '<(DEPTH)/starboard/shared/nouser/user_internal.h',
+ '<(DEPTH)/starboard/shared/posix/directory_create.cc',
+ '<(DEPTH)/starboard/shared/posix/impl/file_can_open.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_close.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_delete.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_flush.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_get_info.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_get_path_info.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_open.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_read.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_seek.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_truncate.h',
+ '<(DEPTH)/starboard/shared/posix/impl/file_write.h',
+ '<(DEPTH)/starboard/shared/posix/memory_allocate_aligned_unchecked.cc',
+ '<(DEPTH)/starboard/shared/posix/memory_flush.cc',
+ '<(DEPTH)/starboard/shared/posix/memory_free_aligned.cc',
+ '<(DEPTH)/starboard/shared/posix/set_non_blocking_internal.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_accept.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_bind.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_clear_last_error.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_connect.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_create.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_destroy.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_free_resolution.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_get_last_error.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_get_local_address.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_internal.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_is_connected.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_is_connected_and_idle.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_join_multicast_group.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_listen.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_receive_from.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_resolve.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_send_to.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_broadcast.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_receive_buffer_size.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_reuse_address.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_send_buffer_size.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_tcp_keep_alive.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_tcp_no_delay.cc',
+ '<(DEPTH)/starboard/shared/posix/socket_set_tcp_window_scaling.cc',
+ '<(DEPTH)/starboard/shared/posix/storage_write_record.cc',
+ '<(DEPTH)/starboard/shared/posix/string_compare_no_case.cc',
+ '<(DEPTH)/starboard/shared/posix/string_compare_no_case_n.cc',
+ '<(DEPTH)/starboard/shared/posix/string_compare_wide.cc',
+ '<(DEPTH)/starboard/shared/posix/string_format.cc',
+ '<(DEPTH)/starboard/shared/posix/string_format_wide.cc',
+ '<(DEPTH)/starboard/shared/posix/system_break_into_debugger.cc',
+ '<(DEPTH)/starboard/shared/posix/system_clear_last_error.cc',
+ '<(DEPTH)/starboard/shared/posix/system_get_error_string.cc',
+ '<(DEPTH)/starboard/shared/posix/system_get_last_error.cc',
+ '<(DEPTH)/starboard/shared/posix/system_get_number_of_processors.cc',
+ '<(DEPTH)/starboard/shared/posix/thread_sleep.cc',
+ '<(DEPTH)/starboard/shared/posix/time_get_monotonic_now.cc',
+ '<(DEPTH)/starboard/shared/posix/time_get_monotonic_thread_now.cc',
+ '<(DEPTH)/starboard/shared/posix/time_get_now.cc',
+ '<(DEPTH)/starboard/shared/posix/time_zone_get_current.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_broadcast.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_create.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_destroy.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_signal.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_wait.cc',
+ '<(DEPTH)/starboard/shared/pthread/condition_variable_wait_timed.cc',
+ '<(DEPTH)/starboard/shared/pthread/mutex_acquire.cc',
+ '<(DEPTH)/starboard/shared/pthread/mutex_acquire_try.cc',
+ '<(DEPTH)/starboard/shared/pthread/mutex_create.cc',
+ '<(DEPTH)/starboard/shared/pthread/mutex_destroy.cc',
+ '<(DEPTH)/starboard/shared/pthread/mutex_release.cc',
+ '<(DEPTH)/starboard/shared/pthread/once.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_create_local_key.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_create_priority.h',
+ '<(DEPTH)/starboard/shared/pthread/thread_destroy_local_key.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_detach.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_get_current.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_get_local_value.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_is_equal.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_join.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_set_local_value.cc',
+ '<(DEPTH)/starboard/shared/pthread/thread_yield.cc',
+ '<(DEPTH)/starboard/shared/pthread/types_public.h',
+ '<(DEPTH)/starboard/shared/signal/crash_signals.h',
+ '<(DEPTH)/starboard/shared/signal/crash_signals_sigaction.cc',
+ '<(DEPTH)/starboard/shared/signal/suspend_signals.cc',
+ '<(DEPTH)/starboard/shared/signal/suspend_signals.h',
+ '<(DEPTH)/starboard/shared/starboard/application.cc',
+ '<(DEPTH)/starboard/shared/starboard/application.h',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_create.cc',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_destroy.cc',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.cc',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/audio_sink_is_valid.cc',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.cc',
+ '<(DEPTH)/starboard/shared/starboard/audio_sink/stub_audio_sink_type.h',
+ '<(DEPTH)/starboard/shared/starboard/command_line.cc',
+ '<(DEPTH)/starboard/shared/starboard/command_line.h',
+ '<(DEPTH)/starboard/shared/starboard/directory_can_open.cc',
+ '<(DEPTH)/starboard/shared/starboard/drm/drm_close_session.cc',
+ '<(DEPTH)/starboard/shared/starboard/drm/drm_destroy_system.cc',
+ '<(DEPTH)/starboard/shared/starboard/drm/drm_generate_session_update_request.cc',
+ '<(DEPTH)/starboard/shared/starboard/drm/drm_system_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/drm/drm_update_session.cc',
+ '<(DEPTH)/starboard/shared/starboard/event_cancel.cc',
+ '<(DEPTH)/starboard/shared/starboard/event_schedule.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_mode_string_to_flags.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_storage/storage_close_record.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_storage/storage_delete_record.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_storage/storage_get_record_size.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_storage/storage_open_record.cc',
+ '<(DEPTH)/starboard/shared/starboard/file_storage/storage_read_record.cc',
+ '<(DEPTH)/starboard/shared/starboard/log_message.cc',
+ '<(DEPTH)/starboard/shared/starboard/log_raw_dump_stack.cc',
+ '<(DEPTH)/starboard/shared/starboard/log_raw_format.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/codec_util.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/codec_util.h',
+ '<(DEPTH)/starboard/shared/starboard/media/media_can_play_mime_and_key_system.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_audio_buffer_budget.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_alignment.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_allocation_unit.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_garbage_collection_duration_threshold.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_padding.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_buffer_storage_type.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_progressive_buffer_budget.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_get_video_buffer_budget.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_is_buffer_pool_allocate_on_demand.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_is_buffer_using_memory_pool.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_util.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/media_util.h',
+ '<(DEPTH)/starboard/shared/starboard/media/mime_type.cc',
+ '<(DEPTH)/starboard/shared/starboard/media/mime_type.h',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_close.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_create.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_destroy.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_get_available.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_is_sample_rate_supported.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_open.cc',
+ '<(DEPTH)/starboard/shared/starboard/microphone/microphone_read.cc',
+ '<(DEPTH)/starboard/shared/starboard/new.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/decoded_audio_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/input_buffer_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/player/job_queue.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/job_queue.h',
+ '<(DEPTH)/starboard/shared/starboard/player/player_get_current_frame.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_get_info.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_get_info2.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_get_maximum_number_of_samples_per_write.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_internal.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/player/player_output_mode_supported.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_seek.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_seek2.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_set_volume.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_worker.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_worker.h',
+ '<(DEPTH)/starboard/shared/starboard/player/player_write_end_of_stream.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_write_sample.cc',
+ '<(DEPTH)/starboard/shared/starboard/player/player_write_sample2.cc',
+ '<(DEPTH)/starboard/shared/starboard/queue_application.cc',
+ '<(DEPTH)/starboard/shared/starboard/queue_application.h',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_cancel.cc',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_create.cc',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_destroy.cc',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_internal.h',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_start.cc',
+ '<(DEPTH)/starboard/shared/starboard/speech_recognizer/speech_recognizer_stop.cc',
+ '<(DEPTH)/starboard/shared/starboard/string_concat.cc',
+ '<(DEPTH)/starboard/shared/starboard/string_concat_wide.cc',
+ '<(DEPTH)/starboard/shared/starboard/string_copy.cc',
+ '<(DEPTH)/starboard/shared/starboard/string_copy_wide.cc',
+ '<(DEPTH)/starboard/shared/starboard/string_duplicate.cc',
+ '<(DEPTH)/starboard/shared/starboard/system_get_random_uint64.cc',
+ '<(DEPTH)/starboard/shared/starboard/system_supports_resume.cc',
+ '<(DEPTH)/starboard/shared/starboard/thread_checker.h',
+ '<(DEPTH)/starboard/shared/starboard/window_set_default_options.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_create_transformer.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_destroy_transformer.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_get_tag.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_set_authenticated_data.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_set_initialization_vector.cc',
+ '<(DEPTH)/starboard/shared/stub/cryptography_transform.cc',
+ '<(DEPTH)/starboard/shared/stub/drm_is_server_certificate_updatable.cc',
+ '<(DEPTH)/starboard/shared/stub/drm_update_server_certificate.cc',
+ '<(DEPTH)/starboard/shared/stub/image_decode.cc',
+ '<(DEPTH)/starboard/shared/stub/image_is_decode_supported.cc',
+ '<(DEPTH)/starboard/shared/stub/system_get_total_gpu_memory.cc',
+ '<(DEPTH)/starboard/shared/stub/system_get_used_gpu_memory.cc',
+ '<(DEPTH)/starboard/shared/stub/system_hide_splash_screen.cc',
+ '<(DEPTH)/starboard/shared/stub/system_request_pause.cc',
+ '<(DEPTH)/starboard/shared/stub/system_request_unpause.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_context_get_pointer.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_sampler_create.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_sampler_destroy.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_sampler_freeze.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_sampler_is_supported.cc',
+ '<(DEPTH)/starboard/shared/stub/thread_sampler_thaw.cc',
+ '<(DEPTH)/starboard/shared/stub/window_get_diagonal_size_in_inches.cc',
+ ],
+ 'conditions': [
+ ['has_input_events_filter=="True"', {
+ 'sources': [
+ 'input_events_filter.cc',
+ 'input_events_filter.h',
+ ],
+ 'defines': [
+ 'STARBOARD_INPUT_EVENTS_FILTER',
+ ],
+ }],
+ ],
+ 'defines': [
+ # This must be defined when building Starboard, and must not when
+ # building Starboard client code.
+ 'STARBOARD_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/starboard/common/common.gyp:common',
+ '<(DEPTH)/third_party/libevent/libevent.gyp:libevent',
+ 'starboard_base_symbolize',
+ ],
+ },
+ ],
+}
diff --git a/src/starboard/android/shared/starboard_platform_tests.gypi b/src/starboard/android/shared/starboard_platform_tests.gypi
new file mode 100644
index 0000000..5b40e63
--- /dev/null
+++ b/src/starboard/android/shared/starboard_platform_tests.gypi
@@ -0,0 +1,49 @@
+# Copyright 2016 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+{
+ 'targets': [
+ {
+ 'target_name': 'starboard_platform_tests',
+ 'type': '<(gtest_target_type)',
+ 'includes': [
+ '<(DEPTH)/starboard/shared/starboard/media/media_tests.gypi',
+ ],
+ 'sources': [
+ '<(DEPTH)/starboard/common/test_main.cc',
+ '<@(media_tests_sources)',
+ 'jni_env_ext_test.cc',
+ ],
+ 'defines': [
+ # This allows the tests to include internal only header files.
+ 'STARBOARD_IMPLEMENTATION',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/starboard/starboard.gyp:starboard',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ ],
+ },
+ {
+ 'target_name': 'starboard_platform_tests_deploy',
+ 'type': 'none',
+ 'dependencies': [
+ '<(DEPTH)/<(starboard_path)/starboard_platform_tests.gyp:starboard_platform_tests',
+ ],
+ 'variables': {
+ 'executable_name': 'starboard_platform_tests',
+ },
+ 'includes': [ '<(DEPTH)/starboard/build/deploy.gypi' ],
+ },
+ ],
+}
diff --git a/src/starboard/android/shared/system_get_connection_type.cc b/src/starboard/android/shared/system_get_connection_type.cc
new file mode 100644
index 0000000..228726d
--- /dev/null
+++ b/src/starboard/android/shared/system_get_connection_type.cc
@@ -0,0 +1,32 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/log.h"
+
+using starboard::android::shared::JniEnvExt;
+
+SbSystemConnectionType SbSystemGetConnectionType() {
+ JniEnvExt* env = JniEnvExt::Get();
+ jboolean isWireless =
+ env->CallStarboardBooleanMethodOrAbort("isCurrentNetworkWireless", "()Z");
+
+ if (isWireless) {
+ return kSbSystemConnectionTypeWireless;
+ } else {
+ return kSbSystemConnectionTypeWired;
+ }
+}
diff --git a/src/starboard/android/shared/system_get_device_type.cc b/src/starboard/android/shared/system_get_device_type.cc
new file mode 100644
index 0000000..97d6a40
--- /dev/null
+++ b/src/starboard/android/shared/system_get_device_type.cc
@@ -0,0 +1,19 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+SbSystemDeviceType SbSystemGetDeviceType() {
+ return kSbSystemDeviceTypeAndroidTV;
+}
diff --git a/src/starboard/android/shared/system_get_locale_id.cc b/src/starboard/android/shared/system_get_locale_id.cc
new file mode 100644
index 0000000..2b11e0e
--- /dev/null
+++ b/src/starboard/android/shared/system_get_locale_id.cc
@@ -0,0 +1,49 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include <string>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/once.h"
+#include "starboard/string.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+namespace {
+
+// A singleton class to hold a locale string
+class LocaleInfo {
+ public:
+ // The Starboard locale id
+ std::string locale_id;
+
+ LocaleInfo() {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ ScopedLocalJavaRef<jstring> result(env->CallStarboardObjectMethodOrAbort(
+ "systemGetLocaleId", "()Ljava/lang/String;"));
+ locale_id = env->GetStringStandardUTFOrAbort(result.Get());
+ }
+};
+
+SB_ONCE_INITIALIZE_FUNCTION(LocaleInfo, GetLocale);
+} // namespace
+
+const char* SbSystemGetLocaleId() {
+ return GetLocale()->locale_id.c_str();
+}
diff --git a/src/starboard/android/shared/system_get_path.cc b/src/starboard/android/shared/system_get_path.cc
new file mode 100644
index 0000000..3a2c323
--- /dev/null
+++ b/src/starboard/android/shared/system_get_path.cc
@@ -0,0 +1,109 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include <linux/limits.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cstring>
+
+#include "starboard/android/shared/file_internal.h"
+#include "starboard/directory.h"
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+using ::starboard::android::shared::g_app_assets_dir;
+using ::starboard::android::shared::g_app_cache_dir;
+using ::starboard::android::shared::g_app_lib_dir;
+
+bool SbSystemGetPath(SbSystemPathId path_id, char* out_path, int path_size) {
+ if (!out_path || !path_size) {
+ return false;
+ }
+
+ const int kPathSize = PATH_MAX;
+ char path[kPathSize];
+ path[0] = '\0';
+
+ switch (path_id) {
+ case kSbSystemPathContentDirectory: {
+ if (SbStringConcat(path, g_app_assets_dir, kPathSize) >= kPathSize) {
+ return false;
+ }
+ break;
+ }
+
+ case kSbSystemPathCacheDirectory: {
+ if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) {
+ return false;
+ }
+ if (SbStringConcat(path, "/cache", kPathSize) >= kPathSize) {
+ return false;
+ }
+
+ SbDirectoryCreate(path);
+ break;
+ }
+
+ case kSbSystemPathDebugOutputDirectory: {
+ if (!SbSystemGetPath(kSbSystemPathTempDirectory, path, kPathSize)) {
+ return false;
+ }
+ if (SbStringConcat(path, "/log", kPathSize) >= kPathSize) {
+ return false;
+ }
+
+ SbDirectoryCreate(path);
+ break;
+ }
+
+ case kSbSystemPathTempDirectory: {
+ if (SbStringCopy(path, g_app_cache_dir, kPathSize) >= kPathSize) {
+ return false;
+ }
+
+ SbDirectoryCreate(path);
+ break;
+ }
+
+ case kSbSystemPathTestOutputDirectory: {
+ return SbSystemGetPath(kSbSystemPathDebugOutputDirectory, out_path,
+ path_size);
+ }
+
+ // We return the library directory as the "executable" since:
+ // a) Unlike the .so itself, it has a valid timestamp of the app install.
+ // b) Its parent directory is still a directory within our app package.
+ case kSbSystemPathExecutableFile: {
+ if (SbStringCopy(path, g_app_lib_dir, kPathSize) >= kPathSize) {
+ return false;
+ }
+ break;
+ }
+
+ default:
+ SB_NOTIMPLEMENTED();
+ return false;
+ }
+
+ int length = strlen(path);
+ if (length < 1 || length > path_size) {
+ return false;
+ }
+
+ SbStringCopy(out_path, path, path_size);
+ return true;
+}
diff --git a/src/starboard/android/shared/system_get_property.cc b/src/starboard/android/shared/system_get_property.cc
new file mode 100644
index 0000000..cbd49e1
--- /dev/null
+++ b/src/starboard/android/shared/system_get_property.cc
@@ -0,0 +1,137 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "sys/system_properties.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/log.h"
+#include "starboard/string.h"
+
+// We can't #include "base/stringize_macros.h" in Starboard
+#define STRINGIZE_NO_EXPANSION(x) #x
+#define STRINGIZE(x) STRINGIZE_NO_EXPANSION(x)
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+namespace {
+
+const char kFriendlyName[] = "Android";
+const char kUnknownValue[] = "unknown";
+// This is a format string template and the %s is meant to be replaced by
+// the Android release version number (e.g. "7.0" for Nougat).
+const char kPlatformNameFormat[] =
+ "Linux " STRINGIZE(ANDROID_ABI) "; Android %s";
+
+bool CopyStringAndTestIfSuccess(char* out_value,
+ int value_length,
+ const char* from_value) {
+ if (SbStringGetLength(from_value) + 1 > value_length)
+ return false;
+ SbStringCopy(out_value, from_value, value_length);
+ return true;
+}
+
+bool GetAndroidSystemProperty(const char* system_property_name,
+ char* out_value,
+ int value_length,
+ const char* default_value) {
+ if (value_length < PROP_VALUE_MAX) {
+ return false;
+ }
+ // Note that __system_property_get returns empty string on no value
+ __system_property_get(system_property_name, out_value);
+
+ if (SbStringGetLength(out_value) == 0) {
+ SbStringCopy(out_value, default_value, value_length);
+ }
+ return true;
+}
+
+// Populate the kPlatformNameFormat with the version number and if
+// |value_length| is large enough to store the result, copy the result into
+// |out_value|.
+bool CopyAndroidPlatformName(char* out_value, int value_length) {
+ // Get the Android version number (e.g. "7.0" for Nougat).
+ const int kStringBufferSize = 256;
+ char version_string_buffer[kStringBufferSize];
+ if (!GetAndroidSystemProperty("ro.build.version.release",
+ version_string_buffer, kStringBufferSize,
+ kUnknownValue)) {
+ return false;
+ }
+
+ char result_string[kStringBufferSize];
+ SbStringFormatF(result_string, kStringBufferSize, kPlatformNameFormat,
+ version_string_buffer);
+
+ return CopyStringAndTestIfSuccess(out_value, value_length, result_string);
+}
+
+} // namespace
+
+bool SbSystemGetProperty(SbSystemPropertyId property_id,
+ char* out_value,
+ int value_length) {
+ if (!out_value || !value_length) {
+ return false;
+ }
+
+ switch (property_id) {
+ case kSbSystemPropertyBrandName:
+ return GetAndroidSystemProperty("ro.product.manufacturer", out_value,
+ value_length, kUnknownValue);
+ case kSbSystemPropertyModelName:
+ return GetAndroidSystemProperty("ro.product.model", out_value,
+ value_length, kUnknownValue);
+ case kSbSystemPropertyFirmwareVersion:
+ return GetAndroidSystemProperty("ro.build.id", out_value, value_length,
+ kUnknownValue);
+ case kSbSystemPropertyChipsetModelNumber:
+ return GetAndroidSystemProperty("ro.board.platform", out_value,
+ value_length, kUnknownValue);
+ case kSbSystemPropertyModelYear:
+ case kSbSystemPropertyNetworkOperatorName:
+ return false;
+
+ case kSbSystemPropertyFriendlyName:
+ return CopyStringAndTestIfSuccess(out_value, value_length, kFriendlyName);
+
+ case kSbSystemPropertyPlatformName:
+ return CopyAndroidPlatformName(out_value, value_length);
+
+ case kSbSystemPropertySpeechApiKey:
+ return false;
+ case kSbSystemPropertyUserAgentAuxField: {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> aux_string(
+ env->CallStarboardObjectMethodOrAbort(
+ "getUserAgentAuxField", "()Ljava/lang/String;"));
+
+ std::string utf_str = env->GetStringStandardUTFOrAbort(aux_string.Get());
+ bool success =
+ CopyStringAndTestIfSuccess(out_value, value_length, utf_str.c_str());
+ return success;
+ }
+ default:
+ SB_DLOG(WARNING) << __FUNCTION__
+ << ": Unrecognized property: " << property_id;
+ break;
+ }
+
+ return false;
+}
diff --git a/src/starboard/android/shared/system_get_stack.cc b/src/starboard/android/shared/system_get_stack.cc
new file mode 100644
index 0000000..05434b4
--- /dev/null
+++ b/src/starboard/android/shared/system_get_stack.cc
@@ -0,0 +1,56 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <unwind.h>
+
+#include "starboard/system.h"
+#include "starboard/log.h"
+
+namespace {
+class CallbackContext {
+ public:
+ void** out_stack;
+ int stack_size;
+ int count;
+ CallbackContext(void** out_stack, int stack_size)
+ : out_stack(out_stack), stack_size(stack_size), count(0) {}
+};
+
+_Unwind_Reason_Code UnwindCallback(struct _Unwind_Context* uwc,
+ void* user_context) {
+ CallbackContext* callback_context =
+ static_cast<CallbackContext*>(user_context);
+ _Unwind_Ptr ip = _Unwind_GetIP(uwc);
+
+ if (ip == 0) {
+ return _URC_END_OF_STACK;
+ }
+
+ if (callback_context->count == callback_context->stack_size) {
+ return _URC_END_OF_STACK;
+ }
+
+ callback_context->out_stack[callback_context->count] =
+ reinterpret_cast<void*>(ip);
+ callback_context->count++;
+ return _URC_NO_REASON;
+}
+} // namespace
+
+int SbSystemGetStack(void** out_stack, int stack_size) {
+ CallbackContext callback_context(out_stack, stack_size);
+
+ _Unwind_Backtrace(UnwindCallback, &callback_context);
+ return callback_context.count;
+}
diff --git a/src/starboard/android/shared/system_has_capability.cc b/src/starboard/android/shared/system_has_capability.cc
new file mode 100644
index 0000000..4d77b0d
--- /dev/null
+++ b/src/starboard/android/shared/system_has_capability.cc
@@ -0,0 +1,31 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "starboard/log.h"
+
+bool SbSystemHasCapability(SbSystemCapabilityId capability_id) {
+ switch (capability_id) {
+ case kSbSystemCapabilityReversedEnterAndBack:
+ return false;
+ case kSbSystemCapabilityCanQueryGPUMemoryStats:
+ return false;
+ case kSbSystemCapabilitySetsInputTimestamp:
+ return false;
+ }
+
+ SB_DLOG(WARNING) << "Unrecognized capability: " << capability_id;
+ return false;
+}
diff --git a/src/starboard/android/shared/system_platform_error.cc b/src/starboard/android/shared/system_platform_error.cc
new file mode 100644
index 0000000..1f66983
--- /dev/null
+++ b/src/starboard/android/shared/system_platform_error.cc
@@ -0,0 +1,92 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include <android/native_activity.h>
+#include <jni.h>
+
+#include "starboard/android/shared/application_android.h"
+#include "starboard/android/shared/jni_env_ext.h"
+
+using starboard::android::shared::ApplicationAndroid;
+using starboard::android::shared::JniEnvExt;
+
+struct SbSystemPlatformErrorPrivate {
+ SbSystemPlatformErrorPrivate(SbSystemPlatformErrorType type,
+ SbSystemPlatformErrorCallback callback,
+ void* user_data)
+ : error_obj(NULL), type(type), callback(callback), user_data(user_data) {}
+
+ jobject error_obj; // global ref to dev.cobalt.PlatformError instance
+ SbSystemPlatformErrorType type;
+ SbSystemPlatformErrorCallback callback;
+ void* user_data;
+};
+
+namespace {
+
+enum {
+ // This must be kept in sync with Java dev.cobalt.PlatformError.ErrorType
+ kJniErrorTypeConnectionError = 0,
+};
+
+} // namespace
+
+SbSystemPlatformError SbSystemRaisePlatformError(
+ SbSystemPlatformErrorType type,
+ SbSystemPlatformErrorCallback callback,
+ void* user_data) {
+ jint jni_error_type;
+ switch (type) {
+ case kSbSystemPlatformErrorTypeConnectionError:
+ jni_error_type = kJniErrorTypeConnectionError;
+ break;
+ default:
+ SB_NOTREACHED();
+ return kSbSystemPlatformErrorInvalid;
+ }
+
+ SbSystemPlatformError error_handle =
+ new SbSystemPlatformErrorPrivate(type, callback, user_data);
+
+ JniEnvExt* env = JniEnvExt::Get();
+ jobject error_obj = env->CallStarboardObjectMethodOrAbort(
+ "raisePlatformError", "(IJ)Ldev/cobalt/coat/PlatformError;",
+ jni_error_type, reinterpret_cast<jlong>(error_handle));
+ error_handle->error_obj = env->NewGlobalRef(error_obj);
+
+ return error_handle;
+}
+
+void SbSystemClearPlatformError(SbSystemPlatformError handle) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallObjectMethodOrAbort(handle->error_obj, "clear", "()V");
+}
+
+extern "C" SB_EXPORT_PLATFORM
+void Java_dev_cobalt_coat_PlatformError_nativeOnCleared(
+ JNIEnv* env, jobject unused_this, jint jni_response, jlong jni_data) {
+ SB_UNREFERENCED_PARAMETER(unused_this);
+ SbSystemPlatformError error_handle =
+ reinterpret_cast<SbSystemPlatformError>(jni_data);
+ env->DeleteGlobalRef(error_handle->error_obj);
+ if (error_handle->callback) {
+ SbSystemPlatformErrorResponse error_response =
+ jni_response < 0 ? kSbSystemPlatformErrorResponseNegative :
+ jni_response > 0 ? kSbSystemPlatformErrorResponsePositive :
+ kSbSystemPlatformErrorResponseCancel;
+ error_handle->callback(error_response, error_handle->user_data);
+ }
+}
diff --git a/src/starboard/android/shared/system_request_stop.cc b/src/starboard/android/shared/system_request_stop.cc
new file mode 100644
index 0000000..85aab60
--- /dev/null
+++ b/src/starboard/android/shared/system_request_stop.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+
+using starboard::android::shared::JniEnvExt;
+
+void SbSystemRequestStop(int error_level) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallStarboardVoidMethodOrAbort("requestStop", "(I)V", error_level);
+}
diff --git a/src/starboard/android/shared/system_request_suspend.cc b/src/starboard/android/shared/system_request_suspend.cc
new file mode 100644
index 0000000..c8f53c4
--- /dev/null
+++ b/src/starboard/android/shared/system_request_suspend.cc
@@ -0,0 +1,24 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/system.h"
+
+#include "starboard/android/shared/jni_env_ext.h"
+
+using starboard::android::shared::JniEnvExt;
+
+void SbSystemRequestSuspend() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallStarboardVoidMethodOrAbort("requestSuspend", "()V");
+}
diff --git a/src/starboard/android/shared/thread_create.cc b/src/starboard/android/shared/thread_create.cc
new file mode 100644
index 0000000..8aee6dd
--- /dev/null
+++ b/src/starboard/android/shared/thread_create.cc
@@ -0,0 +1,149 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/thread.h"
+
+#include <pthread.h>
+#include <sched.h>
+#include <sys/resource.h>
+#include <unistd.h>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/log.h"
+#include "starboard/shared/pthread/is_success.h"
+#include "starboard/shared/pthread/thread_create_priority.h"
+#include "starboard/string.h"
+
+namespace starboard {
+namespace shared {
+namespace pthread {
+
+#if !SB_HAS(THREAD_PRIORITY_SUPPORT)
+// Default implementation without thread priority support
+void ThreadSetPriority(SbThreadPriority /* priority */) {}
+#endif
+
+void PreThreadRun() {}
+
+void PostThreadRun() {
+ // TODO: Currently OnThreadFinish() is specific to the android-forked version
+ // of this file, thread_create.cc. This functionality could be implemented
+ // though similar to ThreadSetPriority(), such that we allow platforms to
+ // override the OnThreadStart()/OnThreadFinish() logic, if they want, and then
+ // we don't have to fork this file. The only reason that is not done
+ // currently is because of bad timing with respect to Starboard interface
+ // changes.
+ android::shared::JniEnvExt::OnThreadShutdown();
+}
+
+} // namespace pthread
+} // namespace shared
+} // namespace starboard
+
+namespace {
+
+struct ThreadParams {
+ SbThreadAffinity affinity;
+ SbThreadEntryPoint entry_point;
+ char name[128];
+ void* context;
+ SbThreadPriority priority;
+};
+
+void* ThreadFunc(void* context) {
+ ThreadParams* thread_params = static_cast<ThreadParams*>(context);
+ SbThreadEntryPoint entry_point = thread_params->entry_point;
+ void* real_context = thread_params->context;
+ SbThreadAffinity affinity = thread_params->affinity;
+ if (thread_params->name[0] != '\0') {
+ SbThreadSetName(thread_params->name);
+ }
+
+ starboard::shared::pthread::ThreadSetPriority(thread_params->priority);
+
+ delete thread_params;
+
+ if (SbThreadIsValidAffinity(affinity)) {
+ cpu_set_t cpu_set;
+ CPU_ZERO(&cpu_set);
+ CPU_SET(affinity, &cpu_set);
+ sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
+ }
+
+ starboard::shared::pthread::PreThreadRun();
+
+ void* return_value = entry_point(real_context);
+
+ starboard::shared::pthread::PostThreadRun();
+
+ return return_value;
+}
+
+} // namespace
+
+SbThread SbThreadCreate(int64_t stack_size,
+ SbThreadPriority priority,
+ SbThreadAffinity affinity,
+ bool joinable,
+ const char* name,
+ SbThreadEntryPoint entry_point,
+ void* context) {
+ if (stack_size < 0 || !entry_point) {
+ return kSbThreadInvalid;
+ }
+
+#if defined(ADDRESS_SANITIZER)
+ // Set a big thread stack size when in ADDRESS_SANITIZER mode.
+ // This eliminates buffer overflows for deeply nested callstacks.
+ if (stack_size == 0) {
+ stack_size = 4096 * 1024; // 4MB
+ }
+#endif
+
+ pthread_attr_t attributes;
+ int result = pthread_attr_init(&attributes);
+ if (!IsSuccess(result)) {
+ return kSbThreadInvalid;
+ }
+
+ pthread_attr_setdetachstate(
+ &attributes,
+ (joinable ? PTHREAD_CREATE_JOINABLE : PTHREAD_CREATE_DETACHED));
+ if (stack_size > 0) {
+ pthread_attr_setstacksize(&attributes, stack_size);
+ }
+
+ ThreadParams* params = new ThreadParams();
+ params->affinity = affinity;
+ params->entry_point = entry_point;
+ params->context = context;
+
+ if (name) {
+ SbStringCopy(params->name, name, SB_ARRAY_SIZE_INT(params->name));
+ } else {
+ params->name[0] = '\0';
+ }
+
+ params->priority = priority;
+
+ SbThread thread = kSbThreadInvalid;
+ result = pthread_create(&thread, &attributes, ThreadFunc, params);
+
+ pthread_attr_destroy(&attributes);
+ if (IsSuccess(result)) {
+ return thread;
+ }
+
+ return kSbThreadInvalid;
+}
diff --git a/src/starboard/android/shared/thread_create_priority.cc b/src/starboard/android/shared/thread_create_priority.cc
new file mode 100644
index 0000000..5247c58
--- /dev/null
+++ b/src/starboard/android/shared/thread_create_priority.cc
@@ -0,0 +1,68 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/shared/pthread/thread_create_priority.h"
+
+#include <sched.h>
+#include <sys/resource.h>
+
+#include "starboard/log.h"
+
+namespace starboard {
+namespace shared {
+namespace pthread {
+
+#if SB_HAS(THREAD_PRIORITY_SUPPORT)
+
+void SetNiceValue(int nice) {
+ int result = setpriority(PRIO_PROCESS, 0, nice);
+ if (result != 0) {
+ SB_NOTREACHED();
+ }
+}
+
+void ThreadSetPriority(SbThreadPriority priority) {
+ // Nice value settings are selected from looking at:
+ // https://android.googlesource.com/platform/frameworks/native/+/jb-dev/include/utils/ThreadDefs.h#35
+ switch (priority) {
+ case kSbThreadPriorityLowest:
+ SetNiceValue(19);
+ break;
+ case kSbThreadPriorityLow:
+ SetNiceValue(10);
+ break;
+ case kSbThreadNoPriority:
+ case kSbThreadPriorityNormal:
+ SetNiceValue(0);
+ break;
+ case kSbThreadPriorityHigh:
+ SetNiceValue(-8);
+ break;
+ case kSbThreadPriorityHighest:
+ SetNiceValue(-16);
+ break;
+ case kSbThreadPriorityRealTime:
+ SetNiceValue(-19);
+ break;
+ default:
+ SB_NOTREACHED();
+ break;
+ }
+}
+
+#endif // SB_HAS(THREAD_PRIORITY_SUPPORT)
+
+} // namespace pthread
+} // namespace shared
+} // namespace starboard
diff --git a/src/starboard/android/shared/thread_get_name.cc b/src/starboard/android/shared/thread_get_name.cc
new file mode 100644
index 0000000..3adbf9a
--- /dev/null
+++ b/src/starboard/android/shared/thread_get_name.cc
@@ -0,0 +1,20 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/thread.h"
+#include <sys/prctl.h>
+
+void SbThreadGetName(char* buffer, int buffer_size) {
+ prctl(PR_GET_NAME, buffer, 0L, 0L, 0L);
+}
diff --git a/src/starboard/android/shared/thread_types_public.h b/src/starboard/android/shared/thread_types_public.h
new file mode 100644
index 0000000..266f513
--- /dev/null
+++ b/src/starboard/android/shared/thread_types_public.h
@@ -0,0 +1,22 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// Includes threading primitive types and initializers.
+
+#ifndef STARBOARD_ANDROID_SHARED_THREAD_TYPES_PUBLIC_H_
+#define STARBOARD_ANDROID_SHARED_THREAD_TYPES_PUBLIC_H_
+
+#include "starboard/shared/pthread/types_public.h"
+
+#endif // STARBOARD_ANDROID_SHARED_THREAD_TYPES_PUBLIC_H_
diff --git a/src/starboard/android/shared/time_zone_get_dst_name.cc b/src/starboard/android/shared/time_zone_get_dst_name.cc
new file mode 100644
index 0000000..fbdeb86
--- /dev/null
+++ b/src/starboard/android/shared/time_zone_get_dst_name.cc
@@ -0,0 +1,33 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/time_zone.h"
+
+#include <time.h>
+
+#if SB_API_VERSION < 6
+const char* SbTimeZoneGetDstName() {
+ // Note tzset() is called in ApplicationAndroid::Initialize()
+
+ // Android's bionic seems not to set tzname[1] when selecting GMT
+ // because timezone is not otherwise available.
+ // But glibc returns "GMT" in both tzname[0] and tzname[1] when
+ // GMT is selected.
+ if (tzname[1][0] == '\0') {
+ return tzname[0];
+ } else {
+ return tzname[1];
+ }
+}
+#endif // SB_API_VERSION < 6
diff --git a/src/starboard/android/shared/time_zone_get_name.cc b/src/starboard/android/shared/time_zone_get_name.cc
new file mode 100644
index 0000000..b2b6c41
--- /dev/null
+++ b/src/starboard/android/shared/time_zone_get_name.cc
@@ -0,0 +1,23 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/time_zone.h"
+
+#include <time.h>
+
+const char* SbTimeZoneGetName() {
+ // Note tzset() is called in ApplicationAndroid::Initialize()
+
+ return tzname[0];
+}
diff --git a/src/starboard/android/shared/trace_util.h b/src/starboard/android/shared/trace_util.h
new file mode 100644
index 0000000..96a8ac2
--- /dev/null
+++ b/src/starboard/android/shared/trace_util.h
@@ -0,0 +1,46 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_TRACE_UTIL_H_
+#define STARBOARD_ANDROID_SHARED_TRACE_UTIL_H_
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// A simple scoped wrapper of |android.os.Trace|.
+struct ScopedTrace {
+ explicit ScopedTrace(const char* section_name) {
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jstring> j_section_name(
+ env->NewStringStandardUTFOrAbort(section_name));
+ env->CallStaticVoidMethodOrAbort("android/os/Trace", "beginSection",
+ "(Ljava/lang/String;)V",
+ j_section_name.Get());
+ }
+
+ ~ScopedTrace() {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallStaticVoidMethodOrAbort("android/os/Trace", "endSection", "()V");
+ }
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_TRACE_UTIL_H_
diff --git a/src/starboard/android/shared/video_decoder.cc b/src/starboard/android/shared/video_decoder.cc
new file mode 100644
index 0000000..a27d637
--- /dev/null
+++ b/src/starboard/android/shared/video_decoder.cc
@@ -0,0 +1,522 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/video_decoder.h"
+
+#include <jni.h>
+
+#include <cmath>
+#include <functional>
+
+#include "starboard/android/shared/application_android.h"
+#include "starboard/android/shared/decode_target_create.h"
+#include "starboard/android/shared/decode_target_internal.h"
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+#include "starboard/android/shared/video_window.h"
+#include "starboard/android/shared/window_internal.h"
+#include "starboard/configuration.h"
+#include "starboard/decode_target.h"
+#include "starboard/drm.h"
+#include "starboard/memory.h"
+#include "starboard/shared/starboard/player/filter/video_frame_internal.h"
+#include "starboard/string.h"
+#include "starboard/thread.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+using ::starboard::shared::starboard::player::filter::VideoFrame;
+using std::placeholders::_1;
+using std::placeholders::_2;
+
+class VideoFrameImpl : public VideoFrame {
+ public:
+ VideoFrameImpl(const DequeueOutputResult& dequeue_output_result,
+ MediaCodecBridge* media_codec_bridge)
+ : VideoFrame(dequeue_output_result.flags & BUFFER_FLAG_END_OF_STREAM
+ ? kMediaTimeEndOfStream
+ : dequeue_output_result.presentation_time_microseconds),
+ dequeue_output_result_(dequeue_output_result),
+ media_codec_bridge_(media_codec_bridge),
+ released_(false) {
+ SB_DCHECK(media_codec_bridge_);
+ }
+
+ ~VideoFrameImpl() {
+ if (!released_) {
+ media_codec_bridge_->ReleaseOutputBuffer(dequeue_output_result_.index,
+ false);
+ if (is_end_of_stream()) {
+ media_codec_bridge_->Flush();
+ }
+ }
+ }
+
+ void Draw(int64_t release_time_in_nanoseconds) {
+ SB_DCHECK(!released_);
+ SB_DCHECK(!is_end_of_stream());
+ released_ = true;
+ media_codec_bridge_->ReleaseOutputBufferAtTimestamp(
+ dequeue_output_result_.index, release_time_in_nanoseconds);
+ }
+
+ private:
+ DequeueOutputResult dequeue_output_result_;
+ MediaCodecBridge* media_codec_bridge_;
+ volatile bool released_;
+};
+
+const SbTime kInitialPrerollTimeout = 250 * kSbTimeMillisecond;
+
+const int kInitialPrerollFrameCount = 8;
+const int kNonInitialPrerollFrameCount = 1;
+
+const int kMaxPendingWorkSize = 128;
+
+// Convenience HDR mastering metadata.
+const SbMediaMasteringMetadata kEmptyMasteringMetadata = {};
+
+// Determine if two |SbMediaMasteringMetadata|s are equal.
+bool Equal(const SbMediaMasteringMetadata& lhs,
+ const SbMediaMasteringMetadata& rhs) {
+ return SbMemoryCompare(&lhs, &rhs, sizeof(SbMediaMasteringMetadata)) == 0;
+}
+
+// Determine if two |SbMediaColorMetadata|s are equal.
+bool Equal(const SbMediaColorMetadata& lhs, const SbMediaColorMetadata& rhs) {
+ return SbMemoryCompare(&lhs, &rhs, sizeof(SbMediaMasteringMetadata)) == 0;
+}
+
+// TODO: For whatever reason, Cobalt will always pass us this us for
+// color metadata, regardless of whether HDR is on or not. Find out if this
+// is intentional or not. It would make more sense if it were NULL.
+// Determine if |color_metadata| is "empty", or "null".
+bool IsIdentity(const SbMediaColorMetadata& color_metadata) {
+ return color_metadata.primaries == kSbMediaPrimaryIdBt709 &&
+ color_metadata.transfer == kSbMediaTransferIdBt709 &&
+ color_metadata.matrix == kSbMediaMatrixIdBt709 &&
+ color_metadata.range == kSbMediaRangeIdLimited &&
+ Equal(color_metadata.mastering_metadata, kEmptyMasteringMetadata);
+}
+
+} // namespace
+
+class VideoDecoder::Sink : public VideoDecoder::VideoRendererSink {
+ public:
+ bool Render() {
+ SB_DCHECK(render_cb_);
+
+ rendered_ = false;
+ render_cb_(std::bind(&Sink::DrawFrame, this, _1, _2));
+
+ return rendered_;
+ }
+
+ private:
+ void SetRenderCB(RenderCB render_cb) override {
+ SB_DCHECK(!render_cb_);
+ SB_DCHECK(render_cb);
+
+ render_cb_ = render_cb;
+ }
+
+ void SetBounds(int z_index, int x, int y, int width, int height) override {
+ SB_UNREFERENCED_PARAMETER(z_index);
+ SB_UNREFERENCED_PARAMETER(x);
+ SB_UNREFERENCED_PARAMETER(y);
+ SB_UNREFERENCED_PARAMETER(width);
+ SB_UNREFERENCED_PARAMETER(height);
+ }
+
+ DrawFrameStatus DrawFrame(const scoped_refptr<VideoFrame>& frame,
+ int64_t release_time_in_nanoseconds) {
+ rendered_ = true;
+ static_cast<VideoFrameImpl*>(frame.get())
+ ->Draw(release_time_in_nanoseconds);
+
+ return kReleased;
+ }
+
+ RenderCB render_cb_;
+ bool rendered_;
+};
+
+VideoDecoder::VideoDecoder(SbMediaVideoCodec video_codec,
+ SbDrmSystem drm_system,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider*
+ decode_target_graphics_context_provider)
+ : video_codec_(video_codec),
+ drm_system_(static_cast<DrmSystem*>(drm_system)),
+ output_mode_(output_mode),
+ decode_target_graphics_context_provider_(
+ decode_target_graphics_context_provider),
+ decode_target_(kSbDecodeTargetInvalid),
+ frame_width_(0),
+ frame_height_(0),
+ first_buffer_received_(false) {
+ if (!InitializeCodec()) {
+ SB_LOG(ERROR) << "Failed to initialize video decoder.";
+ TeardownCodec();
+ }
+}
+
+VideoDecoder::~VideoDecoder() {
+ TeardownCodec();
+ ClearVideoWindow();
+}
+
+scoped_refptr<VideoDecoder::VideoRendererSink> VideoDecoder::GetSink() {
+ if (sink_ == NULL) {
+ sink_ = new Sink;
+ }
+ return sink_;
+}
+
+void VideoDecoder::Initialize(const DecoderStatusCB& decoder_status_cb,
+ const ErrorCB& error_cb) {
+ SB_DCHECK(media_decoder_);
+
+ SB_DCHECK(decoder_status_cb);
+ SB_DCHECK(!decoder_status_cb_);
+ SB_DCHECK(error_cb);
+ SB_DCHECK(!error_cb_);
+
+ decoder_status_cb_ = decoder_status_cb;
+ error_cb_ = error_cb;
+
+ media_decoder_->Initialize(error_cb_);
+}
+
+size_t VideoDecoder::GetPrerollFrameCount() const {
+ if (first_buffer_received_ && first_buffer_timestamp_ != 0) {
+ return kNonInitialPrerollFrameCount;
+ }
+ return kInitialPrerollFrameCount;
+}
+
+SbTime VideoDecoder::GetPrerollTimeout() const {
+ if (first_buffer_received_ && first_buffer_timestamp_ != 0) {
+ return kSbTimeMax;
+ }
+ return kInitialPrerollTimeout;
+}
+
+void VideoDecoder::WriteInputBuffer(
+ const scoped_refptr<InputBuffer>& input_buffer) {
+ SB_DCHECK(input_buffer);
+ SB_DCHECK(decoder_status_cb_);
+
+ if (!first_buffer_received_) {
+ first_buffer_received_ = true;
+ first_buffer_timestamp_ = input_buffer->timestamp();
+
+ // If color metadata is present and is not an identity mapping, then
+ // teardown the codec so it can be reinitalized with the new metadata.
+ auto* color_metadata = input_buffer->video_sample_info()->color_metadata;
+ if (color_metadata && !IsIdentity(*color_metadata)) {
+ SB_DCHECK(!color_metadata_) << "Unexpected residual color metadata.";
+ SB_LOG(INFO) << "Reinitializing codec with HDR color metadata.";
+ TeardownCodec();
+ color_metadata_ = *color_metadata;
+ }
+
+ // Re-initialize the codec now if it was torn down either in |Reset| or
+ // because we need to change the color metadata.
+ if (media_decoder_ == NULL) {
+ if (!InitializeCodec()) {
+ // TODO: Communicate this failure to our clients somehow.
+ SB_LOG(ERROR) << "Failed to reinitialize codec.";
+ }
+ }
+ }
+
+ media_decoder_->WriteInputBuffer(input_buffer);
+ if (number_of_frames_being_decoded_.increment() < kMaxPendingWorkSize) {
+ decoder_status_cb_(kNeedMoreInput, NULL);
+ }
+}
+
+void VideoDecoder::WriteEndOfStream() {
+ SB_DCHECK(decoder_status_cb_);
+
+ if (!first_buffer_received_) {
+ first_buffer_received_ = true;
+ first_buffer_timestamp_ = 0;
+ }
+
+ media_decoder_->WriteEndOfStream();
+}
+
+void VideoDecoder::Reset() {
+ TeardownCodec();
+ number_of_frames_being_decoded_.store(0);
+ first_buffer_received_ = false;
+}
+
+bool VideoDecoder::InitializeCodec() {
+ // Setup the output surface object. If we are in punch-out mode, target
+ // the passed in Android video surface. If we are in decode-to-texture
+ // mode, create a surface from a new texture target and use that as the
+ // output surface.
+ jobject j_output_surface = NULL;
+ switch (output_mode_) {
+ case kSbPlayerOutputModePunchOut: {
+ j_output_surface = GetVideoSurface();
+ } break;
+ case kSbPlayerOutputModeDecodeToTexture: {
+ // A width and height of (0, 0) is provided here because Android doesn't
+ // actually allocate any memory into the texture at this time. That is
+ // done behind the scenes, the acquired texture is not actually backed
+ // by texture data until updateTexImage() is called on it.
+ SbDecodeTarget decode_target =
+ DecodeTargetCreate(decode_target_graphics_context_provider_,
+ kSbDecodeTargetFormat1PlaneRGBA, 0, 0);
+ if (!SbDecodeTargetIsValid(decode_target)) {
+ SB_LOG(ERROR) << "Could not acquire a decode target from provider.";
+ return false;
+ }
+ j_output_surface = decode_target->data->surface;
+
+ starboard::ScopedLock lock(decode_target_mutex_);
+ decode_target_ = decode_target;
+ } break;
+ case kSbPlayerOutputModeInvalid: {
+ SB_NOTREACHED();
+ } break;
+ }
+ if (!j_output_surface) {
+ SB_LOG(ERROR) << "Video surface does not exist.";
+ return false;
+ }
+
+ ANativeWindow* video_window = GetVideoWindow();
+ if (!video_window) {
+ SB_LOG(ERROR)
+ << "Can't initialize the codec since we don't have a video window.";
+ return false;
+ }
+ int width = ANativeWindow_getWidth(video_window);
+ int height = ANativeWindow_getHeight(video_window);
+
+ jobject j_media_crypto = drm_system_ ? drm_system_->GetMediaCrypto() : NULL;
+ SB_DCHECK(!drm_system_ || j_media_crypto);
+ media_decoder_.reset(new MediaDecoder(
+ this, video_codec_, width, height, j_output_surface, drm_system_,
+ color_metadata_ ? &*color_metadata_ : nullptr));
+ if (media_decoder_->is_valid()) {
+ if (error_cb_) {
+ media_decoder_->Initialize(error_cb_);
+ }
+ return true;
+ }
+ media_decoder_.reset();
+ return false;
+}
+
+void VideoDecoder::TeardownCodec() {
+ media_decoder_.reset();
+ color_metadata_ = starboard::nullopt;
+
+ starboard::ScopedLock lock(decode_target_mutex_);
+ if (SbDecodeTargetIsValid(decode_target_)) {
+ SbDecodeTargetReleaseInGlesContext(decode_target_graphics_context_provider_,
+ decode_target_);
+ decode_target_ = kSbDecodeTargetInvalid;
+ }
+}
+
+void VideoDecoder::ProcessOutputBuffer(
+ MediaCodecBridge* media_codec_bridge,
+ const DequeueOutputResult& dequeue_output_result) {
+ SB_DCHECK(decoder_status_cb_);
+ SB_DCHECK(dequeue_output_result.index >= 0);
+
+ number_of_frames_being_decoded_.decrement();
+ bool is_end_of_stream =
+ dequeue_output_result.flags & BUFFER_FLAG_END_OF_STREAM;
+ decoder_status_cb_(
+ is_end_of_stream ? kBufferFull : kNeedMoreInput,
+ new VideoFrameImpl(dequeue_output_result, media_codec_bridge));
+}
+
+void VideoDecoder::RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) {
+ SB_DCHECK(media_codec_bridge);
+ SB_DLOG(INFO) << "Output format changed, trying to dequeue again.";
+ // Record the latest width/height of the decoded input.
+ SurfaceDimensions output_dimensions =
+ media_codec_bridge->GetOutputDimensions();
+ frame_width_ = output_dimensions.width;
+ frame_height_ = output_dimensions.height;
+}
+
+bool VideoDecoder::Tick(MediaCodecBridge* media_codec_bridge) {
+ return sink_->Render();
+}
+
+void VideoDecoder::OnFlushing() {
+ decoder_status_cb_(kReleaseAllFrames, NULL);
+}
+
+namespace {
+void updateTexImage(jobject surface_texture) {
+ JniEnvExt* env = JniEnvExt::Get();
+ env->CallVoidMethodOrAbort(surface_texture, "updateTexImage", "()V");
+}
+
+void getTransformMatrix(jobject surface_texture, float* matrix4x4) {
+ JniEnvExt* env = JniEnvExt::Get();
+
+ jfloatArray java_array = env->NewFloatArray(16);
+ SB_DCHECK(java_array);
+
+ env->CallVoidMethodOrAbort(surface_texture, "getTransformMatrix", "([F)V",
+ java_array);
+
+ jfloat* array_values = env->GetFloatArrayElements(java_array, 0);
+ SbMemoryCopy(matrix4x4, array_values, sizeof(float) * 16);
+
+ env->DeleteLocalRef(java_array);
+}
+
+// Rounds the float to the nearest integer, and also does a DCHECK to make sure
+// that the input float was already near an integer value.
+int RoundToNearInteger(float x) {
+ int rounded = static_cast<int>(x + 0.5f);
+ return rounded;
+}
+
+// Converts a 4x4 matrix representing the texture coordinate transform into
+// an equivalent rectangle representing the region within the texture where
+// the pixel data is valid. Note that the width and height of this region may
+// be negative to indicate that that axis should be flipped.
+void SetDecodeTargetContentRegionFromMatrix(
+ SbDecodeTargetInfoContentRegion* content_region,
+ int width,
+ int height,
+ const float* matrix4x4) {
+ // Ensure that this matrix contains no rotations or shears. In other words,
+ // make sure that we can convert it to a decode target content region without
+ // losing any information.
+ SB_DCHECK(matrix4x4[1] == 0.0f);
+ SB_DCHECK(matrix4x4[2] == 0.0f);
+ SB_DCHECK(matrix4x4[3] == 0.0f);
+
+ SB_DCHECK(matrix4x4[4] == 0.0f);
+ SB_DCHECK(matrix4x4[6] == 0.0f);
+ SB_DCHECK(matrix4x4[7] == 0.0f);
+
+ SB_DCHECK(matrix4x4[8] == 0.0f);
+ SB_DCHECK(matrix4x4[9] == 0.0f);
+ SB_DCHECK(matrix4x4[10] == 1.0f);
+ SB_DCHECK(matrix4x4[11] == 0.0f);
+
+ SB_DCHECK(matrix4x4[14] == 0.0f);
+ SB_DCHECK(matrix4x4[15] == 1.0f);
+
+ float origin_x = matrix4x4[12];
+ float origin_y = matrix4x4[13];
+
+ float extent_x = matrix4x4[0] + matrix4x4[12];
+ float extent_y = matrix4x4[5] + matrix4x4[13];
+
+ SB_DCHECK(origin_y >= 0.0f);
+ SB_DCHECK(origin_y <= 1.0f);
+ SB_DCHECK(origin_x >= 0.0f);
+ SB_DCHECK(origin_x <= 1.0f);
+ SB_DCHECK(extent_x >= 0.0f);
+ SB_DCHECK(extent_x <= 1.0f);
+ SB_DCHECK(extent_y >= 0.0f);
+ SB_DCHECK(extent_y <= 1.0f);
+
+ // Flip the y-axis to match ContentRegion's coordinate system.
+ origin_y = 1.0f - origin_y;
+ extent_y = 1.0f - extent_y;
+
+ content_region->left = RoundToNearInteger(origin_x * width);
+ content_region->right = RoundToNearInteger(extent_x * width);
+
+ // Note that in GL coordinates, the origin is the bottom and the extent
+ // is the top.
+ content_region->top = RoundToNearInteger(extent_y * height);
+ content_region->bottom = RoundToNearInteger(origin_y * height);
+}
+} // namespace
+
+// When in decode-to-texture mode, this returns the current decoded video frame.
+SbDecodeTarget VideoDecoder::GetCurrentDecodeTarget() {
+ SB_DCHECK(output_mode_ == kSbPlayerOutputModeDecodeToTexture);
+ // We must take a lock here since this function can be called from a separate
+ // thread.
+ starboard::ScopedLock lock(decode_target_mutex_);
+
+ if (SbDecodeTargetIsValid(decode_target_)) {
+ updateTexImage(decode_target_->data->surface_texture);
+
+ float matrix4x4[16];
+ getTransformMatrix(decode_target_->data->surface_texture, matrix4x4);
+ SetDecodeTargetContentRegionFromMatrix(
+ &decode_target_->data->info.planes[0].content_region, frame_width_,
+ frame_height_, matrix4x4);
+
+ // Take this opportunity to update the decode target's width and height.
+ decode_target_->data->info.planes[0].width = frame_width_;
+ decode_target_->data->info.planes[0].height = frame_height_;
+ decode_target_->data->info.width = frame_width_;
+ decode_target_->data->info.height = frame_height_;
+
+ SbDecodeTarget out_decode_target = new SbDecodeTargetPrivate;
+ out_decode_target->data = decode_target_->data;
+
+ return out_decode_target;
+ } else {
+ return kSbDecodeTargetInvalid;
+ }
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+namespace starboard {
+namespace shared {
+namespace starboard {
+namespace player {
+namespace filter {
+
+// static
+bool VideoDecoder::OutputModeSupported(SbPlayerOutputMode output_mode,
+ SbMediaVideoCodec codec,
+ SbDrmSystem drm_system) {
+ if (output_mode == kSbPlayerOutputModePunchOut) {
+ return true;
+ }
+
+ if (output_mode == kSbPlayerOutputModeDecodeToTexture) {
+ return !SbDrmSystemIsValid(drm_system);
+ }
+
+ return false;
+}
+
+} // namespace filter
+} // namespace player
+} // namespace starboard
+} // namespace shared
+} // namespace starboard
diff --git a/src/starboard/android/shared/video_decoder.h b/src/starboard/android/shared/video_decoder.h
new file mode 100644
index 0000000..0a4bd13
--- /dev/null
+++ b/src/starboard/android/shared/video_decoder.h
@@ -0,0 +1,124 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_VIDEO_DECODER_H_
+#define STARBOARD_ANDROID_SHARED_VIDEO_DECODER_H_
+
+#include <deque>
+
+#include "starboard/android/shared/drm_system.h"
+#include "starboard/android/shared/media_codec_bridge.h"
+#include "starboard/android/shared/media_decoder.h"
+#include "starboard/atomic.h"
+#include "starboard/common/optional.h"
+#include "starboard/common/ref_counted.h"
+#include "starboard/decode_target.h"
+#include "starboard/media.h"
+#include "starboard/player.h"
+#include "starboard/shared/internal_only.h"
+#include "starboard/shared/starboard/player/filter/video_decoder_internal.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_sink.h"
+#include "starboard/shared/starboard/player/input_buffer_internal.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class VideoDecoder
+ : public ::starboard::shared::starboard::player::filter::VideoDecoder,
+ private MediaDecoder::Host {
+ public:
+ typedef ::starboard::shared::starboard::player::filter::VideoRendererSink
+ VideoRendererSink;
+
+ class Sink;
+
+ VideoDecoder(SbMediaVideoCodec video_codec,
+ SbDrmSystem drm_system,
+ SbPlayerOutputMode output_mode,
+ SbDecodeTargetGraphicsContextProvider*
+ decode_target_graphics_context_provider);
+ ~VideoDecoder() override;
+
+ scoped_refptr<VideoRendererSink> GetSink();
+
+ void Initialize(const DecoderStatusCB& decoder_status_cb,
+ const ErrorCB& error_cb) override;
+ size_t GetPrerollFrameCount() const override;
+ SbTime GetPrerollTimeout() const override;
+ size_t GetMaxNumberOfCachedFrames() const override { return 12; }
+
+ void WriteInputBuffer(const scoped_refptr<InputBuffer>& input_buffer)
+ override;
+ void WriteEndOfStream() override;
+ void Reset() override;
+ SbDecodeTarget GetCurrentDecodeTarget() override;
+
+ bool is_valid() const { return media_decoder_ != NULL; }
+
+ private:
+ // Attempt to initialize the codec. Returns whether initialization was
+ // successful.
+ bool InitializeCodec();
+ void TeardownCodec();
+
+ void ProcessOutputBuffer(MediaCodecBridge* media_codec_bridge,
+ const DequeueOutputResult& output) override;
+ void RefreshOutputFormat(MediaCodecBridge* media_codec_bridge) override;
+ bool Tick(MediaCodecBridge* media_codec_bridge) override;
+ void OnFlushing() override;
+
+ // These variables will be initialized inside ctor or Initialize() and will
+ // not be changed during the life time of this class.
+ const SbMediaVideoCodec video_codec_;
+ DecoderStatusCB decoder_status_cb_;
+ ErrorCB error_cb_;
+ DrmSystem* drm_system_;
+
+ SbPlayerOutputMode output_mode_;
+
+ SbDecodeTargetGraphicsContextProvider*
+ decode_target_graphics_context_provider_;
+
+ // If decode-to-texture is enabled, then we store the decode target texture
+ // inside of this |decode_target_| member.
+ SbDecodeTarget decode_target_;
+
+ // Since GetCurrentDecodeTarget() needs to be called from an arbitrary thread
+ // to obtain the current decode target (which ultimately ends up being a
+ // copy of |decode_target_|), we need to safe-guard access to |decode_target_|
+ // and we do so through this mutex.
+ starboard::Mutex decode_target_mutex_;
+
+ // The width and height of the latest decoded frame.
+ int32_t frame_width_;
+ int32_t frame_height_;
+
+ // The last enqueued |SbMediaColorMetadata|.
+ optional<SbMediaColorMetadata> color_metadata_;
+
+ scoped_ptr<MediaDecoder> media_decoder_;
+
+ atomic_int32_t number_of_frames_being_decoded_;
+ scoped_refptr<Sink> sink_;
+
+ bool first_buffer_received_;
+ volatile SbTime first_buffer_timestamp_;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_VIDEO_DECODER_H_
diff --git a/src/starboard/android/shared/video_frame_release_time_adjuster.cc b/src/starboard/android/shared/video_frame_release_time_adjuster.cc
new file mode 100644
index 0000000..30f4b07
--- /dev/null
+++ b/src/starboard/android/shared/video_frame_release_time_adjuster.cc
@@ -0,0 +1,170 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/video_frame_release_time_adjuster.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper() {
+ this(DISPLAY_REFRESH_RATE_UNKNOWN);
+}
+
+VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper(Context context) {
+ this(getDefaultDisplayRefreshRate(context));
+}
+
+VideoFrameReleaseTimeHelper::VideoFrameReleaseTimeHelper(
+ double defaultDisplayRefreshRate) {
+ useDefaultDisplayVsync =
+ defaultDisplayRefreshRate != DISPLAY_REFRESH_RATE_UNKNOWN;
+ if (useDefaultDisplayVsync) {
+ vsyncSampler = VSyncSampler.getInstance();
+ vsyncDurationNs = (long)(NANOS_PER_SECOND / defaultDisplayRefreshRate);
+ vsyncOffsetNs = (vsyncDurationNs * VSYNC_OFFSET_PERCENTAGE) / 100;
+ } else {
+ vsyncSampler = null;
+ vsyncDurationNs = -1; // Value unused.
+ vsyncOffsetNs = -1; // Value unused.
+ }
+}
+
+void VideoFrameReleaseTimeHelper::enable() {
+ haveSync = false;
+ if (useDefaultDisplayVsync) {
+ vsyncSampler.addObserver();
+ }
+}
+
+void VideoFrameReleaseTimeHelper::disable() {
+ if (useDefaultDisplayVsync) {
+ vsyncSampler.removeObserver();
+ }
+}
+
+long VideoFrameReleaseTimeHelper::adjustReleaseTime(
+ long framePresentationTimeUs,
+ long unadjustedReleaseTimeNs) {
+ long framePresentationTimeNs = framePresentationTimeUs * 1000;
+
+ // Until we know better, the adjustment will be a no-op.
+ long adjustedFrameTimeNs = framePresentationTimeNs;
+ long adjustedReleaseTimeNs = unadjustedReleaseTimeNs;
+
+ if (haveSync) {
+ // See if we've advanced to the next frame.
+ if (framePresentationTimeUs != lastFramePresentationTimeUs) {
+ frameCount++;
+ adjustedLastFrameTimeNs = pendingAdjustedFrameTimeNs;
+ }
+ if (frameCount >= MIN_FRAMES_FOR_ADJUSTMENT) {
+ // We're synced and have waited the required number of frames to apply an
+ // adjustment. Calculate the average frame time across all the frames
+ // we've seen since the last sync. This will typically give us a frame
+ // rate at a finer granularity than the frame times themselves (which
+ // often only have millisecond granularity).
+ long averageFrameDurationNs =
+ (framePresentationTimeNs - syncFramePresentationTimeNs) / frameCount;
+ // Project the adjusted frame time forward using the average.
+ long candidateAdjustedFrameTimeNs =
+ adjustedLastFrameTimeNs + averageFrameDurationNs;
+
+ if (isDriftTooLarge(candidateAdjustedFrameTimeNs,
+ unadjustedReleaseTimeNs)) {
+ haveSync = false;
+ } else {
+ adjustedFrameTimeNs = candidateAdjustedFrameTimeNs;
+ adjustedReleaseTimeNs = syncUnadjustedReleaseTimeNs +
+ adjustedFrameTimeNs -
+ syncFramePresentationTimeNs;
+ }
+ } else {
+ // We're synced but haven't waited the required number of frames to apply
+ // an adjustment. Check drift anyway.
+ if (isDriftTooLarge(framePresentationTimeNs, unadjustedReleaseTimeNs)) {
+ haveSync = false;
+ }
+ }
+ }
+
+ // If we need to sync, do so now.
+ if (!haveSync) {
+ syncFramePresentationTimeNs = framePresentationTimeNs;
+ syncUnadjustedReleaseTimeNs = unadjustedReleaseTimeNs;
+ frameCount = 0;
+ haveSync = true;
+ onSynced();
+ }
+
+ lastFramePresentationTimeUs = framePresentationTimeUs;
+ pendingAdjustedFrameTimeNs = adjustedFrameTimeNs;
+
+ if (vsyncSampler == null || vsyncSampler.sampledVsyncTimeNs == 0) {
+ return adjustedReleaseTimeNs;
+ }
+
+ // Find the timestamp of the closest vsync. This is the vsync that we're
+ // targeting.
+ long snappedTimeNs = closestVsync(
+ adjustedReleaseTimeNs, vsyncSampler.sampledVsyncTimeNs, vsyncDurationNs);
+ // Apply an offset so that we release before the target vsync, but after the
+ // previous one.
+ return snappedTimeNs - vsyncOffsetNs;
+}
+
+void VideoFrameReleaseTimeHelper::onSynced() {
+ // Do nothing.
+}
+
+boolean VideoFrameReleaseTimeHelper::isDriftTooLarge(long frameTimeNs,
+ long releaseTimeNs) {
+ long elapsedFrameTimeNs = frameTimeNs - syncFramePresentationTimeNs;
+ long elapsedReleaseTimeNs = releaseTimeNs - syncUnadjustedReleaseTimeNs;
+ return Math.abs(elapsedReleaseTimeNs - elapsedFrameTimeNs) >
+ MAX_ALLOWED_DRIFT_NS;
+}
+
+static long VideoFrameReleaseTimeHelper::closestVsync(long releaseTime,
+ long sampledVsyncTime,
+ long vsyncDuration) {
+ long vsyncCount = (releaseTime - sampledVsyncTime) / vsyncDuration;
+ long snappedTimeNs = sampledVsyncTime + (vsyncDuration * vsyncCount);
+ long snappedBeforeNs;
+ long snappedAfterNs;
+ if (releaseTime <= snappedTimeNs) {
+ snappedBeforeNs = snappedTimeNs - vsyncDuration;
+ snappedAfterNs = snappedTimeNs;
+ } else {
+ snappedBeforeNs = snappedTimeNs;
+ snappedAfterNs = snappedTimeNs + vsyncDuration;
+ }
+ long snappedAfterDiff = snappedAfterNs - releaseTime;
+ long snappedBeforeDiff = releaseTime - snappedBeforeNs;
+ return snappedAfterDiff < snappedBeforeDiff ? snappedAfterNs
+ : snappedBeforeNs;
+}
+
+static double VideoFrameReleaseTimeHelper::getDefaultDisplayRefreshRate(
+ Context context) {
+ WindowManager manager =
+ (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
+ return manager.getDefaultDisplay() != null
+ ? manager.getDefaultDisplay().getRefreshRate()
+ : DISPLAY_REFRESH_RATE_UNKNOWN;
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/video_frame_release_time_adjuster.h b/src/starboard/android/shared/video_frame_release_time_adjuster.h
new file mode 100644
index 0000000..a345660
--- /dev/null
+++ b/src/starboard/android/shared/video_frame_release_time_adjuster.h
@@ -0,0 +1,189 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_VIDEO_FRAME_RELEASE_TIME_ADJUSTER_H_
+#define STARBOARD_ANDROID_SHARED_VIDEO_FRAME_RELEASE_TIME_ADJUSTER_H_
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class VideoFrameReleaseTimeAdjuster {
+ public:
+ /**
+ * Constructs an instance that smooths frame release timestamps but does not
+ * align them with the default display's vsync signal.
+ */
+ VideoFrameReleaseTimeHelper();
+
+ /**
+ * Constructs an instance that smooths frame release timestamps and aligns
+ * them with the default display's vsync signal.
+ *
+ * @param context A context from which information about the default display
+ * can be retrieved.
+ */
+ VideoFrameReleaseTimeHelper(Context context);
+
+ /** Enables the helper. */
+ void enable();
+
+ /** Disables the helper. */
+ void disable();
+
+ /**
+ * Adjusts a frame release timestamp.
+ *
+ * @param framePresentationTimeUs The frame's presentation time, in
+ * microseconds.
+ * @param unadjustedReleaseTimeNs The frame's unadjusted release time, in
+ * nanoseconds and in the same time base as {@link System#nanoTime()}.
+ * @return The adjusted frame release timestamp, in nanoseconds and in the
+ * same time base as
+ * {@link System#nanoTime()}.
+ */
+ long adjustReleaseTime(long framePresentationTimeUs,
+ long unadjustedReleaseTimeNs);
+
+ protected:
+ void onSynced();
+
+ private:
+ VideoFrameReleaseTimeHelper(double defaultDisplayRefreshRate);
+
+ boolean isDriftTooLarge(long frameTimeNs, long releaseTimeNs);
+
+ static long closestVsync(long releaseTime,
+ long sampledVsyncTime,
+ long vsyncDuration);
+
+ static double getDefaultDisplayRefreshRate(Context context);
+
+ /**
+ * Samples display vsync timestamps. A single instance using a single {@link
+ * Choreographer} is shared by all {@link VideoFrameReleaseTimeHelper}
+ * instances. This is done to avoid a resource leak in the platform on API
+ * levels prior to 23.
+ */
+ class VSyncSampler implements FrameCallback, Handler.Callback {
+ public:
+ static VSyncSampler getInstance() { return INSTANCE; }
+
+ VSyncSampler() {
+ choreographerOwnerThread =
+ new HandlerThread("ChoreographerOwner:Handler");
+ choreographerOwnerThread.start();
+ handler = new Handler(choreographerOwnerThread.getLooper(), this);
+ handler.sendEmptyMessage(CREATE_CHOREOGRAPHER);
+ }
+
+ /**
+ * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is
+ * observing {@link #sampledVsyncTimeNs}, and hence that the value should be
+ * periodically updated.
+ */
+ void addObserver() { handler.sendEmptyMessage(MSG_ADD_OBSERVER); }
+
+ /**
+ * Notifies the sampler that a {@link VideoFrameReleaseTimeHelper} is no
+ * longer observing {@link #sampledVsyncTimeNs}.
+ */
+ void removeObserver() { handler.sendEmptyMessage(MSG_REMOVE_OBSERVER); }
+
+ void doFrame(long vsyncTimeNs) {
+ sampledVsyncTimeNs = vsyncTimeNs;
+ choreographer.postFrameCallbackDelayed(this,
+ CHOREOGRAPHER_SAMPLE_DELAY_MILLIS);
+ }
+
+ boolean handleMessage(Message message) {
+ switch (message.what) {
+ case CREATE_CHOREOGRAPHER: {
+ createChoreographerInstanceInternal();
+ return true;
+ }
+ case MSG_ADD_OBSERVER: {
+ addObserverInternal();
+ return true;
+ }
+ case MSG_REMOVE_OBSERVER: {
+ removeObserverInternal();
+ return true;
+ }
+ default: { return false; }
+ }
+ }
+
+ volatile long sampledVsyncTimeNs;
+
+ private:
+ void createChoreographerInstanceInternal() {
+ choreographer = Choreographer.getInstance();
+ }
+
+ void addObserverInternal() {
+ observerCount++;
+ if (observerCount == 1) {
+ choreographer.postFrameCallback(this);
+ }
+ }
+
+ void removeObserverInternal() {
+ observerCount--;
+ if (observerCount == 0) {
+ choreographer.removeFrameCallback(this);
+ sampledVsyncTimeNs = 0;
+ }
+ }
+
+ static final int CREATE_CHOREOGRAPHER = 0;
+ static final int MSG_ADD_OBSERVER = 1;
+ static final int MSG_REMOVE_OBSERVER = 2;
+
+ static final VSyncSampler INSTANCE = new VSyncSampler();
+
+ final Handler handler;
+ final HandlerThread choreographerOwnerThread;
+ Choreographer choreographer;
+ int observerCount;
+ };
+
+ static final double DISPLAY_REFRESH_RATE_UNKNOWN = -1;
+ static final long CHOREOGRAPHER_SAMPLE_DELAY_MILLIS = 500;
+ static final long MAX_ALLOWED_DRIFT_NS = 20000000;
+
+ static final long VSYNC_OFFSET_PERCENTAGE = 80;
+ static final int MIN_FRAMES_FOR_ADJUSTMENT = 6;
+ static final long NANOS_PER_SECOND = 1000000000L;
+
+ final VSyncSampler vsyncSampler;
+ final boolean useDefaultDisplayVsync;
+ final long vsyncDurationNs;
+ final long vsyncOffsetNs;
+
+ long lastFramePresentationTimeUs;
+ long adjustedLastFrameTimeNs;
+ long pendingAdjustedFrameTimeNs;
+
+ boolean haveSync;
+ long syncUnadjustedReleaseTimeNs;
+ long syncFramePresentationTimeNs;
+ long frameCount;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_VIDEO_FRAME_RELEASE_TIME_ADJUSTER_H_
diff --git a/src/starboard/android/shared/video_render_algorithm.cc b/src/starboard/android/shared/video_render_algorithm.cc
new file mode 100644
index 0000000..639ae01
--- /dev/null
+++ b/src/starboard/android/shared/video_render_algorithm.cc
@@ -0,0 +1,124 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/video_render_algorithm.h"
+
+#include <algorithm>
+
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/media_common.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+const SbTimeMonotonic kBufferTooLateThreshold = -30 * kSbTimeMillisecond;
+const SbTimeMonotonic kBufferReadyThreshold = 50 * kSbTimeMillisecond;
+
+jlong GetSystemNanoTime() {
+ timespec now;
+ clock_gettime(CLOCK_MONOTONIC, &now);
+ return now.tv_sec * 1000000000LL + now.tv_nsec;
+}
+
+} // namespace
+
+void VideoRenderAlgorithm::Render(
+ MediaTimeProvider* media_time_provider,
+ std::list<scoped_refptr<VideoFrame>>* frames,
+ VideoRendererSink::DrawFrameCB draw_frame_cb) {
+ SB_DCHECK(media_time_provider);
+ SB_DCHECK(frames);
+ SB_DCHECK(draw_frame_cb);
+
+ while (frames->size() > 0) {
+ if (frames->front()->is_end_of_stream()) {
+ frames->pop_front();
+ SB_DCHECK(frames->empty())
+ << "Expected end of stream output buffer to be the last buffer.";
+ break;
+ }
+
+ bool is_audio_playing;
+ bool is_audio_eos_played;
+ bool is_underflow;
+ SbTime playback_time = media_time_provider->GetCurrentMediaTime(
+ &is_audio_playing, &is_audio_eos_played, &is_underflow);
+ if (!is_audio_playing) {
+ break;
+ }
+
+ jlong early_us = frames->front()->timestamp() - playback_time;
+
+ auto system_time_ns = GetSystemNanoTime();
+ auto unadjusted_frame_release_time_ns =
+ system_time_ns + (early_us * kSbTimeNanosecondsPerMicrosecond);
+
+ auto adjusted_release_time_ns =
+ video_frame_release_time_helper_.AdjustReleaseTime(
+ frames->front()->timestamp(), unadjusted_frame_release_time_ns);
+
+ early_us = (adjusted_release_time_ns - system_time_ns) /
+ kSbTimeNanosecondsPerMicrosecond;
+
+ if (early_us < kBufferTooLateThreshold) {
+ frames->pop_front();
+ ++dropped_frames_;
+ } else if (early_us < kBufferReadyThreshold) {
+ auto status = draw_frame_cb(frames->front(), adjusted_release_time_ns);
+ SB_DCHECK(status == VideoRendererSink::kReleased);
+ frames->pop_front();
+ } else {
+ break;
+ }
+ }
+}
+
+VideoRenderAlgorithm::VideoFrameReleaseTimeHelper::
+ VideoFrameReleaseTimeHelper() {
+ auto* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> j_context(env->CallStarboardObjectMethodOrAbort(
+ "getApplicationContext", "()Landroid/content/Context;"));
+ j_video_frame_release_time_helper_ =
+ env->NewObjectOrAbort("dev/cobalt/media/VideoFrameReleaseTimeHelper",
+ "(Landroid/content/Context;)V", j_context.Get());
+ j_video_frame_release_time_helper_ =
+ env->ConvertLocalRefToGlobalRef(j_video_frame_release_time_helper_);
+ env->CallVoidMethod(j_video_frame_release_time_helper_, "enable", "()V");
+}
+
+VideoRenderAlgorithm::VideoFrameReleaseTimeHelper::
+ ~VideoFrameReleaseTimeHelper() {
+ SB_DCHECK(j_video_frame_release_time_helper_);
+ auto* env = JniEnvExt::Get();
+ env->CallVoidMethod(j_video_frame_release_time_helper_, "disable", "()V");
+ env->DeleteGlobalRef(j_video_frame_release_time_helper_);
+ j_video_frame_release_time_helper_ = nullptr;
+}
+
+jlong VideoRenderAlgorithm::VideoFrameReleaseTimeHelper::AdjustReleaseTime(
+ jlong frame_presentation_time_us,
+ jlong unadjusted_release_time_ns) {
+ SB_DCHECK(j_video_frame_release_time_helper_);
+ auto* env = JniEnvExt::Get();
+ return env->CallLongMethodOrAbort(
+ j_video_frame_release_time_helper_, "adjustReleaseTime", "(JJ)J",
+ frame_presentation_time_us, unadjusted_release_time_ns);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/video_render_algorithm.h b/src/starboard/android/shared/video_render_algorithm.h
new file mode 100644
index 0000000..927b514
--- /dev/null
+++ b/src/starboard/android/shared/video_render_algorithm.h
@@ -0,0 +1,55 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_VIDEO_RENDER_ALGORITHM_H_
+#define STARBOARD_ANDROID_SHARED_VIDEO_RENDER_ALGORITHM_H_
+
+#include <list>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/shared/starboard/player/filter/video_renderer_internal.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+class VideoRenderAlgorithm : public ::starboard::shared::starboard::player::
+ filter::VideoRenderAlgorithm {
+ public:
+ void Render(MediaTimeProvider* media_time_provider,
+ std::list<scoped_refptr<VideoFrame>>* frames,
+ VideoRendererSink::DrawFrameCB draw_frame_cb) override;
+ int GetDroppedFrames() override { return dropped_frames_; }
+
+ private:
+ class VideoFrameReleaseTimeHelper {
+ public:
+ VideoFrameReleaseTimeHelper();
+ ~VideoFrameReleaseTimeHelper();
+ jlong AdjustReleaseTime(jlong frame_presentation_time_us,
+ jlong unadjusted_release_time_ns);
+
+ private:
+ jobject j_video_frame_release_time_helper_ = nullptr;
+ };
+
+ VideoFrameReleaseTimeHelper video_frame_release_time_helper_;
+ int dropped_frames_ = 0;
+};
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_VIDEO_RENDER_ALGORITHM_H_
diff --git a/src/starboard/android/shared/video_window.cc b/src/starboard/android/shared/video_window.cc
new file mode 100644
index 0000000..da4bbf0
--- /dev/null
+++ b/src/starboard/android/shared/video_window.cc
@@ -0,0 +1,207 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/android/shared/video_window.h"
+
+#include <android/native_window.h>
+#include <android/native_window_jni.h>
+#include <EGL/egl.h>
+#include <GLES2/gl2.h>
+#include <jni.h>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/condition_variable.h"
+#include "starboard/configuration.h"
+#include "starboard/log.h"
+#include "starboard/shared/gles/gl_call.h"
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+namespace {
+
+// Global pointer to the single video surface.
+jobject g_j_video_surface = NULL;
+// Global pointer to the single video window.
+ANativeWindow* g_native_video_window = NULL;
+
+// Facilitate synchronization of punch-out videos.
+SbMutex g_bounds_updates_mutex = SB_MUTEX_INITIALIZER;
+SbConditionVariable g_bounds_updates_condition =
+ SB_CONDITION_VARIABLE_INITIALIZER;
+int g_bounds_updates_needed = 0;
+int g_bounds_updates_scheduled = 0;
+
+} // namespace
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_VideoSurfaceView_nativeOnVideoSurfaceChanged(
+ JNIEnv* env,
+ jobject unused_this,
+ jobject surface) {
+ if (g_j_video_surface) {
+ // TODO: Ensure that the decoder isn't still using the surface.
+ env->DeleteGlobalRef(g_j_video_surface);
+ }
+ if (g_native_video_window) {
+ // TODO: Ensure that the decoder isn't still using the window.
+ ANativeWindow_release(g_native_video_window);
+ }
+ if (surface) {
+ g_j_video_surface = env->NewGlobalRef(surface);
+ g_native_video_window = ANativeWindow_fromSurface(env, surface);
+ } else {
+ g_j_video_surface = NULL;
+ g_native_video_window = NULL;
+ }
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_VideoSurfaceView_nativeOnLayoutNeeded() {
+ SbMutexAcquire(&g_bounds_updates_mutex);
+ ++g_bounds_updates_needed;
+ SbMutexRelease(&g_bounds_updates_mutex);
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_VideoSurfaceView_nativeOnLayoutScheduled() {
+ SbMutexAcquire(&g_bounds_updates_mutex);
+ ++g_bounds_updates_scheduled;
+ SbMutexRelease(&g_bounds_updates_mutex);
+}
+
+extern "C" SB_EXPORT_PLATFORM void
+Java_dev_cobalt_media_VideoSurfaceView_nativeOnGlobalLayout() {
+ SbMutexAcquire(&g_bounds_updates_mutex);
+ g_bounds_updates_needed -= g_bounds_updates_scheduled;
+ g_bounds_updates_scheduled = 0;
+ if (g_bounds_updates_needed <= 0) {
+ g_bounds_updates_needed = 0;
+ SbConditionVariableSignal(&g_bounds_updates_condition);
+ }
+ SbMutexRelease(&g_bounds_updates_mutex);
+}
+
+jobject GetVideoSurface() {
+ return g_j_video_surface;
+}
+
+ANativeWindow* GetVideoWindow() {
+ return g_native_video_window;
+}
+
+void ClearVideoWindow() {
+ if (!g_native_video_window) {
+ SB_LOG(INFO) << "Tried to clear video window when it was null.";
+ return;
+ }
+
+ EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
+ eglInitialize(display, NULL, NULL);
+ if (display == EGL_NO_DISPLAY) {
+ SB_DLOG(ERROR) << "Found no EGL display in ClearVideoWindow";
+ return;
+ }
+
+ const EGLint kAttributeList[] = {
+ EGL_RED_SIZE,
+ 8,
+ EGL_GREEN_SIZE,
+ 8,
+ EGL_BLUE_SIZE,
+ 8,
+ EGL_ALPHA_SIZE,
+ 8,
+ EGL_RENDERABLE_TYPE,
+ EGL_OPENGL_ES2_BIT,
+ EGL_NONE,
+ 0,
+ EGL_NONE,
+ };
+
+ // First, query how many configs match the given attribute list.
+ EGLint num_configs = 0;
+ EGL_CALL(eglChooseConfig(display, kAttributeList, NULL, 0, &num_configs));
+ SB_DCHECK(num_configs != 0);
+
+ // Allocate space to receive the matching configs and retrieve them.
+ EGLConfig* configs = new EGLConfig[num_configs];
+ EGL_CALL(eglChooseConfig(display, kAttributeList, configs, num_configs,
+ &num_configs));
+
+ EGLNativeWindowType native_window =
+ static_cast<EGLNativeWindowType>(g_native_video_window);
+ EGLConfig config;
+
+ // Find the first config that successfully allow a window surface to be
+ // created.
+ EGLSurface surface;
+ for (int config_number = 0; config_number < num_configs; ++config_number) {
+ config = configs[config_number];
+ surface = eglCreateWindowSurface(display, config, native_window, NULL);
+ if (eglGetError() == EGL_SUCCESS)
+ break;
+ }
+ if (surface == EGL_NO_SURFACE) {
+ SB_DLOG(ERROR) << "Found no EGL surface in ClearVideoWindow";
+ return;
+ }
+ SB_DCHECK(surface != EGL_NO_SURFACE);
+
+ delete[] configs;
+
+ // Create an OpenGL ES 2.0 context.
+ EGLContext context = EGL_NO_CONTEXT;
+ EGLint context_attrib_list[] = {
+ EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE,
+ };
+ context =
+ eglCreateContext(display, config, EGL_NO_CONTEXT, context_attrib_list);
+ SB_DCHECK(eglGetError() == EGL_SUCCESS);
+ SB_DCHECK(context != EGL_NO_CONTEXT);
+
+ /* connect the context to the surface */
+ EGL_CALL(eglMakeCurrent(display, surface, surface, context));
+
+ GL_CALL(glClearColor(0, 0, 0, 1));
+ GL_CALL(glClear(GL_COLOR_BUFFER_BIT));
+ GL_CALL(glFlush());
+ EGL_CALL(eglSwapBuffers(display, surface));
+
+ // Cleanup all used resources.
+ EGL_CALL(
+ eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT));
+ EGL_CALL(eglDestroyContext(display, context));
+ EGL_CALL(eglDestroySurface(display, surface));
+ EGL_CALL(eglTerminate(display));
+}
+
+void WaitForVideoBoundsUpdate() {
+ SbMutexAcquire(&g_bounds_updates_mutex);
+ if (g_bounds_updates_needed > 0) {
+ // Use a timed wait to deal with possible race conditions in which
+ // suspend occurs in the middle of a bounds update.
+ SbConditionVariableWaitTimed(&g_bounds_updates_condition,
+ &g_bounds_updates_mutex,
+ 100 * kSbTimeMillisecond);
+ g_bounds_updates_needed = 0;
+ g_bounds_updates_scheduled = 0;
+ }
+ SbMutexRelease(&g_bounds_updates_mutex);
+}
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
diff --git a/src/starboard/android/shared/video_window.h b/src/starboard/android/shared/video_window.h
new file mode 100644
index 0000000..39cb33f
--- /dev/null
+++ b/src/starboard/android/shared/video_window.h
@@ -0,0 +1,43 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_VIDEO_WINDOW_H_
+#define STARBOARD_ANDROID_SHARED_VIDEO_WINDOW_H_
+
+#include <android/native_window.h>
+#include <jni.h>
+
+namespace starboard {
+namespace android {
+namespace shared {
+
+// Returns the surface which video should be rendered. This is the surface
+// that owns the native window returned by |GetVideoWindow|.
+jobject GetVideoSurface();
+
+// Returns the native window into which video should be rendered.
+ANativeWindow* GetVideoWindow();
+
+// Clear the video window by painting it Black. This function is safe to call
+// regardless of whether the video window has been initialized or not.
+void ClearVideoWindow();
+
+// Wait for all outstanding adjustments of video bounds before returning.
+void WaitForVideoBoundsUpdate();
+
+} // namespace shared
+} // namespace android
+} // namespace starboard
+
+#endif // STARBOARD_ANDROID_SHARED_VIDEO_WINDOW_H_
diff --git a/src/starboard/android/shared/window_blur_on_screen_keyboard.cc b/src/starboard/android/shared/window_blur_on_screen_keyboard.cc
new file mode 100644
index 0000000..fe84c62
--- /dev/null
+++ b/src/starboard/android/shared/window_blur_on_screen_keyboard.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+void SbWindowBlurOnScreenKeyboard(SbWindow window, int ticket) {
+ // Stub.
+ return;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_create.cc b/src/starboard/android/shared/window_create.cc
new file mode 100644
index 0000000..ec3ecf7
--- /dev/null
+++ b/src/starboard/android/shared/window_create.cc
@@ -0,0 +1,22 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#include "starboard/android/shared/application_android.h"
+
+SbWindow SbWindowCreate(const SbWindowOptions* options) {
+ return starboard::android::shared::ApplicationAndroid::Get()->CreateWindow(
+ options);
+}
diff --git a/src/starboard/android/shared/window_destroy.cc b/src/starboard/android/shared/window_destroy.cc
new file mode 100644
index 0000000..afb0c7e
--- /dev/null
+++ b/src/starboard/android/shared/window_destroy.cc
@@ -0,0 +1,22 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#include "starboard/android/shared/application_android.h"
+
+bool SbWindowDestroy(SbWindow window) {
+ return starboard::android::shared::ApplicationAndroid::Get()->DestroyWindow(
+ window);
+}
diff --git a/src/starboard/android/shared/window_focus_on_screen_keyboard.cc b/src/starboard/android/shared/window_focus_on_screen_keyboard.cc
new file mode 100644
index 0000000..00dea03
--- /dev/null
+++ b/src/starboard/android/shared/window_focus_on_screen_keyboard.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+void SbWindowFocusOnScreenKeyboard(SbWindow window, int ticket) {
+ // Stub.
+ return;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_get_on_screen_keyboard_bounding_rect.cc b/src/starboard/android/shared/window_get_on_screen_keyboard_bounding_rect.cc
new file mode 100644
index 0000000..90bcf2f
--- /dev/null
+++ b/src/starboard/android/shared/window_get_on_screen_keyboard_bounding_rect.cc
@@ -0,0 +1,23 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+bool SbWindowGetOnScreenKeyboardBoundingRect(SbWindow window,
+ SbWindowRect* bounding_rect) {
+ // Stub.
+ return true;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_get_platform_handle.cc b/src/starboard/android/shared/window_get_platform_handle.cc
new file mode 100644
index 0000000..60fd357
--- /dev/null
+++ b/src/starboard/android/shared/window_get_platform_handle.cc
@@ -0,0 +1,25 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#include "starboard/android/shared/window_internal.h"
+
+void* SbWindowGetPlatformHandle(SbWindow window) {
+ if (!SbWindowIsValid(window)) {
+ return NULL;
+ }
+ // EGLNativeWindowType and ANativeWindow* are the same.
+ return window->native_window;
+}
diff --git a/src/starboard/android/shared/window_get_size.cc b/src/starboard/android/shared/window_get_size.cc
new file mode 100644
index 0000000..c4bbebd
--- /dev/null
+++ b/src/starboard/android/shared/window_get_size.cc
@@ -0,0 +1,51 @@
+// Copyright 2016 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include <android/native_window.h>
+
+#include "starboard/android/shared/jni_env_ext.h"
+#include "starboard/android/shared/jni_utils.h"
+#include "starboard/android/shared/window_internal.h"
+#include "starboard/log.h"
+#include "starboard/window.h"
+
+using starboard::android::shared::JniEnvExt;
+using starboard::android::shared::ScopedLocalJavaRef;
+
+bool SbWindowGetSize(SbWindow window, SbWindowSize* size) {
+ if (!SbWindowIsValid(window)) {
+ SB_DLOG(ERROR) << __FUNCTION__ << ": Invalid window.";
+ return false;
+ }
+
+ size->width = ANativeWindow_getWidth(window->native_window);
+ size->height = ANativeWindow_getHeight(window->native_window);
+
+ JniEnvExt* env = JniEnvExt::Get();
+ ScopedLocalJavaRef<jobject> display_size(
+ env->CallStarboardObjectMethodOrAbort("getDisplaySize",
+ "()Landroid/util/Size;"));
+ int display_width =
+ env->CallIntMethodOrAbort(display_size.Get(), "getWidth", "()I");
+ int display_height =
+ env->CallIntMethodOrAbort(display_size.Get(), "getHeight", "()I");
+
+ // In the off chance we have non-square pixels, use the max ratio so the
+ // highest quality video suitable to the device gets selected.
+ size->video_pixel_ratio = std::max(
+ static_cast<float>(display_width) / size->width,
+ static_cast<float>(display_height) / size->height);
+
+ return true;
+}
diff --git a/src/starboard/android/shared/window_hide_on_screen_keyboard.cc b/src/starboard/android/shared/window_hide_on_screen_keyboard.cc
new file mode 100644
index 0000000..4ecb6e8
--- /dev/null
+++ b/src/starboard/android/shared/window_hide_on_screen_keyboard.cc
@@ -0,0 +1,25 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#include "starboard/android/shared/application_android.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+void SbWindowHideOnScreenKeyboard(SbWindow window, int ticket) {
+ starboard::android::shared::ApplicationAndroid::Get()
+ ->SbWindowHideOnScreenKeyboard(window, ticket);
+ return;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_internal.h b/src/starboard/android/shared/window_internal.h
new file mode 100644
index 0000000..24a6ed7
--- /dev/null
+++ b/src/starboard/android/shared/window_internal.h
@@ -0,0 +1,26 @@
+// Copyright 2017 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef STARBOARD_ANDROID_SHARED_WINDOW_INTERNAL_H_
+#define STARBOARD_ANDROID_SHARED_WINDOW_INTERNAL_H_
+
+#include <android/native_window.h>
+
+#include "starboard/window.h"
+
+struct SbWindowPrivate {
+ ANativeWindow* native_window;
+};
+
+#endif // STARBOARD_ANDROID_SHARED_WINDOW_INTERNAL_H_
diff --git a/src/starboard/android/shared/window_is_on_screen_keyboard_shown.cc b/src/starboard/android/shared/window_is_on_screen_keyboard_shown.cc
new file mode 100644
index 0000000..711f85c
--- /dev/null
+++ b/src/starboard/android/shared/window_is_on_screen_keyboard_shown.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+bool SbWindowIsOnScreenKeyboardShown(SbWindow window) {
+ // Stub.
+ return true;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_set_on_screen_keyboard_keep_focus.cc b/src/starboard/android/shared/window_set_on_screen_keyboard_keep_focus.cc
new file mode 100644
index 0000000..fd38256
--- /dev/null
+++ b/src/starboard/android/shared/window_set_on_screen_keyboard_keep_focus.cc
@@ -0,0 +1,22 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+void SbWindowSetOnScreenKeyboardKeepFocus(SbWindow window, bool keep_focus) {
+ // Stub.
+ return;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)
diff --git a/src/starboard/android/shared/window_show_on_screen_keyboard.cc b/src/starboard/android/shared/window_show_on_screen_keyboard.cc
new file mode 100644
index 0000000..d5be7c8
--- /dev/null
+++ b/src/starboard/android/shared/window_show_on_screen_keyboard.cc
@@ -0,0 +1,27 @@
+// Copyright 2018 The Cobalt Authors. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "starboard/window.h"
+
+#include "starboard/android/shared/application_android.h"
+
+#if SB_HAS(ON_SCREEN_KEYBOARD)
+void SbWindowShowOnScreenKeyboard(SbWindow window,
+ const char* input_text,
+ int ticket) {
+ starboard::android::shared::ApplicationAndroid::Get()
+ ->SbWindowShowOnScreenKeyboard(window, input_text, ticket);
+ return;
+}
+#endif // SB_HAS(ON_SCREEN_KEYBOARD)