diff --git a/android/build.gradle b/android/build.gradle index 157ca6ca14..01bd6f8b2f 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -11,6 +11,7 @@ androidGitVersion { } dependencies { + // 1.2.0 is the newest version we can use that won't complain about minSdk version. def appcompat_version = "1.2.0" implementation "androidx.appcompat:appcompat:$appcompat_version" @@ -21,7 +22,7 @@ dependencies { } android { - flavorDimensions "variant" + flavorDimensions += "variant" namespace 'org.ppsspp.ppsspp' signingConfigs { debug { @@ -45,10 +46,20 @@ android { } } - compileSdk 33 + compileSdk 34 ndkVersion "21.4.7075529" defaultConfig { + /* + configurations.all { + resolutionStrategy { + // Newer versions are not compatible with our minsdk. Should find a way to exclude it entirely + // since we have no use for this transitive dependency. + force 'androidx.emoji2:emoji2-views-helper:1.0.0' + } + } + */ + applicationId 'org.ppsspp.ppsspp' if (androidGitVersion.name() != "unknown" && androidGitVersion.code() >= 14000000) { // Start using automatic Android version numbers from version 1.4. @@ -63,7 +74,7 @@ android { new File("versioncode.txt").write(androidGitVersion.code().toString()) minSdk 9 - targetSdk 33 + targetSdk 34 if (project.hasProperty("ANDROID_VERSION_CODE") && project.hasProperty("ANDROID_VERSION_NAME")) { versionCode ANDROID_VERSION_CODE versionName ANDROID_VERSION_NAME diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index 74a30bfc94..f49e51529b 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -547,6 +547,8 @@ bool System_GetPropertyBool(SystemProperty prop) { return deviceType != DEVICE_TYPE_VR; case SYSPROP_HAS_ACCELEROMETER: return deviceType == DEVICE_TYPE_MOBILE; + case SYSPROP_CAN_CREATE_SHORTCUT: + return false; // We can't create shortcuts directly from game code, but we can from the Android UI. #ifndef HTTPS_NOT_AVAILABLE case SYSPROP_SUPPORTS_HTTPS: return !g_Config.bDisableHTTPS; @@ -1662,7 +1664,7 @@ static void VulkanEmuThread(ANativeWindow *wnd) { // NOTE: This is defunct and not working, due to how the Android storage functions currently require // a PpssppActivity specifically and we don't have one here. -extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv *env, jclass, jstring jpath) { +extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv * env, jclass, jstring jpath) { bool teardownThreadManager = false; if (!g_threadManager.IsInitialized()) { INFO_LOG(SYSTEM, "No thread manager - initializing one"); @@ -1710,3 +1712,58 @@ extern "C" jstring Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameName(JNIEnv return env->NewStringUTF(result.c_str()); } + + +extern "C" +JNIEXPORT jbyteArray JNICALL +Java_org_ppsspp_ppsspp_ShortcutActivity_queryGameIcon(JNIEnv * env, jclass clazz, jstring jpath) { + bool teardownThreadManager = false; + if (!g_threadManager.IsInitialized()) { + INFO_LOG(SYSTEM, "No thread manager - initializing one"); + // Need a thread manager. + teardownThreadManager = true; + g_threadManager.Init(1, 1); + } + // TODO: implement requestIcon() + + Path path = Path(GetJavaString(env, jpath)); + + INFO_LOG(SYSTEM, "queryGameIcon(%s)", path.c_str()); + + jbyteArray result = nullptr; + + GameInfoCache *cache = new GameInfoCache(); + std::shared_ptr info = cache->GetInfo(nullptr, path, GameInfoFlags::ICON); + // Wait until it's done: this is synchronous, unfortunately. + if (info) { + INFO_LOG(SYSTEM, "GetInfo successful, waiting"); + int attempts = 1000; + while (!info->Ready(GameInfoFlags::ICON)) { + sleep_ms(1); + attempts--; + if (!attempts) { + break; + } + } + INFO_LOG(SYSTEM, "Done waiting"); + if (info->Ready(GameInfoFlags::ICON)) { + if (!info->icon.data.empty()) { + INFO_LOG(SYSTEM, "requestIcon: Got icon"); + result = env->NewByteArray(info->icon.data.size()); + env->SetByteArrayRegion(result, 0, info->icon.data.size(), (const jbyte *)info->icon.data.data()); + } + } else { + INFO_LOG(SYSTEM, "requestIcon: Filetype unknown"); + } + } else { + INFO_LOG(SYSTEM, "No info from cache"); + } + + delete cache; + + if (teardownThreadManager) { + g_threadManager.Teardown(); + } + + return result; +} diff --git a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java index d21c3bddca..f7717f823a 100644 --- a/android/src/org/ppsspp/ppsspp/ShortcutActivity.java +++ b/android/src/org/ppsspp/ppsspp/ShortcutActivity.java @@ -4,6 +4,8 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.Intent; import android.content.Intent.ShortcutIconResource; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -75,6 +77,7 @@ public class ShortcutActivity extends Activity { } public static native String queryGameName(String path); + public static native byte[] queryGameIcon(String path); // Create shortcut as response for ACTION_CREATE_SHORTCUT intent. private void respondToShortcutRequest(Uri uri) { @@ -99,13 +102,13 @@ public class ShortcutActivity extends Activity { if (path.startsWith("content://")) { String [] segments = path.split("/"); try { - pathStr = java.net.URLDecoder.decode(segments[segments.length - 1], StandardCharsets.UTF_8.name()); + pathStr = java.net.URLDecoder.decode(segments[segments.length - 1], "UTF-8"); } catch (Exception e) { Log.i(TAG, "Exception getting name: " + e); } } else if (path.startsWith("file:///")) { try { - pathStr = java.net.URLDecoder.decode(path.substring(7), StandardCharsets.UTF_8.name()); + pathStr = java.net.URLDecoder.decode(path.substring(7), "UTF-8"); } catch (Exception e) { Log.i(TAG, "Exception getting name: " + e); } @@ -116,16 +119,19 @@ public class ShortcutActivity extends Activity { String[] pathSegments = pathStr.split("/"); name = pathSegments[pathSegments.length - 1]; - /* - // No longer working for various reasons. - PpssppActivity.CheckABIAndLoadLibrary(); - String name = queryGameName(path); - if (name.equals("")) { + String gameName = queryGameName(path); + byte [] iconData = null; + if (gameName.equals("")) { Log.i(TAG, "Failed to retrieve game name - ignoring."); - showBadGameMessage(); - return; - }*/ + // This probably happened because PPSSPP isn't running so the GameInfoCache isn't working. + // Let's just continue with our fallback name until we can fix that. + // showBadGameMessage(); + // return; + } else { + name = gameName; + iconData = queryGameIcon(path); + } Log.i(TAG, "Game name: " + name + " : Creating shortcut to " + uri.toString()); @@ -134,9 +140,23 @@ public class ShortcutActivity extends Activity { Intent responseIntent = new Intent(); responseIntent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); responseIntent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); - ShortcutIconResource iconResource = ShortcutIconResource.fromContext(this, R.drawable.ic_launcher); - responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + boolean setIcon = false; + if (iconData != null) { + // Try to create a PNG from the iconData. + Bitmap bmp = BitmapFactory.decodeByteArray(iconData, 0, iconData.length); + if (bmp != null) { + // Scale it to a square. + Bitmap scaledBitmap = Bitmap.createScaledBitmap(bmp, 144, 144, true); + responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON, scaledBitmap); + } + setIcon = true; + } + if (!setIcon) { + // Fall back to the PPSSPP icon. + ShortcutIconResource iconResource = ShortcutIconResource.fromContext(this, R.drawable.ic_launcher); + responseIntent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + } setResult(RESULT_OK, responseIntent); // Must call finish for result to be returned immediately