Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for platform-specific app permissions #547

Closed
freakboy3742 opened this issue Dec 21, 2020 · 29 comments · Fixed by #1599
Closed

Add support for platform-specific app permissions #547

freakboy3742 opened this issue Dec 21, 2020 · 29 comments · Fixed by #1599
Labels
android The issue relates to Android mobile support. enhancement New features, or improvements to existing features. iOS The issue relates to Apple iOS mobile support. macOS The issue relates to Apple macOS support.

Comments

@freakboy3742
Copy link
Member

Is your feature request related to a problem? Please describe.

Some platforms (most notably mobile platforms, but desktop platforms are moving in a similar direction) have explicit permissions for accessing certain hardware features, such as:

  • Geolocation
  • Network access
  • File system access
  • Camera access
  • Accelerometer
  • Push notifications

At present, Briefcase provides no way to customise these permissions when rolling out an app.

Describe the solution you'd like

We should add a configuration parameter that can be used to inject permissions into the app template. Suggested format:

[tool.briefcase.app.helloworld.iOS.permissions]

NSCameraUsageDescription = "Helloworld will use your camera to take pictures of cats"

In this example asking for access to the camera on iOS, the key is the platform-specific permission, and the value is a short explanation for why the permission is required. The explanatory text is required on iOS and macOS; it does not appear to be required on Android, but could still serve as useful documentation.

These definitions would then be passed to the app template, and injected as required.

Two "stretch" goals that might be worth considering:

  1. "Universal" permission aliases Every platform has permissions for accessing the camera, geolocation, and so on. It may be worth establishing a "Briefcase alias" for these universal permissions, which is mapped to the platform specific name (or names) as required. That way, you could ask for camera permission, rather than needing to know the platform-specific name of each permission.

  2. Extracting required permissions from library metadata Manually defining permissions is simple from an implementation perspective, but doesn't lead to a good user experience. It is reasonable to expect that first-time app developers won't understand why their app doesn't work when they're calling a camera API, but haven't manually added permissions. Ideally, we would be able to determine the permissions required based on the libraries that are used. For example, a toga-camera library would declare in library metadata that it requires the camera permission, and that would be automatically injected into the app template. The explanatory text associated with that permission would need to be a placeholder; however, we can provide placeholder text, and warn the user that placeholder text has been provided and should be overridden (in a similar way to how default icons are currently used and a warning provided).

However, regardless of whether we hit these stretch goals, a framework for platform-specific permissions will be required. This is because OS release schedules are decoupled from Briefcase release schedules; a new OS may introduce new permissions (or alter permission semantics); and we need to be able to support those permissions without requiring a new Briefcase release.

Describe alternatives you've considered

The current alternative is to:

  1. manually modify the generated project generated by briefcase create
  2. develop a custom platform template that includes the required permissions

(1) isn't persistent between app builds; (2) requires more effort than should be required.

@freakboy3742 freakboy3742 added enhancement New features, or improvements to existing features. up-for-grabs iOS The issue relates to Apple iOS mobile support. macOS The issue relates to Apple macOS support. android The issue relates to Android mobile support. labels Dec 21, 2020
@ecmel
Copy link

ecmel commented Apr 1, 2022

This is a much needed feature

@gauravghodinde
Copy link

isn't there a way to give permissions to apps on android?

@mhsmith
Copy link
Member

mhsmith commented Aug 2, 2022

@gauravghodinde: Currently the only way is to manually edit the AndroidManifest file in the generated project. Note that this will be overwritten every time you run briefcase create android.

@mhsmith
Copy link
Member

mhsmith commented Aug 3, 2022

#472 contains ideas for how we could update a template without regenerating the whole project and forcing a full rebuild.

@yousifamanuel
Copy link

I would like to know if this feature will be added within next year

@mhsmith
Copy link
Member

mhsmith commented Aug 25, 2022

That depends on how many people need it. If anyone wants this feature, please post the following information:

  • which platform your app runs on
  • what you're trying to achieve
  • which permission you need

@yousifamanuel
Copy link

Thank you for responding.

  • which platform your app runs on
    Android
  • what you're trying to achieve
    Accessing the camera to read bar and QR codes
  • which permission you need
    Camera and Memory permission

@mhsmith
Copy link
Member

mhsmith commented Aug 30, 2022

Thanks: that's a common use case, so it'll be one of the first things we look at in this area.

@yousifamanuel
Copy link

yousifamanuel commented Aug 30, 2022

thank you; that would be great. I would like to use the Beeware framework in an university course to teach student agile project management. This decision was made so we can move from Android Studio which needs more resources and computational power to run.

@mhsmith
Copy link
Member

mhsmith commented Nov 23, 2022

We could support Android permissions and various other customizations by adding a manifest_extra_content option in the pyproject.toml file, similar to build_gradle_extra_content which was added in beeware/briefcase-android-gradle-template#57.

However, this would be more difficult than the Dockerfile and build.gradle cases, because you can't simply concatenate two XML files: they would need to be merged.

Also, simply adding a permission to the manifest isn't enough: you also need ask the user to grant it at runtime. So this would be much easier if there was a higher-level API as mentioned above.

@julianrwood
Copy link

Thank for all the effort guys. just jotting myself down as i'd love this type of feature.

which platform your app runs on
Android
what you're trying to achieve
Accessing a file directory (I'm just making an app for myself so having a defined file structure that I can view the files in is important to me)

which permission you need
File system access

Cheers guys!

@mhsmith
Copy link
Member

mhsmith commented May 31, 2023

Hi @julianrwood,

Basic file system access on Android doesn't require any permissions. If you're using Toga, you can use App.paths.app for read-only data from your app's source code directory, and App.paths.data for a read-write directory which you can use however you want.

Although this API is available in the current stable Toga release, it isn't documented in the "stable" version of the docs, so I've linked to the "latest" version. This will be fixed by the next Toga release.

There used to be permissions for accessing the device's photos, downloads, etc., but newer versions of Android no longer allow direct access to these locations even if you have the permission. Instead, you should use the system file picker, as shown here and here.

@julianrwood
Copy link

Hey @mhsmith
Thanks heaps mate, this will do exactly what I need it to.
I ended up going with something like below.

cacheLocation = toga.app.App.app.paths.cache

Appreciate the help and the extra resources. The android app development is new to me, it's hella cool though and you guys are doing awesome work

@ghost
Copy link

ghost commented Jul 18, 2023

WIthout access to network permissions, Toga doesn't really help much in Android. If you could fix this with a template and make a short tutorial on how to use it, then I'd develop 100s of apps using Toga. You have a great start, but it won't spread far unless you fix REST capabilities.

@mhsmith
Copy link
Member

mhsmith commented Jul 18, 2023

The Briefcase Android template already has network permissions (see here). If you're having a problem, please create a separate issue and give full details.

@ghost
Copy link

ghost commented Jul 18, 2023 via email

@ghost
Copy link

ghost commented Jul 18, 2023 via email

@freakboy3742
Copy link
Member Author

If you could fix this with a template and make a short tutorial on how to use it, then I'd develop 100s of apps using Toga. You have a great start, but it won't spread far unless you fix REST capabilities.

I'll add that the BeeWare tutorial explicitly includes a step that is an example of performing a REST request.

@ghost
Copy link

ghost commented Jul 19, 2023

Its clear that you are very protective of your software. When I tried it back in Dec/Jan the network permissions werent baked into the template. Since picking Android back up, I looked back into the project and noticed permissions werent easy to add into the Beeware enterprise from the start. Thats the main takeaway. We dont want to rebuild or dive into the gradle files to make the changes.

Separately, the Java api should be abstracted through base classes. Firstly, as Python programmers we hate seeing Java bindings explicitly. Second, it looks more professional. Third, it allows programmers the ability to extend these base classes.The point I was trying to make is that I have to search for hours to figure out how these bindings work. Wastes our time.

Finally, the point of having a cross platform app builder is to be able to natively integrate with the platform. The last time I tried it, apk signing was virtually non existant. Making an easier pipeline for signing to get onto the google play store should be a priority.

These are necessary items that need to mature for user acceptance. Originally, I was going to help with this project. Unfortunately, it still needs to mature.

@freakboy3742
Copy link
Member Author

Thank you for your feedback. We are very much aware that permissions are a required feature. That is why this ticket is open. We are also aware that APK signing is a feature we require; that is why #1268 exists.

Unfortunately, the only way for any project to become mature is for it to start as a less mature project. We have prioritised features that we have evaluated to be more pressing to the app development process. This doesn't mean that permissions aren't important; just that in our estimation, they're less important than the other features we have chosen to implement. You are more than welcome to have a different opinion on those priorities.

@mhsmith
Copy link
Member

mhsmith commented Jul 19, 2023

When I tried it back in Dec/Jan the network permissions werent baked into the template.

According to the Git history, they've been in the template for 3 years, so the cause of your issue must have been something else. If you can reproduce it and let us know the details, we'll look into it.

@mhsmith
Copy link
Member

mhsmith commented Nov 15, 2023

For some example Android code, see #1497 (reply in thread).

@freakboy3742
Copy link
Member Author

freakboy3742 commented Dec 14, 2023

Here's a survey of what we need to be able to support for app permissions.

Defining permissions

iOS

An app doesn't need to pre-declare permissions; but it does need to provide keys in the Info.plist file that describe the reason that specific permissions may be requested. This text is shown to the user when the permission is requested.

There are also some boolean flags that indicate an initial state of a sensor (e.g., location services starting in a "reduced accuracy" mode). However, these can be changed at runtime.

Finally, some app features (e.g., iCloud access, Push notifications) need to be added as a "capability", which become part of the Xcode project definition.

macOS

As with iOS, there are some permissions that need descriptions in the Info.plist file; however, there's a lot less of these than on iOS. There is also a need to add capabilities for some features.

macOS apps also need to declare boolean flags in the Entitlements.plist file when the app is signed.

Android

Android uses declarations in the AndroidManifest.xml; these take 3 forms:

  • <uses-permission> to declare a specific permission might be requested
  • <uses-feature> to declare that a specific piece of hardware is used by the app. Can be set to required=True/False; this controls if the app is offered at all by the play store. Some permissions (e.g., camera) will imply a feature, set to required=True.
  • <meta-data> to declare things like API keys.

Common permissions

This isn't a comprehensive list of all permissions; but it is the set of features that are likely targets for Toga in the near future.

Camera

  • iOS:
    • Plist: NSCameraUsageDescription
    • Plist: NSMicrophoneUsageDescription
  • macOS
    • Entitlement: com.apple.security.device.camera
    • Entitlement: com.apple.security.device.audio-input
  • Android
    • Feature: android.hardware.CAMERA (implied, defaulting to required=True if permission is added)
    • Permission: android.permission.CAMERA
    • Permission: android.permission.RECORD_AUDIO

Location

  • iOS:
    • Plist: NSLocationWhenInUseUsageDescription
    • Plist: NSLocationAlwaysAndWhenInUseUsageDescription
    • Plist: NSLocationDefaultAccuracyReduced
  • macOS: CoreLocation
    • Plist: NSLocationUsageDescription
    • Entitlement: com.apple.security.personal-information.location
  • Android
    • Permission: android.permission.ACCESS_COARSE_LOCATION
    • Permission: android.permission.ACCESS_FINE_LOCATION
    • Permission: android.permission.ACCESS_BACKGROUND_LOCATION

Kinematics

  • iOS: CoreMotion
    • Plist: NSFallDetectionUsageDescription (only needed for fall detection)
  • macOS: CoreMotion
    • None?
  • Android:
    • Permission: android.permission.HIGH_SAMPLING_RATE_SENSORS (if required; also impacted by user turning off the microphone?)

Photo library

  • iOS:
    • Plist: NSPhotoLibraryAddUsageDescription
  • macOS:
    • Entitlement: com.apple.security.personal-information.photos-library
  • Android:
    • Permission: android.permission.READ_MEDIA_VISUAL_USER_SELECTED

File storage

  • iOS:
    • No real analog; there's no external storage to access, other than iCloud.
    • UIFileSharingEnabled, LSSupportsOpeningDocumentsInPlace - used to allow other apps to access the Documents folder of the app
    • UISupportDocumentBrowser - App is document-based
    • CloudKit is used to access iCloud Files; this has a whole configuration unto itself.
  • macOS:
    • No pre-declared permissions; specific file locations will automatically prompt a question
  • Android:
    • Permission: android.permission.EXTERNAL_FILE_STORAGE (not required on Android 10+)

Push Notifications

  • iOS/macOS:
    • Capability: Push Notifications
    • No specific permissions configuration required;
  • Android
    • Permission: android.permission.POST_NOTIFICATIONS (Android 13+)

Other

  • Android
    • Permission: android.permission.INTERNET to access the internet. Currently installed by default)
    • Permission: android.permission.ACCESS_NETWORK_STATE to access information about networks (required for network access). Currently installed by default

@freakboy3742
Copy link
Member Author

API proposal

permissions table

We will add a permissions table as a new app configuration key. The keys on the table are then used as specific permissions.

Briefcase will maintain a list of "common" permissions that have mappings to platform-specific permissions. Any keys that aren't explicitly "common" permissions will be used as user extension permissions (this allows for platform-specific interpretations).

For each permission, the value is expected to be a string that describes why the permission is needed (in a form that could be displayed to the user as an explanatory note). On platforms that need that description, it will be used verbatim; on other platforms, the existence of a non-empty string will be interpreted as a boolean.

The permissions table will be cumulative (like requires), so that a base set of permissions can be declared, with platform-specific extensions. Any key that isn't a known "common" permission will be used verbatim as the local platform value for the permission.

On macOS, the value of permissions will require some light interpretation; any key starting with com.apple. will be added to entitlements.plist; all others will be added as plist keys. The app template will be modified to add the 2 default entitlements currently being used (unsigned Executable Memory and Disable Library Validation). For backwards compatibility, if no permissions are defined in the app, these two keys will be added automatically (with a warning to the user that this has been done).

On Android, the app template will be modified to add the default platform permissions for network access. For backwards compatibility, if no permissions are defined in the app, these keys will be added automatically (with a warning to the user that this has been done).

Common permissions

We will initially support the following common permissions:

  • camera
  • microphone
  • coarse_location
  • fine_location
  • background_location
  • photo_library

These keys can all be made to map to platform-specific interpretations described in the previous post; the mapping is a little complex for location on macOS, but should be obvious enough.

device_requires table

We will also add a device_requires table. This will be used to indicate any device-specific requirements. The keys will be "common" definitions, or platform specific interpretations; the values will be booleans.

Initially, this table will only be used by Android.

On Android, if a common permission has a matching feature (e.g., requesting camera permissions implies a camera feature), the use of the permission will imply device_requires.camera = False. However, any user-provided value will override this default.

Initially, this will be used to set "requires=False" features for android.hardware.camera, android.hardware.camera.any, android.hardware.camera.front, and android.hardware.camera.external if a camera permission is requested.

As with permissions, any unrecognised device_requires keys will be passed verbatim as uses-feature keys.

Extra configuration

In addition to these specific permission configurations, we will also add "_extra" configuration items for the files where we're adding specific configurations. This includes adding custom info.plist content for macOS and iOS, custom entitlements.plist content for macOS, and custom content in the <manifest> block of AndroidManifest.xml.

Implementation

  1. Read the full pyproject.toml
  2. Merge the app/platform specific keys into a single permission and device_requires block using dict.update()
  3. As backend-specific code, process each key in permissions. If it's a common key:
    • remove it from the permission table,
    • convert to the platform-specific form. If there's no platform interpretation required, discard.
    • if the key doesn't already exist in the permission table, add it. If it does exist, discard.
    • if a common device requirement must be added, and the common requirement isn't defined, add it
  4. As backend-specific code, process each device_requires key. If it's a common key:
    • remove it from the device_requires table,
    • convert to the platform-specific form. If there's no platform interpretation required, discard.
    • if the key doesn't already exist in the device_requires table, add it. If it does exist, discard.

The permission and device_requires tables should now be key-value mappings of platform-specific keys. These can be passed to the template, along with any extras, for rendering.

Plist files are XML where the value tags are "types" (e.g., <string>, <true/>), so we will write some helper filters for converting Python types to Plist values.

Example

[tool.briefcase.app.myapp]
...
# Briefcase "common" permissions
permission.camera = "This app uses the camera to allow you to photograph cute cats"
permission.coarse_location = "This app captures your location so you can find cats near you".

[tool.briefcase.app.myapp.iOS]
# An override of the common definition for camera permissions
permission.camera = "This iOS app uses the camera"

# A specific iOS permission, overriding any common permissions
permission.NSMicrophoneUsageDescription = "iOS has special microphone needs"

# A block that will be dropped verbatim into the info.plist for the app
info_plist_extra = """
	<key>NSSomeKey</key>
	<string>The value of the custom key</string>
"""

[tool.briefcase.app.myapp.macOS]
# An override of the common definition for camera permissions
permission.camera = "This macOS app uses the camera"

# A specific macOS permission, overriding any common permissions. Added to info.plist
permission.NSLocationUsageDescription = "macOS has special microphone needs"

# A specific macOS entitlement, overriding any common permissions. Added to Entitlements.plist, based on the "com.apple" prefix.
permission."com.apple.security.device.audio-input" = True

# A block that will be dropped verbatim into the info.plist for the app
info_plist_extra = """
	<key>NSSomeKey</key>
	<string>The value of the custom key</string>
"""
# A block that will be dropped verbatim into the info.plist for the app
entitlements_plist_extra = """
	<key>com.apple.domain.thing</key>
	<string>The value of the custom key</string>
"""

[tool.briefcase.app.myapp.android]

# Extra content that is inserted into the <manifest> block of androidManifest.xml
manifest_extra = """
   <meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="MY_KEY" />
 """

# Make the camera a required device
device_requries.camera = True

# Require a custom Android bluetooth feature.
device_requires."android.hardware.bluetooth" = True

# A different (but equivalent) format for TOML tables.
[tool.briefcase.app.myapp.android.permission]
# An override of the common definition for camera permissions. Android doesn't need the description, so any truthy value is fine.
camera = True

# A specific Android permission, overriding any common permissions. Added to androidManifest.xml
"android.permission.RECORD_AUDIO" = True

Open questions

  • Does this make sense?
  • Are there any obvious gaps that I've missed?
  • Is the 'magical' info/entitlements processing on macOS confusing?
  • Regarding Android Manifest extra content: the only "extra" needed for permissions is in the <manifest> block; but should we pre-empt future requirements by making the extras a table? e.g.,
android_manifest.manifest_attrs_extra = "extra attributes in the manifest tag"
android_manifest.manifest_extra = "extra content in the manifest block"
android_manifest.application_attrs_extra = "extra attributes on the <application> tag"
android_manifest.application_extra = "extra content in the application block"
android_manifest.activity_attrs_extra = "extra attributes on the <activity> tag"
android_manifest.activity_extra = "extra content in the <activity>  block"

Even this definition assumes there's only ever one application, and one activity... but maybe that's sufficient for the foreseeable future?

@Fipaddict
Copy link

That depends on how many people need it. If anyone wants this feature, please post the following information:

* which platform your app runs on
  • Android
* what you're trying to achieve
  • Access to external files (download directory)
* which permission you need
  • android.permission.WRITE_EXTERNAL_STORAGE
  • android.permission.READ_EXTERNAL_STORAGE

@mhsmith
Copy link
Member

mhsmith commented Jan 10, 2024

The API design looks good, just a few comments:

On Android, the app template will be modified to add the default platform permissions for network access. For backwards compatibility, if no permissions are defined in the app, these keys will be added automatically (with a warning to the user that this has been done).

The permissions are already in the template, so I don't understand this.

Initially, this will be used to set "requires=False" features for android.hardware.camera, android.hardware.camera.any, android.hardware.camera.front, and android.hardware.camera.external if a camera permission is requested.

I don't think it's worth adding a general Briefcase configuration mechanism for something which only exists on one platform. The default behavior seems reasonable, and if the developer cares about making their app available to the tiny minority of devices which don't have a camera, they can do that with manifest_extra.

manifest_extra = """
  <meta-data
       android:name="com.google.android.maps.v2.API_KEY"
       android:value="MY_KEY" />
"""

<meta-data> isn't a valid child of <manifest>: see here.

Regarding Android Manifest extra content: the only "extra" needed for permissions is in the <manifest> block; but should we pre-empt future requirements by making the extras a table?

It would be cleaner to just make it accept nested XML tags, e.g. the above example would become:

manifest_extra = """
   <application>
   <meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="MY_KEY" />
   </application>
 """

But there's no need to do that yet.

@mhsmith
Copy link
Member

mhsmith commented Jan 10, 2024

@Fipaddict: Unfortunately those permissions don't work anymore on current versions of Android. Instead, you can launch the system file picker as shown at beeware/toga#1158 (comment).

@freakboy3742
Copy link
Member Author

On Android, the app template will be modified to add the default platform permissions for network access. For backwards compatibility, if no permissions are defined in the app, these keys will be added automatically (with a warning to the user that this has been done).

The permissions are already in the template, so I don't understand this.

What I was aiming at was consolidating all permissions in the same place, rather than having some in the template, and some in the permissions mechanism.

Initially, this will be used to set "requires=False" features for android.hardware.camera, android.hardware.camera.any, android.hardware.camera.front, and android.hardware.camera.external if a camera permission is requested.

I don't think it's worth adding a general Briefcase configuration mechanism for something which only exists on one platform. The default behavior seems reasonable, and if the developer cares about making their app available to the tiny minority of devices which don't have a camera, they can do that with manifest_extra.

Yeah - I wasn't 100% happy with device_requires as an API spec, and the deeper I get into the implementation, the less I like it. I think there's a way around this that preserves the flexibility without needing a cross-platform layer - details to come in the PR I'm working on.

<meta-data> isn't a valid child of <manifest>: see here.

Ok - the specific example there was a flub on my part. What I was trying to highlight was that we could/would specify configuration items in pyproject.toml that insert arbitrary verbatim blocks of content into the AndroidManifest XML.

Regarding Android Manifest extra content: the only "extra" needed for permissions is in the <manifest> block; but should we pre-empt future requirements by making the extras a table?

It would be cleaner to just make it accept nested XML tags, e.g. the above example would become:

manifest_extra = """
   <application>
   <meta-data
        android:name="com.google.android.maps.v2.API_KEY"
        android:value="MY_KEY" />
   </application>
 """

But there's no need to do that yet.

Except that this also adds a second application tag, doesn't it? Unless you're proposing some sort of XML-merging... which is definitely more than I was planning on doing here.

@mhsmith
Copy link
Member

mhsmith commented Jan 11, 2024

I was proposing XML merging, but there's no need to include it as part of this work. Just pointing out that it would be a backwards-compatible generalization of the current work, which would support arbitrary additions to the manifest file without the need to have a separate Briefcase configuration item for every single block.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
android The issue relates to Android mobile support. enhancement New features, or improvements to existing features. iOS The issue relates to Apple iOS mobile support. macOS The issue relates to Apple macOS support.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants