Skip to content
Closed
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
9 changes: 9 additions & 0 deletions engine/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@
<uses-permission android:name="android.permission.VIBRATE" />
<!-- CRxTRDude - Allows the use of a wake lock -->
<uses-permission android:name="android.permission.WAKE_LOCK" />
<!--
Frontend launch (see GameActivity): a pak that lives outside this app's
own storage can only be read with a shared storage permission. Android 11
and later grant it from the "All files access" settings page; older
versions use the classic storage permission.
-->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:maxSdkVersion="32" />

<application android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@
import android.content.BroadcastReceiver;
import android.content.Intent;
import android.content.IntentFilter;
import android.Manifest;
import android.app.AppOpsManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.provider.Settings;
import android.widget.Toast;
import java.io.File;
import org.jetbrains.annotations.Nullable;

/**
Expand All @@ -51,6 +58,168 @@ public class GameActivity extends SDLActivity {
protected static WakeLock wakeLock;
protected static View decorView;

// ------------------------------------------------------------------------ //
// Frontend launch.
//
// A frontend (Daijishō, Pegasus, ES-DE, a launcher script...) can start this
// activity directly and name the pak to run, so the player lands in the game
// instead of the pak selection menu:
//
// am start -n org.openbor.engine/.GameActivity \
// -e pak /storage/emulated/0/Games/openbor/Game.pak
//
// The path travels to the native main() as its single command line
// argument, the same way every desktop port takes a pak on the command
// line. Without the extra, or when the file cannot be read, nothing changes:
// the engine shows its menu as before.
//
// A pak outside this app's own storage needs a shared storage permission.
// When it is missing, the user gets the permission dialog (Android 10 and
// older) or the "All files access" settings page (Android 11 and later)
// and can retry from the frontend afterwards.
// ------------------------------------------------------------------------ //

/** Intent string extra that carries the absolute path of the pak to launch. */
public static final String EXTRA_PAK_PATH = "pak";

/** Pak path taken from the launching intent, or null for the menu flow. */
private String frontendPakPath = null;

/**
* Hands the pak named by the launching intent to the native main() as
* argv[1]. SDL calls this on its own thread after onCreate() has run.
*/
@Override
protected String[] getArguments()
{
if (frontendPakPath == null)
{
return super.getArguments();
}

return new String[] { frontendPakPath };
}

/**
* Reads EXTRA_PAK_PATH from the launching intent and checks that the engine
* will be able to open the file.
*
* @return the pak path to run, or null to fall back to the menu.
*/
private String resolveFrontendPak()
{
Intent intent = getIntent();

if (intent == null)
{
return null;
}

String path = intent.getStringExtra(EXTRA_PAK_PATH);

if (path == null || path.isEmpty())
{
return null;
}

File pak = new File(path);

if (pak.canRead())
{
Log.i("OpenBOR", "Launching pak from intent: " + path);
return path;
}

// Without a shared storage permission the system hides the file, so
// exists() cannot tell a missing pak from a blocked one. Ask for the
// permission first; the frontend can launch again once it is granted.
if (!hasSharedStoragePermission())
{
Log.w("OpenBOR", "Pak from intent needs shared storage permission: " + path);
Toast.makeText(this, "OpenBOR needs storage access to open paks from other apps. Grant it, then launch again.", Toast.LENGTH_LONG).show();
requestSharedStoragePermission();
return null;
}

Log.w("OpenBOR", "Pak from intent not found or unreadable: " + path);
Toast.makeText(this, "Pak not found: " + path, Toast.LENGTH_LONG).show();
return null;
}

/** Request code for the classic storage permission dialog (Android 10 and older). */
private static final int REQUEST_SHARED_STORAGE = 0x504B; // "PK"

/**
* App op behind the "All files access" switch (AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE,
* which the SDK does not expose).
*/
private static final String OP_MANAGE_EXTERNAL_STORAGE = "android:manage_external_storage";

/**
* @return true when this app may read files outside its own storage:
* "All files access" on Android 11 and later, the classic storage
* permission before that.
*/
private boolean hasSharedStoragePermission()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
{
// Install-time permission model: granted with the manifest entry.
return true;
}

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
{
return checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}

// Same rule the platform applies for Environment.isExternalStorageManager():
// the op is on, or it is untouched and the permission itself was granted.
// Evaluated here because some vendor builds return true from that method
// while the settings switch is still off and every read fails.
AppOpsManager ops = getSystemService(AppOpsManager.class);
int mode = ops.unsafeCheckOpNoThrow(OP_MANAGE_EXTERNAL_STORAGE, getApplicationInfo().uid, getPackageName());

if (mode == AppOpsManager.MODE_ALLOWED)
{
return true;
}

return mode == AppOpsManager.MODE_DEFAULT
&& checkSelfPermission(Manifest.permission.MANAGE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
}

/**
* Asks for the shared storage permission. Android 11 and later only grant
* "All files access" from a system settings page, so that page is opened
* for this app; older versions show the normal permission dialog.
*/
private void requestSharedStoragePermission()
{
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
{
return;
}

if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
{
requestPermissions(new String[] { Manifest.permission.READ_EXTERNAL_STORAGE }, REQUEST_SHARED_STORAGE);
return;
}

Intent settings = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION, Uri.parse("package:" + getPackageName()));

try
{
startActivity(settings);
}
catch (android.content.ActivityNotFoundException e)
{
Log.w("OpenBOR", "No settings page for All files access: " + e.getMessage());
}
}
// ------------------------------------------------------------------------ //

//needed to fix sdk 34+ crashing
@Override
public Intent registerReceiver(@Nullable BroadcastReceiver receiver, IntentFilter filter) {
Expand Down Expand Up @@ -119,6 +288,9 @@ protected void onCreate(Bundle savedInstanceState) {
// call parent's implementation
super.onCreate(savedInstanceState);
Log.v("OpenBOR", "onCreate called");

// Frontend launch: pick up the pak named in the intent, if any.
frontendPakPath = resolveFrontendPak();
//msmalik681 copy pak for custom apk and notify is paks folder empty
// CopyPak();

Expand Down