Skip to content

Commit 9c0a17d

Browse files
committed
Added async variants of RequestPermission function that don't freeze the app unnecessarily
1 parent 9c6272a commit 9c0a17d

File tree

10 files changed

+157
-38
lines changed

10 files changed

+157
-38
lines changed

.github/README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,11 @@ Beginning with *6.0 Marshmallow*, Android apps must request runtime permissions
8282

8383
`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).
8484

85+
`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.
86+
- **PermissionCallback** takes `NativeCamera.Permission permission` parameter
87+
88+
`Task<NativeCamera.Permission> NativeCamera.RequestPermissionAsync( bool isPicturePermission )`: Another asynchronous variant of *RequestPermission* (requires Unity 2018.4 or later).
89+
8590
`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).
8691

8792
`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*.
@@ -137,6 +142,14 @@ void Update()
137142
}
138143
}
139144

145+
// Example code doesn't use this function but it is here for reference. It's recommended to ask for permissions manually using the
146+
// RequestPermissionAsync methods prior to calling NativeCamera functions
147+
private async void RequestPermissionAsynchronously( bool isPicturePermission )
148+
{
149+
NativeCamera.Permission permission = await NativeCamera.RequestPermissionAsync( isPicturePermission );
150+
Debug.Log( "Permission result: " + permission );
151+
}
152+
140153
private void TakePicture( int maxSize )
141154
{
142155
NativeCamera.Permission permission = NativeCamera.TakePicture( ( path ) =>

Plugins/NativeCamera/Android/NCCallbackHelper.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,16 @@ private void Update()
1616
{
1717
if( mainThreadAction != null )
1818
{
19-
System.Action temp = mainThreadAction;
20-
mainThreadAction = null;
21-
temp();
19+
try
20+
{
21+
System.Action temp = mainThreadAction;
22+
mainThreadAction = null;
23+
temp();
24+
}
25+
finally
26+
{
27+
Destroy( gameObject );
28+
}
2229
}
2330
}
2431

Plugins/NativeCamera/Android/NCCameraCallbackAndroid.cs

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,7 @@ public NCCameraCallbackAndroid( NativeCamera.CameraCallback callback ) : base( "
1616

1717
public void OnMediaReceived( string path )
1818
{
19-
callbackHelper.CallOnMainThread( () => MediaReceiveCallback( path ) );
20-
}
21-
22-
private void MediaReceiveCallback( string path )
23-
{
24-
if( string.IsNullOrEmpty( path ) )
25-
path = null;
26-
27-
try
28-
{
29-
if( callback != null )
30-
callback( path );
31-
}
32-
finally
33-
{
34-
Object.Destroy( callbackHelper.gameObject );
35-
}
19+
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
3620
}
3721
}
3822
}

Plugins/NativeCamera/Android/NCPermissionCallbackAndroid.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,22 @@ public void OnPermissionResult( int result )
2525
}
2626
}
2727
}
28+
29+
public class NCPermissionCallbackAsyncAndroid : AndroidJavaProxy
30+
{
31+
private readonly NativeCamera.PermissionCallback callback;
32+
private readonly NCCallbackHelper callbackHelper;
33+
34+
public NCPermissionCallbackAsyncAndroid( NativeCamera.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeCameraPermissionReceiver" )
35+
{
36+
this.callback = callback;
37+
callbackHelper = new GameObject( "NCCallbackHelper" ).AddComponent<NCCallbackHelper>();
38+
}
39+
40+
public void OnPermissionResult( int result )
41+
{
42+
callbackHelper.CallOnMainThread( () => callback( (NativeCamera.Permission) result ) );
43+
}
44+
}
2845
}
2946
#endif

Plugins/NativeCamera/NativeCamera.cs

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public enum PreferredCamera { Default = -1, Rear = 0, Front = 1 }
5353
// EXIF orientation: http://sylvana.net/jpegcrop/exif_orientation.html (indices are reordered)
5454
public enum ImageOrientation { Unknown = -1, Normal = 0, Rotate90 = 1, Rotate180 = 2, Rotate270 = 3, FlipHorizontal = 4, Transpose = 5, FlipVertical = 6, Transverse = 7 };
5555

56+
public delegate void PermissionCallback( Permission permission );
5657
public delegate void CameraCallback( string path );
5758

5859
#region Platform Specific Elements
@@ -90,7 +91,7 @@ private static AndroidJavaObject Context
9091
private static extern int _NativeCamera_CheckPermission();
9192

9293
[System.Runtime.InteropServices.DllImport( "__Internal" )]
93-
private static extern int _NativeCamera_RequestPermission();
94+
private static extern int _NativeCamera_RequestPermission( int asyncMode );
9495

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

174175
public static Permission RequestPermission( bool isPicturePermission )
175176
{
177+
// Don't block the main thread if the permission is already granted
178+
if( CheckPermission( isPicturePermission ) == Permission.Granted )
179+
return Permission.Granted;
180+
176181
#if !UNITY_EDITOR && UNITY_ANDROID
177182
object threadLock = new object();
178183
lock( threadLock )
@@ -193,12 +198,34 @@ public static Permission RequestPermission( bool isPicturePermission )
193198
return (Permission) nativeCallback.Result;
194199
}
195200
#elif !UNITY_EDITOR && UNITY_IOS
196-
return (Permission) _NativeCamera_RequestPermission();
201+
return (Permission) _NativeCamera_RequestPermission( 0 );
197202
#else
198203
return Permission.Granted;
199204
#endif
200205
}
201206

207+
public static void RequestPermissionAsync( PermissionCallback callback, bool isPicturePermission )
208+
{
209+
#if !UNITY_EDITOR && UNITY_ANDROID
210+
NCPermissionCallbackAsyncAndroid nativeCallback = new NCPermissionCallbackAsyncAndroid( callback );
211+
AJC.CallStatic( "RequestPermission", Context, nativeCallback, isPicturePermission, (int) Permission.ShouldAsk );
212+
#elif !UNITY_EDITOR && UNITY_IOS
213+
NCPermissionCallbackiOS.Initialize( callback );
214+
_NativeCamera_RequestPermission( 1 );
215+
#else
216+
callback( Permission.Granted );
217+
#endif
218+
}
219+
220+
#if UNITY_2018_4_OR_NEWER && !NATIVE_CAMERA_DISABLE_ASYNC_FUNCTIONS
221+
public static Task<Permission> RequestPermissionAsync( bool isPicturePermission )
222+
{
223+
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
224+
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), isPicturePermission );
225+
return tcs.Task;
226+
}
227+
#endif
228+
202229
public static bool CanOpenSettings()
203230
{
204231
#if !UNITY_EDITOR && UNITY_IOS

Plugins/NativeCamera/README.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
= Native Camera for Android & iOS (v1.3.9) =
1+
= Native Camera for Android & iOS (v1.4.0) =
22

33
Online documentation & example code available at: https://github.com/yasirkula/UnityNativeCamera
44
E-mail: yasirkula@gmail.com
@@ -38,6 +38,7 @@ enum NativeCamera.Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
3838
enum NativeCamera.Quality { Default = -1, Low = 0, Medium = 1, High = 2 };
3939
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)
4040

41+
delegate void PermissionCallback( NativeCamera.Permission permission );
4142
delegate void CameraCallback( string path );
4243

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

70+
// 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
71+
void NativeCamera.RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes );
72+
Task<NativeCamera.Permission> NativeCamera.RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes );
73+
6974
// 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
7075
void NativeCamera.OpenSettings();
7176
bool NativeCamera.CanOpenSettings();
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
#if UNITY_EDITOR || UNITY_IOS
2+
using UnityEngine;
3+
4+
namespace NativeCameraNamespace
5+
{
6+
public class NCPermissionCallbackiOS : MonoBehaviour
7+
{
8+
private static NCPermissionCallbackiOS instance;
9+
private NativeCamera.PermissionCallback callback;
10+
11+
public static void Initialize( NativeCamera.PermissionCallback callback )
12+
{
13+
if( instance == null )
14+
{
15+
instance = new GameObject( "NCPermissionCallbackiOS" ).AddComponent<NCPermissionCallbackiOS>();
16+
DontDestroyOnLoad( instance.gameObject );
17+
}
18+
else if( instance.callback != null )
19+
instance.callback( NativeCamera.Permission.ShouldAsk );
20+
21+
instance.callback = callback;
22+
}
23+
24+
public void OnPermissionRequested( string message )
25+
{
26+
NativeCamera.PermissionCallback _callback = callback;
27+
callback = null;
28+
29+
if( _callback != null )
30+
_callback( (NativeCamera.Permission) int.Parse( message ) );
31+
}
32+
}
33+
}
34+
#endif

Plugins/NativeCamera/iOS/NCPermissionCallbackiOS.cs.meta

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Plugins/NativeCamera/iOS/NativeCamera.mm

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
@interface UNativeCamera:NSObject
1616
+ (int)checkPermission;
17-
+ (int)requestPermission;
17+
+ (int)requestPermission:(BOOL)asyncMode;
1818
+ (int)canOpenSettings;
1919
+ (void)openSettings;
2020
+ (int)hasCamera;
@@ -55,7 +55,16 @@ + (int)checkPermission
5555
}
5656

5757
// Credit: https://stackoverflow.com/a/20464727/2373034
58-
+ (int)requestPermission
58+
+ (int)requestPermission:(BOOL)asyncMode
59+
{
60+
int result = [self requestPermissionInternal:asyncMode];
61+
if( asyncMode && result >= 0 ) // Result returned immediately, forward it
62+
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", [self getCString:[NSString stringWithFormat:@"%d", result]] );
63+
64+
return result;
65+
}
66+
67+
+ (int)requestPermissionInternal:(BOOL)asyncMode
5968
{
6069
if( CHECK_IOS_VERSION( @"7.0" ) )
6170
{
@@ -64,17 +73,28 @@ + (int)requestPermission
6473
return 1;
6574
else if( status == AVAuthorizationStatusNotDetermined )
6675
{
67-
__block BOOL authorized = NO;
68-
69-
dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
70-
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
76+
if( asyncMode )
7177
{
72-
authorized = granted;
73-
dispatch_semaphore_signal( sema );
74-
}];
75-
dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );
76-
77-
return authorized ? 1 : 0;
78+
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
79+
{
80+
UnitySendMessage( "NCPermissionCallbackiOS", "OnPermissionRequested", granted ? "1" : "0" );
81+
}];
82+
83+
return -1;
84+
}
85+
else
86+
{
87+
__block BOOL authorized = NO;
88+
dispatch_semaphore_t sema = dispatch_semaphore_create( 0 );
89+
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^( BOOL granted )
90+
{
91+
authorized = granted;
92+
dispatch_semaphore_signal( sema );
93+
}];
94+
dispatch_semaphore_wait( sema, DISPATCH_TIME_FOREVER );
95+
96+
return authorized ? 1 : 0;
97+
}
7898
}
7999
else
80100
return 0;
@@ -552,9 +572,9 @@ + (char *)getCString:(NSString *)source
552572
return [UNativeCamera checkPermission];
553573
}
554574

555-
extern "C" int _NativeCamera_RequestPermission()
575+
extern "C" int _NativeCamera_RequestPermission( int asyncMode )
556576
{
557-
return [UNativeCamera requestPermission];
577+
return [UNativeCamera requestPermission:( asyncMode == 1 )];
558578
}
559579

560580
extern "C" int _NativeCamera_CanOpenSettings()

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "com.yasirkula.nativecamera",
33
"displayName": "Native Camera",
4-
"version": "1.3.9",
4+
"version": "1.4.0",
55
"documentationUrl": "https://github.com/yasirkula/UnityNativeCamera",
66
"changelogUrl": "https://github.com/yasirkula/UnityNativeCamera/releases",
77
"licensesUrl": "https://github.com/yasirkula/UnityNativeCamera/blob/master/LICENSE.txt",

0 commit comments

Comments
 (0)