1
0
Fork 0
mirror of https://github.com/juce-framework/JUCE.git synced 2026-02-08 04:20:09 +00:00

Projucer: Support file permissions in Android 33

This commit is contained in:
reuk 2022-11-08 15:38:31 +00:00
parent 2dc90bd6e6
commit e3e8b8a91d
No known key found for this signature in database
GPG key ID: 9ADCD339CFC98A11
4 changed files with 129 additions and 61 deletions

View file

@ -71,6 +71,19 @@ public:
createOtherExporterProperties (props);
}
void updateDeprecatedSettings() override
{
const auto needsExternalRead = getSettingString (Ids::androidExternalReadNeeded);
settings.removeProperty (Ids::androidExternalReadNeeded, nullptr);
if (needsExternalRead.isEmpty())
return;
androidReadMediaAudioPermission .setValue (needsExternalRead, nullptr);
androidReadMediaImagesPermission.setValue (needsExternalRead, nullptr);
androidReadMediaVideoPermission .setValue (needsExternalRead, nullptr);
}
static String getDisplayName() { return "Android"; }
static String getValueTreeTypeName() { return "ANDROIDSTUDIO"; }
static String getTargetFolderName() { return "Android"; }
@ -94,7 +107,8 @@ public:
androidCustomActivityClass, androidCustomApplicationClass, androidManifestCustomXmlElements,
androidGradleSettingsContent, androidVersionCode, androidMinimumSDK, androidTargetSDK, androidTheme,
androidExtraAssetsFolder, androidOboeRepositoryPath, androidInternetNeeded, androidMicNeeded, androidCameraNeeded,
androidBluetoothNeeded, androidExternalReadPermission, androidExternalWritePermission,
androidBluetoothNeeded, androidReadMediaAudioPermission, androidReadMediaImagesPermission,
androidReadMediaVideoPermission, androidExternalWritePermission,
androidInAppBillingPermission, androidVibratePermission, androidOtherPermissions, androidPushNotifications,
androidEnableRemoteNotifications, androidRemoteNotificationsConfigFile, androidEnableContentSharing, androidKeyStore,
androidKeyStorePass, androidKeyAlias, androidKeyAliasPass, gradleVersion, gradleToolchain, androidPluginVersion;
@ -124,7 +138,9 @@ public:
androidMicNeeded (settings, Ids::microphonePermissionNeeded, getUndoManager(), false),
androidCameraNeeded (settings, Ids::cameraPermissionNeeded, getUndoManager(), false),
androidBluetoothNeeded (settings, Ids::androidBluetoothNeeded, getUndoManager(), true),
androidExternalReadPermission (settings, Ids::androidExternalReadNeeded, getUndoManager(), true),
androidReadMediaAudioPermission (settings, Ids::androidReadMediaAudioPermission, getUndoManager(), true),
androidReadMediaImagesPermission (settings, Ids::androidReadMediaImagesPermission, getUndoManager(), true),
androidReadMediaVideoPermission (settings, Ids::androidReadMediaVideoPermission, getUndoManager(), true),
androidExternalWritePermission (settings, Ids::androidExternalWriteNeeded, getUndoManager(), true),
androidInAppBillingPermission (settings, Ids::androidInAppBilling, getUndoManager(), false),
androidVibratePermission (settings, Ids::androidVibratePermissionNeeded, getUndoManager(), false),
@ -1103,8 +1119,14 @@ private:
props.add (new ChoicePropertyComponent (androidBluetoothNeeded, "Bluetooth Permissions Required"),
"If enabled, this will set the android.permission.BLUETOOTH and android.permission.BLUETOOTH_ADMIN flag in the manifest. This is required for Bluetooth MIDI on Android.");
props.add (new ChoicePropertyComponent (androidExternalReadPermission, "Read From External Storage"),
"If enabled, this will set the android.permission.READ_EXTERNAL_STORAGE flag in the manifest.");
props.add (new ChoicePropertyComponent (androidReadMediaAudioPermission, "Read Audio From External Storage"),
"If enabled, this will set the android.permission.READ_MEDIA_AUDIO and android.permission.READ_EXTERNAL_STORAGE flags in the manifest.");
props.add (new ChoicePropertyComponent (androidReadMediaImagesPermission, "Read Images From External Storage"),
"If enabled, this will set the android.permission.READ_MEDIA_IMAGES and android.permission.READ_EXTERNAL_STORAGE flags in the manifest.");
props.add (new ChoicePropertyComponent (androidReadMediaVideoPermission, "Read Video From External Storage"),
"If enabled, this will set the android.permission.READ_MEDIA_VIDEO and android.permission.READ_EXTERNAL_STORAGE flags in the manifest.");
props.add (new ChoicePropertyComponent (androidExternalWritePermission, "Write to External Storage"),
"If enabled, this will set the android.permission.WRITE_EXTERNAL_STORAGE flag in the manifest.");
@ -1680,6 +1702,15 @@ private:
// This permission only has an effect on SDK version 28 and lower
if (permission == "android.permission.WRITE_EXTERNAL_STORAGE")
usesPermission->setAttribute ("android:maxSdkVersion", "28");
// https://developer.android.com/training/data-storage/shared/documents-files
// If the SDK version is <= 28, READ_EXTERNAL_STORAGE is required to access any
// media file, including files created by the current app.
// If the SDK version is <= 32, READ_EXTERNAL_STORAGE is required to access other
// apps' media files.
// This permission has no effect on later Android versions.
if (permission == "android.permission.READ_EXTERNAL_STORAGE")
usesPermission->setAttribute ("android:maxSdkVersion", "32");
}
}
@ -1845,7 +1876,18 @@ private:
s.add ("android.permission.ACCESS_COARSE_LOCATION");
}
if (androidExternalReadPermission.get())
if (androidReadMediaAudioPermission.get())
s.add ("android.permission.READ_MEDIA_AUDIO");
if (androidReadMediaImagesPermission.get())
s.add ("android.permission.READ_MEDIA_IMAGES");
if (androidReadMediaVideoPermission.get())
s.add ("android.permission.READ_MEDIA_VIDEO");
if ( androidReadMediaAudioPermission.get()
|| androidReadMediaImagesPermission.get()
|| androidReadMediaVideoPermission.get())
s.add ("android.permission.READ_EXTERNAL_STORAGE");
if (androidExternalWritePermission.get())

View file

@ -235,6 +235,9 @@ namespace Ids
DECLARE_ID (androidCustomStringXmlElements);
DECLARE_ID (androidBluetoothNeeded);
DECLARE_ID (androidExternalReadNeeded);
DECLARE_ID (androidReadMediaAudioPermission);
DECLARE_ID (androidReadMediaImagesPermission);
DECLARE_ID (androidReadMediaVideoPermission);
DECLARE_ID (androidExternalWriteNeeded);
DECLARE_ID (androidInAppBilling);
DECLARE_ID (androidVibratePermissionNeeded);

View file

@ -86,7 +86,22 @@ public:
writeExternalStorage = 4,
/** Permission to use camera */
camera = 5
camera = 5,
/** Permission to read audio files that your app didn't create.
Has the same effect as readExternalStorage on iOS and Android versions before 33.
*/
readMediaAudio = 6,
/** Permission to read image files that your app didn't create.
Has the same effect as readExternalStorage on iOS and Android versions before 33.
*/
readMediaImages = 7,
/** Permission to read video files that your app didn't create.
Has the same effect as readExternalStorage on iOS and Android versions before 33.
*/
readMediaVideo = 8
};
//==============================================================================

View file

@ -24,15 +24,39 @@ namespace juce
{
//==============================================================================
static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionID permission)
static StringArray jucePermissionToAndroidPermissions (RuntimePermissions::PermissionID permission)
{
const auto externalStorageOrMedia = [] (const auto* newPermission)
{
return getAndroidSDKVersion() < 33 ? "android.permission.READ_EXTERNAL_STORAGE" : newPermission;
};
switch (permission)
{
case RuntimePermissions::recordAudio: return "android.permission.RECORD_AUDIO";
case RuntimePermissions::bluetoothMidi: return "android.permission.ACCESS_FINE_LOCATION";
case RuntimePermissions::readExternalStorage: return "android.permission.READ_EXTERNAL_STORAGE";
case RuntimePermissions::writeExternalStorage: return "android.permission.WRITE_EXTERNAL_STORAGE";
case RuntimePermissions::camera: return "android.permission.CAMERA";
case RuntimePermissions::recordAudio: return { "android.permission.RECORD_AUDIO" };
case RuntimePermissions::bluetoothMidi: return { "android.permission.ACCESS_FINE_LOCATION" };
case RuntimePermissions::writeExternalStorage: return { "android.permission.WRITE_EXTERNAL_STORAGE" };
case RuntimePermissions::camera: return { "android.permission.CAMERA" };
case RuntimePermissions::readExternalStorage:
{
// See: https://developer.android.com/reference/android/Manifest.permission#READ_EXTERNAL_STORAGE
if (getAndroidSDKVersion() < 33)
return { "android.permission.READ_EXTERNAL_STORAGE" };
return { "android.permission.READ_MEDIA_AUDIO",
"android.permission.READ_MEDIA_IMAGES",
"android.permission.READ_MEDIA_VIDEO" };
}
case RuntimePermissions::readMediaAudio:
return { externalStorageOrMedia ("android.permission.READ_MEDIA_AUDIO") };
case RuntimePermissions::readMediaImages:
return { externalStorageOrMedia ("android.permission.READ_MEDIA_IMAGES") };
case RuntimePermissions::readMediaVideo:
return { externalStorageOrMedia ("android.permission.READ_MEDIA_VIDEO") };
}
// invalid permission
@ -42,53 +66,28 @@ static String jucePermissionToAndroidPermission (RuntimePermissions::PermissionI
static RuntimePermissions::PermissionID androidPermissionToJucePermission (const String& permission)
{
if (permission == "android.permission.RECORD_AUDIO") return RuntimePermissions::recordAudio;
else if (permission == "android.permission.ACCESS_FINE_LOCATION") return RuntimePermissions::bluetoothMidi;
else if (permission == "android.permission.READ_EXTERNAL_STORAGE") return RuntimePermissions::readExternalStorage;
else if (permission == "android.permission.WRITE_EXTERNAL_STORAGE") return RuntimePermissions::writeExternalStorage;
else if (permission == "android.permission.CAMERA") return RuntimePermissions::camera;
static const std::map<String, RuntimePermissions::PermissionID> map
{
{ "android.permission.RECORD_AUDIO", RuntimePermissions::recordAudio },
{ "android.permission.ACCESS_FINE_LOCATION", RuntimePermissions::bluetoothMidi },
{ "android.permission.READ_EXTERNAL_STORAGE", RuntimePermissions::readExternalStorage },
{ "android.permission.WRITE_EXTERNAL_STORAGE", RuntimePermissions::writeExternalStorage },
{ "android.permission.CAMERA", RuntimePermissions::camera },
{ "android.permission.READ_MEDIA_AUDIO", RuntimePermissions::readMediaAudio },
{ "android.permission.READ_MEDIA_IMAGES", RuntimePermissions::readMediaImages },
{ "android.permission.READ_MEDIA_VIDEO", RuntimePermissions::readMediaVideo },
};
return static_cast<RuntimePermissions::PermissionID> (-1);
const auto iter = map.find (permission);
return iter != map.cend() ? iter->second
: static_cast<RuntimePermissions::PermissionID> (-1);
}
//==============================================================================
struct PermissionsRequest
{
PermissionsRequest() {}
// using "= default" on the following method triggers an internal compiler error
// in Android NDK 17
PermissionsRequest (const PermissionsRequest& o)
: callback (o.callback), permission (o.permission)
{}
PermissionsRequest (PermissionsRequest&& o)
: callback (std::move (o.callback)), permission (o.permission)
{
o.permission = static_cast<RuntimePermissions::PermissionID> (-1);
}
PermissionsRequest (RuntimePermissions::Callback && callbackToUse,
RuntimePermissions::PermissionID permissionToRequest)
: callback (std::move (callbackToUse)), permission (permissionToRequest)
{}
PermissionsRequest& operator= (const PermissionsRequest & o)
{
callback = o.callback;
permission = o.permission;
return *this;
}
PermissionsRequest& operator= (PermissionsRequest && o)
{
callback = std::move (o.callback);
permission = o.permission;
return *this;
}
RuntimePermissions::Callback callback;
RuntimePermissions::PermissionID permission;
RuntimePermissions::PermissionID permission = static_cast<RuntimePermissions::PermissionID> (-1);
};
//==============================================================================
@ -165,8 +164,7 @@ struct PermissionsOverlay : FragmentOverlay
{
auto &request = requests.front();
StringArray permissionsArray{
jucePermissionToAndroidPermission (request.permission)};
auto permissionsArray = jucePermissionToAndroidPermissions (request.permission);
auto jPermissionsArray = juceStringArrayToJava (permissionsArray);
@ -199,9 +197,16 @@ struct PermissionsOverlay : FragmentOverlay
//==============================================================================
void RuntimePermissions::request (PermissionID permission, Callback callback)
{
auto requestedPermission = jucePermissionToAndroidPermission (permission);
const auto requestedPermissions = jucePermissionToAndroidPermissions (permission);
if (! isPermissionDeclaredInManifest (requestedPermission))
const auto allPermissionsInManifest = std::all_of (requestedPermissions.begin(),
requestedPermissions.end(),
[] (const auto& p)
{
return isPermissionDeclaredInManifest (p);
});
if (! allPermissionsInManifest)
{
// Error! If you want to be able to request this runtime permission, you
// also need to declare it in your app's manifest. You can do so via
@ -220,7 +225,7 @@ void RuntimePermissions::request (PermissionID permission, Callback callback)
return;
}
PermissionsRequest request (std::move (callback), permission);
PermissionsRequest request { std::move (callback), permission };
static CriticalSection overlayGuard;
ScopedLock lock (overlayGuard);
@ -250,12 +255,15 @@ bool RuntimePermissions::isGranted (PermissionID permission)
{
auto* env = getEnv();
auto requestedPermission = jucePermissionToAndroidPermission (permission);
int result = env->CallIntMethod (getAppContext().get(), AndroidContext.checkCallingOrSelfPermission,
javaString (requestedPermission).get());
const auto requestedPermissions = jucePermissionToAndroidPermissions (permission);
return std::all_of (requestedPermissions.begin(), requestedPermissions.end(), [env] (const auto& p)
{
return 0 == env->CallIntMethod (getAppContext().get(),
AndroidContext.checkCallingOrSelfPermission,
javaString (p).get());
});
return result == 0 /* PERMISSION_GRANTED */;
}
} // namespace juce