blob: dbdb8c074057e81f887140836e69ec57b272f71c [file] [log] [blame]
// 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 "game-activity/GameActivity.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/android/shared/log_internal.h"
#include "starboard/common/file.h"
#include "starboard/common/semaphore.h"
#include "starboard/common/string.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
#include "starboard/directory.h"
#include "starboard/file.h"
#endif
#include "starboard/event.h"
#include "starboard/log.h"
#include "starboard/shared/starboard/command_line.h"
#include "starboard/shared/starboard/starboard_switches.h"
#include "starboard/thread.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
#include "third_party/crashpad/wrapper/wrapper.h" // nogncheck
#endif
namespace starboard {
namespace android {
namespace shared {
namespace {
using ::starboard::shared::starboard::CommandLine;
typedef ::starboard::android::shared::ApplicationAndroid::AndroidCommand
AndroidCommand;
SbThread g_starboard_thread = kSbThreadInvalid;
Semaphore* g_app_created_semaphore = nullptr;
// 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());
}
SB_LOG(INFO) << "GetStartDeepLink: " << start_url;
return start_url;
}
#if SB_IS(EVERGREEN_COMPATIBLE)
bool CopyDirContents(const std::string& src_dir_path,
const std::string& dst_dir_path) {
SbDirectory src_dir = SbDirectoryOpen(src_dir_path.c_str(), NULL);
if (!SbDirectoryIsValid(src_dir)) {
SB_LOG(WARNING) << "Failed to open dir=" << src_dir_path;
return false;
}
std::vector<char> filename_buffer(kSbFileMaxName);
while (SbDirectoryGetNext(src_dir, filename_buffer.data(),
filename_buffer.size())) {
std::string filename(filename_buffer.begin(), filename_buffer.end());
std::string path_to_src_file = src_dir_path + kSbFileSepString + filename;
SbFile src_file =
SbFileOpen(path_to_src_file.c_str(), kSbFileOpenOnly | kSbFileRead,
nullptr, nullptr);
if (src_file == kSbFileInvalid) {
SB_LOG(WARNING) << "Failed to open file=" << path_to_src_file;
return false;
}
SbFileInfo info;
if (!SbFileGetInfo(src_file, &info)) {
SB_LOG(WARNING) << "Failed to get info for file=" << path_to_src_file;
SbFileClose(src_file);
return false;
}
int file_size = static_cast<int>(info.size);
// Read in bytes from src file
char file_contents_buffer[file_size];
int read = SbFileReadAll(src_file, file_contents_buffer, file_size);
if (read == -1) {
SB_LOG(WARNING) << "SbFileReadAll failed for file=" << path_to_src_file;
return false;
}
const std::string file_contents =
std::string(file_contents_buffer, file_size);
SbFileClose(src_file);
// Write bytes out to dst file
std::string path_to_dst_file = dst_dir_path;
path_to_dst_file.append(kSbFileSepString);
path_to_dst_file.append(filename);
SbFile dst_file =
SbFileOpen(path_to_dst_file.c_str(), kSbFileCreateAlways | kSbFileWrite,
NULL, NULL);
if (dst_file == kSbFileInvalid) {
SB_LOG(WARNING) << "Failed to open file=" << path_to_dst_file;
return false;
}
int wrote = SbFileWriteAll(dst_file, file_contents.c_str(), file_size);
RecordFileWriteStat(wrote);
if (wrote == -1) {
SB_LOG(WARNING) << "SbFileWriteAll failed for file=" << path_to_dst_file;
return false;
}
SbFileClose(dst_file);
}
SbDirectoryClose(src_dir);
return true;
}
// Extracts CA certificates from the APK to the file system and returns the path
// to the directory containing the extracted certifictes, or an empty string on
// error.
std::string ExtractCertificatesToFileSystem() {
std::vector<char> apk_path_buffer(kSbFileMaxPath);
if (!SbSystemGetPath(kSbSystemPathContentDirectory, apk_path_buffer.data(),
apk_path_buffer.size())) {
SB_LOG(WARNING) << "Failed to get path to content dir in APK";
return "";
}
std::string apk_path(apk_path_buffer.data());
apk_path.append(std::string(kSbFileSepString) + "app" + kSbFileSepString +
"cobalt" + kSbFileSepString + "content" + kSbFileSepString +
"ssl" + kSbFileSepString + "certs");
if (!SbFileExists(apk_path.c_str())) {
SB_LOG(WARNING) << "CA certificates directory not found in APK";
return "";
}
std::vector<char> file_system_path_buffer(kSbFileMaxPath);
if (!SbSystemGetPath(kSbSystemPathCacheDirectory,
file_system_path_buffer.data(),
file_system_path_buffer.size())) {
SB_LOG(WARNING) << "Failed to get path to cache dir on file system";
return "";
}
std::string file_system_path(file_system_path_buffer.data());
file_system_path.append(std::string(kSbFileSepString) + "certs");
if (!SbDirectoryCreate(file_system_path.c_str())) {
SB_LOG(WARNING) << "Failed to create new dir for CA certificates";
return "";
}
if (!CopyDirContents(apk_path, file_system_path)) {
SB_LOG(WARNING) << "Failed to copy CA certificates to the file system";
return "";
}
return file_system_path;
}
void InstallCrashpadHandler(const CommandLine& command_line) {
if (command_line.HasSwitch(
starboard::shared::starboard::kStartHandlerAtLaunch)) {
SB_LOG(WARNING) << "--"
<< starboard::shared::starboard::kStartHandlerAtLaunch
<< " not supported for AOSP Evergreen, not installing "
<< "Crashpad handler";
return;
}
std::string extracted_ca_certificates_path =
ExtractCertificatesToFileSystem();
if (extracted_ca_certificates_path.empty()) {
SB_LOG(WARNING) << "Failed to extract CA certificates to file system, not "
<< "installing Crashpad handler";
return;
}
third_party::crashpad::wrapper::InstallCrashpadHandler(
/*start_at_crash=*/true, extracted_ca_certificates_path);
}
#endif // SB_IS(EVERGREEN_COMPATIBLE)
void* ThreadEntryPoint(void* context) {
g_app_created_semaphore = static_cast<Semaphore*>(context);
#if SB_API_VERSION >= 15
int unused_value = -1;
int error_level = SbRunStarboardMain(unused_value, nullptr, SbEventHandle);
#else
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ApplicationAndroid app(looper);
CommandLine command_line(GetArgs());
LogInit(command_line);
#if SB_IS(EVERGREEN_COMPATIBLE)
InstallCrashpadHandler(command_line);
#endif // SB_IS(EVERGREEN_COMPATIBLE)
// 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 GameActivity_onCreate() that it may proceed.
g_app_created_semaphore->Put();
// Enter the Starboard run loop until stopped.
int error_level =
app.Run(std::move(command_line), GetStartDeepLink().c_str());
#endif // SB_API_VERSION >= 15
// 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(GameActivity* activity) {
if (g_app_running) {
ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStart);
}
}
void OnResume(GameActivity* activity) {
if (g_app_running) {
// Stop the MediaPlaybackService if activity state transits from background
// to foreground. Note that the MediaPlaybackService may already have
// been stopped before Cobalt's lifecycle state transits from Concealed
// to Frozen.
ApplicationAndroid::Get()->StopMediaPlaybackService();
ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kResume);
}
}
void OnPause(GameActivity* activity) {
if (g_app_running) {
// Start the MediaPlaybackService before activity state transits from
// foreground to background.
ApplicationAndroid::Get()->StartMediaPlaybackService();
ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kPause);
}
}
void OnStop(GameActivity* activity) {
if (g_app_running) {
ApplicationAndroid::Get()->SendAndroidCommand(AndroidCommand::kStop);
}
}
bool OnTouchEvent(GameActivity* activity,
const GameActivityMotionEvent* event) {
if (g_app_running) {
return ApplicationAndroid::Get()->SendAndroidMotionEvent(event);
}
return false;
}
bool OnKey(GameActivity* activity, const GameActivityKeyEvent* event) {
if (g_app_running) {
return ApplicationAndroid::Get()->SendAndroidKeyEvent(event);
}
return false;
}
void OnWindowFocusChanged(GameActivity* activity, bool focused) {
if (g_app_running) {
ApplicationAndroid::Get()->SendAndroidCommand(
focused ? AndroidCommand::kWindowFocusGained
: AndroidCommand::kWindowFocusLost);
}
}
void OnNativeWindowCreated(GameActivity* activity, ANativeWindow* window) {
if (g_app_running) {
ApplicationAndroid::Get()->SendAndroidCommand(
AndroidCommand::kNativeWindowCreated, window);
}
}
void OnNativeWindowDestroyed(GameActivity* activity, ANativeWindow* window) {
if (g_app_running) {
ApplicationAndroid::Get()->SendAndroidCommand(
AndroidCommand::kNativeWindowDestroyed);
}
}
extern "C" SB_EXPORT_PLATFORM void GameActivity_onCreate(
GameActivity* 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->onTouchEvent = OnTouchEvent;
activity->callbacks->onKeyDown = OnKey;
activity->callbacks->onKeyUp = OnKey;
activity->callbacks->onWindowFocusChanged = OnWindowFocusChanged;
activity->callbacks->onNativeWindowCreated = OnNativeWindowCreated;
activity->callbacks->onNativeWindowDestroyed = OnNativeWindowDestroyed;
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);
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VolumeStateReceiver_nativeVolumeChanged(JNIEnv* env,
jobject jcaller,
jint volumeDelta) {
if (g_app_running) {
SbKey key =
volumeDelta > 0 ? SbKey::kSbKeyVolumeUp : SbKey::kSbKeyVolumeDown;
ApplicationAndroid::Get()->SendKeyboardInject(key);
}
}
extern "C" SB_EXPORT_PLATFORM void
Java_dev_cobalt_coat_VolumeStateReceiver_nativeMuteChanged(JNIEnv* env,
jobject jcaller) {
if (g_app_running) {
ApplicationAndroid::Get()->SendKeyboardInject(SbKey::kSbKeyVolumeMute);
}
}
} // namespace
#if SB_API_VERSION >= 15
extern "C" int SbRunStarboardMain(int argc,
char** argv,
SbEventHandleCallback callback) {
ALooper* looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
ApplicationAndroid app(looper, callback);
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 GameActivity_onCreate() that it may proceed.
g_app_created_semaphore->Put();
// Enter the Starboard run loop until stopped.
int error_level =
app.Run(std::move(command_line), GetStartDeepLink().c_str());
return error_level;
}
#endif // SB_API_VERSION >= 15
} // namespace shared
} // namespace android
} // namespace starboard