Skip to content

Commit

Permalink
Added async variants of RequestPermission function that don't freeze …
Browse files Browse the repository at this point in the history
…the app unnecessarily
  • Loading branch information
yasirkula committed Jul 13, 2023
1 parent 9c6272a commit 9c0a17d
Show file tree
Hide file tree
Showing 10 changed files with 157 additions and 38 deletions.
13 changes: 13 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ Beginning with *6.0 Marshmallow*, Android apps must request runtime permissions

`NativeCamera.Permission NativeCamera.RequestPermission( bool isPicturePermission )`: requests permission to access the camera 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". Note that TakePicture and RecordVideo functions call RequestPermission internally and execute only if the permission is granted (the result of RequestPermission is then returned).

`void NativeCamera.RequestPermissionAsync( PermissionCallback callback, bool isPicturePermission )`: Asynchronous variant of *RequestPermission*. Unlike RequestPermission, this function doesn't freeze the app unnecessarily before the permission dialog is displayed. So it's recommended to call this function instead.
- **PermissionCallback** takes `NativeCamera.Permission permission` parameter

`Task<NativeCamera.Permission> NativeCamera.RequestPermissionAsync( bool isPicturePermission )`: Another asynchronous variant of *RequestPermission* (requires Unity 2018.4 or later).

`NativeCamera.OpenSettings()`: opens the settings for this app, from where the user can manually grant permission in case current permission state is *Permission.Denied* (Android requires *Storage* and, if declared in AndroidManifest, *Camera* permissions; iOS requires *Camera* permission).

`bool NativeCamera.CanOpenSettings()`: on iOS versions prior to 8.0, opening settings from within the app is not possible and in this case, this function returns *false*. Otherwise, it returns *true*.
Expand Down Expand Up @@ -137,6 +142,14 @@ void Update()
}
}

// Example code doesn't use this function but it is here for reference. It's recommended to ask for permissions manually using the
// RequestPermissionAsync methods prior to calling NativeCamera functions
private async void RequestPermissionAsynchronously( bool isPicturePermission )
{
NativeCamera.Permission permission = await NativeCamera.RequestPermissionAsync( isPicturePermission );
Debug.Log( "Permission result: " + permission );
}

private void TakePicture( int maxSize )
{
NativeCamera.Permission permission = NativeCamera.TakePicture( ( path ) =>
Expand Down
13 changes: 10 additions & 3 deletions Plugins/NativeCamera/Android/NCCallbackHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,16 @@ private void Update()
{
if( mainThreadAction != null )
{
System.Action temp = mainThreadAction;
mainThreadAction = null;
temp();
try
{
System.Action temp = mainThreadAction;
mainThreadAction = null;
temp();
}
finally
{
Destroy( gameObject );
}
}
}

Expand Down
18 changes: 1 addition & 17 deletions Plugins/NativeCamera/Android/NCCameraCallbackAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,7 @@ public NCCameraCallbackAndroid( NativeCamera.CameraCallback callback ) : base( "

public void OnMediaReceived( string path )
{
callbackHelper.CallOnMainThread( () => MediaReceiveCallback( path ) );
}

private void MediaReceiveCallback( string path )
{
if( string.IsNullOrEmpty( path ) )
path = null;

try
{
if( callback != null )
callback( path );
}
finally
{
Object.Destroy( callbackHelper.gameObject );
}
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
}
}
}
Expand Down
17 changes: 17 additions & 0 deletions Plugins/NativeCamera/Android/NCPermissionCallbackAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,22 @@ public void OnPermissionResult( int result )
}
}
}

public class NCPermissionCallbackAsyncAndroid : AndroidJavaProxy
{
private readonly NativeCamera.PermissionCallback callback;
private readonly NCCallbackHelper callbackHelper;

public NCPermissionCallbackAsyncAndroid( NativeCamera.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeCameraPermissionReceiver" )
{
this.callback = callback;
callbackHelper = new GameObject( "NCCallbackHelper" ).AddComponent<NCCallbackHelper>();
}

public void OnPermissionResult( int result )
{
callbackHelper.CallOnMainThread( () => callback( (NativeCamera.Permission) result ) );
}
}
}
#endif
31 changes: 29 additions & 2 deletions Plugins/NativeCamera/NativeCamera.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public enum PreferredCamera { Default = -1, Rear = 0, Front = 1 }
// EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };

public delegate void PermissionCallback( Permission permission );
public delegate void CameraCallback( string path );

#region Platform Specific Elements
Expand Down Expand Up @@ -90,7 +91,7 @@ private static AndroidJavaObject Context
private static extern int _NativeCamera_CheckPermission();

[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeCamera_RequestPermission();
private static extern int _NativeCamera_RequestPermission( int asyncMode );

[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeCamera_CanOpenSettings();
Expand Down Expand Up @@ -173,6 +174,10 @@ public static Permission CheckPermission( bool isPicturePermission )

public static Permission RequestPermission( bool isPicturePermission )
{
// Don't block the main thread if the permission is already granted
if( CheckPermission( isPicturePermission ) == Permission.Granted )
return Permission.Granted;

#if !UNITY_EDITOR && UNITY_ANDROID
object threadLock = new object();
lock( threadLock )
Expand All @@ -193,12 +198,34 @@ public static Permission RequestPermission( bool isPicturePermission )
return (Permission) nativeCallback.Result;
}
#elif !UNITY_EDITOR && UNITY_IOS
return (Permission) _NativeCamera_RequestPermission();
return (Permission) _NativeCamera_RequestPermission( 0 );
#else
return Permission.Granted;
#endif
}

public static void RequestPermissionAsync( PermissionCallback callback, bool isPicturePermission )
{
#if !UNITY_EDITOR && UNITY_ANDROID
NCPermissionCallbackAsyncAndroid nativeCallback = new NCPermissionCallbackAsyncAndroid( callback );
AJC.CallStatic( "RequestPermission", Context, nativeCallback, isPicturePermission, (int) Permission.ShouldAsk );
#elif !UNITY_EDITOR && UNITY_IOS
NCPermissionCallbackiOS.Initialize( callback );
_NativeCamera_RequestPermission( 1 );
#else
callback( Permission.Granted );
#endif
}

#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
public static Task<Permission> RequestPermissionAsync( bool isPicturePermission )
{
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), isPicturePermission );
return tcs.Task;
}
#endif

public static bool CanOpenSettings()
{
#if !UNITY_EDITOR && UNITY_IOS
Expand Down
7 changes: 6 additions & 1 deletion Plugins/NativeCamera/README.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
= Native Camera for Android & iOS (v1.3.9) =
= Native Camera for Android & iOS (v1.4.0) =

Online documentation & example code available at: https://github.com/yasirkula/UnityNativeCamera
E-mail: [email protected]
Expand Down Expand Up @@ -38,6 +38,7 @@ enum NativeCamera.Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
enum NativeCamera.Quality { Default = -1, Low = 0, Medium = 1, High = 2 };
enum NativeCamera.ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 }; // EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)

delegate void PermissionCallback( NativeCamera.Permission permission );
delegate void CameraCallback( string path );

//// Accessing Camera ////
Expand Down Expand Up @@ -66,6 +67,10 @@ bool NativeCamera.IsCameraBusy(); // returns true if the camera is currently ope
NativeCamera.Permission NativeCamera.CheckPermission( bool isPicturePermission );
NativeCamera.Permission NativeCamera.RequestPermission( bool isPicturePermission );

// Asynchronous variants of RequestPermission. Unlike RequestPermission, these functions don't freeze the app unnecessarily before the permission dialog is displayed. So it's recommended to call these functions instead
void NativeCamera.RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes );
Task<NativeCamera.Permission> NativeCamera.RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes );

// If permission state is Permission.Denied, user must grant the necessary permission(s) manually from the Settings (Android requires Storage and, if declared in AndroidManifest, Camera permissions; iOS requires Camera permission). These functions help you open the Settings directly from within the app
void NativeCamera.OpenSettings();
bool NativeCamera.CanOpenSettings();
Expand Down
34 changes: 34 additions & 0 deletions Plugins/NativeCamera/iOS/NCPermissionCallbackiOS.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#if UNITY_EDITOR || UNITY_IOS
using UnityEngine;

namespace NativeCameraNamespace
{
public class NCPermissionCallbackiOS : MonoBehaviour
{
private static NCPermissionCallbackiOS instance;
private NativeCamera.PermissionCallback callback;

public static void Initialize( NativeCamera.PermissionCallback callback )
{
if( instance == null )
{
instance = new GameObject( "NCPermissionCallbackiOS" ).AddComponent<NCPermissionCallbackiOS>();
DontDestroyOnLoad( instance.gameObject );
}
else if( instance.callback != null )
instance.callback( NativeCamera.Permission.ShouldAsk );

instance.callback = callback;
}

public void OnPermissionRequested( string message )
{
NativeCamera.PermissionCallback _callback = callback;
callback = null;

if( _callback != null )
_callback( (NativeCamera.Permission) int.Parse( message ) );
}
}
}
#endif
12 changes: 12 additions & 0 deletions Plugins/NativeCamera/iOS/NCPermissionCallbackiOS.cs.meta

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

48 changes: 34 additions & 14 deletions Plugins/NativeCamera/iOS/NativeCamera.mm
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

@interface UNativeCamera:NSObject
+ (int)checkPermission;
+ (int)requestPermission;
+ (int)requestPermission:(BOOL)asyncMode;
+ (int)canOpenSettings;
+ (void)openSettings;
+ (int)hasCamera;
Expand Down Expand Up @@ -55,7 +55,16 @@ + (int)checkPermission
}

// Credit: https://stackoverflow.com/a/20464727/2373034
+ (int)requestPermission
+ (int)requestPermission:(BOOL)asyncMode
{
int result = [self requestPermissionInternal:asyncMode];
if( asyncMode && result >= 0 ) // Result returned immediately, forward it
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", [self getCString:[NSString stringWithFormat:@"%d", result]] );

return result;
}

+ (int)requestPermissionInternal:(BOOL)asyncMode
{
if( CHECK_IOS_VERSION( @"7.0" ) )
{
Expand All @@ -64,17 +73,28 @@ + (int)requestPermission
return 1;
else if( status == AVAuthorizationStatusNotDetermined )
{
__block BOOL authorized = NO;

dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
if( asyncMode )
{
authorized = granted;
dispatch_semaphore_signal( sema );
}];
dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );

return authorized ? 1 : 0;
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
{
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", granted ? "1" : "0" );
}];

return -1;
}
else
{
__block BOOL authorized = NO;
dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
{
authorized = granted;
dispatch_semaphore_signal( sema );
}];
dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );

return authorized ? 1 : 0;
}
}
else
return 0;
Expand Down Expand Up @@ -552,9 +572,9 @@ + (char *)getCString:(NSString *)source
return [UNativeCamera checkPermission];
}

extern "C" int _NativeCamera_RequestPermission()
extern "C" int _NativeCamera_RequestPermission( int asyncMode )
{
return [UNativeCamera requestPermission];
return [UNativeCamera requestPermission:( asyncMode == 1 )];
}

extern "C" int _NativeCamera_CanOpenSettings()
Expand Down
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.nativecamera",
"displayName": "Native Camera",
"version": "1.3.9",
"version": "1.4.0",
"documentationUrl": "https://github.com/yasirkula/UnityNativeCamera",
"changelogUrl": "https://github.com/yasirkula/UnityNativeCamera/releases",
"licensesUrl": "https://github.com/yasirkula/UnityNativeCamera/blob/master/LICENSE.txt",
Expand Down

0 comments on commit 9c0a17d

Please sign in to comment.