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 12, 2023
1 parent 1283f87 commit fd69fa2
Show file tree
Hide file tree
Showing 10 changed files with 232 additions and 98 deletions.
13 changes: 13 additions & 0 deletions .github/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,11 @@ There are two functions to handle permissions with this plugin:

`NativeGallery.Permission NativeGallery.RequestPermission( PermissionType permissionType, MediaType mediaTypes )`: requests permission to access Gallery/Photos 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 the SaveImageToGallery/SaveVideoToGallery and GetImageFromGallery/GetVideoFromGallery functions call RequestPermission internally and execute only if the permission is granted (the result of RequestPermission is also returned).

`void NativeGallery.RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes )`: 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 `NativeGallery.Permission permission` parameter

`Task<NativeGallery.Permission> NativeGallery.RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes )`: Another asynchronous variant of *RequestPermission* (requires Unity 2018.4 or later).

`NativeGallery.OpenSettings()`: opens the settings for this app, from where the user can manually grant permission in case current permission state is *Permission.Denied* (on Android, the necessary permission is named *Storage* and on iOS, the necessary permission is named *Photos*).

`bool NativeGallery.CanOpenSettings()`: on iOS versions prior to 8.0, opening settings from within app is not possible and in this case, this function returns *false*. Otherwise, it returns *true*.
Expand Down Expand Up @@ -197,6 +202,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 NativeGallery functions
private async void RequestPermissionAsynchronously( NativeGallery.PermissionType permissionType, NativeGallery.MediaType mediaTypes )
{
NativeGallery.Permission permission = await NativeGallery.RequestPermissionAsync( permissionType, mediaTypes );
Debug.Log( "Permission result: " + permission );
}

private IEnumerator TakeScreenshotAndSave()
{
yield return new WaitForEndOfFrame();
Expand Down
13 changes: 10 additions & 3 deletions Plugins/NativeGallery/Android/NGCallbackHelper.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
36 changes: 2 additions & 34 deletions Plugins/NativeGallery/Android/NGMediaReceiveCallbackAndroid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public NGMediaReceiveCallbackAndroid( NativeGallery.MediaPickCallback callback,

public void OnMediaReceived( string path )
{
callbackHelper.CallOnMainThread( () => MediaReceiveCallback( path ) );
callbackHelper.CallOnMainThread( () => callback( !string.IsNullOrEmpty( path ) ? path : null ) );
}

public void OnMultipleMediaReceived( string paths )
Expand Down Expand Up @@ -53,39 +53,7 @@ public void OnMultipleMediaReceived( string paths )
result = pathsSplit;
}

callbackHelper.CallOnMainThread( () => MediaReceiveMultipleCallback( result ) );
}

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

try
{
if( callback != null )
callback( path );
}
finally
{
Object.Destroy( callbackHelper.gameObject );
}
}

private void MediaReceiveMultipleCallback( string[] paths )
{
if( paths != null && paths.Length == 0 )
paths = null;

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

public class NGPermissionCallbackAsyncAndroid : AndroidJavaProxy
{
private readonly NativeGallery.PermissionCallback callback;
private readonly NGCallbackHelper callbackHelper;

public NGPermissionCallbackAsyncAndroid( NativeGallery.PermissionCallback callback ) : base( "com.yasirkula.unity.NativeGalleryPermissionReceiver" )
{
this.callback = callback;
callbackHelper = new GameObject( "NGCallbackHelper" ).AddComponent<NGCallbackHelper>();
}

public void OnPermissionResult( int result )
{
callbackHelper.CallOnMainThread( () => callback( (NativeGallery.Permission) result ) );
}
}
}
#endif
43 changes: 36 additions & 7 deletions Plugins/NativeGallery/NativeGallery.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public enum MediaType { Image = 1, Video = 2, Audio = 4 };
// 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 MediaSaveCallback( bool success, string path );
public delegate void MediaPickCallback( string path );
public delegate void MediaPickMultipleCallback( string[] paths );
Expand Down Expand Up @@ -94,7 +95,7 @@ private static AndroidJavaObject Context
private static extern int _NativeGallery_CheckPermission( int readPermission, int permissionFreeMode );

[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern int _NativeGallery_RequestPermission( int readPermission, int permissionFreeMode );
private static extern int _NativeGallery_RequestPermission( int readPermission, int permissionFreeMode, int asyncMode );

[System.Runtime.InteropServices.DllImport( "__Internal" )]
private static extern void _NativeGallery_ShowLimitedLibraryPicker();
Expand Down Expand Up @@ -186,16 +187,18 @@ public static Permission CheckPermission( PermissionType permissionType, MediaTy

return result;
#elif !UNITY_EDITOR && UNITY_IOS
// result == 3: LimitedAccess permission on iOS, no need to handle it when PermissionFreeMode is set to true
int result = _NativeGallery_CheckPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 );
return result == 3 ? Permission.Granted : (Permission) result;
return ProcessPermission( (Permission) _NativeGallery_CheckPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 ) );
#else
return Permission.Granted;
#endif
}

public static Permission RequestPermission( PermissionType permissionType, MediaType mediaTypes )
{
// Don't block the main thread if the permission is already granted
if( CheckPermission( permissionType, mediaTypes ) == Permission.Granted )
return Permission.Granted;

#if !UNITY_EDITOR && UNITY_ANDROID
object threadLock = new object();
lock( threadLock )
Expand All @@ -216,14 +219,40 @@ public static Permission RequestPermission( PermissionType permissionType, Media
return (Permission) nativeCallback.Result;
}
#elif !UNITY_EDITOR && UNITY_IOS
// result == 3: LimitedAccess permission on iOS, no need to handle it when PermissionFreeMode is set to true
int result = _NativeGallery_RequestPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0 );
return result == 3 ? Permission.Granted : (Permission) result;
return ProcessPermission( (Permission) _NativeGallery_RequestPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0, 0 ) );
#else
return Permission.Granted;
#endif
}

public static void RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes )
{
#if !UNITY_EDITOR && UNITY_ANDROID
NGPermissionCallbackAsyncAndroid nativeCallback = new NGPermissionCallbackAsyncAndroid( callback );
AJC.CallStatic( "RequestPermission", Context, nativeCallback, permissionType == PermissionType.Read, (int) mediaTypes, (int) Permission.ShouldAsk );
#elif !UNITY_EDITOR && UNITY_IOS
NGPermissionCallbackiOS.Initialize( ( result ) => callback( ProcessPermission( result ) ) );
_NativeGallery_RequestPermission( permissionType == PermissionType.Read ? 1 : 0, PermissionFreeMode ? 1 : 0, 1 );
#else
callback( Permission.Granted );
#endif
}

#if UNITY_2018_4_OR_NEWER && !NATIVE_GALLERY_DISABLE_ASYNC_FUNCTIONS
public static Task<Permission> RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes )
{
TaskCompletionSource<Permission> tcs = new TaskCompletionSource<Permission>();
RequestPermissionAsync( ( permission ) => tcs.SetResult( permission ), permissionType, mediaTypes );
return tcs.Task;
}
#endif

private static Permission ProcessPermission( Permission permission )
{
// result == 3: LimitedAccess permission on iOS, no need to handle it when PermissionFreeMode is set to true
return ( PermissionFreeMode && (int) permission == 3 ) ? Permission.Granted : permission;
}

// This function isn't needed when PermissionFreeMode is set to true
private static void TryExtendLimitedAccessPermission()
{
Expand Down
7 changes: 6 additions & 1 deletion Plugins/NativeGallery/README.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
= Native Gallery for Android & iOS (v1.7.3) =
= Native Gallery for Android & iOS (v1.7.4) =

Online documentation & example code available at: https://github.com/yasirkula/UnityNativeGallery
E-mail: [email protected]
Expand Down Expand Up @@ -57,6 +57,7 @@ enum NativeGallery.Permission { Denied = 0, Granted = 1, ShouldAsk = 2 };
enum NativeGallery.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)
enum MediaType { Image = 1, Video = 2, Audio = 4 };

delegate void PermissionCallback( NativeGallery.Permission permission );
delegate void MediaSaveCallback( bool success, string path );
delegate void NativeGallery.MediaPickCallback( string path );
delegate void MediaPickMultipleCallback( string[] paths );
Expand Down Expand Up @@ -113,6 +114,10 @@ bool NativeGallery.IsMediaPickerBusy();
NativeGallery.Permission NativeGallery.CheckPermission( PermissionType permissionType, MediaType mediaTypes );
NativeGallery.Permission NativeGallery.RequestPermission( PermissionType permissionType, MediaType mediaTypes );

// 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 NativeGallery.RequestPermissionAsync( PermissionCallback callback, PermissionType permissionType, MediaType mediaTypes );
Task<NativeGallery.Permission> NativeGallery.RequestPermissionAsync( PermissionType permissionType, MediaType mediaTypes );

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

namespace NativeGalleryNamespace
{
public class NGPermissionCallbackiOS : MonoBehaviour
{
private static NGPermissionCallbackiOS instance;
private NativeGallery.PermissionCallback callback;

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

instance.callback = callback;
}

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

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

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

Loading

0 comments on commit fd69fa2

Please sign in to comment.