Skip to content

Commit a62e232

Browse files
committed
android: V12 Update
1 parent fab31c8 commit a62e232

File tree

18 files changed

+479
-387
lines changed

18 files changed

+479
-387
lines changed

.github/workflows/c-cpp.yml

Lines changed: 233 additions & 233 deletions
Large diffs are not rendered by default.

android/build.gradle

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,16 @@ android {
44
namespace "org.vita3k.emulator"
55
compileSdk 34
66
ndkVersion "26.3.11579264"
7-
buildToolsVersion "34.0.0"
7+
buildFeatures {
8+
buildConfig true
9+
}
810

911
defaultConfig {
1012
applicationId "org.vita3k.emulator"
1113
minSdk 24
1214
targetSdk 34
13-
versionCode 11
14-
versionName "0.2.0-11"
15+
versionCode 12
16+
versionName "0.2.0-12"
1517

1618
externalNativeBuild {
1719
cmake {

android/src/main/AndroidManifest.xml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,16 @@
4545

4646
<!-- Allow downloading to the external storage on Android 5.1 and older -->
4747
<!-- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="22" /> -->
48+
<!-- For android API <= 29 (Up to Android 10), use Legacy storage, otherwise use the MANAGE_EXTERNAL_STORAGE permission -->
4849
<uses-permission
4950
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
50-
android:maxSdkVersion="28" />
51+
android:maxSdkVersion="29" />
52+
<uses-permission
53+
android:name="android.permission.READ_EXTERNAL_STORAGE"
54+
android:maxSdkVersion="29" />
55+
<uses-permission
56+
android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
57+
android:minSdkVersion="30" />
5158

5259
<!-- Allow access to Bluetooth devices -->
5360
<!-- Currently this is just for Steam Controller support and requires setting SDL_HINT_JOYSTICK_HIDAPI_STEAM -->
@@ -70,6 +77,8 @@
7077
then replace "SDLActivity" with the name of your class (e.g. "MyGame")
7178
in the XML below.
7279
An example Java class can be found in README-android.md
80+
81+
The requestLegacyExternalStorage parameter only has an effect when running on Android <= 10
7382
-->
7483
<application android:label="@string/app_name"
7584
android:icon="@mipmap/ic_launcher"
@@ -78,7 +87,9 @@
7887
android:hardwareAccelerated="true"
7988
android:appCategory="game"
8089
android:isGame="true"
81-
android:hasFragileUserData="true" tools:targetApi="q">
90+
android:hasFragileUserData="true"
91+
android:requestLegacyExternalStorage="true"
92+
tools:targetApi="q">
8293

8394
<!-- Example of setting SDL hints from AndroidManifest.xml:
8495
<meta-data android:name="SDL_ENV.SDL_ACCELEROMETER_AS_JOYSTICK" android:value="0"/>
@@ -89,8 +100,10 @@
89100
android:launchMode="singleInstance"
90101
android:configChanges="layoutDirection|locale|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
91102
android:preferMinimalPostProcessing="true"
103+
android:theme="@style/Theme.Design.NoActionBar"
92104
android:exported="true"
93-
>
105+
tools:targetApi="r">
106+
94107
<intent-filter>
95108
<action android:name="android.intent.action.MAIN" />
96109
<category android:name="android.intent.category.LAUNCHER" />

android/src/main/java/org/vita3k/emulator/EmuSurface.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
package org.vita3k.emulator;
22

33
import android.content.Context;
4+
import android.view.MotionEvent;
45
import android.view.SurfaceHolder;
6+
import android.view.View;
57

68
import org.libsdl.app.SDLSurface;
9+
import org.vita3k.emulator.overlay.InputOverlay;
710

811
public class EmuSurface extends SDLSurface {
912

13+
private InputOverlay mOverlay;
14+
15+
public InputOverlay getmOverlay() {
16+
return mOverlay;
17+
}
18+
1019
public EmuSurface(Context context){
1120
super(context);
21+
mOverlay = new InputOverlay(context);
1222
}
1323

1424
@Override
@@ -23,6 +33,16 @@ public void surfaceDestroyed(SurfaceHolder holder) {
2333
super.surfaceDestroyed(holder);
2434
}
2535

36+
@Override
37+
public boolean onTouch(View v, MotionEvent event) {
38+
if(mOverlay.onTouch(v, event)){
39+
// priority is given to the overlay
40+
return true;
41+
}
42+
43+
return super.onTouch(v, event);
44+
}
45+
2646
public native void setSurfaceStatus(boolean surface_present);
2747

2848
}

android/src/main/java/org/vita3k/emulator/Emulator.java

Lines changed: 96 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,33 @@
11
package org.vita3k.emulator;
22

33

4-
import android.content.ContentResolver;
54
import android.content.Context;
65
import android.content.Intent;
76
import android.content.res.AssetFileDescriptor;
8-
import android.database.Cursor;
97
import android.graphics.Bitmap;
108
import android.graphics.BitmapFactory;
119
import android.net.Uri;
10+
import android.os.Build;
11+
import android.os.Environment;
1212
import android.os.ParcelFileDescriptor;
13-
import android.provider.OpenableColumns;
13+
import android.provider.Settings;
14+
import android.system.ErrnoException;
15+
import android.system.Os;
16+
import android.view.Surface;
1417
import android.view.ViewGroup;
1518

1619
import androidx.annotation.Keep;
1720
import androidx.core.content.pm.ShortcutInfoCompat;
1821
import androidx.core.content.pm.ShortcutManagerCompat;
1922
import androidx.core.graphics.drawable.IconCompat;
23+
import androidx.documentfile.provider.DocumentFile;
2024

2125
import com.jakewharton.processphoenix.ProcessPhoenix;
2226

2327
import java.io.File;
2428
import java.io.FileNotFoundException;
2529
import java.io.FileOutputStream;
30+
import java.io.IOException;
2631
import java.io.InputStream;
2732
import java.util.ArrayList;
2833

@@ -32,11 +37,11 @@
3237

3338
public class Emulator extends SDLActivity
3439
{
35-
private InputOverlay mOverlay;
3640
private String currentGameId = "";
41+
private EmuSurface mSurface;
3742

38-
public InputOverlay getInputOverlay(){
39-
return mOverlay;
43+
public InputOverlay getmOverlay() {
44+
return mSurface.getmOverlay();
4045
}
4146

4247
@Keep
@@ -69,17 +74,17 @@ protected String[] getLibraries() {
6974
@Override
7075
protected SDLSurface createSDLSurface(Context context) {
7176
// Create the input overlay in the same time
72-
mOverlay = new InputOverlay(this);
73-
return new EmuSurface(context);
77+
mSurface = new EmuSurface(context);
78+
return mSurface;
7479
}
7580

7681
@Override
7782
protected void setupLayout(ViewGroup layout){
7883
super.setupLayout(layout);
79-
layout.addView(mOverlay);
84+
layout.addView(getmOverlay());
8085
}
8186

82-
static private String APP_RESTART_PARAMETERS = "AppStartParameters";
87+
static private final String APP_RESTART_PARAMETERS = "AppStartParameters";
8388

8489
@Override
8590
protected String[] getArguments() {
@@ -107,7 +112,7 @@ protected void onNewIntent(Intent intent){
107112

108113
@Keep
109114
public void restartApp(String app_path, String exec_path, String exec_args){
110-
ArrayList<String> args = new ArrayList<String>();
115+
ArrayList<String> args = new ArrayList<>();
111116

112117
// first build the args given to Vita3K when it restarts
113118
// this is similar to run_execv in main.cpp
@@ -134,17 +139,43 @@ public void restartApp(String app_path, String exec_path, String exec_args){
134139
}
135140

136141
static final int FILE_DIALOG_CODE = 545;
142+
static final int FOLDER_DIALOG_CODE = 546;
143+
static final int STORAGE_MANAGER_DIALOG_CODE = 547;
137144

138145
@Keep
139-
public void showFileDialog(){
146+
public void showFileDialog() {
140147
Intent intent = new Intent()
141148
.setType("*/*")
142-
.setAction(Intent.ACTION_GET_CONTENT);
149+
.setAction(Intent.ACTION_GET_CONTENT)
150+
.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
143151

144152
intent = Intent.createChooser(intent, "Choose a file");
145153
startActivityForResult(intent, FILE_DIALOG_CODE);
146154
}
147155

156+
private boolean isStorageManagerEnabled(){
157+
return (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) && Environment.isExternalStorageManager();
158+
}
159+
160+
@Keep
161+
public void showFolderDialog() {
162+
// If running Android 10-, SDL should have already asked for read and write permissions
163+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || isStorageManagerEnabled()) {
164+
Intent intent = new Intent()
165+
.setAction(Intent.ACTION_OPEN_DOCUMENT_TREE)
166+
.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
167+
168+
intent = Intent.createChooser(intent, "Choose a folder");
169+
startActivityForResult(intent, FOLDER_DIALOG_CODE);
170+
} else {
171+
Intent intent = new Intent()
172+
.setAction(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION)
173+
.setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID));
174+
175+
startActivityForResult(intent, STORAGE_MANAGER_DIALOG_CODE);
176+
}
177+
}
178+
148179
private File getFileFromUri(Uri uri){
149180
try {
150181
InputStream inputStream = getContentResolver().openInputStream(uri);
@@ -166,75 +197,76 @@ private File getFileFromUri(Uri uri){
166197
}
167198
}
168199

169-
// from https://stackoverflow.com/questions/5568874/how-to-extract-the-file-name-from-uri-returned-from-intent-action-get-content
170-
private String getFileName(Uri uri){
171-
String result = null;
172-
if(uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)){
173-
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
174-
try {
175-
if(cursor != null && cursor.moveToFirst()){
176-
int name_index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
177-
if(name_index >= 0)
178-
result = cursor.getString(name_index);
179-
}
180-
} finally {
181-
cursor.close();
182-
}
183-
}
184-
185-
if(result == null){
186-
result = uri.getLastPathSegment();
187-
}
188-
189-
return result;
190-
}
191-
192200
@Override
193201
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
194202
super.onActivityResult(requestCode, resultCode, data);
195203

196204
if(requestCode == FILE_DIALOG_CODE){
205+
String result_path = "";
206+
int result_fd = -1;
197207
if(resultCode == RESULT_OK){
198208
Uri result_uri = data.getData();
199-
String filename = getFileName(result_uri);
200-
String result_uri_string = result_uri.toString();
201-
int result_fd = -1;
202-
try {
203-
AssetFileDescriptor asset_fd = getContentResolver().openAssetFileDescriptor(result_uri, "r");
204-
// if the file is less than 64 KB, make a temporary copy
205-
if(asset_fd.getLength() >= 64*1024) {
206-
ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(result_uri, "r");
207-
result_fd = file_descr.detachFd();
209+
try (AssetFileDescriptor asset_fd = getContentResolver().openAssetFileDescriptor(result_uri, "r")){
210+
// if the file is less than 4 KB, make a temporary copy
211+
if(asset_fd.getLength() >= 4*1024) {
212+
try (ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(result_uri, "r")) {
213+
result_fd = file_descr.detachFd();
214+
// in case the last call returns a ErrnoException
215+
result_path = result_uri.toString();
216+
result_path = Os.readlink("/proc/self/fd/" + result_fd);
217+
}
208218
} else {
209219
File f = getFileFromUri(result_uri);
210-
result_uri_string = f.getAbsolutePath();
220+
result_path = f.getAbsolutePath();
221+
}
222+
} catch (ErrnoException | IOException e) {
223+
}
224+
}
225+
filedialogReturn(result_path, result_fd);
226+
} else if(requestCode == FOLDER_DIALOG_CODE){
227+
String result_path = "";
228+
if(resultCode == RESULT_OK){
229+
Uri result_uri = data.getData();
230+
DocumentFile tree = DocumentFile.fromTreeUri(getApplicationContext(), result_uri);
231+
try(ParcelFileDescriptor file_descr = getContentResolver().openFileDescriptor(tree.getUri(), "r")) {
232+
int result_fd = file_descr.getFd();
233+
234+
result_path = Os.readlink("/proc/self/fd/" + result_fd);
235+
// replace /mnt/user/{id} with /storage
236+
if(result_path.startsWith("/mnt/user/")){
237+
result_path = result_path.substring("/mnt/user/".length());
238+
result_path = "/storage" + result_path.substring(result_path.indexOf('/'));
211239
}
212-
} catch (FileNotFoundException e) {
240+
} catch (ErrnoException | IOException e) {
213241
}
214-
filedialogReturn(result_uri_string, result_fd, filename);
215-
} else if(resultCode == RESULT_CANCELED){
216-
filedialogReturn("", -1, "");
242+
}
243+
filedialogReturn(result_path, 0);
244+
} else if (requestCode == STORAGE_MANAGER_DIALOG_CODE) {
245+
if (isStorageManagerEnabled()) {
246+
showFolderDialog();
247+
} else {
248+
filedialogReturn("", -1);
217249
}
218250
}
219251
}
220252

221253
@Keep
222254
public void setControllerOverlayState(int overlay_mask, boolean edit, boolean reset){
223-
mOverlay.setState(overlay_mask);
224-
mOverlay.setIsInEditMode(edit);
255+
getmOverlay().setState(overlay_mask);
256+
getmOverlay().setIsInEditMode(edit);
225257

226258
if(reset)
227-
mOverlay.resetButtonPlacement();
259+
getmOverlay().resetButtonPlacement();
228260
}
229261

230262
@Keep
231263
public void setControllerOverlayScale(float scale){
232-
mOverlay.setScale(scale);
264+
getmOverlay().setScale(scale);
233265
}
234266

235267
@Keep
236268
public void setControllerOverlayOpacity(int opacity){
237-
mOverlay.setOpacity(opacity);
269+
getmOverlay().setOpacity(opacity);
238270
}
239271

240272
@Keep
@@ -270,5 +302,13 @@ public boolean createShortcut(String game_id, String game_name){
270302
return true;
271303
}
272304

273-
public native void filedialogReturn(String result_uri, int result_fd, String filename);
305+
@Keep
306+
public boolean isDefaultOrientationLandscape() {
307+
// we know the current device orientation is landscape
308+
// so the default one is also landscape if and only if the rotation is 0 or 180
309+
int rotation = getWindowManager().getDefaultDisplay().getRotation();
310+
return rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180;
311+
}
312+
313+
public native void filedialogReturn(String result_path, int result_fd);
274314
}

android/src/main/java/org/vita3k/emulator/overlay/InputOverlay.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,9 @@ public InputOverlay(Context context/*, AttributeSet attrs*/)
106106
defaultOverlay();
107107

108108
// Set the on touch listener.
109-
setOnTouchListener(this);
109+
// Do not register the overlay as a touch listener
110+
// Instead let EmuSurface forward touch events
111+
// setOnTouchListener(this);
110112

111113
// Force draw
112114
setWillNotDraw(false);
@@ -126,7 +128,7 @@ public InputOverlay(Context context/*, AttributeSet attrs*/)
126128
@Override
127129
public void run() {
128130
Emulator emu = (Emulator) SDL.getContext();
129-
emu.getInputOverlay().tick();
131+
emu.getmOverlay().tick();
130132
}
131133
}, 1000, 1000);
132134

0 commit comments

Comments
 (0)