diff --git a/cobalt/android/BUILD.gn b/cobalt/android/BUILD.gn index bcd4910f3ec..8dc79ff14e9 100644 --- a/cobalt/android/BUILD.gn +++ b/cobalt/android/BUILD.gn @@ -32,13 +32,21 @@ jinja_template("cobalt_manifest") { variables = [ "manifest_package=dev.cobalt.coat" ] } +generate_jni("jni_headers") { + sources = [ "apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java" ] +} + # TODO(cobalt): Re-enable or remove disabled java files. android_library("cobalt_apk_java") { testonly = true resources_package = "dev.cobalt.coat" + annotation_processor_deps = [ "//base/android/jni_generator:jni_processor" ] + deps = [ ":cobalt_java_resources", + ":jni_headers", "//base:base_java", + "//base:jni_java", "//base:process_launcher_java", "//build/android:build_java", "//components/embedder_support/android:view_java", @@ -56,6 +64,7 @@ android_library("cobalt_apk_java") { "//ui/android:ui_no_recycler_view_java", "//url:gurl_java", ] + sources = [ "apk/app/src/app/java/dev/cobalt/app/CobaltApplication.java", "apk/app/src/app/java/dev/cobalt/app/MainActivity.java", diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index 731f3015ed2..6b088d2a91a 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java @@ -52,8 +52,12 @@ import java.util.HashMap; import java.util.Locale; import java.util.TimeZone; +import org.chromium.base.annotations.CalledByNative; +import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.NativeMethods; /** Implementation of the required JNI methods called by the Starboard C++ code. */ +@JNINamespace("starboard::android::shared") public class StarboardBridge { /** Interface to be implemented by the Android Application hosting the starboard app. */ @@ -145,7 +149,18 @@ public StarboardBridge( private native void closeNativeStarboard(long nativeApp); - private native long nativeCurrentMonotonicTime(); + @NativeMethods + interface Natives { + void onStop(); + + long currentMonotonicTime(); + + // boolean initJNI(); + + // long startNativeStarboard(); + + // void closeNativeStarboard(long nativeApp); + } protected void onActivityStart(Activity activity) { Log.e(TAG, "onActivityStart ran"); @@ -164,8 +179,6 @@ protected void onActivityStop(Activity activity) { afterStopped(); } - private native void nativeOnStop(); - protected void onActivityDestroy(Activity activity) { if (applicationStopped) { // We can't restart the starboard app, so kill the process for a clean start next time. @@ -188,7 +201,7 @@ protected void onServiceDestroy(Service service) { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected void beforeStartOrResume() { Log.i(TAG, "Prepare to resume"); // Bring our platform services to life before resuming so that they're ready to deal with @@ -204,7 +217,7 @@ protected void beforeStartOrResume() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected void beforeSuspend() { try { Log.i(TAG, "Prepare to suspend"); @@ -222,7 +235,7 @@ protected void beforeSuspend() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected void afterStopped() { applicationStopped = true; ttsHelper.shutdown(); @@ -242,20 +255,20 @@ protected void afterStopped() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected void applicationStarted() { applicationReady = true; } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected void applicationStopping() { applicationReady = false; applicationStopped = true; } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public void requestSuspend() { Activity activity = activityHolder.get(); if (activity != null) { @@ -275,7 +288,7 @@ public boolean onSearchRequested() { // private native boolean nativeOnSearchRequested(); @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public Context getApplicationContext() { if (appContext == null) { throw new IllegalArgumentException("appContext cannot be null"); @@ -284,7 +297,7 @@ public Context getApplicationContext() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative void raisePlatformError(@PlatformError.ErrorType int errorType, long data) { PlatformError error = new PlatformError(activityHolder, errorType, data); error.raise(); @@ -304,7 +317,7 @@ protected Holder getActivityHolder() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String[] getArgs() { if (args == null) { throw new IllegalArgumentException("args cannot be null"); @@ -314,7 +327,7 @@ protected String[] getArgs() { /** Returns the URL from the Intent that started the app. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getStartDeepLink() { if (startDeepLink == null) { throw new IllegalArgumentException("startDeepLink cannot be null"); @@ -342,7 +355,7 @@ private void nativeHandleDeepLink(String url) { * May be overridden for use cases that need to segregate storage. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getFilesAbsolutePath() { return appContext.getFilesDir().getAbsolutePath(); } @@ -352,7 +365,7 @@ protected String getFilesAbsolutePath() { * overridden for use cases that need to segregate storage. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getCacheAbsolutePath() { return appContext.getCacheDir().getAbsolutePath(); } @@ -364,6 +377,9 @@ protected String getCacheAbsolutePath() { */ @SuppressWarnings("unused") @UsedByNative + // TODO: (cobalt b/372559388) Migrate complicated returned type functions to JNI zero. + // The @CalledByNative annotation has strict signature parsing rules, + // and Pair is not be supported well. Pair getLocalInterfaceAddressAndNetmask(boolean wantIPv6) { try { Enumeration it = NetworkInterface.getNetworkInterfaces(); @@ -411,7 +427,7 @@ Pair getLocalInterfaceAddressAndNetmask(boolean wantIPv6) { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative CobaltTextToSpeechHelper getTextToSpeechHelper() { if (ttsHelper == null) { throw new IllegalArgumentException("ttsHelper cannot be null for native code"); @@ -423,7 +439,7 @@ CobaltTextToSpeechHelper getTextToSpeechHelper() { * @return A new CaptionSettings object with the current system caption settings. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative CaptionSettings getCaptionSettings() { CaptioningManager cm = (CaptioningManager) appContext.getSystemService(Context.CAPTIONING_SERVICE); @@ -432,13 +448,13 @@ CaptionSettings getCaptionSettings() { /** Java-layer implementation of SbSystemGetLocaleId. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative String systemGetLocaleId() { return Locale.getDefault().toLanguageTag(); } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative String getTimeZoneId() { Locale locale = Locale.getDefault(); Calendar calendar = Calendar.getInstance(locale); @@ -450,19 +466,19 @@ String getTimeZoneId() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative SizeF getDisplayDpi() { return DisplayUtil.getDisplayDpi(); } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative Size getDisplaySize() { return DisplayUtil.getSystemDisplaySize(); } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public ResourceOverlay getResourceOverlay() { if (resourceOverlay == null) { throw new IllegalArgumentException("resourceOverlay cannot be null for native code"); @@ -484,7 +500,7 @@ private static String getSystemProperty(String name) { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative Size getDeviceResolution() { String displaySize = android.os.Build.VERSION.SDK_INT < 28 @@ -508,7 +524,7 @@ Size getDeviceResolution() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative boolean isNetworkConnected() { if (networkStatus == null) { throw new IllegalArgumentException("networkStatus cannot be null for native code"); @@ -522,7 +538,7 @@ boolean isNetworkConnected() { * @return true if no device is connected. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public boolean isMicrophoneDisconnected() { // A check specifically for microphones is not available before API 28, so it is assumed that a // connected input audio device is a microphone. @@ -551,7 +567,7 @@ public boolean isMicrophoneDisconnected() { * @return true if the microphone mute is on. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public boolean isMicrophoneMute() { AudioManager audioManager = (AudioManager) appContext.getSystemService(AUDIO_SERVICE); return audioManager.isMicrophoneMute(); @@ -561,7 +577,7 @@ public boolean isMicrophoneMute() { * @return true if we have an active network connection and it's on an wireless network. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative boolean isCurrentNetworkWireless() { ConnectivityManager connMgr = (ConnectivityManager) appContext.getSystemService(Context.CONNECTIVITY_SERVICE); @@ -581,7 +597,7 @@ boolean isCurrentNetworkWireless() { * @return true if the user has enabled accessibility high contrast text in the operating system. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative boolean isAccessibilityHighContrastTextEnabled() { AccessibilityManager am = (AccessibilityManager) appContext.getSystemService(Context.ACCESSIBILITY_SERVICE); @@ -596,7 +612,7 @@ boolean isAccessibilityHighContrastTextEnabled() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative void updateMediaSession( int playbackState, long actions, @@ -615,7 +631,7 @@ void updateMediaSession( } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public void deactivateMediaSession() { // TODO(b/377019873): re-enable Log.e(TAG, "MediaSession is disabled"); @@ -624,7 +640,7 @@ public void deactivateMediaSession() { /** Returns string for kSbSystemPropertyUserAgentAuxField */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getUserAgentAuxField() { StringBuilder sb = new StringBuilder(); @@ -652,7 +668,7 @@ protected String getUserAgentAuxField() { /** Returns string for kSbSystemPropertyAdvertisingId */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getAdvertisingId() { // TODO(b/377049113): re-enable Log.e(TAG, "IFA is disabled"); @@ -662,7 +678,7 @@ protected String getAdvertisingId() { /** Returns boolean for kSbSystemPropertyLimitAdTracking */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected boolean getLimitAdTracking() { // TODO(b/377049113): re-enable Log.e(TAG, "IFA is disabled"); @@ -671,7 +687,7 @@ protected boolean getLimitAdTracking() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative AudioOutputManager getAudioOutputManager() { if (audioOutputManager == null) { throw new IllegalArgumentException("audioOutputManager cannot be null for native code"); @@ -681,7 +697,7 @@ AudioOutputManager getAudioOutputManager() { /** Returns Java layer implementation for AudioPermissionRequester */ // @SuppressWarnings("unused") - // @UsedByNative + // @CalledByNative // AudioPermissionRequester getAudioPermissionRequester() { // return audioPermissionRequester; // } @@ -691,7 +707,7 @@ AudioOutputManager getAudioOutputManager() { // } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public void resetVideoSurface() { Activity activity = activityHolder.get(); if (activity instanceof CobaltActivity) { @@ -700,7 +716,7 @@ public void resetVideoSurface() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public void setVideoSurfaceBounds(final int x, final int y, final int width, final int height) { Activity activity = activityHolder.get(); if (activity instanceof CobaltActivity) { @@ -710,7 +726,7 @@ public void setVideoSurfaceBounds(final int x, final int y, final int width, fin /** Return supported hdr types. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public int[] getSupportedHdrTypes() { Display defaultDisplay = DisplayUtil.getDefaultDisplay(); if (defaultDisplay == null) { @@ -739,7 +755,7 @@ public boolean hasCobaltService(String serviceName) { return cobaltServiceFactories.get(serviceName) != null; } - public CobaltService openCobaltService(long nativeService, String serviceName) { + public CobaltService openCobaltService(long nativeService, String serviceName) { // ??????????? if (cobaltServices.get(serviceName) != null) { // Attempting to re-open an already open service fails. Log.e(TAG, String.format("Cannot open already open service %s", serviceName)); @@ -788,12 +804,12 @@ public byte[] sendToCobaltService(String serviceName, byte [] data) { /** Returns the application start timestamp. */ @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected long getAppStartTimestamp() { Activity activity = activityHolder.get(); if (activity instanceof CobaltActivity) { long javaStartTimestamp = ((CobaltActivity) activity).getAppStartTimestamp(); - long cppTimestamp = nativeCurrentMonotonicTime(); + long cppTimestamp = StarboardBridgeJni.get().currentMonotonicTime(); long javaStopTimestamp = System.nanoTime(); return cppTimestamp - (javaStopTimestamp - javaStartTimestamp) / timeNanosecondsPerMicrosecond; @@ -802,7 +818,7 @@ protected long getAppStartTimestamp() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative void reportFullyDrawn() { Activity activity = activityHolder.get(); if (activity != null) { @@ -811,7 +827,7 @@ void reportFullyDrawn() { } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative public void setCrashContext(String key, String value) { Log.i(TAG, "setCrashContext Called: " + key + ", " + value); crashContext.put(key, value); @@ -829,19 +845,19 @@ public void registerCrashContextUpdateHandler(CrashContextUpdateHandler handler) } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected boolean getIsAmatiDevice() { return this.isAmatiDevice; } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected String getBuildFingerprint() { return Build.FINGERPRINT; } @SuppressWarnings("unused") - @UsedByNative + @CalledByNative protected long getPlayServicesVersion() { try { if (android.os.Build.VERSION.SDK_INT < 28) { diff --git a/starboard/android/shared/BUILD.gn b/starboard/android/shared/BUILD.gn index 7d081f02bf7..2801ca2a6b8 100644 --- a/starboard/android/shared/BUILD.gn +++ b/starboard/android/shared/BUILD.gn @@ -265,6 +265,8 @@ static_library("starboard_platform") { #"speech_synthesis_internal.cc", #"speech_synthesis_is_supported.cc", #"speech_synthesis_speak.cc", + "starboard_bridge.cc", + "starboard_bridge.h", "system_get_extensions.cc", "system_get_locale_id.cc", "system_get_path.cc", @@ -316,12 +318,14 @@ static_library("starboard_platform") { public_deps = [ ":starboard_base_symbolize", ":starboard_jni_state", + "//cobalt/android:jni_headers", "//starboard/common", "//starboard/shared/starboard/media:media_util", "//starboard/shared/starboard/player/filter:filter_based_player_sources", ] deps = [ + "//base", "//third_party/libevent", "//third_party/opus", ] diff --git a/starboard/android/shared/android_main.cc b/starboard/android/shared/android_main.cc index 0ed8090673a..fbc37107b75 100644 --- a/starboard/android/shared/android_main.cc +++ b/starboard/android/shared/android_main.cc @@ -35,6 +35,8 @@ #include "starboard/crashpad_wrapper/wrapper.h" // nogncheck #endif +#include "starboard/android/shared/starboard_bridge.h" + namespace starboard { namespace android { namespace shared { @@ -292,10 +294,14 @@ void StarboardThreadLaunch() { } #endif // SB_IS(EVERGREEN_COMPATIBLE) +// TODO: consolidate this function when fully deprecate JniEnvExt. extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_coat_StarboardBridge_initJNI( JniEnvExt* env, jobject starboard_bridge) { JniEnvExt::Initialize(env, starboard_bridge); + + // Initialize the singleton instance of StarboardBridge + StarboardBridge::GetInstance()->Initialize(env, starboard_bridge); } extern "C" SB_EXPORT_PLATFORM jlong diff --git a/starboard/android/shared/application_android.cc b/starboard/android/shared/application_android.cc index 5d076a7f268..9db7e234b47 100644 --- a/starboard/android/shared/application_android.cc +++ b/starboard/android/shared/application_android.cc @@ -24,6 +24,7 @@ #include #include +#include "base/android/jni_android.h" #include "starboard/extension/accessibility.h" #include "starboard/android/shared/file_internal.h" @@ -44,14 +45,6 @@ namespace starboard { namespace android { namespace shared { -namespace { -int64_t GetAppStartTimestamp() { - JniEnvExt* env = JniEnvExt::Get(); - jlong app_start_timestamp = - env->CallStarboardLongMethodOrAbort("getAppStartTimestamp", "()J"); - return app_start_timestamp; -} -} // namespace // TODO(cobalt, b/378708359): Remove this dummy init. void stubSbEventHandle(const SbEvent* event) { @@ -70,18 +63,19 @@ ApplicationAndroid::ApplicationAndroid( // from the assets. The use ICU is used in our logging. SbFileAndroidInitialize(); - JniEnvExt* env = JniEnvExt::Get(); - jobject local_ref = env->CallStarboardObjectMethodOrAbort( - "getResourceOverlay", "()Ldev/cobalt/coat/ResourceOverlay;"); - resource_overlay_ = env->ConvertLocalRefToGlobalRef(local_ref); + base::android::ScopedJavaLocalRef resource_overlay = + starboard_bridge_->GetResourceOverlay(); + resource_overlay_ = + JniEnvExt::Get()->ConvertLocalRefToGlobalRef(resource_overlay.obj()); + SbAudioSinkPrivate::Initialize(); - app_start_timestamp_ = GetAppStartTimestamp(); - env->CallStarboardVoidMethodOrAbort("applicationStarted", "()V"); + + app_start_timestamp_ = starboard_bridge_->GetAppStartTimestamp(); + starboard_bridge_->ApplicationStarted(); } ApplicationAndroid::~ApplicationAndroid() { - JniEnvExt* env = JniEnvExt::Get(); - env->CallStarboardVoidMethodOrAbort("applicationStopping", "()V"); + starboard_bridge_->ApplicationStopping(); // The application is exiting. // Release the global reference. @@ -94,12 +88,6 @@ ApplicationAndroid::~ApplicationAndroid() { JniEnvExt::OnThreadShutdown(); } -extern "C" SB_EXPORT_PLATFORM void -Java_dev_cobalt_coat_StarboardBridge_nativeOnStop(JniEnvExt* env) { - SbAudioSinkPrivate::TearDown(); - SbFileAndroidTeardown(); -} - extern "C" SB_EXPORT_PLATFORM jboolean Java_dev_cobalt_coat_StarboardBridge_nativeOnSearchRequested( JniEnvExt* env, @@ -108,14 +96,6 @@ Java_dev_cobalt_coat_StarboardBridge_nativeOnSearchRequested( return true; } -extern "C" SB_EXPORT_PLATFORM jlong -Java_dev_cobalt_coat_StarboardBridge_nativeCurrentMonotonicTime( - JNIEnv* env, - jobject jcaller, - jboolean online) { - return CurrentMonotonicTime(); -} - extern "C" SB_EXPORT_PLATFORM void Java_dev_cobalt_coat_CobaltSystemConfigChangeReceiver_nativeDateTimeConfigurationChanged( JNIEnv* env, diff --git a/starboard/android/shared/application_android.h b/starboard/android/shared/application_android.h index 16c2a577840..c97c11b76f8 100644 --- a/starboard/android/shared/application_android.h +++ b/starboard/android/shared/application_android.h @@ -29,6 +29,8 @@ #include "starboard/shared/starboard/queue_application.h" #include "starboard/types.h" +#include "starboard/android/shared/starboard_bridge.h" + namespace starboard { namespace android { namespace shared { @@ -66,6 +68,9 @@ class ApplicationAndroid void WakeSystemEventWait() override {} private: + // starboard_bridge_ is a global singleton, use a raw pointer to not interfere + // with it's lifecycle management. + StarboardBridge* starboard_bridge_; jobject resource_overlay_; Mutex overlay_mutex_; diff --git a/starboard/android/shared/starboard_bridge.cc b/starboard/android/shared/starboard_bridge.cc new file mode 100644 index 00000000000..734bbf54673 --- /dev/null +++ b/starboard/android/shared/starboard_bridge.cc @@ -0,0 +1,65 @@ +// Copyright 2024 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/starboard_bridge.h" + +#include "starboard/android/shared/file_internal.h" +#include "starboard/common/time.h" +#include "starboard/media.h" +#include "starboard/shared/starboard/audio_sink/audio_sink_internal.h" + +// Must come after all headers that specialize FromJniType() / ToJniType(). +#include "cobalt/android/jni_headers/StarboardBridge_jni.h" + +namespace starboard { +namespace android { +namespace shared { + +extern "C" SB_EXPORT_PLATFORM void JNI_StarboardBridge_OnStop(JNIEnv* env) { + SbAudioSinkPrivate::TearDown(); + SbFileAndroidTeardown(); +} + +extern "C" SB_EXPORT_PLATFORM jlong +JNI_StarboardBridge_CurrentMonotonicTime(JNIEnv* env) { + return CurrentMonotonicTime(); +} + +long StarboardBridge::GetAppStartTimestamp() { + JNIEnv* env = base::android::AttachCurrentThread(); + CHECK(env); + return Java_StarboardBridge_getAppStartTimestamp(env, j_starboard_bridge_); +} + +base::android::ScopedJavaLocalRef +StarboardBridge::GetResourceOverlay() { + JNIEnv* env = base::android::AttachCurrentThread(); + CHECK(env); + return Java_StarboardBridge_getResourceOverlay(env, j_starboard_bridge_); +} + +void StarboardBridge::ApplicationStarted() { + JNIEnv* env = base::android::AttachCurrentThread(); + CHECK(env); + return Java_StarboardBridge_applicationStarted(env, j_starboard_bridge_); +} + +void StarboardBridge::ApplicationStopping() { + JNIEnv* env = base::android::AttachCurrentThread(); + CHECK(env); + return Java_StarboardBridge_applicationStopping(env, j_starboard_bridge_); +} +} // namespace shared +} // namespace android +} // namespace starboard diff --git a/starboard/android/shared/starboard_bridge.h b/starboard/android/shared/starboard_bridge.h new file mode 100644 index 00000000000..87321883399 --- /dev/null +++ b/starboard/android/shared/starboard_bridge.h @@ -0,0 +1,64 @@ +// Copyright 2024 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_STARBOARD_BRIDGE_H_ +#define STARBOARD_ANDROID_SHARED_STARBOARD_BRIDGE_H_ + +#include + +#include "base/android/scoped_java_ref.h" +#include "base/memory/singleton.h" + +namespace starboard { +namespace android { +namespace shared { + +class StarboardBridge { + public: + // Returns the singleton. + static StarboardBridge* GetInstance() { + return base::Singleton::get(); + } + + void Initialize(JNIEnv* env, jobject obj) { + j_starboard_bridge_.Reset(env, obj); + } + + long GetAppStartTimestamp(); + + base::android::ScopedJavaLocalRef GetResourceOverlay(); + + void ApplicationStarted(); + + void ApplicationStopping(); + + private: + StarboardBridge() = default; + ~StarboardBridge() = default; + + // Prevent copy construction and assignment + StarboardBridge(const StarboardBridge&) = delete; + StarboardBridge& operator=(const StarboardBridge&) = delete; + + friend struct base::DefaultSingletonTraits; + + // Java StarboardBridge instance. + base::android::ScopedJavaGlobalRef j_starboard_bridge_; +}; + +} // namespace shared +} // namespace android +} // namespace starboard + +#endif // STARBOARD_ANDROID_SHARED_STARBOARD_BRIDGE_H_