Unity/Tips

Unity manifest 위치, 수정, 권한 요청

최애뎡 2021. 6. 1. 22:52
728x90
반응형

먼저

https://docs.unity3d.com/kr/2020.3/Manual/android-manifest.html

 

Android 매니페스트 - Unity 매뉴얼

Android 매니페스트는 Android 앱에 대한 중요한 메타 데이터가 포함된 XML 파일입니다. 여기에는 패키지 이름, 액티비티 이름, 메인 작업(앱 엔트리 포인트), Android 버전 지원, 하드웨어 기능 지원, 권

docs.unity3d.com

https://docs.unity3d.com/kr/2020.3/Manual/android-RequestingPermissions.html

 

권한 요청 - Unity 매뉴얼

Android 6(API 레벨 23) 이상에서는 Android.Permission API를 사용하여 일반적으로 필요한 시스템 기능(예: 카메라, 마이크, 위치 정보)을 사용하기 위한 권한을 애플리케이션이 시동될 때가 아닌 필요한

docs.unity3d.com

를 참고해야 한다. (2020.3.4f로 작업 중)

 

* 항상 여러 블로그에서 올라온 글은 그 당시에 작업한 내용이기 때문에 그것을 그대로 해서 안된다 된다 를 판단하지 말고 Unity Documentation에서 지금 본인이 쓰는 버전에 맞는 내용을 검색해서 읽어 보는 것이 가장 효율적이다.

 

이번에 Unity에서 webcamtexture을 사용하면서 여러 작업들을 하다 보니 android에서 저장 권한과 카메라 권한을 요청하는 작업이 필요했다.  -> 첫 실행 시 한 번은 물어보긴 하는데 그 뒤로는 따로 요청이 안뜨기 때문에

 

일단 manifest가 생성되는 위치가 예전에 비해 상당히 많이 바뀌었다.

-> 보통 \Temp\StagingArea\Android에 manifest가 있었는데 doc를 참조하니 

-> \Temp\gradleOut\unityLibrary\src\main에 들어가야 볼 수 있다.

 

 

찾은 manifest를 Assets -> Plugins -> Android [없음 만들면 됨]에 복사하고 manifest에서 추가해야 할 게 있음 추가하고 빌드하면 된다.

 

첫 앱이 구동될 때 필요한 권한들이 있으면 유니티에서 권한을 요청하게 되는데 이를 막기 위해

<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />

만 xml에 추가하였다.

 

위와 같은 코드들은 doc에 정말 친절하게 잘 나와있다.  (사실 블로그는 늘 참고용이고 정확한 내용을 찾아야 한다.)

 

예로 권한을 요청하기 위해 

if (!Permission.HasUserAuthorizedPermission(Permission.Camera) // Camera에 대한 권한이 있는지 확인

뭐 요런 식으로 일단 카메라에 대한 권한이 있는지 없는지 확인하고

권한이 없을 시에 

Permission.RequestUserPermission(Permission.Camera) // Camera에 대한 권한을 요청

요런 식으로 권한을 요청하면 된다. (팝업이 뜸)

 

아 그리고 이렇게 요청하면 팝업창을 띄어주는데 팝업이 뜨면 Focus가 나가게 된다.

그래서!

구글링 중 본 코드인데

yield return new WaitForSeconds(0.2f);

yield return new WaitUntil(() => Application.isFocused == true);

이런 식으로 coroutine안에서 0.2초 후에 포커스가 다시 돌아올 때까지 기다렸다가 돌아오면 다음 코드를 실행한다.

이게 참 중요한 게 권한 요청 팝업을 띄우고 포커스가 나갔을 때 승인을 체크하는 과정을 기다렸다가 다시 앱으로 돌아와서 다음 코드를 실행시켜주는 것

-> 즉 이 뒤에 다시 한번 권한에 대한 승인이 났는지를 판별하여 뭐 여러 작업을 진행할 수 있다.

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.IO;
using UnityEngine.Android;
using System;

public class CanvasMenuManger : MonoBehaviour
{
    public FaceAnalyzerExample.FaceAnalyzerExample FAE;
    
    [Header("ScreenShot")]
    public Camera camera_;
    private int resWidth;
    private int resHeight;
    string path;
    string path_Save;
    Texture2D screenShot;
    public GameObject CapturePanel;
    public RawImage CaptureTexture;
    private byte[] bytes;
    public GameObject Popup;
    public Text Popup_Txt;

    [Header("Bottom Menu Control")]
    public GameObject closeMenu;
    public RectTransform Obj_BottomMenu;
    [SerializeField] bool bool_MenuToggle = false;

    private bool onCheck = false;
    private int cameraCheck = 0;

    private void Start()
    {
        if (Application.platform == RuntimePlatform.Android)
        {
            //<meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
            StartCoroutine("CameraPermissionCheckCoroutine");
        }

        resWidth = Screen.width;
        resHeight = Screen.height;

        if (Application.platform == RuntimePlatform.WindowsEditor) path = "C:/Users/ChoiDaeYoung/Pictures/";
        if (Application.platform == RuntimePlatform.Android) path = Application.persistentDataPath + "/ 디렉토리 경로 /";

        Debug.Log(path);
    }

    #region ScreenShot
    public void ClickScreenShot()
    {
        Debug.Log("ClickScreenShot");

        if (Application.platform == RuntimePlatform.WindowsEditor) ScreenShot();
        if (Application.platform == RuntimePlatform.Android)
        {
            if (!onCheck) StartCoroutine("WritePermissionCheckCoroutine");
        }
    }

    void ScreenShot()
    {
        Debug.Log("DoScreenshot");
        if (!Directory.Exists(path)) Directory.CreateDirectory(path);
        RenderTexture rt = new RenderTexture(resWidth, resHeight, 24);
        camera_.targetTexture = rt;
        screenShot = new Texture2D(resWidth, resHeight, TextureFormat.RGB24, false);
        Rect rec = new Rect(0, 0, screenShot.width, screenShot.height);
        camera_.Render();
        RenderTexture.active = rt;
        screenShot.ReadPixels(new Rect(0, 0, resWidth, resHeight), 0, 0);
        screenShot.Apply();

        bytes = screenShot.EncodeToJPG();
        path_Save = path + System.DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss") + ".jpg";

        CaptureTexture.texture = screenShot;
        CapturePanel.SetActive(true);
    }

    public void StoreScreenBtnClicked()
    {
        File.WriteAllBytes(path_Save, bytes);

        if (Application.platform == RuntimePlatform.Android)
        {
            AndroidJavaClass classPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
            AndroidJavaObject objActivity = classPlayer.GetStatic<AndroidJavaObject>("currentActivity");
            AndroidJavaClass classUri = new AndroidJavaClass("android.net.Uri");
            AndroidJavaObject objIntent = new AndroidJavaObject("android.content.Intent", new object[2] { "android.intent.action.MEDIA_SCANNER_SCAN_FILE", classUri.CallStatic<AndroidJavaObject>("parse", "file://" + path) });
            objActivity.Call("sendBroadcast", objIntent);
        }
        
        Destroy(screenShot);
        CapturePanel.SetActive(false);
    }

    public void BackBtnClicked() => CapturePanel.SetActive(false);

    IEnumerator WritePermissionCheckCoroutine()
    {
        Debug.Log("WritePermission_couroutine");
        onCheck = true;

        yield return new WaitForEndOfFrame();
        if (Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite) == false)
        {
            Permission.RequestUserPermission(Permission.ExternalStorageWrite);

            yield return new WaitForSeconds(0.2f); // 0.2초의 딜레이 후 focus를 체크하자.
            yield return new WaitUntil(() => Application.isFocused == true); // 팝업창이 뜨면서 포커스가 false가 돼기 때문에 팝업창에서 작업을 완료한 뒤 포커스가 다시 돌아온것을 확인

            if (Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite) == false)
            {
                Popup_Txt.text = "단말기의 \"설정 > 어플리케이션 > 권한 > 저장공간\"을 ON으로 설정해주세요.";
                Popup.SetActive(true);                
                onCheck = false;
                yield break;
            }
        }

        ScreenShot();
        onCheck = false;
    }

    IEnumerator CameraPermissionCheckCoroutine()
    {
        Debug.Log("CameraPermission_couroutine");

        yield return new WaitForEndOfFrame();
        if (Permission.HasUserAuthorizedPermission(Permission.Camera) == false)
        {            
            if (cameraCheck == 0) Permission.RequestUserPermission(Permission.Camera);
            else
            {
                Popup_Txt.text = "카메라를 사용할 수 없습니다. 단말기의 \"설정 > 어플리케이션 > 권한 > 카메라\"를 ON으로 설정해주세요.";
                if (!Popup.activeInHierarchy) Popup.SetActive(true);
            }
            ++cameraCheck;

            yield return new WaitForSeconds(0.2f); 
            yield return new WaitUntil(() => Application.isFocused == true);

            if (Permission.HasUserAuthorizedPermission(Permission.Camera) == false)
            {
                StartCoroutine("CameraPermissionCheckCoroutine");
                yield break;
            }
        }

        Popup.SetActive(false);
        FAE.Run();
    }

    private void OpenAppSetting() //어플리케이션 설정 창
    {
        try
        {
#if UNITY_ANDROID
            using (var unityClass = new AndroidJavaClass("com.unity3d.player.UnityPlayer"))
            using (AndroidJavaObject currentActivityObject = unityClass.GetStatic<AndroidJavaObject>("currentActivity"))
            {
                string packageName = currentActivityObject.Call<string>("getPackageName");

                using (var uriClass = new AndroidJavaClass("android.net.Uri"))
                using (AndroidJavaObject uriObject = uriClass.CallStatic<AndroidJavaObject>("fromParts", "package", packageName, null))
                using (var intentObject = new AndroidJavaObject("android.content.Intent", "android.settings.APPLICATION_DETAILS_SETTINGS", uriObject))
                {
                    intentObject.Call<AndroidJavaObject>("addCategory", "android.intent.category.DEFAULT");
                    intentObject.Call<AndroidJavaObject>("setFlags", 0x10000000);
                    currentActivityObject.Call("startActivity", intentObject);
                }
            }
#endif
        }
        catch (Exception ex)
        {
            Debug.LogException(ex);
        }
    }

    public void BtnClickedPopup() => OpenAppSetting();    
    #endregion

    #region BottomMenu
    public void BottomMenuToggle()
    {
        if (bool_MenuToggle = !bool_MenuToggle) MenuUp();
        else MenuDown();
    }

    public void MenuUp()
    {
        iTween.ValueTo(Obj_BottomMenu.gameObject, iTween.Hash(
            "from", Obj_BottomMenu.anchoredPosition,
            "to", new Vector2(0, 0),
            "easeType", "easeOutExpo",
            "onupdatetarget", this.gameObject,
            "onupdate", "MoveGuiElement"));
        closeMenu.SetActive(true);
    }

    public void MenuDown()
    {
        closeMenu.SetActive(false);
        iTween.ValueTo(Obj_BottomMenu.gameObject, iTween.Hash(
            "from", Obj_BottomMenu.anchoredPosition,
            "to", new Vector2(0, -250),
            "easeType", "easeOutExpo",
            "onupdatetarget", this.gameObject,
            "onupdate", "MoveGuiElement"));
    }

    public void MoveGuiElement(Vector2 position)
    {
        Obj_BottomMenu.anchoredPosition = position;
    }
    #endregion
}
반응형