Updates how the REQUEST_INSTALL_PACKAGES permission is handled

- Only request the permission the first time the editor tries to open an apk
- Disable the permission for the HorizonOS build as the HorizonOS store doesn't support it yet
This commit is contained in:
Fredia Huya-Kouadio 2024-12-23 23:04:50 -08:00
parent 0f95e9f8e6
commit 7ad9d23a1d
7 changed files with 53 additions and 58 deletions

View file

@ -52,7 +52,7 @@ void register_android_exporter() {
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD)); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/debug_keystore_pass", PROPERTY_HINT_PASSWORD));
#ifdef ANDROID_ENABLED #ifdef ANDROID_ENABLED
EDITOR_DEF_BASIC("export/android/install_exported_apk", true); EDITOR_DEF_BASIC("export/android/install_exported_apk", !OS::get_singleton()->has_feature("horizonos"));
#else #else
EDITOR_DEF_BASIC("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME")); EDITOR_DEF_BASIC("export/android/java_sdk_path", OS::get_singleton()->get_environment("JAVA_HOME"));
EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR)); EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/android/java_sdk_path", PROPERTY_HINT_GLOBAL_DIR));

View file

@ -34,6 +34,9 @@
<!-- Scene api --> <!-- Scene api -->
<uses-permission android:name="com.oculus.permission.USE_SCENE" /> <uses-permission android:name="com.oculus.permission.USE_SCENE" />
<!-- Temp removal of the 'REQUEST_INSTALL_PACKAGES' permission as it's currently forbidden by the Horizon OS store -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" tools:node="remove" />
<application> <application>
<activity <activity

View file

@ -130,11 +130,20 @@ abstract class BaseGodotEditor : GodotActivity() {
*/ */
@CallSuper @CallSuper
protected open fun getExcludedPermissions(): MutableSet<String> { protected open fun getExcludedPermissions(): MutableSet<String> {
return mutableSetOf( val excludedPermissions = mutableSetOf(
// The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project // The RECORD_AUDIO permission is requested when the "audio/driver/enable_input" project
// setting is enabled. // setting is enabled.
Manifest.permission.RECORD_AUDIO Manifest.permission.RECORD_AUDIO,
) )
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
excludedPermissions.add(
// The REQUEST_INSTALL_PACKAGES permission is requested the first time we attempt to
// open an apk file.
Manifest.permission.REQUEST_INSTALL_PACKAGES,
)
}
return excludedPermissions
} }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {

View file

@ -1039,7 +1039,8 @@ class Godot(private val context: Context) {
} }
fun requestPermission(name: String?): Boolean { fun requestPermission(name: String?): Boolean {
return requestPermission(name, getActivity()) val activity = getActivity() ?: return false
return requestPermission(name, activity)
} }
fun requestPermissions(): Boolean { fun requestPermissions(): Boolean {

View file

@ -30,13 +30,12 @@
package org.godotengine.godot; package org.godotengine.godot;
import org.godotengine.godot.error.Error;
import org.godotengine.godot.input.GodotEditText; import org.godotengine.godot.input.GodotEditText;
import android.app.Activity; import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.graphics.Point;
import android.graphics.Rect; import android.graphics.Rect;
import android.net.Uri; import android.net.Uri;
import android.os.Build; import android.os.Build;
@ -47,7 +46,6 @@ import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.Display; import android.view.Display;
import android.view.DisplayCutout; import android.view.DisplayCutout;
import android.view.Surface;
import android.view.WindowInsets; import android.view.WindowInsets;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
@ -121,10 +119,10 @@ public class GodotIO {
} }
activity.startActivity(intent); activity.startActivity(intent);
return 0; return Error.OK.toNativeValue();
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Unable to open uri " + uriString, e); Log.e(TAG, "Unable to open uri " + uriString, e);
return 1; return Error.FAILED.toNativeValue();
} }
} }

View file

@ -92,7 +92,7 @@ enum class Error(private val description: String) {
} }
} }
internal fun toNativeValue(): Int = this.ordinal fun toNativeValue(): Int = this.ordinal
override fun toString(): String { override fun toString(): String {
return description return description

View file

@ -44,11 +44,13 @@ import android.provider.Settings;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -76,11 +78,11 @@ public final class PermissionsUtil {
* @param activity the caller activity for this method. * @param activity the caller activity for this method.
* @return true/false. "true" if permissions are already granted, "false" if a permissions request was dispatched. * @return true/false. "true" if permissions are already granted, "false" if a permissions request was dispatched.
*/ */
public static boolean requestPermissions(Activity activity, List<String> permissions) { public static boolean requestPermissions(@NonNull Activity activity, List<String> permissions) {
if (activity == null) { return requestPermissions(activity, permissions, REQUEST_ALL_PERMISSION_REQ_CODE);
return false; }
}
private static boolean requestPermissions(@NonNull Activity activity, List<String> permissions, int requestCode) {
if (permissions == null || permissions.isEmpty()) { if (permissions == null || permissions.isEmpty()) {
return true; return true;
} }
@ -90,6 +92,7 @@ public final class PermissionsUtil {
return true; return true;
} }
boolean dispatchedPermissionsRequest = false;
Set<String> requestedPermissions = new HashSet<>(); Set<String> requestedPermissions = new HashSet<>();
for (String permission : permissions) { for (String permission : permissions) {
try { try {
@ -104,6 +107,7 @@ public final class PermissionsUtil {
Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION); Intent intent = new Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION);
activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE); activity.startActivityForResult(intent, REQUEST_MANAGE_EXTERNAL_STORAGE_REQ_CODE);
} }
dispatchedPermissionsRequest = true;
} }
} else if (permission.equals(Manifest.permission.REQUEST_INSTALL_PACKAGES)) { } else if (permission.equals(Manifest.permission.REQUEST_INSTALL_PACKAGES)) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !activity.getPackageManager().canRequestPackageInstalls()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !activity.getPackageManager().canRequestPackageInstalls()) {
@ -111,6 +115,7 @@ public final class PermissionsUtil {
Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES); Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName()))); intent.setData(Uri.parse(String.format("package:%s", activity.getPackageName())));
activity.startActivityForResult(intent, REQUEST_INSTALL_PACKAGES_REQ_CODE); activity.startActivityForResult(intent, REQUEST_INSTALL_PACKAGES_REQ_CODE);
dispatchedPermissionsRequest = true;
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "Unable to request permission " + Manifest.permission.REQUEST_INSTALL_PACKAGES); Log.e(TAG, "Unable to request permission " + Manifest.permission.REQUEST_INSTALL_PACKAGES);
} }
@ -129,13 +134,12 @@ public final class PermissionsUtil {
} }
} }
if (requestedPermissions.isEmpty()) { if (!requestedPermissions.isEmpty()) {
// If list is empty, all of dangerous permissions were granted. activity.requestPermissions(requestedPermissions.toArray(new String[0]), requestCode);
return true; dispatchedPermissionsRequest = true;
} }
activity.requestPermissions(requestedPermissions.toArray(new String[0]), REQUEST_ALL_PERMISSION_REQ_CODE); return !dispatchedPermissionsRequest;
return false;
} }
/** /**
@ -144,57 +148,37 @@ public final class PermissionsUtil {
* @param activity the caller activity for this method. * @param activity the caller activity for this method.
* @return true/false. "true" if permission is already granted, "false" if a permission request was dispatched. * @return true/false. "true" if permission is already granted, "false" if a permission request was dispatched.
*/ */
public static boolean requestPermission(String permissionName, Activity activity) { public static boolean requestPermission(String permissionName, @NonNull Activity activity) {
if (activity == null || TextUtils.isEmpty(permissionName)) { if (TextUtils.isEmpty(permissionName)) {
return false;
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
// Not necessary, asked on install already
return true; return true;
} }
final int requestCode;
final String updatedPermissionName;
switch (permissionName) { switch (permissionName) {
case "RECORD_AUDIO": case "RECORD_AUDIO":
case Manifest.permission.RECORD_AUDIO: updatedPermissionName = Manifest.permission.RECORD_AUDIO;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { requestCode = REQUEST_RECORD_AUDIO_PERMISSION;
activity.requestPermissions(new String[] { Manifest.permission.RECORD_AUDIO }, REQUEST_RECORD_AUDIO_PERMISSION); break;
return false;
}
return true;
case "CAMERA": case "CAMERA":
case Manifest.permission.CAMERA: updatedPermissionName = Manifest.permission.CAMERA;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) { requestCode = REQUEST_CAMERA_PERMISSION;
activity.requestPermissions(new String[] { Manifest.permission.CAMERA }, REQUEST_CAMERA_PERMISSION); break;
return false;
}
return true;
case "VIBRATE": case "VIBRATE":
case Manifest.permission.VIBRATE: updatedPermissionName = Manifest.permission.VIBRATE;
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.VIBRATE) != PackageManager.PERMISSION_GRANTED) { requestCode = REQUEST_VIBRATE_PERMISSION;
activity.requestPermissions(new String[] { Manifest.permission.VIBRATE }, REQUEST_VIBRATE_PERMISSION); break;
return false;
}
return true;
default: default:
// Check if the given permission is a dangerous permission updatedPermissionName = permissionName;
try { requestCode = REQUEST_SINGLE_PERMISSION_REQ_CODE;
PermissionInfo permissionInfo = getPermissionInfo(activity, permissionName); break;
int protectionLevel = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P ? permissionInfo.getProtection() : permissionInfo.protectionLevel;
if ((protectionLevel & PermissionInfo.PROTECTION_DANGEROUS) == PermissionInfo.PROTECTION_DANGEROUS && ContextCompat.checkSelfPermission(activity, permissionName) != PackageManager.PERMISSION_GRANTED) {
activity.requestPermissions(new String[] { permissionName }, REQUEST_SINGLE_PERMISSION_REQ_CODE);
return false;
}
} catch (PackageManager.NameNotFoundException e) {
// Unknown permission - return false as it can't be granted.
Log.w(TAG, "Unable to identify permission " + permissionName, e);
return false;
}
return true;
} }
List<String> permissions = Collections.singletonList(updatedPermissionName);
return requestPermissions(activity, permissions, requestCode);
} }
/** /**