Skip to content

Android: Upgrade SDK and target versions, implement shortcut icons #19139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 14 additions & 3 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -21,7 +22,7 @@ dependencies {
}

android {
flavorDimensions "variant"
flavorDimensions += "variant"
namespace 'org.ppsspp.ppsspp'
signingConfigs {
debug {
Expand All @@ -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.
Expand All @@ -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
Expand Down
59 changes: 58 additions & 1 deletion android/jni/app-android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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<GameInfo> 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;
}
44 changes: 32 additions & 12 deletions android/src/org/ppsspp/ppsspp/ShortcutActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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;
Expand Down Expand Up @@ -75,6 +77,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
}

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) {
Expand All @@ -99,13 +102,13 @@ private void respondToShortcutRequest(Uri uri) {
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);
}
Expand All @@ -116,16 +119,19 @@ private void respondToShortcutRequest(Uri uri) {
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());

Expand All @@ -134,9 +140,23 @@ private void respondToShortcutRequest(Uri uri) {
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
Expand Down
Loading