Merge pull request #21836 from hrydgard/misc-work

Implement support for Android 17 behavior changes
This commit is contained in:
Henrik Rydgård
2026-06-15 13:39:48 +02:00
committed by GitHub
13 changed files with 122 additions and 30 deletions
+3
View File
@@ -18,6 +18,7 @@
enum SystemPermission {
SYSTEM_PERMISSION_STORAGE,
SYSTEM_PERMISSION_LOCAL_NETWORK,
};
enum PermissionStatus {
@@ -242,6 +243,8 @@ enum SystemProperty {
SYSPROP_USE_APP_STORE,
SYSPROP_SUPPORTS_SHARE_TEXT,
SYSPROP_CAN_RESTRICT_ORIENTATION,
SYSPROP_INSTALLER_NAME, // Useful on Android to check if we were installed from the play store.
};
+1 -1
View File
@@ -294,7 +294,7 @@ void DisplayLayoutScreen::CreateViews() {
rightColumn->Add(new CheckBox(&config.bIgnoreScreenInsets, gr->T("Ignore camera notch when centering")));
}
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
if (System_GetPropertyBool(SYSPROP_CAN_RESTRICT_ORIENTATION)) {
rightColumn->Add(new Spacer(12.0f));
AddRotationPicker(screenManager(), rightColumn, true);
}
+4 -4
View File
@@ -1424,15 +1424,15 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) {
PopupSliderChoice *exitConfirmation = systemSettings->Add(new PopupSliderChoice(&g_Config.iAskForExitConfirmationAfterSeconds, 0, 1200, 300, sy->T("Ask for exit confirmation after seconds"), screenManager(), "s"));
exitConfirmation->SetZeroLabel(sy->T("Off"));
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
if (System_GetPropertyBool(SYSPROP_CAN_RESTRICT_ORIENTATION)) {
auto co = GetI18NCategory(I18NCat::CONTROLS);
// Display rotation
AddRotationPicker(screenManager(), systemSettings, true);
}
if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
systemSettings->Add(new CheckBox(&g_Config.bSustainedPerformanceMode, sy->T("Sustained performance mode")))->OnClick.Handle(this, &GameSettingsScreen::OnSustainedPerformanceModeChange);
}
if (System_GetPropertyBool(SYSPROP_SUPPORTS_SUSTAINED_PERF_MODE)) {
systemSettings->Add(new CheckBox(&g_Config.bSustainedPerformanceMode, sy->T("Sustained performance mode")))->OnClick.Handle(this, &GameSettingsScreen::OnSustainedPerformanceModeChange);
}
systemSettings->Add(new Choice(sy->T("Restore Default Settings")))->OnClick.Handle(this, &GameSettingsScreen::OnRestoreDefaultSettings);
+1 -1
View File
@@ -760,7 +760,7 @@ void GamePauseScreen::CreateViews() {
screenManager()->push(new GameScreen(gamePath_, true));
});
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_MOBILE) {
if (System_GetPropertyBool(SYSPROP_CAN_RESTRICT_ORIENTATION)) {
AddRotationPicker(screenManager(), middleColumn, false);
}
+23
View File
@@ -32,6 +32,7 @@
#include "Common/Net/Resolve.h"
#include "Common/Net/URL.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/System/System.h"
#include "Common/System/Request.h"
#include "Common/File/PathBrowser.h"
@@ -313,12 +314,34 @@ void RemoteISOScreen::update() {
serverRunning_ = nowRunning;
}
void RemoteISOScreen::sendMessage(UIMessage message, const char *value) {
UITabbedBaseDialogScreen::sendMessage(message, value);
switch (message) {
case UIMessage::PERMISSION_GRANTED:
if (equals(value, "local_network")) {
RecreateViews();
} else {
ERROR_LOG(Log::UI, "Unknown permission granted: %s", value);
}
break;
}
}
void RemoteISOScreen::CreateConnectTab(UI::ViewGroup *tab) {
auto di = GetI18NCategory(I18NCat::DIALOG);
auto ri = GetI18NCategory(I18NCat::REMOTEISO);
using namespace UI;
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_LOCAL_NETWORK);
if (status != PERMISSION_STATUS_GRANTED) {
// Proceed with local network functionality
tab->Add(new Choice(ri->T("Ask for network permission")))->OnClick.Add([this](UI::EventParams &e) {
System_AskForPermission(SYSTEM_PERMISSION_LOCAL_NETWORK);
});
return;
}
if (serverRunning_) {
tab->Add(new NoticeView(NoticeLevel::SUCCESS, ri->T("Currently sharing"), "", new LinearLayoutParams(Margins(12, 5, 0, 5))));
} else {
+2
View File
@@ -46,6 +46,8 @@ protected:
void HandleStopServer(UI::EventParams &e);
void HandleBrowse(UI::EventParams &e);
void sendMessage(UIMessage message, const char *value) override;
UI::TextView *firewallWarning_ = nullptr;
bool serverRunning_ = false;
bool serverStopping_ = false;
+1
View File
@@ -31,6 +31,7 @@
<uses-permission-sdk-23 android:name="android.permission.TRANSMIT_IR" />
<uses-permission-sdk-23 android:name="android.permission.CAMERA" />
<uses-permission-sdk-23 android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_LOCAL_NETWORK" />
<!-- AndroidX minimum SDK workaround. We don't care if it's broken on older versions. -->
<uses-sdk tools:overrideLibrary="androidx.appcompat.resources,androidx.appcompat,androidx.fragment,androidx.drawerlayout,androidx.vectordrawable.animated,androidx.vectordrawable,androidx.viewpager,androidx.loader,androidx.activity,androidx.annotation,androidx.customview,androidx.cursoradapter,androidx.arch,androidx.collection,androidx.core,androidx.versionedparcelable,androidx.interpolator,androidx.lifecycle,androidx.loader,androidx.savedstate,androidx.lifecycle.viewmodel,androidx.lifecycle.livedata,androidx.lifecycle.livedata.core,androidx.arch.core,androidx.documentfile"/>
+3 -7
View File
@@ -1,7 +1,3 @@
import com.google.protobuf.gradle.*
import org.gradle.process.ExecOperations
import java.io.ByteArrayOutputStream
plugins {
id("com.android.application")
id("com.google.protobuf")
@@ -67,7 +63,7 @@ dependencies {
// Will replace with a different plugin soon.
implementation("androidx.appcompat:appcompat:1.7.1")
implementation("androidx.documentfile:documentfile:1.1.0")
implementation("com.google.protobuf:protobuf-javalite:4.33.5")
implementation("com.google.protobuf:protobuf-javalite:4.35.1")
}
protobuf {
@@ -110,8 +106,8 @@ android {
ndkVersion = "29.0.14206865"
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
lint {
+33 -11
View File
@@ -150,6 +150,7 @@ static int optimalSampleRate = 0;
static int sampleRate = 0;
static int framesPerBuffer = 0;
static int androidVersion;
static int smallestScreenWidthDp;
static int deviceType;
// This is the ACTUAL display size, not the hardware scaled display size.
@@ -529,6 +530,12 @@ bool System_GetPropertyBool(SystemProperty prop) {
return false; // We can't create shortcuts directly from game code, but we can from the Android UI.
case SYSPROP_DISPLAY_HAS_CAMERA_CUTOUT:
return g_hasCameraCutout;
case SYSPROP_CAN_RESTRICT_ORIENTATION:
// On Android 17+, large displays (sw600dp+) ignore orientation restrictions.
if (androidVersion >= 37 && smallestScreenWidthDp >= 600) {
return false;
}
return true;
#ifndef HTTPS_NOT_AVAILABLE
case SYSPROP_SUPPORTS_HTTPS:
return !g_Config.bDisableHTTPS;
@@ -738,7 +745,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
(JNIEnv * env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath,
jstring jdataDir, jstring jexternalStorageDir, jstring jexternalFilesDir, jstring jNativeLibDir,
jstring jadditionalStorageDirs, jstring jcacheDir, jstring jshortcutParam, jstring jInstallerName,
jint jAndroidVersion, jstring jboard) {
jint jAndroidVersion, jstring jboard, jint jSmallestScreenWidthDp) {
SetCurrentThreadName("androidInit");
// Makes sure we get early permission grants.
@@ -750,6 +757,7 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init
renderer_inited = false;
exitRenderLoop = false;
androidVersion = jAndroidVersion;
smallestScreenWidthDp = jSmallestScreenWidthDp;
deviceType = jdeviceType;
Path apkPath(GetJavaString(env, japkpath));
@@ -1246,6 +1254,9 @@ void System_AskForPermission(SystemPermission permission) {
case SYSTEM_PERMISSION_STORAGE:
PushCommand("ask_permission", "storage");
break;
case SYSTEM_PERMISSION_LOCAL_NETWORK:
PushCommand("ask_permission", "local_network");
break;
}
}
@@ -1450,18 +1461,29 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_sendMessageFromJava(JNI
if (msg == "moga") {
mogaVersion = prm;
} else if (msg == "permission_pending") {
INFO_LOG(Log::System, "STORAGE PERMISSION: PENDING");
// TODO: Add support for other permissions
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
// Don't need to send along, nothing else is listening.
if (prm == "storage") {
INFO_LOG(Log::System, "STORAGE PERMISSION: PENDING");
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_PENDING;
} else if (prm == "local_network") {
INFO_LOG(Log::System, "LOCAL NETWORK PERMISSION: PENDING");
permissions[SYSTEM_PERMISSION_LOCAL_NETWORK] = PERMISSION_STATUS_PENDING;
}
} else if (msg == "permission_denied") {
INFO_LOG(Log::System, "STORAGE PERMISSION: DENIED");
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
// Don't need to send along, nothing else is listening.
if (prm == "storage") {
INFO_LOG(Log::System, "STORAGE PERMISSION: DENIED");
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_DENIED;
} else if (prm == "local_network") {
INFO_LOG(Log::System, "LOCAL NETWORK PERMISSION: DENIED");
permissions[SYSTEM_PERMISSION_LOCAL_NETWORK] = PERMISSION_STATUS_DENIED;
}
} else if (msg == "permission_granted") {
INFO_LOG(Log::System, "STORAGE PERMISSION: GRANTED");
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
// Send along.
if (prm == "storage") {
INFO_LOG(Log::System, "STORAGE PERMISSION: GRANTED");
permissions[SYSTEM_PERMISSION_STORAGE] = PERMISSION_STATUS_GRANTED;
} else if (prm == "local_network") {
INFO_LOG(Log::System, "LOCAL NETWORK PERMISSION: GRANTED");
permissions[SYSTEM_PERMISSION_LOCAL_NETWORK] = PERMISSION_STATUS_GRANTED;
}
System_PostUIMessage(UIMessage::PERMISSION_GRANTED, prm);
} else if (msg == "sustained_perf_supported") {
sustainedPerfSupported = true;
@@ -44,7 +44,9 @@ public class ContentUri {
Log.e(TAG, "Failed to get file descriptor for " + uriString);
return -1;
}
return filePfd.detachFd(); // Take ownership of the fd.
// Review note: We intentionally "leak" the fd here, the caller takes ownership and closes
// it when done with it.
return filePfd.detachFd();
}
} catch (java.lang.IllegalArgumentException e) {
// This exception is long and ugly and really just means file not found.
@@ -70,13 +72,13 @@ public class ContentUri {
final String documentName = c.getString(0);
if (prefixLowercase != null && !prefixLowercase.isEmpty()) {
// Filter by prefix, case insensitive.
if (!documentName.toLowerCase(Locale.ROOT).startsWith(prefixLowercase)) {
if (!documentName.regionMatches(true, 0, prefixLowercase, 0, prefixLowercase.length())) {
return null;
}
}
final String mimeType = c.getString(3);
final boolean isDirectory = mimeType.equals(DocumentsContract.Document.MIME_TYPE_DIR);
final boolean isDirectory = DocumentsContract.Document.MIME_TYPE_DIR.equals(mimeType);
final long size = isDirectory ? 0 : c.getLong(1);
final long lastModified = c.getLong(4);
@@ -222,6 +224,8 @@ public class ContentUri {
@Keep
@SuppressWarnings("unused")
// NOTE: If the file already exists, this will NOT return an error, instead the OS will create a new
// file appending an incrementing number, for example (1), to the filename.
public static int contentUriCreateFile(Activity activity, String rootTreeUri, String fileName) {
try {
Uri uri = Uri.parse(rootTreeUri);
@@ -232,7 +236,7 @@ public class ContentUri {
DocumentFile createdFile = documentFile.createFile("application/octet-stream", fileName);
return createdFile != null ? STORAGE_ERROR_SUCCESS : STORAGE_ERROR_UNKNOWN;
} else {
Log.e(TAG, "contentUriCreateFile: fromTreeUrisv returned null");
Log.e(TAG, "contentUriCreateFile: fromTreeUri returned null");
return STORAGE_ERROR_UNKNOWN;
}
} catch (Exception e) {
+1 -1
View File
@@ -29,7 +29,7 @@ public class NativeApp {
public static final int RESULT_ERROR_ACTIVITY_NOT_FOUND = 1;
public static final int RESULT_ERROR_OTHER_ACTIVITY_ERROR = 2;
public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalStorageDir, String extFilesDir, String nativeLibDir, String additionalStorageDirs, String cacheDir, String shortcutParam, String installerName, int androidVersion, String board);
public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalStorageDir, String extFilesDir, String nativeLibDir, String additionalStorageDirs, String cacheDir, String shortcutParam, String installerName, int androidVersion, String board, int smallestScreenWidthDp);
public static native void audioInit();
public static native void audioShutdown();
public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate);
@@ -143,11 +143,15 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
private static final String[] permissionsForMicrophone = {
Manifest.permission.RECORD_AUDIO
};
private static final String[] permissionsForLocalNetwork = {
"android.permission.ACCESS_LOCAL_NETWORK"
};
public static final int REQUEST_CODE_STORAGE_PERMISSION = 1;
public static final int REQUEST_CODE_LOCATION_PERMISSION = 2;
public static final int REQUEST_CODE_CAMERA_PERMISSION = 3;
public static final int REQUEST_CODE_MICROPHONE_PERMISSION = 4;
public static final int REQUEST_CODE_LOCAL_NETWORK_PERMISSION = 5;
// Once we received a "modern" mouse event, we stop listening to old style mouse
// button events.
@@ -255,6 +259,16 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
} else {
NativeApp.sendMessageFromJava("permission_denied", "storage");
}
if (Build.VERSION.SDK_INT >= 35) {
if (this.checkSelfPermission("android.permission.ACCESS_LOCAL_NETWORK") == PackageManager.PERMISSION_GRANTED) {
NativeApp.sendMessageFromJava("permission_granted", "local_network");
} else {
NativeApp.sendMessageFromJava("permission_denied", "local_network");
}
} else {
NativeApp.sendMessageFromJava("permission_granted", "local_network");
}
}
}
@@ -292,6 +306,13 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
NativeApp.audioRecording_Start();
}
break;
case REQUEST_CODE_LOCAL_NETWORK_PERMISSION:
if (permissionsGranted(permissions, grantResults)) {
NativeApp.sendMessageFromJava("permission_granted", "local_network");
} else {
NativeApp.sendMessageFromJava("permission_denied", "local_network");
}
break;
default:
}
}
@@ -512,8 +533,9 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
PackageManager packageManager = getPackageManager();
String installerName = getInstallerName(packageManager);
int smallestScreenWidthDp = getResources().getConfiguration().smallestScreenWidthDp;
NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate);
NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, extStorageDir, externalFilesDir, nativeLibDir, additionalStorageDirs, cacheDir, shortcut, installerName, Build.VERSION.SDK_INT, Build.BOARD);
NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, extStorageDir, externalFilesDir, nativeLibDir, additionalStorageDirs, cacheDir, shortcut, installerName, Build.VERSION.SDK_INT, Build.BOARD, smallestScreenWidthDp);
// Allow C++ to tell us to use JavaGL or not.
javaGL = "true".equalsIgnoreCase(NativeApp.queryConfig("androidJavaGL"));
@@ -584,6 +606,11 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
@SuppressLint("SourceLockedOrientationActivity")
private void updateScreenRotation(String cause) {
if (Build.VERSION.SDK_INT >= 37 && getResources().getConfiguration().smallestScreenWidthDp >= 600) {
// Android 17+ on large screens (sw600dp+) ignores orientation requests to push for adaptive apps.
// If we try anyway, it's just a waste of time and might cause weirdness.
return;
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
if (isInMultiWindowMode()) {
// Do not try to enforce rotation! This can result in re-init loops.
@@ -1761,6 +1788,17 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
NativeApp.sendMessageFromJava("permission_granted", "storage");
}
return true;
} else if (command.equals("ask_permission") && params.equals("local_network")) {
if (Build.VERSION.SDK_INT >= 35) {
if (askForPermissions(permissionsForLocalNetwork, REQUEST_CODE_LOCAL_NETWORK_PERMISSION)) {
NativeApp.sendMessageFromJava("permission_pending", "local_network");
} else {
NativeApp.sendMessageFromJava("permission_granted", "local_network");
}
} else {
NativeApp.sendMessageFromJava("permission_granted", "local_network");
}
return true;
} else if (command.equals("gps_command")) {
if (params.equals("open")) {
if (!askForPermissions(permissionsForLocation, REQUEST_CODE_LOCATION_PERMISSION)) {
+3
View File
@@ -412,6 +412,9 @@ bool System_GetPropertyBool(SystemProperty prop) {
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
return true;
case SYSPROP_CAN_RESTRICT_ORIENTATION:
return true;
default:
return false;
}