Implementing Android 6.0 permissions in unity3d

2019-02-02 17:41发布

I've installed Android Support Library but in the developer.android site says that for implementing it on my project I need to edit my build.gradle file that I don't have because it's an Unity project.

I've created a build.gradle file copying the content of this website: http://gradleplease.appspot.com/ and I put that file on the root of my Unity project but when I try to use the library it does not work

if (ContextCompat.checkSelfPermission(thisActivity,
                                      Manifest.permission.READ_CONTACTS)
    != PackageManager.PERMISSION_GRANTED) {

    // Should we show an explanation?
    if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
                                                            Manifest.permission.READ_CONTACTS)) {

        // Show an expanation to the user *asynchronously* -- don't block
        // this thread waiting for the user's response! After the user
        // sees the explanation, try again to request the permission.

    } else {

        // No explanation needed, we can request the permission.

        ActivityCompat.requestPermissions(thisActivity,
                                          new String[]{Manifest.permission.READ_CONTACTS},
        MY_PERMISSIONS_REQUEST_READ_CONTACTS);

        // MY_PERMISSIONS_REQUEST_READ_CONTACTS is an
        // app-defined int constant. The callback method gets the
        // result of the request.
    }
}

6条回答
等我变得足够好
2楼-- · 2019-02-02 17:57

In addition to Jason Knight's post (which I used for my own Unity plugin for handling runtime permissions):

I used Android studio for creating a plugin. I followed the directions on the following site and worked perfectly: http://www.thegamecontriver.com/2015/04/android-plugin-unity-android-studio.html

I also added another method using the shouldShowRequestPermissionRationale() function so I'm able to hide certain UI elements if the user denied the permission and checked the "Don't ask again" checkbox.

查看更多
手持菜刀,她持情操
3楼-- · 2019-02-02 18:01

The other answers (especially Jason Knight's) have been super helpful to me, but I had to adjust the code to get it to work, so I'm sharing those changes here.

As noted in the comments, that code has this error in Android Studio: final Fragment request = new Fragment(); part saying "Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static"

Now I'm not a Java expert so maybe I did things wrong, but I tried to adjust things as explained here: Fragments should be static such that they can be re-instantiated by the system, and anonymous classes are not static

Mostly, I split the Fragment into a new class, so that it's not an anonymous class. So now there are two Java files:

package com.synapse.unityplugins;

import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;

import com.unity3d.player.UnityPlayer;

public class PermissionGranter {
    public final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "SynapsePlugin_listener";
    public final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
    public final static String PERMISSION_GRANTED = "PERMISSION_GRANTED";
    public final static String PERMISSION_DENIED = "PERMISSION_DENIED";

    // only implemented WRITE_EXTERNAL_STORAGE so far
    public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
    {
        switch (permissionEnum) {
            case 0:
                return Manifest.permission.WRITE_EXTERNAL_STORAGE;
            // "and the rest is still unwritten" - Natasha Bedingfield
        }
        Log.e("PermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
        throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
    }

    public static void grantPermission(int permissionEnum)
    {
        final Activity act = UnityPlayer.currentActivity;
        Log.i("PermissionGranter","grantPermission " + permissionEnum) ;
        if (Build.VERSION.SDK_INT < 23) {
            Log.i("PermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
            return;
        }

        try {
            final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
            if (act.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
                Log.i("PermissionGranter", "already granted");
                UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                return;
            }

            final Fragment request = PermissionRequestFragment.newInstance(permissionEnum);
            final FragmentManager fragmentManager = act.getFragmentManager();
            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(0, request);
            fragmentTransaction.commit();
        }
        catch(Exception error)
        {
            Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
        }
    }
}

That's the main plugin class, and here's the fragment:

package com.synapse.unityplugins;

import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;

import com.unity3d.player.UnityPlayer;

public class PermissionRequestFragment extends Fragment {
    public static PermissionRequestFragment newInstance(int permissionEnum) {
        PermissionRequestFragment frag = new PermissionRequestFragment();
        Bundle args = new Bundle();
        args.putInt("requested", permissionEnum);
        frag.setArguments(args);
        return frag;
    }

    @Override
    public void onStart() {
        super.onStart();
        int permissionEnum = getArguments().getInt("requested");
        final int PERMISSIONS_REQUEST_CODE = permissionEnum;

        try {
            final String permissionFromEnumInt = PermissionGranter.getPermissionStringFromEnumInt(permissionEnum);
            String[] permissionsToRequest = new String[]{permissionFromEnumInt};
            Log.i("PermissionGranter", "fragment start " + permissionsToRequest[0]);
            requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
        } catch (Exception error) {
            Log.w("PermissionGranter", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        Log.i("PermissionGranter", "onRequestPermissionsResult");
        int permissionEnum = getArguments().getInt("requested");
        final int PERMISSIONS_REQUEST_CODE = permissionEnum;
        if (requestCode != PERMISSIONS_REQUEST_CODE)
            return;

        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

            // permission was granted, yay! Do the task now
            Log.i("PermissionGranter", PermissionGranter.PERMISSION_GRANTED);
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_GRANTED);
        } else {

            // permission denied, boo! Disable the functionality that needed it
            Log.i("PermissionGranter", PermissionGranter.PERMISSION_DENIED);
            UnityPlayer.UnitySendMessage(PermissionGranter.UNITY_CALLBACK_GAMEOBJECT_NAME,
                    PermissionGranter.UNITY_CALLBACK_METHOD_NAME, PermissionGranter.PERMISSION_DENIED);
        }

        final Activity act = UnityPlayer.currentActivity;
        final FragmentManager fragmentManager = act.getFragmentManager();
        FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
        fragmentTransaction.remove(this);
        fragmentTransaction.commit();
    }
}

And for completeness here's the C# within Unity:

using UnityEngine;
using System.Collections;
using System;

public class SynapsePlugin : MonoBehaviour {

    // subscribe to this callback to see if your permission was granted.
    public static Action<bool> PermissionRequestCallback;

    // for now, it only implements the external storage permission
    public enum AndroidPermission {
        WRITE_EXTERNAL_STORAGE
    }

    public static void GrantPermission(AndroidPermission permission) {
        if (!initialized)
            initialize ();

        PermissionGranterClass.CallStatic ("grantPermission", (int)permission);
    }







    //////////////////////////////
    /// Initialization Stuff /////
    //////////////////////////////

    private const string PLUGIN_LISTENER_NAME = "SynapsePlugin_listener"; // must match UnitySendMessage call in Java

    // it's a singleton, but no one needs to know about it. hush hush. dont touch me.
    private static SynapsePlugin instance;
    private static bool initialized = false;

    static AndroidJavaClass PermissionGranterClass;
    private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match Java
    private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match Java

    // runs automatically when making calls, or can pre-init manually
    public static void initialize() {

        // add object to scene
        if (instance == null) {
            GameObject go = new GameObject();
            go.name = PLUGIN_LISTENER_NAME;

            // instance will also be set in awake, but having it here as well seems extra safe
            instance = go.AddComponent<SynapsePlugin>();
        }

        // get the jni stuff
        new AndroidJavaClass("com.synapse.unityplugins.PermissionGranter");

        initialized = true;
    }

    public void Awake() {
        DontDestroyOnLoad (this.gameObject);

        // instance is also set in initialize.
        // having it here ensures this thing doesnt break
        // if you added this component to the scene manually
        instance = this;

        if (name != PLUGIN_LISTENER_NAME)
            name = PLUGIN_LISTENER_NAME;
    }

    // we're calling this method from the java side.
    // the method name and gameobject must match Java's UnitySendMessage
    private void permissionRequestCallbackInternal(string message) {
        bool permissionGranted = (message == PERMISSION_GRANTED);
        if (PermissionRequestCallback != null)
            PermissionRequestCallback(permissionGranted);
    }
}
查看更多
混吃等死
4楼-- · 2019-02-02 18:06

You need Java code to ask for permission, and you need an interface into said Java code from Unity's C# runtime. You need to create a Unity Plugin to do this.

Below is the plugin that I've created to grant the WRITE_EXTERNAL_STORAGE permission at runtime.

You need a project structure like this:

Plugins/
  Android/
    NoodlePermissionGranter/
      project.properties
      AndroidManifest.xml
      NoodlePermissionGranter.cs
      libs/
        NoodlePermissionGranter.jar

NoodlePermissionGranter.cs:

      ///////////////////////////////////////////////////////////
     ///////////////// NoodlePermissionGranter /////////////////
    /// Implements runtime granting of Android permissions. ///
   /// This is necessary for Android M (6.0) and above. //////
  ///////////////////////////////////////////////////////////   
 //////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////

using UnityEngine;
using System.Collections;
using System;

public class NoodlePermissionGranter : MonoBehaviour {

    // subscribe to this callback to see if your permission was granted.
    public static Action<bool> PermissionRequestCallback;

    // for now, it only implements the external storage permission
    public enum NoodleAndroidPermission
    {
        WRITE_EXTERNAL_STORAGE
    }
    public static void GrantPermission(NoodleAndroidPermission permission) 
    {
        if (!initialized)
            initialize ();

        noodlePermissionGranterClass.CallStatic ("grantPermission", activity, (int)permission);
    }







      //////////////////////////////
     /// Initialization Stuff /////
    //////////////////////////////

    // it's a singleton, but no one needs to know about it. hush hush. dont touch me.
    private static NoodlePermissionGranter instance;
    private static bool initialized = false;

    public void Awake()
    {
        // instance is also set in initialize.
        // having it here ensures this thing doesnt break
        // if you added this component to the scene manually
        instance = this;
        DontDestroyOnLoad (this.gameObject);
        // object name must match UnitySendMessage call in NoodlePermissionGranter.java
        if (name != NOODLE_PERMISSION_GRANTER)
            name = NOODLE_PERMISSION_GRANTER;
    }


    private static void initialize()
    {
        // runs once when you call GrantPermission

        // add object to scene
        if (instance == null) {
            GameObject go = new GameObject();
            // instance will also be set in awake, but having it here as well seems extra safe
            instance = go.AddComponent<NoodlePermissionGranter>();
            // object name must match UnitySendMessage call in NoodlePermissionGranter.java
            go.name = NOODLE_PERMISSION_GRANTER; 
        }

        // get the jni stuff. we need the activty class and the NoodlePermissionGranter class.
        noodlePermissionGranterClass = new AndroidJavaClass("com.noodlecake.unityplugins.NoodlePermissionGranter");
        AndroidJavaClass u3d = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
        activity = u3d.GetStatic<AndroidJavaObject> ("currentActivity");

        initialized = true;
    }







      ///////////////////
     //// JNI Stuff ////
    ///////////////////

    static AndroidJavaClass noodlePermissionGranterClass;
    static AndroidJavaObject activity;
    private const string WRITE_EXTERNAL_STORAGE="WRITE_EXTERNAL_STORAGE";
    private const string PERMISSION_GRANTED = "PERMISSION_GRANTED"; // must match NoodlePermissionGranter.java
    private const string PERMISSION_DENIED = "PERMISSION_DENIED"; // must match NoodlePermissionGranter.java
    private const string NOODLE_PERMISSION_GRANTER = "NoodlePermissionGranter"; // must match UnitySendMessage call in NoodlePermissionGranter.java

    private void permissionRequestCallbackInternal(string message)
    {
        // were calling this method from the java side.
        // the method name and gameobject must match NoodlePermissionGranter.java's UnitySendMessage
        bool permissionGranted = (message == PERMISSION_GRANTED);
        if (PermissionRequestCallback != null)
            PermissionRequestCallback (permissionGranted);
    }
}

NoodlePermissionGranter.java:

package com.noodlecake.unityplugins;


      ///////////////////////////////////////////////////////////
     ///////////////// NoodlePermissionGranter /////////////////
    /// Implements runtime granting of Android permissions. ///
   /// This is necessary for Android M (6.0) and above. //////
  ///////////////////////////////////////////////////////////   
 //////////////////// Noodlecake Studios ///////////////////
///////////////////////////////////////////////////////////

import android.Manifest;
import android.os.Build;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.util.Log;
import android.content.pm.PackageManager;
import java.io.File;
import com.unity3d.player.UnityPlayerActivity;
import com.unity3d.player.UnityPlayer;

public class NoodlePermissionGranter
{
    // Only implements WRITE_EXTERNAL_STORAGE so far.
    // Implement the rest by matching the enum in NoodlePermissionGranter.cs
    // to the getPermissionStringFromEnumInt below.

    private final static String UNITY_CALLBACK_GAMEOBJECT_NAME = "NoodlePermissionGranter";
    private final static String UNITY_CALLBACK_METHOD_NAME = "permissionRequestCallbackInternal";
    private final static String PERMISSION_GRANTED = "PERMISSION_GRANTED"; // this will be an arg to the above method
    private final static String PERMISSION_DENIED = "PERMISSION_DENIED";

    public static String getPermissionStringFromEnumInt(int permissionEnum) throws Exception
    {
        switch (permissionEnum)
        {
            case 0:
                return Manifest.permission.WRITE_EXTERNAL_STORAGE;
            // "and the rest is still unwritten" - Natasha Bedingfield
        }
        Log.e("NoodlePermissionGranter", "Error. Unknown permissionEnum " + permissionEnum);
        throw new Exception(String.format("Error. Unknown permissionEnum %d",permissionEnum));
    }

    public static void grantPermission(Activity currentActivity, int permissionEnum)
    {
        // permission enum must match ordering in NoodlePermissionGranter.cs
        final Activity act = currentActivity;
        Log.i("NoodlePermissionGranter","grantPermission " + permissionEnum) ;
        if (Build.VERSION.SDK_INT < 23) {
            Log.i("NoodlePermissionGranter","Build.VERSION.SDK_INT < 23 (" + Build.VERSION.SDK_INT+")");
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
            return;
        }

        try
        {
            final int PERMISSIONS_REQUEST_CODE = permissionEnum;
            final String permissionFromEnumInt = getPermissionStringFromEnumInt(permissionEnum);
            if (currentActivity.checkCallingOrSelfPermission(permissionFromEnumInt) == PackageManager.PERMISSION_GRANTED) {
                Log.i("NoodlePermissionGranter", "already granted");
                UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                return;
            }

            final FragmentManager fragmentManager = currentActivity.getFragmentManager();
            final Fragment request = new Fragment() {

                @Override public void onStart()
                {
                    super.onStart();
                    Log.i("NoodlePermissionGranter","fragment start");
                    String[] permissionsToRequest = new String [] {permissionFromEnumInt};
                    Log.i("NoodlePermissionGranter","fragment start " + permissionsToRequest[0]);
                    requestPermissions(permissionsToRequest, PERMISSIONS_REQUEST_CODE);
                }

                @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults)
                {
                    Log.i("NoodlePermissionGranter", "onRequestPermissionsResult");
                    if (requestCode != PERMISSIONS_REQUEST_CODE)
                        return;

                    if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        // permission was granted, yay! Do the
                        // contacts-related task you need to do.
                        Log.i("NoodlePermissionGranter", PERMISSION_GRANTED);
                        UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_GRANTED);
                    } else {

                        // permission denied, boo! Disable the
                        // functionality that depends on this permission.
                        Log.i("NoodlePermissionGranter",PERMISSION_DENIED);
                        UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
                    }


                    FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
                    fragmentTransaction.remove(this);
                    fragmentTransaction.commit();

                    // shouldBeOkayToStartTheApplicationNow();
                }
            };

            FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(0, request);
            fragmentTransaction.commit();
        }
        catch(Exception error)
        {
            Log.w("[NoodlePermissionGranter]", String.format("Unable to request permission: %s", error.getMessage()));
            UnityPlayer.UnitySendMessage(UNITY_CALLBACK_GAMEOBJECT_NAME, UNITY_CALLBACK_METHOD_NAME, PERMISSION_DENIED);
        }
    }

}

BuildNoodlePermissionGranter.sh

export JAVA_HOME=/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
ClASSPATH=$UNITY_ROOT"/Unity.app/Contents/PlaybackEngines/AndroidPlayer/Variations/mono/Release/Classes/classes.jar"

javac NoodlePermissionGranter.java -bootclasspath $ANDROID_SDK_ROOT/platforms/android-23/android.jar -classpath $ClASSPATH -d .
javap -s com.noodlecake.unityplugins.NoodlePermissionGranter
jar cvfM NoodlePermissionGranter.jar com/
rm -rf com

You need project.properties and a dummy AndroidManifest.xml in order to have Unity package a jar outside of Plugins/Android/libs

project.properties

target=android-9
android.library=true

AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.noodlecake.unityplugins.noodlepermissiongranter"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk android:targetSdkVersion="23" />
</manifest>

It'd be nice if the PermissionRequestCallback supplied the requested permission enum as a parameter, but UnityPlayer.UnitySendMessage only supports a single string argument and I decided not to implemented the string serialization (using JSON to do this would be a good choice).

查看更多
疯言疯语
5楼-- · 2019-02-02 18:09

Well if you can use Android Studio and write java codes then...

public interface PermissionAction {
    int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL = 1;
    //You can add other integers too if you want
}

public void RequestPermissions(){

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        // Here, thisActivity is the current activity
        if (ContextCompat.checkSelfPermission(MainActivity.this,
                Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {

            ActivityCompat.requestPermissions(MainActivity.this,
                    new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    PermissionAction.MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL);
        }
        //More if statements for other permissions
    }

And below that

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
        case PermissionAction.MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {

                // permission was granted, yay! Do the contacts-related task you need to do.
                Toast.makeText(this, "WRITE SUCCESS", Toast.LENGTH_SHORT).show();

            } else {
                // permission denied, boo! Disable the functionality that depends on this permission.
                Toast.makeText(this, "Permission denied to write your External storage", Toast.LENGTH_LONG).show();

            }
            return;
        }
        //More Cases for other permissions
    }
}

Then you can basically call the RequestPermissions(); in OnCreate Method

And the most important thing here you need support-compat-25.1.0.aar file (or the last version of this file) from your

"SDK > extras > Android > m2repository > com > Android > support > support-compat"

Put the support-compat-25.1.0.aar file in to Assets/Plugins/Android with your other plugin files

Thats it. in addition to that you can use hawkwood's manifest example for disable unity's extra permissions since you created your own.

查看更多
淡お忘
6楼-- · 2019-02-02 18:14

Another addition to Jason's excellent code for Unity 5.3.3 and up (I'm using 5.4), I added this to the manifest to block Unity from automatically asking at launch:

 <application>
     <meta-data android:name="unityplayer.SkipPermissionsDialog" android:value="true" />
 </application>
查看更多
ら.Afraid
7楼-- · 2019-02-02 18:14

I've used Jason knight's answer to create this plugin that does the job, the whole code is available on the github repo.

There is also unity package file for easy integration.

查看更多
登录 后发表回答