mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Added feature RuntimePermissions, which allows to request permissions at runtime to access the microphone and bluetooth (required for Android apps using SDK Level 23 and above).
This commit is contained in:
parent
c8428f2168
commit
9ea874428c
13 changed files with 452 additions and 26 deletions
|
|
@ -252,19 +252,22 @@ public:
|
|||
File javaSourceFolder (coreModule->getFolder().getChildFile ("native")
|
||||
.getChildFile ("java"));
|
||||
|
||||
String juceMidiCode, juceMidiImports;
|
||||
String juceMidiCode, juceMidiImports, juceRuntimePermissionsCode;
|
||||
|
||||
juceMidiImports << newLine;
|
||||
|
||||
if (getMinimumSDKVersionString().getIntValue() >= 23)
|
||||
{
|
||||
File javaAndroidMidi (javaSourceFolder.getChildFile ("AndroidMidi.java"));
|
||||
File javaRuntimePermissions (javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java"));
|
||||
|
||||
juceMidiImports << "import android.media.midi.*;" << newLine
|
||||
<< "import android.bluetooth.*;" << newLine
|
||||
<< "import android.bluetooth.le.*;" << newLine;
|
||||
|
||||
juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className);
|
||||
|
||||
juceRuntimePermissionsCode = javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -287,6 +290,8 @@ public:
|
|||
newFile << juceMidiImports;
|
||||
else if (line.contains ("$$JuceAndroidMidiCode$$"))
|
||||
newFile << juceMidiCode;
|
||||
else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$"))
|
||||
newFile << juceRuntimePermissionsCode;
|
||||
else
|
||||
newFile << line.replace ("JuceAppActivity", className)
|
||||
.replace ("package com.juce;", "package " + package + ";") << newLine;
|
||||
|
|
|
|||
|
|
@ -626,6 +626,17 @@ private:
|
|||
return result;
|
||||
}
|
||||
|
||||
String createDependencies (const String& indent) const
|
||||
{
|
||||
String result;
|
||||
|
||||
result << "dependencies {" << newLine
|
||||
<< indent << "compile \"com.android.support:support-v4:+\"" << newLine // needed for ContextCompat and ActivityCompat
|
||||
<< "}" << newLine;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void writeBuildDotGradleApp (const File& folder) const
|
||||
{
|
||||
MemoryOutputStream memoryOutputStream;
|
||||
|
|
@ -656,7 +667,8 @@ private:
|
|||
<< CodeHelpers::indent (createModelDotAndroidDotSigningConfigs (indent), indent.length(), true)
|
||||
<< newLine
|
||||
<< CodeHelpers::indent (createModelDotAndroidDotProductFlavors (indent), indent.length(), true)
|
||||
<< "}";
|
||||
<< "}" << newLine << newLine
|
||||
<< createDependencies (indent);
|
||||
|
||||
overwriteFileIfDifferentOrThrow (folder.getChildFile ("app/build.gradle"), memoryOutputStream);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,25 +195,51 @@ public:
|
|||
STREAM_MUSIC, sampleRate, CHANNEL_OUT_STEREO, ENCODING_PCM_16BIT,
|
||||
(jint) (minBufferSizeOut * numDeviceOutputChannels * sizeof (int16)), MODE_STREAM));
|
||||
|
||||
if (env->CallIntMethod (outputDevice, AudioTrack.getState) != STATE_UNINITIALIZED)
|
||||
int outputDeviceState = env->CallIntMethod (outputDevice, AudioTrack.getState);
|
||||
if (outputDeviceState > 0)
|
||||
{
|
||||
isRunning = true;
|
||||
}
|
||||
else
|
||||
outputDevice.clear(); // failed to open the device
|
||||
{
|
||||
// failed to open the device
|
||||
outputDevice.clear();
|
||||
lastError = "Error opening audio output device: android.media.AudioTrack failed with state = " + String (outputDeviceState);
|
||||
}
|
||||
}
|
||||
|
||||
if (numClientInputChannels > 0 && numDeviceInputChannelsAvailable > 0)
|
||||
{
|
||||
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
|
||||
inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor,
|
||||
0 /* (default audio source) */, sampleRate,
|
||||
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
|
||||
ENCODING_PCM_16BIT,
|
||||
(jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16))));
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
|
||||
{
|
||||
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
|
||||
// before trying to open an audio input device. This is not going to work!
|
||||
jassertfalse;
|
||||
|
||||
if (env->CallIntMethod (inputDevice, AudioRecord.getState) != STATE_UNINITIALIZED)
|
||||
isRunning = true;
|
||||
inputDevice.clear();
|
||||
lastError = "Error opening audio input device: the app was not granted android.permission.RECORD_AUDIO";
|
||||
}
|
||||
else
|
||||
inputDevice.clear(); // failed to open the device
|
||||
{
|
||||
numDeviceInputChannels = jmin (numClientInputChannels, numDeviceInputChannelsAvailable);
|
||||
inputDevice = GlobalRef (env->NewObject (AudioRecord, AudioRecord.constructor,
|
||||
0 /* (default audio source) */, sampleRate,
|
||||
numDeviceInputChannelsAvailable > 1 ? CHANNEL_IN_STEREO : CHANNEL_IN_MONO,
|
||||
ENCODING_PCM_16BIT,
|
||||
(jint) (minBufferSizeIn * numDeviceInputChannels * sizeof (int16))));
|
||||
|
||||
int inputDeviceState = env->CallIntMethod (inputDevice, AudioRecord.getState);
|
||||
if (inputDeviceState > 0)
|
||||
{
|
||||
isRunning = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// failed to open the device
|
||||
inputDevice.clear();
|
||||
lastError = "Error opening audio input device: android.media.AudioRecord failed with state = " + String (inputDeviceState);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isRunning)
|
||||
|
|
|
|||
|
|
@ -145,12 +145,32 @@ public:
|
|||
<< ", sampleRate = " << sampleRate);
|
||||
|
||||
if (numInputChannels > 0)
|
||||
recorder = engine.createRecorder (numInputChannels, sampleRate,
|
||||
audioBuffersToEnqueue, actualBufferSize);
|
||||
{
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::recordAudio))
|
||||
{
|
||||
// If you hit this assert, you probably forgot to get RuntimePermissions::recordAudio
|
||||
// before trying to open an audio input device. This is not going to work!
|
||||
jassertfalse;
|
||||
lastError = "Error opening OpenSL input device: the app was not granted android.permission.RECORD_AUDIO";
|
||||
}
|
||||
else
|
||||
{
|
||||
recorder = engine.createRecorder (numInputChannels, sampleRate,
|
||||
audioBuffersToEnqueue, actualBufferSize);
|
||||
|
||||
if (recorder == nullptr)
|
||||
lastError = "Error opening OpenSL input device: creating Recorder failed.";
|
||||
}
|
||||
}
|
||||
|
||||
if (numOutputChannels > 0)
|
||||
player = engine.createPlayer (numOutputChannels, sampleRate,
|
||||
audioBuffersToEnqueue, actualBufferSize);
|
||||
{
|
||||
player = engine.createPlayer (numOutputChannels, sampleRate,
|
||||
audioBuffersToEnqueue, actualBufferSize);
|
||||
|
||||
if (player == nullptr)
|
||||
lastError = "Error opening OpenSL input device: creating Player failed.";
|
||||
}
|
||||
|
||||
// pre-fill buffers
|
||||
for (int i = 0; i < audioBuffersToEnqueue; ++i)
|
||||
|
|
|
|||
|
|
@ -427,6 +427,15 @@ private:
|
|||
//==============================================================================
|
||||
bool BluetoothMidiDevicePairingDialogue::open()
|
||||
{
|
||||
if (! RuntimePermissions::isGranted (RuntimePermissions::bluetoothMidi))
|
||||
{
|
||||
// If you hit this assert, you probably forgot to get RuntimePermissions::bluetoothMidi.
|
||||
// This is not going to work, boo! The pairing dialogue won't be able to scan for or
|
||||
// find any devices, it will just display an empty list, so don't bother opening it.
|
||||
jassertfalse;
|
||||
return false;
|
||||
}
|
||||
|
||||
BluetoothMidiSelectorOverlay* overlay = new BluetoothMidiSelectorOverlay;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -144,6 +144,7 @@ namespace juce
|
|||
#include "maths/juce_Expression.cpp"
|
||||
#include "maths/juce_Random.cpp"
|
||||
#include "memory/juce_MemoryBlock.cpp"
|
||||
#include "misc/juce_RuntimePermissions.cpp"
|
||||
#include "misc/juce_Result.cpp"
|
||||
#include "misc/juce_Uuid.cpp"
|
||||
#include "network/juce_MACAddress.cpp"
|
||||
|
|
@ -228,6 +229,7 @@ namespace juce
|
|||
#include "native/juce_android_Network.cpp"
|
||||
#include "native/juce_android_SystemStats.cpp"
|
||||
#include "native/juce_android_Threads.cpp"
|
||||
#include "native/juce_android_RuntimePermissions.cpp"
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
|||
|
|
@ -242,6 +242,7 @@ extern JUCE_API void JUCE_CALLTYPE logAssertion (const char* file, int line) noe
|
|||
#include "maths/juce_BigInteger.h"
|
||||
#include "maths/juce_Expression.h"
|
||||
#include "maths/juce_Random.h"
|
||||
#include "misc/juce_RuntimePermissions.h"
|
||||
#include "misc/juce_Uuid.h"
|
||||
#include "misc/juce_WindowsRegistry.h"
|
||||
#include "system/juce_SystemStats.h"
|
||||
|
|
|
|||
48
modules/juce_core/misc/juce_RuntimePermissions.cpp
Normal file
48
modules/juce_core/misc/juce_RuntimePermissions.cpp
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2016 - ROLI Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#if ! JUCE_ANDROID // We currently don't request runtime permissions on any other platform
|
||||
// than Android, so this file contains a dummy implementation for those.
|
||||
// This may change in the future.
|
||||
|
||||
void RuntimePermissions::request (PermissionID /*permission*/, Callback callback)
|
||||
{
|
||||
callback (true);
|
||||
}
|
||||
|
||||
bool RuntimePermissions::isRequired (PermissionID /*permission*/)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool RuntimePermissions::isGranted (PermissionID /*permission*/)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
||||
131
modules/juce_core/misc/juce_RuntimePermissions.h
Normal file
131
modules/juce_core/misc/juce_RuntimePermissions.h
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2016 - ROLI Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_RUNTIMEPERMISSIONS_H_INCLUDED
|
||||
#define JUCE_RUNTIMEPERMISSIONS_H_INCLUDED
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Class to handle app runtime permissions for certain functionality on some platforms.
|
||||
|
||||
The use of this class is currently only required if the app should run on
|
||||
Android API level 23 and higher.
|
||||
|
||||
On lower API levels, the permissions are specified in the app manifest. On iOS,
|
||||
runtime permission requests are handled automatically by the Apple APIs and not
|
||||
manually in the app code. On Windows, OS X, and Linux, runtime permissions are not
|
||||
used at all. In all these cases, request() will simply call through to the
|
||||
callback with no overhead and pass true (making it safe to use on all platforms).
|
||||
|
||||
For example, to enable audio recording on Android in your cross-platform app,
|
||||
you could modify your code as follows:
|
||||
|
||||
Old code:
|
||||
|
||||
audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr);
|
||||
|
||||
New code:
|
||||
|
||||
RuntimePermissions::request (
|
||||
RuntimePermissions::audioRecording,
|
||||
[this] (bool wasGranted)
|
||||
{
|
||||
if (! wasGranted)
|
||||
{
|
||||
// e.g. display an error or initialise with 0 input channels
|
||||
return;
|
||||
}
|
||||
|
||||
audioDeviceManager.initialise (2, 2, nullptr, true, String(), nullptr);
|
||||
}
|
||||
);
|
||||
*/
|
||||
class JUCE_API RuntimePermissions
|
||||
{
|
||||
public:
|
||||
//==========================================================================
|
||||
enum PermissionID
|
||||
{
|
||||
/** Permission to access the microphone (required on Android).
|
||||
You need to request this, for example, to initialise an AudioDeviceManager with
|
||||
a non-zero number of input channels, and to open the default audio input device.
|
||||
*/
|
||||
recordAudio = 1,
|
||||
|
||||
/** Permission to scan for and pair to Bluetooth MIDI devices (required on Android).
|
||||
You need to request this before calling BluetoothMidiDevicePairingDialogue::open(),
|
||||
otherwise no devices will be found.
|
||||
*/
|
||||
bluetoothMidi = 2,
|
||||
};
|
||||
|
||||
//==========================================================================
|
||||
/** Function type of runtime permission request callbacks. */
|
||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS
|
||||
typedef std::function<void (bool)> Callback;
|
||||
#else
|
||||
typedef void (*Callback) (bool);
|
||||
#endif
|
||||
|
||||
//==========================================================================
|
||||
/** Call this method to request a runtime permission.
|
||||
|
||||
@param permission The PermissionID of the permission you want to request.
|
||||
|
||||
@param callback The callback to be called after the request has been granted
|
||||
or denied; the argument passed will be true if the permission
|
||||
has been granted and false otherwise.
|
||||
|
||||
If no runtime request is required or possible to obtain the permission, the
|
||||
callback will be called immediately. The argument passed in will be true
|
||||
if the permission is granted or no permission is required on this platform,
|
||||
and false otherwise.
|
||||
|
||||
If a runtime request is required to obtain the permission, the callback
|
||||
will be called asynchronously after the OS has granted or denied the requested
|
||||
permission (typically by displaying a dialog box to the user and waiting until
|
||||
the user has responded).
|
||||
*/
|
||||
static void request (PermissionID permission, Callback callback);
|
||||
|
||||
/** Returns whether a runtime request is required to obtain the permission
|
||||
on the current platform.
|
||||
*/
|
||||
static bool isRequired (PermissionID permission);
|
||||
|
||||
/** Returns true if the app has been already granted this permission, either
|
||||
via a previous runtime request or otherwise, or no permission is necessary.
|
||||
|
||||
Note that this can be false even if isRequired returns false. In this case,
|
||||
the permission can not be obtained at all at runtime.
|
||||
*/
|
||||
static bool isGranted (PermissionID permission);
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_RUNTIMEPERMISSIONS_H_INCLUDED
|
||||
14
modules/juce_core/native/java/AndroidRuntimePermissions.java
Normal file
14
modules/juce_core/native/java/AndroidRuntimePermissions.java
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
private native void androidRuntimePermissionsCallback (boolean permissionWasGranted, long ptrToCallback);
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult (int permissionID, String permissions[], int[] grantResults)
|
||||
{
|
||||
boolean permissionsGranted = (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED);
|
||||
|
||||
if (! permissionsGranted)
|
||||
Log.d ("JUCE", "onRequestPermissionsResult: runtime permission was DENIED: " + getAndroidPermissionName (permissionID));
|
||||
|
||||
Long ptrToCallback = permissionCallbackPtrMap.get (permissionID);
|
||||
permissionCallbackPtrMap.remove (permissionID);
|
||||
androidRuntimePermissionsCallback (permissionsGranted, ptrToCallback);
|
||||
}
|
||||
|
|
@ -30,13 +30,12 @@ import android.content.DialogInterface;
|
|||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.Environment;
|
||||
import android.view.*;
|
||||
|
|
@ -50,19 +49,16 @@ import android.text.InputType;
|
|||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import java.lang.Runnable;
|
||||
import java.util.List;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Hashtable;
|
||||
import java.util.TimerTask;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
import java.net.HttpURLConnection;
|
||||
import android.media.AudioManager;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
||||
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.Manifest;
|
||||
$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the introjucer!
|
||||
|
||||
|
||||
|
|
@ -75,6 +71,73 @@ public class JuceAppActivity extends Activity
|
|||
System.loadLibrary ("juce_jni");
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
public boolean isPermissionDeclaredInManifest (int permissionID)
|
||||
{
|
||||
String permissionToCheck = getAndroidPermissionName(permissionID);
|
||||
|
||||
try
|
||||
{
|
||||
PackageInfo info = getPackageManager().getPackageInfo(getApplicationContext().getPackageName(), PackageManager.GET_PERMISSIONS);
|
||||
|
||||
if (info.requestedPermissions != null)
|
||||
for (String permission : info.requestedPermissions)
|
||||
if (permission.equals (permissionToCheck))
|
||||
return true;
|
||||
}
|
||||
catch (PackageManager.NameNotFoundException e)
|
||||
{
|
||||
Log.d ("JUCE", "isPermissionDeclaredInManifest: PackageManager.NameNotFoundException = " + e.toString());
|
||||
}
|
||||
|
||||
Log.d ("JUCE", "isPermissionDeclaredInManifest: could not find requested permission " + permissionToCheck);
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// these have to match the values of enum PermissionID in C++ class RuntimePermissions:
|
||||
private static final int JUCE_PERMISSIONS_RECORD_AUDIO = 1;
|
||||
private static final int JUCE_PERMISSIONS_BLUETOOTH_MIDI = 2;
|
||||
|
||||
private static String getAndroidPermissionName (int permissionID)
|
||||
{
|
||||
switch (permissionID)
|
||||
{
|
||||
case JUCE_PERMISSIONS_RECORD_AUDIO: return Manifest.permission.RECORD_AUDIO;
|
||||
case JUCE_PERMISSIONS_BLUETOOTH_MIDI: return Manifest.permission.ACCESS_COARSE_LOCATION;
|
||||
}
|
||||
|
||||
// unknown permission ID!
|
||||
assert false;
|
||||
return new String();
|
||||
}
|
||||
|
||||
public boolean isPermissionGranted (int permissionID)
|
||||
{
|
||||
return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private Map<Integer, Long> permissionCallbackPtrMap;
|
||||
|
||||
public void requestRuntimePermission (int permissionID, long ptrToCallback)
|
||||
{
|
||||
String permissionName = getAndroidPermissionName (permissionID);
|
||||
|
||||
if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED)
|
||||
{
|
||||
// remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously
|
||||
permissionCallbackPtrMap.put (permissionID, ptrToCallback);
|
||||
ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
// permissions were already granted before, we can call callback directly
|
||||
androidRuntimePermissionsCallback (true, ptrToCallback);
|
||||
}
|
||||
}
|
||||
|
||||
$$JuceAndroidRuntimePermissionsCode$$ // If you get an error here, you need to re-save your project with the introjucer!
|
||||
|
||||
//==============================================================================
|
||||
public static class MidiPortID extends Object
|
||||
{
|
||||
|
|
@ -138,6 +201,8 @@ public class JuceAppActivity extends Activity
|
|||
setContentView (viewHolder);
|
||||
|
||||
setVolumeControlStream (AudioManager.STREAM_MUSIC);
|
||||
|
||||
permissionCallbackPtrMap = new HashMap<Integer, Long>();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -294,6 +294,9 @@ extern AndroidSystem android;
|
|||
METHOD (setCurrentThreadPriority, "setCurrentThreadPriority", "(I)I") \
|
||||
METHOD (hasSystemFeature, "hasSystemFeature", "(Ljava/lang/String;)Z" ) \
|
||||
METHOD (createNewThread, "createNewThread", "(JLjava/lang/String;J)Ljava/lang/Thread;") \
|
||||
METHOD (requestRuntimePermission, "requestRuntimePermission", "(IJ)V" ) \
|
||||
METHOD (isPermissionGranted, "isPermissionGranted", "(I)Z" ) \
|
||||
METHOD (isPermissionDeclaredInManifest, "isPermissionDeclaredInManifest", "(I)Z" ) \
|
||||
|
||||
DECLARE_JNI_CLASS (JuceAppActivity, JUCE_ANDROID_ACTIVITY_CLASSPATH);
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
|
|
|||
90
modules/juce_core/native/juce_android_RuntimePermissions.cpp
Normal file
90
modules/juce_core/native/juce_android_RuntimePermissions.cpp
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the juce_core module of the JUCE library.
|
||||
Copyright (c) 2016 - ROLI Ltd.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with
|
||||
or without fee is hereby granted, provided that the above copyright notice and this
|
||||
permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
|
||||
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
|
||||
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
|
||||
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
NOTE! This permissive ISC license applies ONLY to files within the juce_core module!
|
||||
All other JUCE modules are covered by a dual GPL/commercial license, so if you are
|
||||
using any other modules, be sure to check that you also comply with their license.
|
||||
|
||||
For more details, visit www.juce.com
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace
|
||||
{
|
||||
void handleAndroidCallback (bool permissionWasGranted, RuntimePermissions::Callback* callbackPtr)
|
||||
{
|
||||
if (callbackPtr == nullptr)
|
||||
{
|
||||
// got a nullptr passed in from java! this should never happen...
|
||||
jassertfalse;
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_ptr<RuntimePermissions::Callback> uptr (callbackPtr);
|
||||
(*uptr) (permissionWasGranted);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_JNI_CALLBACK (JUCE_ANDROID_ACTIVITY_CLASSNAME,
|
||||
androidRuntimePermissionsCallback,
|
||||
void, (JNIEnv* env, jobject /*javaObjectHandle*/, jboolean permissionsGranted, jlong callbackPtr))
|
||||
{
|
||||
setEnv (env);
|
||||
handleAndroidCallback (permissionsGranted != 0,
|
||||
reinterpret_cast<RuntimePermissions::Callback*> (callbackPtr));
|
||||
}
|
||||
|
||||
void RuntimePermissions::request (PermissionID permission, Callback callback)
|
||||
{
|
||||
if (! android.activity.callBooleanMethod (JuceAppActivity.isPermissionDeclaredInManifest, (jint) permission))
|
||||
{
|
||||
// Error! If you want to be able to request this runtime permission, you
|
||||
// also need to declare it in your app's manifest. You can do so via
|
||||
// the Introjucer. Otherwise this can't work.
|
||||
jassertfalse;
|
||||
|
||||
callback (false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (JUCE_ANDROID_API_VERSION < 23)
|
||||
{
|
||||
// There is no runtime permission system on API level below 23. As long as the
|
||||
// permission is in the manifest (seems to be the case), we can simply ask Android
|
||||
// if the app has the permission, and then directly call through to the callback.
|
||||
callback (isGranted (permission));
|
||||
return;
|
||||
}
|
||||
|
||||
// we need to move the callback object to the heap so Java can keep track of the pointer
|
||||
// and asynchronously pass it back to us (to be called and then deleted)
|
||||
Callback* callbackPtr = new Callback (std::move (callback));
|
||||
android.activity.callVoidMethod (JuceAppActivity.requestRuntimePermission, permission, (jlong) callbackPtr);
|
||||
}
|
||||
|
||||
bool RuntimePermissions::isRequired (PermissionID /*permission*/)
|
||||
{
|
||||
return JUCE_ANDROID_API_VERSION >= 23;
|
||||
}
|
||||
|
||||
bool RuntimePermissions::isGranted (PermissionID permission)
|
||||
{
|
||||
return android.activity.callBooleanMethod (JuceAppActivity.isPermissionGranted, permission);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue