mirror of
https://github.com/libretro/RetroArch.git
synced 2024-12-11 18:23:45 +00:00
586 lines
16 KiB
C
586 lines
16 KiB
C
/* RetroArch - A frontend for libretro.
|
|
* Copyright (C) 2011-2017 - Daniel De Matteis
|
|
* Copyright (C) 2014-2017 - Jean-André Santoni
|
|
* Copyright (C) 2016-2019 - Brad Parker
|
|
* Copyright (C) 2019-2020 - James Leaver
|
|
*
|
|
* RetroArch is free software: you can redistribute it and/or modify it under the terms
|
|
* of the GNU General Public License as published by the Free Software Found-
|
|
* ation, either version 3 of the License, or (at your option) any later version.
|
|
*
|
|
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
|
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
|
* PURPOSE. See the GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with RetroArch.
|
|
* If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "com_retroarch_browser_retroactivity_RetroActivityCommon.h"
|
|
|
|
#ifdef HAVE_THREADS
|
|
#include <rthreads/rthreads.h>
|
|
#include <retro_assert.h>
|
|
#include <stdlib.h>
|
|
#endif
|
|
|
|
#include <string/stdstring.h>
|
|
#include "../frontend/drivers/platform_unix.h"
|
|
|
|
#include "play_feature_delivery.h"
|
|
|
|
/***************************/
|
|
/* Globals (do not fix...) */
|
|
/***************************/
|
|
|
|
/* Due to the way the JNI interface works,
|
|
* core download status updates happen
|
|
* asynchronously in a manner that we cannot
|
|
* capture using any standard means. We therefore
|
|
* have to implement status monitoring via an
|
|
* ugly hack, involving a mutex-locked global
|
|
* status struct... */
|
|
|
|
typedef struct
|
|
{
|
|
#ifdef HAVE_THREADS
|
|
slock_t *enabled_lock;
|
|
slock_t *status_lock;
|
|
#endif
|
|
unsigned download_progress;
|
|
enum play_feature_delivery_install_status last_status;
|
|
char last_core_name[256];
|
|
bool enabled;
|
|
bool enabled_set;
|
|
bool active;
|
|
} play_feature_delivery_state_t;
|
|
|
|
static play_feature_delivery_state_t play_feature_delivery_state = {
|
|
|
|
#ifdef HAVE_THREADS
|
|
NULL, /* enabled_lock */
|
|
NULL, /* status_lock */
|
|
#endif
|
|
0, /* download_progress */
|
|
PLAY_FEATURE_DELIVERY_IDLE, /* last_status */
|
|
{'\0'}, /* last_core_name */
|
|
false, /* enabled */
|
|
false, /* enabled_set */
|
|
false, /* active */
|
|
};
|
|
|
|
static play_feature_delivery_state_t* play_feature_delivery_get_state(void)
|
|
{
|
|
return &play_feature_delivery_state;
|
|
}
|
|
|
|
/**********************/
|
|
/* JNI Native Methods */
|
|
/**********************/
|
|
|
|
/*
|
|
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
|
|
* Method: coreInstallInitiated
|
|
* Signature: (Ljava/lang/String;Z)V
|
|
*/
|
|
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallInitiated
|
|
(JNIEnv *env, jobject this_obj, jstring core_name, jboolean successful)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
|
|
/* Lock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(state->status_lock);
|
|
#endif
|
|
|
|
/* Only update status if an install is active */
|
|
if (state->active)
|
|
{
|
|
/* Convert Java-style string to a proper char array */
|
|
const char *core_name_c = (*env)->GetStringUTFChars(
|
|
env, core_name, NULL);
|
|
|
|
/* Ensure that status update is for the
|
|
* correct core */
|
|
if (string_is_equal(state->last_core_name, core_name_c))
|
|
{
|
|
if (successful)
|
|
state->last_status = PLAY_FEATURE_DELIVERY_STARTING;
|
|
else
|
|
{
|
|
state->last_status = PLAY_FEATURE_DELIVERY_FAILED;
|
|
state->active = false;
|
|
}
|
|
}
|
|
|
|
/* Must always 'release' the converted string */
|
|
(*env)->ReleaseStringUTFChars(env, core_name, core_name_c);
|
|
}
|
|
|
|
/* Unlock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(state->status_lock);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Class: com_retroarch_browser_retroactivity_RetroActivityCommon
|
|
* Method: coreInstallStatusChanged
|
|
* Signature: ([Ljava/lang/String;IJJ)V
|
|
*/
|
|
JNIEXPORT void JNICALL Java_com_retroarch_browser_retroactivity_RetroActivityCommon_coreInstallStatusChanged
|
|
(JNIEnv *env, jobject thisObj, jobjectArray core_names, jint status, jlong bytes_downloaded, jlong total_bytes_to_download)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
|
|
/* Lock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(state->status_lock);
|
|
#endif
|
|
|
|
/* Only update status if an install is active */
|
|
if (state->active)
|
|
{
|
|
/* Note: core_names is a list of cores that
|
|
* are currently installing. We should check
|
|
* that state->last_core_name is in this list
|
|
* before updating the status, but if multiple
|
|
* installs are queued then it seems dubious
|
|
* to filter like this - i.e. is it possible
|
|
* for an entry to drop off the queue before
|
|
* the entire transaction is complete? If so,
|
|
* then we may risk 'missing' the final status
|
|
* update...
|
|
* We therefore just monitor the transaction
|
|
* as a whole, and disregard core names... */
|
|
|
|
/* Determine download progress */
|
|
if (total_bytes_to_download > 0)
|
|
{
|
|
state->download_progress = (unsigned)
|
|
(((float)bytes_downloaded * 100.0f /
|
|
(float)total_bytes_to_download) + 0.5f);
|
|
state->download_progress = (state->download_progress > 100) ?
|
|
100 : state->download_progress;
|
|
}
|
|
else
|
|
state->download_progress = 100;
|
|
|
|
/* Check status */
|
|
switch (status)
|
|
{
|
|
case 0: /* INSTALL_STATUS_DOWNLOADING */
|
|
state->last_status = PLAY_FEATURE_DELIVERY_DOWNLOADING;
|
|
break;
|
|
case 1: /* INSTALL_STATUS_INSTALLING */
|
|
state->last_status = PLAY_FEATURE_DELIVERY_INSTALLING;
|
|
break;
|
|
case 2: /* INSTALL_STATUS_INSTALLED */
|
|
state->last_status = PLAY_FEATURE_DELIVERY_INSTALLED;
|
|
state->active = false;
|
|
break;
|
|
case 3: /* INSTALL_STATUS_FAILED */
|
|
default:
|
|
state->last_status = PLAY_FEATURE_DELIVERY_FAILED;
|
|
state->active = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Unlock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(state->status_lock);
|
|
#endif
|
|
}
|
|
|
|
/******************/
|
|
/* Initialisation */
|
|
/******************/
|
|
|
|
/* Must be called upon program initialisation */
|
|
void play_feature_delivery_init(void)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
|
|
play_feature_delivery_deinit();
|
|
|
|
#ifdef HAVE_THREADS
|
|
if (!state->enabled_lock)
|
|
state->enabled_lock = slock_new();
|
|
|
|
retro_assert(state->enabled_lock);
|
|
|
|
if (!state->status_lock)
|
|
state->status_lock = slock_new();
|
|
|
|
retro_assert(state->status_lock);
|
|
#endif
|
|
|
|
/* Note: Would like to cache whether this
|
|
* is a Play Store build here, but
|
|
* play_feature_delivery_init() is called
|
|
* too early in the startup sequence... */
|
|
}
|
|
|
|
/* Must be called upon program termination */
|
|
void play_feature_delivery_deinit(void)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
|
|
#ifdef HAVE_THREADS
|
|
if (state->enabled_lock)
|
|
{
|
|
slock_free(state->enabled_lock);
|
|
state->enabled_lock = NULL;
|
|
}
|
|
|
|
if (state->status_lock)
|
|
{
|
|
slock_free(state->status_lock);
|
|
state->status_lock = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/**********/
|
|
/* Status */
|
|
/**********/
|
|
|
|
static bool play_feature_delivery_get_core_name(
|
|
const char *core_file, char *core_name, size_t len)
|
|
{
|
|
size_t core_file_len;
|
|
|
|
if (string_is_empty(core_file))
|
|
return false;
|
|
|
|
core_file_len = strlen(core_file);
|
|
|
|
if (len < core_file_len)
|
|
return false;
|
|
|
|
/* Ensure that core_file has the correct
|
|
* suffix */
|
|
if (!string_ends_with_size(core_file, "_libretro_android.so",
|
|
core_file_len, STRLEN_CONST("_libretro_android.so")))
|
|
return false;
|
|
|
|
/* Copy core_file and remove suffix */
|
|
strlcpy(core_name, core_file, len);
|
|
core_name[core_file_len - STRLEN_CONST("_libretro_android.so")] = '\0';
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Returns true if current build utilises
|
|
* play feature delivery
|
|
* > Relies on a Java function call, and is
|
|
* therefore slow */
|
|
static bool play_feature_delivery_enabled_internal(void)
|
|
{
|
|
JNIEnv *env = jni_thread_getenv();
|
|
struct android_app *app = (struct android_app*)g_android;
|
|
bool enabled = false;
|
|
|
|
if (!env ||
|
|
!app ||
|
|
!app->isPlayStoreBuild)
|
|
return false;
|
|
|
|
CALL_BOOLEAN_METHOD(env, enabled, app->activity->clazz,
|
|
app->isPlayStoreBuild);
|
|
|
|
return enabled;
|
|
}
|
|
|
|
/* Returns true if current build utilises
|
|
* play feature delivery */
|
|
bool play_feature_delivery_enabled(void)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
bool enabled;
|
|
|
|
/* Lock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(state->enabled_lock);
|
|
#endif
|
|
|
|
/* Calling Java functions is slow. We need to
|
|
* check Play Store build status frequently,
|
|
* often in loops, so rely on a cached global
|
|
* status flag instead dealing with Java
|
|
* interfaces */
|
|
if (!state->enabled_set)
|
|
{
|
|
state->enabled = play_feature_delivery_enabled_internal();
|
|
state->enabled_set = true;
|
|
}
|
|
|
|
enabled = state->enabled;
|
|
|
|
/* Unlock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(state->enabled_lock);
|
|
#endif
|
|
|
|
return enabled;
|
|
}
|
|
|
|
/* Returns a list of cores currently available
|
|
* via play feature delivery.
|
|
* Returns a new string_list on success, or
|
|
* NULL on failure */
|
|
struct string_list *play_feature_delivery_available_cores(void)
|
|
{
|
|
JNIEnv *env = jni_thread_getenv();
|
|
struct android_app *app = (struct android_app*)g_android;
|
|
struct string_list *core_list = string_list_new();
|
|
union string_list_elem_attr attr;
|
|
jobjectArray available_cores;
|
|
jsize num_cores;
|
|
size_t i;
|
|
|
|
attr.i = 0;
|
|
|
|
if (!env ||
|
|
!app ||
|
|
!app->getAvailableCores ||
|
|
!core_list)
|
|
goto error;
|
|
|
|
/* Get list of available cores */
|
|
CALL_OBJ_METHOD(env, available_cores, app->activity->clazz,
|
|
app->getAvailableCores);
|
|
num_cores = (*env)->GetArrayLength(env, available_cores);
|
|
|
|
for (i = 0; i < num_cores; i++)
|
|
{
|
|
/* Extract element of available cores array */
|
|
jstring core_name_jni = (jstring)
|
|
((*env)->GetObjectArrayElement(env, available_cores, i));
|
|
const char *core_name = NULL;
|
|
|
|
/* Convert Java-style string to a proper char array */
|
|
core_name = (*env)->GetStringUTFChars(env, core_name_jni, NULL);
|
|
|
|
if (!string_is_empty(core_name))
|
|
{
|
|
char core_file[256];
|
|
core_file[0] = '\0';
|
|
|
|
/* Generate core file name */
|
|
strlcpy(core_file, core_name, sizeof(core_file));
|
|
strlcat(core_file, "_libretro_android.so", sizeof(core_file));
|
|
|
|
/* Add entry to list */
|
|
if (!string_is_empty(core_file))
|
|
string_list_append(core_list, core_file, attr);
|
|
}
|
|
|
|
/* Must always 'release' the converted string */
|
|
(*env)->ReleaseStringUTFChars(env, core_name_jni, core_name);
|
|
}
|
|
|
|
if (core_list->size < 1)
|
|
goto error;
|
|
|
|
return core_list;
|
|
|
|
error:
|
|
if (core_list)
|
|
string_list_free(core_list);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* Returns true if specified core is currently
|
|
* installed via play feature delivery */
|
|
bool play_feature_delivery_core_installed(const char *core_file)
|
|
{
|
|
JNIEnv *env = jni_thread_getenv();
|
|
struct android_app *app = (struct android_app*)g_android;
|
|
jobjectArray installed_cores;
|
|
jsize num_cores;
|
|
char core_name[256];
|
|
size_t i;
|
|
|
|
core_name[0] = '\0';
|
|
|
|
if (!env ||
|
|
!app ||
|
|
!app->getInstalledCores)
|
|
return false;
|
|
|
|
/* Extract core name */
|
|
if (!play_feature_delivery_get_core_name(
|
|
core_file, core_name, sizeof(core_name)))
|
|
return false;
|
|
|
|
/* Get list of installed cores */
|
|
CALL_OBJ_METHOD(env, installed_cores, app->activity->clazz,
|
|
app->getInstalledCores);
|
|
num_cores = (*env)->GetArrayLength(env, installed_cores);
|
|
|
|
for (i = 0; i < num_cores; i++)
|
|
{
|
|
/* Extract element of installed cores array */
|
|
jstring installed_core_name_jni = (jstring)
|
|
((*env)->GetObjectArrayElement(env, installed_cores, i));
|
|
const char *installed_core_name = NULL;
|
|
|
|
/* Convert Java-style string to a proper char array */
|
|
installed_core_name = (*env)->GetStringUTFChars(
|
|
env, installed_core_name_jni, NULL);
|
|
|
|
/* Check for a match */
|
|
if (!string_is_empty(installed_core_name) &&
|
|
string_is_equal(core_name, installed_core_name))
|
|
{
|
|
/* Must always 'release' the converted string */
|
|
(*env)->ReleaseStringUTFChars(env,
|
|
installed_core_name_jni, installed_core_name);
|
|
return true;
|
|
}
|
|
|
|
/* Must always 'release' the converted string */
|
|
(*env)->ReleaseStringUTFChars(env,
|
|
installed_core_name_jni, installed_core_name);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Fetches last recorded status of the most
|
|
* recently initiated play feature delivery
|
|
* install transaction.
|
|
* 'progress' is an integer from 0-100.
|
|
* Returns true if a transaction is currently
|
|
* in progress. */
|
|
bool play_feature_delivery_download_status(
|
|
enum play_feature_delivery_install_status *status,
|
|
unsigned *progress)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
bool active;
|
|
|
|
/* Lock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(state->status_lock);
|
|
#endif
|
|
|
|
/* Copy status parameters */
|
|
if (status)
|
|
*status = state->last_status;
|
|
|
|
if (progress)
|
|
*progress = state->download_progress;
|
|
|
|
active = state->active;
|
|
|
|
/* Unlock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(state->status_lock);
|
|
#endif
|
|
|
|
return active;
|
|
}
|
|
|
|
/***********/
|
|
/* Control */
|
|
/***********/
|
|
|
|
/* Initialises download of the specified core.
|
|
* Returns false in the event of an error.
|
|
* Download status should be monitored via
|
|
* play_feature_delivery_download_status() */
|
|
bool play_feature_delivery_download(const char *core_file)
|
|
{
|
|
play_feature_delivery_state_t* state = play_feature_delivery_get_state();
|
|
JNIEnv *env = jni_thread_getenv();
|
|
struct android_app *app = (struct android_app*)g_android;
|
|
bool success = false;
|
|
char core_name[256];
|
|
jstring core_name_jni;
|
|
|
|
core_name[0] = '\0';
|
|
|
|
if (!env ||
|
|
!app ||
|
|
!app->downloadCore)
|
|
return false;
|
|
|
|
/* Extract core name */
|
|
if (!play_feature_delivery_get_core_name(
|
|
core_file, core_name, sizeof(core_name)))
|
|
return false;
|
|
|
|
/* Lock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_lock(state->status_lock);
|
|
#endif
|
|
|
|
/* We only support one download at a time */
|
|
if (!state->active)
|
|
{
|
|
/* Update status */
|
|
state->download_progress = 0;
|
|
state->last_status = PLAY_FEATURE_DELIVERY_PENDING;
|
|
state->active = true;
|
|
strlcpy(state->last_core_name, core_name,
|
|
sizeof(state->last_core_name));
|
|
|
|
/* Convert core name to a Java-style string */
|
|
core_name_jni = (*env)->NewStringUTF(env, core_name);
|
|
|
|
/* Request download */
|
|
CALL_VOID_METHOD_PARAM(env, app->activity->clazz,
|
|
app->downloadCore, core_name_jni);
|
|
|
|
/* Free core_name_jni reference */
|
|
(*env)->DeleteLocalRef(env, core_name_jni);
|
|
|
|
success = true;
|
|
}
|
|
|
|
/* Unlock mutex */
|
|
#ifdef HAVE_THREADS
|
|
slock_unlock(state->status_lock);
|
|
#endif
|
|
|
|
return success;
|
|
}
|
|
|
|
/* Deletes specified core.
|
|
* Returns false in the event of an error. */
|
|
bool play_feature_delivery_delete(const char *core_file)
|
|
{
|
|
JNIEnv *env = jni_thread_getenv();
|
|
struct android_app *app = (struct android_app*)g_android;
|
|
char core_name[256];
|
|
jstring core_name_jni;
|
|
|
|
core_name[0] = '\0';
|
|
|
|
if (!env ||
|
|
!app ||
|
|
!app->deleteCore)
|
|
return false;
|
|
|
|
/* Extract core name */
|
|
if (!play_feature_delivery_get_core_name(
|
|
core_file, core_name, sizeof(core_name)))
|
|
return false;
|
|
|
|
/* Convert to a Java-style string */
|
|
core_name_jni = (*env)->NewStringUTF(env, core_name);
|
|
|
|
/* Request core deletion */
|
|
CALL_VOID_METHOD_PARAM(env, app->activity->clazz,
|
|
app->deleteCore, core_name_jni);
|
|
|
|
/* Free core_name_jni reference */
|
|
(*env)->DeleteLocalRef(env, core_name_jni);
|
|
|
|
return true;
|
|
}
|