Skip to content

Commit

Permalink
BREAKING CHANGE: CheckPermission now returns bool + added async varia…
Browse files Browse the repository at this point in the history
…nts of RequestPermission functions that don't freeze the app unnecessarily (fixed #14)
  • Loading branch information
yasirkula committed Jul 9, 2023
1 parent 3e03bad commit 2113d0b
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 163 deletions.
48 changes: 28 additions & 20 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

*Based on UnityAndroidPermissions (MIT License): https://github.com/Over17/UnityAndroidPermissions*

This plugin helps you query/request runtime permissions **synchronously** on Android M and later. It also works on older Android versions and detects whether a requested permission is declared in AndroidManifest or not.
This plugin helps you query/request runtime permissions on Android M and later. It also works on older Android versions and detects whether a requested permission is declared in AndroidManifest or not.

## INSTALLATION

Expand All @@ -38,16 +38,20 @@ Before we start, there is one optional step: by default, Unity shows a permissio

You can use the following *static* functions of **AndroidRuntimePermissions** to manage runtime permissions:

`Permission CheckPermission( string permission )`: checks whether or not the permission is granted. **Permission** is an enum that can take 3 values:
`bool CheckPermission( string permission )`: checks whether or not the permission is granted

`bool[] CheckPermissions( params string[] permissions )`: queries multiple permissions simultaneously. The returned array will contain one entry per queried permission

`Permission RequestPermission( string permission )`: requests a permission from the user and returns the result. It is recommended to show a brief explanation before asking the permission so that user understands why the permission is needed and doesn't click Deny or worse, "Don't ask again". **Permission** is an enum that can take 3 values:
- **Granted**: permission is granted
- **ShouldAsk**: permission is not granted yet, but we can ask the user for permission via *RequestPermission* function. As long as the user doesn't select "Don't ask again" while denying the permission, ShouldAsk is returned
- **ShouldAsk**: permission is denied but we can ask the user for permission once again. As long as the user doesn't select "Don't ask again" while denying the permission, ShouldAsk is returned
- **Denied**: we don't have permission and we can't ask the user for permission. In this case, user has to give the permission from app's Settings. This happens when user selects "Don't ask again" while denying the permission or when user is not allowed to give that permission (parental controls etc.)

`Permission[] CheckPermissions( params string[] permissions )`: queries multiple permissions simultaneously. The returned array will contain one Permission entry per each queried permission
`Permission[] RequestPermissions( params string[] permissions )`: requests multiple permissions simultaneously

`Permission RequestPermission( string permission )`: requests a permission from the user and returns the result. It is recommended to show a brief explanation before asking the permission so that user understands why the permission is needed and doesn't click Deny or worse, "Don't ask again"
`Task<Permission> RequestPermissionAsync( string permission )`: asynchronous version of *RequestPermission*. Unlike *RequestPermission*, this function doesn't freeze the app unnecessarily before the permission dialog is displayed

`Permission[] RequestPermissions( params string[] permissions )`: requests multiple permissions simultaneously
`Task<Permission[]> RequestPermissionsAsync( string[] permissions )`: asynchronous version of *RequestPermissions*

`void OpenSettings()`: opens the settings for this app, from where the user can manually grant permission(s) in case a needed permission's state is *Permission.Denied*

Expand All @@ -59,19 +63,23 @@ The following code requests *ACCESS_FINE_LOCATION* permission (it must be declar
void Update()
{
if( Input.GetMouseButtonDown( 0 ) && Input.mousePosition.x > Screen.width * 0.8f && Input.mousePosition.y < Screen.height * 0.2f )
{
AndroidRuntimePermissions.Permission result = AndroidRuntimePermissions.RequestPermission( "android.permission.ACCESS_FINE_LOCATION" );
if( result == AndroidRuntimePermissions.Permission.Granted )
Debug.Log( "We have permission to read from external storage!" );
else
Debug.Log( "Permission state: " + result );

// Requesting ACCESS_FINE_LOCATION and CAMERA permissions simultaneously
//AndroidRuntimePermissions.Permission[] result = AndroidRuntimePermissions.RequestPermissions( "android.permission.ACCESS_FINE_LOCATION", "android.permission.CAMERA" );
//if( result[0] == AndroidRuntimePermissions.Permission.Granted && result[1] == AndroidRuntimePermissions.Permission.Granted )
// Debug.Log( "We have all the permissions!" );
//else
// Debug.Log( "Some permission(s) are not granted..." );
}
RequestPermission();
}

async void RequestPermission()
{
AndroidRuntimePermissions.Permission result = await AndroidRuntimePermissions.RequestPermissionAsync( "android.permission.ACCESS_FINE_LOCATION" );
//AndroidRuntimePermissions.Permission result = AndroidRuntimePermissions.RequestPermission( "android.permission.ACCESS_FINE_LOCATION" ); // Synchronous version (not recommended)
if( result == AndroidRuntimePermissions.Permission.Granted )
Debug.Log( "We have permission to access fine location!" );
else
Debug.Log( "Permission state: " + result );

// Requesting ACCESS_FINE_LOCATION and CAMERA permissions simultaneously
//AndroidRuntimePermissions.Permission[] result = await AndroidRuntimePermissions.RequestPermissionsAsync( "android.permission.ACCESS_FINE_LOCATION", "android.permission.CAMERA" );
//if( result[0] == AndroidRuntimePermissions.Permission.Granted && result[1] == AndroidRuntimePermissions.Permission.Granted )
// Debug.Log( "We have all the permissions!" );
//else
// Debug.Log( "Some permission(s) are not granted..." );
}
```
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ namespace AndroidRuntimePermissionsNamespace
public class PermissionCallbackAsync : AndroidJavaProxy
{
private readonly string[] permissions;
private readonly AndroidRuntimePermissions.PermissionResultMultiple callback;
private readonly AndroidRuntimePermissions.AsyncPermissionResult callback;
private readonly PermissionCallbackHelper callbackHelper;

public PermissionCallbackAsync( string[] permissions, AndroidRuntimePermissions.PermissionResultMultiple callback ) : base( "com.yasirkula.unity.RuntimePermissionsReceiver" )
internal PermissionCallbackAsync( string[] permissions, AndroidRuntimePermissions.AsyncPermissionResult callback ) : base( "com.yasirkula.unity.RuntimePermissionsReceiver" )
{
this.permissions = permissions;
this.callback = callback;
Expand All @@ -26,7 +26,7 @@ private void ExecuteCallback( string result )
try
{
if( callback != null )
callback( permissions, AndroidRuntimePermissions.ProcessPermissionRequest( permissions, result ) );
callback( AndroidRuntimePermissions.ProcessPermissionRequestResult( permissions, result ) );
}
finally
{
Expand Down
119 changes: 30 additions & 89 deletions Plugins/AndroidRuntimePermissions/AndroidRuntimePermissions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
#endif

using System;
using System.Text;
#if UNITY_2018_4_OR_NEWER && !ANDROID_RUNTIME_PERMISSIONS_DISABLE_ASYNC_FUNCTIONS
using System.Threading.Tasks;
#endif
using UnityEngine;
#if UNITY_ANDROID
using AndroidRuntimePermissionsNamespace;
Expand All @@ -13,8 +15,7 @@ public static class AndroidRuntimePermissions
{
public enum Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };

public delegate void PermissionResult( string permission, Permission result );
public delegate void PermissionResultMultiple( string[] permissions, Permission[] result );
internal delegate void AsyncPermissionResult( Permission[] result );

#region Native Properties
#if IS_ANDROID_PLATFORM
Expand Down Expand Up @@ -59,50 +60,33 @@ public static void OpenSettings()
#endif
}

public static Permission CheckPermission( string permission )
public static bool CheckPermission( string permission )
{
#if IS_ANDROID_PLATFORM
return CheckPermissions( permission )[0];
#else
return Permission.Granted;
#endif
}

public static Permission[] CheckPermissions( params string[] permissions )
public static bool[] CheckPermissions( params string[] permissions )
{
ValidateArgument( permissions );

#if IS_ANDROID_PLATFORM
string resultRaw = AJC.CallStatic<string>( "CheckPermission", permissions, Context );
if( resultRaw.Length != permissions.Length )
{
Debug.LogError( "CheckPermissions: something went wrong" );
return null;
}
throw new Exception( "CheckPermissions: something went wrong" );

Permission[] result = new Permission[permissions.Length];
bool[] result = new bool[permissions.Length];
for( int i = 0; i < result.Length; i++ )
{
Permission _permission = resultRaw[i].ToPermission();
if( _permission == Permission.Denied && GetCachedPermission( permissions[i], Permission.ShouldAsk ) != Permission.Denied )
_permission = Permission.ShouldAsk;

result[i] = _permission;
}
result[i] = resultRaw[i].ToPermission() == Permission.Granted;

return result;
#else
return GetDummyResult( permissions );
return GetDummyResult( permissions, true );
#endif
}

public static Permission RequestPermission( string permission )
{
#if IS_ANDROID_PLATFORM
return RequestPermissions( permission )[0];
#else
return Permission.Granted;
#endif
}

public static Permission[] RequestPermissions( params string[] permissions )
Expand All @@ -115,97 +99,54 @@ public static Permission[] RequestPermissions( params string[] permissions )
lock( threadLock )
{
nativeCallback = new PermissionCallback( threadLock );
AJC.CallStatic( "RequestPermission", permissions, Context, nativeCallback, GetCachedPermissions( permissions ) );
AJC.CallStatic( "RequestPermission", permissions, Context, nativeCallback, new string( (char) ( '0' + (int) Permission.ShouldAsk ), permissions.Length ) );

if( nativeCallback.Result == null )
System.Threading.Monitor.Wait( threadLock );
}

return ProcessPermissionRequest( permissions, nativeCallback.Result );
return ProcessPermissionRequestResult( permissions, nativeCallback.Result );
#else
return GetDummyResult( permissions );
return GetDummyResult( permissions, Permission.Granted );
#endif
}

private static void RequestPermissionAsync( string permission, PermissionResult callback )
#if UNITY_2018_4_OR_NEWER && !ANDROID_RUNTIME_PERMISSIONS_DISABLE_ASYNC_FUNCTIONS
public static async Task<Permission> RequestPermissionAsync( string permission )
{
#if IS_ANDROID_PLATFORM
RequestPermissionsAsync( new string[1] { permission }, ( permissions, result ) =>
{
if( callback != null )
callback( permissions[0], result[0] );
} );
#else
if( callback != null )
callback( permission, Permission.Granted );
#endif
return ( await RequestPermissionsAsync( permission ) )[0];
}

private static void RequestPermissionsAsync( string[] permissions, PermissionResultMultiple callback )
public static Task<Permission[]> RequestPermissionsAsync( params string[] permissions )
{
ValidateArgument( permissions );

#if IS_ANDROID_PLATFORM
PermissionCallbackAsync nativeCallback = new PermissionCallbackAsync( permissions, callback );
AJC.CallStatic( "RequestPermission", permissions, Context, nativeCallback, GetCachedPermissions( permissions ) );
TaskCompletionSource<Permission[]> tcs = new TaskCompletionSource<Permission[]>();
PermissionCallbackAsync nativeCallback = new PermissionCallbackAsync( permissions, ( result ) => tcs.SetResult( result ) );
AJC.CallStatic( "RequestPermission", permissions, Context, nativeCallback, new string( (char) ( '0' + (int) Permission.ShouldAsk ), permissions.Length ) );

return tcs.Task;
#else
if( callback != null )
callback( permissions, GetDummyResult( permissions ) );
return Task.FromResult( GetDummyResult( permissions, Permission.Granted ) );
#endif
}
#endif
#endregion

#region Helper Functions
public static Permission[] ProcessPermissionRequest( string[] permissions, string resultRaw )
internal static Permission[] ProcessPermissionRequestResult( string[] permissions, string resultRaw )
{
if( resultRaw.Length != permissions.Length )
{
Debug.LogError( "RequestPermissions: something went wrong" );
return null;
}
throw new Exception( "RequestPermissions: something went wrong" );

bool shouldUpdateCache = false;
Permission[] result = new Permission[permissions.Length];
for( int i = 0; i < result.Length; i++ )
{
Permission _permission = resultRaw[i].ToPermission();
result[i] = _permission;

if( CachePermission( permissions[i], _permission ) )
shouldUpdateCache = true;
}

if( shouldUpdateCache )
PlayerPrefs.Save();
result[i] = resultRaw[i].ToPermission();

return result;
}

private static Permission GetCachedPermission( string permission, Permission defaultValue )
{
return (Permission) PlayerPrefs.GetInt( "ARTP_" + permission, (int) defaultValue );
}

private static string GetCachedPermissions( string[] permissions )
{
StringBuilder cachedPermissions = new StringBuilder( permissions.Length );
for( int i = 0; i < permissions.Length; i++ )
cachedPermissions.Append( (int) GetCachedPermission( permissions[i], Permission.ShouldAsk ) );

return cachedPermissions.ToString();
}

private static bool CachePermission( string permission, Permission value )
{
if( PlayerPrefs.GetInt( "ARTP_" + permission, -1 ) != (int) value )
{
PlayerPrefs.SetInt( "ARTP_" + permission, (int) value );
return true;
}

return false;
}

private static void ValidateArgument( string[] permissions )
{
if( permissions == null || permissions.Length == 0 )
Expand All @@ -218,11 +159,11 @@ private static void ValidateArgument( string[] permissions )
}
}

private static Permission[] GetDummyResult( string[] permissions )
private static T[] GetDummyResult<T>( string[] permissions, T value )
{
Permission[] result = new Permission[permissions.Length];
T[] result = new T[permissions.Length];
for( int i = 0; i < result.Length; i++ )
result[i] = Permission.Granted;
result[i] = value;

return result;
}
Expand Down
9 changes: 0 additions & 9 deletions Plugins/AndroidRuntimePermissions/Editor.meta

This file was deleted.

22 changes: 0 additions & 22 deletions Plugins/AndroidRuntimePermissions/Editor/ARPPostProcessBuild.cs

This file was deleted.

This file was deleted.

18 changes: 11 additions & 7 deletions Plugins/AndroidRuntimePermissions/README.txt
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
= Android Runtime Permissions =
= Android Runtime Permissions (v1.2.0) =

Online documentation & example code available at: https://github.com/yasirkula/UnityAndroidRuntimePermissions
E-mail: [email protected]

1. ABOUT
This plugin helps you query/request runtime permissions synchronously on Android M and later.
This plugin helps you query/request runtime permissions on Android M and later.

2. HOW TO
You can use the following static functions of AndroidRuntimePermissions to manage runtime permissions:

- Permission CheckPermission( string permission ): checks whether or not the permission is granted. Permission is an enum that can take 3 values:
- bool CheckPermission( string permission ): checks whether or not the permission is granted

- bool[] CheckPermissions( params string[] permissions ): queries multiple permissions simultaneously. The returned array will contain one entry per queried permission

- Permission RequestPermission( string permission ): requests a permission from the user and returns the result. It is recommended to show a brief explanation before asking the permission so that user understands why the permission is needed and doesn't click Deny or worse, "Don't ask again". Permission is an enum that can take 3 values:
-- Granted: permission is granted
-- ShouldAsk: permission is not granted yet, but we can ask the user for permission via RequestPermission function. As long as the user doesn't select "Don't ask again" while denying the permission, ShouldAsk is returned
-- ShouldAsk: permission is denied but we can ask the user for permission once again. As long as the user doesn't select "Don't ask again" while denying the permission, ShouldAsk is returned
-- Denied: we don't have permission and we can't ask the user for permission. In this case, user has to give the permission from app's Settings. This happens when user selects "Don't ask again" while denying the permission or when user is not allowed to give that permission (parental controls etc.)

- Permission[] CheckPermissions( params string[] permissions ): queries multiple permissions simultaneously. The returned array will contain one Permission entry per each queried permission
- Permission[] RequestPermissions( params string[] permissions ): requests multiple permissions simultaneously

- Permission RequestPermission( string permission ): requests a permission from the user and returns the result. It is recommended to show a brief explanation before asking the permission so that user understands why the permission is needed and doesn't click Deny or worse, "Don't ask again"
- Task<Permission> RequestPermissionAsync( string permission ): asynchronous version of RequestPermission. Unlike RequestPermission, this function doesn't freeze the app unnecessarily before the permission dialog is displayed

- Permission[] RequestPermissions( params string[] permissions ): requests multiple permissions simultaneously
- Task<Permission[]> RequestPermissionsAsync( string[] permissions ): asynchronous version of RequestPermissions

- void OpenSettings(): opens the settings for this app, from where the user can manually grant permission(s) in case a needed permission's state is Permission.Denied
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "com.yasirkula.androidruntimepermissions",
"displayName": "Android Runtime Permissions",
"version": "1.1.6",
"version": "1.2.0",
"documentationUrl": "https://github.com/yasirkula/UnityAndroidRuntimePermissions",
"changelogUrl": "https://github.com/yasirkula/UnityAndroidRuntimePermissions/releases",
"licensesUrl": "https://github.com/yasirkula/UnityAndroidRuntimePermissions/blob/master/LICENSE.txt",
Expand Down

0 comments on commit 2113d0b

Please sign in to comment.