1
1
package org .vita3k .emulator ;
2
2
3
3
4
- import android .content .ContentResolver ;
5
4
import android .content .Context ;
6
5
import android .content .Intent ;
7
6
import android .content .res .AssetFileDescriptor ;
8
- import android .database .Cursor ;
9
7
import android .graphics .Bitmap ;
10
8
import android .graphics .BitmapFactory ;
11
9
import android .net .Uri ;
10
+ import android .os .Build ;
11
+ import android .os .Environment ;
12
12
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 ;
14
17
import android .view .ViewGroup ;
15
18
16
19
import androidx .annotation .Keep ;
17
20
import androidx .core .content .pm .ShortcutInfoCompat ;
18
21
import androidx .core .content .pm .ShortcutManagerCompat ;
19
22
import androidx .core .graphics .drawable .IconCompat ;
23
+ import androidx .documentfile .provider .DocumentFile ;
20
24
21
25
import com .jakewharton .processphoenix .ProcessPhoenix ;
22
26
23
27
import java .io .File ;
24
28
import java .io .FileNotFoundException ;
25
29
import java .io .FileOutputStream ;
30
+ import java .io .IOException ;
26
31
import java .io .InputStream ;
27
32
import java .util .ArrayList ;
28
33
32
37
33
38
public class Emulator extends SDLActivity
34
39
{
35
- private InputOverlay mOverlay ;
36
40
private String currentGameId = "" ;
41
+ private EmuSurface mSurface ;
37
42
38
- public InputOverlay getInputOverlay () {
39
- return mOverlay ;
43
+ public InputOverlay getmOverlay () {
44
+ return mSurface . getmOverlay () ;
40
45
}
41
46
42
47
@ Keep
@@ -69,17 +74,17 @@ protected String[] getLibraries() {
69
74
@ Override
70
75
protected SDLSurface createSDLSurface (Context context ) {
71
76
// 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 ;
74
79
}
75
80
76
81
@ Override
77
82
protected void setupLayout (ViewGroup layout ){
78
83
super .setupLayout (layout );
79
- layout .addView (mOverlay );
84
+ layout .addView (getmOverlay () );
80
85
}
81
86
82
- static private String APP_RESTART_PARAMETERS = "AppStartParameters" ;
87
+ static private final String APP_RESTART_PARAMETERS = "AppStartParameters" ;
83
88
84
89
@ Override
85
90
protected String [] getArguments () {
@@ -107,7 +112,7 @@ protected void onNewIntent(Intent intent){
107
112
108
113
@ Keep
109
114
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 <>();
111
116
112
117
// first build the args given to Vita3K when it restarts
113
118
// 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){
134
139
}
135
140
136
141
static final int FILE_DIALOG_CODE = 545 ;
142
+ static final int FOLDER_DIALOG_CODE = 546 ;
143
+ static final int STORAGE_MANAGER_DIALOG_CODE = 547 ;
137
144
138
145
@ Keep
139
- public void showFileDialog (){
146
+ public void showFileDialog () {
140
147
Intent intent = new Intent ()
141
148
.setType ("*/*" )
142
- .setAction (Intent .ACTION_GET_CONTENT );
149
+ .setAction (Intent .ACTION_GET_CONTENT )
150
+ .putExtra (Intent .EXTRA_LOCAL_ONLY , true );
143
151
144
152
intent = Intent .createChooser (intent , "Choose a file" );
145
153
startActivityForResult (intent , FILE_DIALOG_CODE );
146
154
}
147
155
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
+
148
179
private File getFileFromUri (Uri uri ){
149
180
try {
150
181
InputStream inputStream = getContentResolver ().openInputStream (uri );
@@ -166,75 +197,76 @@ private File getFileFromUri(Uri uri){
166
197
}
167
198
}
168
199
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
-
192
200
@ Override
193
201
protected void onActivityResult (int requestCode , int resultCode , Intent data ) {
194
202
super .onActivityResult (requestCode , resultCode , data );
195
203
196
204
if (requestCode == FILE_DIALOG_CODE ){
205
+ String result_path = "" ;
206
+ int result_fd = -1 ;
197
207
if (resultCode == RESULT_OK ){
198
208
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
+ }
208
218
} else {
209
219
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 ('/' ));
211
239
}
212
- } catch (FileNotFoundException e ) {
240
+ } catch (ErrnoException | IOException e ) {
213
241
}
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 );
217
249
}
218
250
}
219
251
}
220
252
221
253
@ Keep
222
254
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 );
225
257
226
258
if (reset )
227
- mOverlay .resetButtonPlacement ();
259
+ getmOverlay () .resetButtonPlacement ();
228
260
}
229
261
230
262
@ Keep
231
263
public void setControllerOverlayScale (float scale ){
232
- mOverlay .setScale (scale );
264
+ getmOverlay () .setScale (scale );
233
265
}
234
266
235
267
@ Keep
236
268
public void setControllerOverlayOpacity (int opacity ){
237
- mOverlay .setOpacity (opacity );
269
+ getmOverlay () .setOpacity (opacity );
238
270
}
239
271
240
272
@ Keep
@@ -270,5 +302,13 @@ public boolean createShortcut(String game_id, String game_name){
270
302
return true ;
271
303
}
272
304
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 );
274
314
}
0 commit comments