Hy guys
Situation
I have been updated MyApp to target Android 12 and ran into some odd behavior with the new SCHEDULE_EXACT_ALARM permission.
I have following problem with the new introduced permission
In my AndroidManifest.xml I declared the permission as following:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.myfirm.myapp">
.... more permissions
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application>
....
</application>
<service
android:name="com.myfirm..services.KernelStartedService"
android:enabled="true"
android:exported="true"
android:process=":KernelStartedService">
<intent-filter>
<action android:name="com.myfirm.service.kernel.api.ANDROID_KERNEL_SERVICE_BINDING_ACTION" />
</intent-filter>
</service>
</manifest>
As you can see in the declaration the value of the process attributes starts with a colon (:)
this means a new process, private to the application, is created when it's needed and the service runs in that process.
Before Starting or binding this service I invoke a test if the permission is given with this helper function:
private fun canExactAlarmsBeScheduled(): Boolean {
val alarmManager = this.getSystemService(ALARM_SERVICE) as AlarmManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
alarmManager.canScheduleExactAlarms()
} else {
true // below API it can always be scheduled
}
}
In the onStart Method of the first Activity I then do a test if the permission is givien, if not, I provoke showing a dialog to the user that he needs to give this permission to my app:
override fun onStart() {
// non relevant code here
if (canExactAlarmsBeScheduled()) {
initApplication() // here the service will be started/ bound
} else {
// start System Settings to enable alarms, coming back to Myapp will
// provoke an onStart again
AlertDialog.Builder(this)
.setMessage(getString(R.string.bootstrap_need_permission_to_schedule_alarms,getString(R.string.app_name)))
.setPositiveButton(getString(R.string.dialog_ok)) { _: DialogInterface, _: Int ->
intent = Intent(ACTION_REQUEST_SCHEDULE_EXACT_ALARM).apply {
data = Uri.fromParts(PACKAGE_SCHEME, packageName, null)
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
}
startActivity(intent)
}
.show()
}
}
}
This above is working well for the MyApp.
Problem:
I do ensure that only if the Permission of Scheduling exact alarms is given, the above mentioned Service "KernelStartedService" will be started in a new process.
The already granted permission is not taken by the new process in which the service is running in
If later on in classes that are running in this dedicated Process are testing alarmManager.canScheduleExactAlarms(). Then it ALWAYS is returning false!
Question:
How can I achieve to take over the permission SCHEDULE_EXACT_ALARM also for the process called ":KernelStartedService"?
The other permissions like...
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
...are all respected also for this new process. Why is the declaration
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
not respected in the separate process ":KernelStartedService"
As far as I see, this is a bug of Android 12 (API31 -App) when it creates the Process not applying this permission to the new process.
Expected behavior
I would expect that if the permissions are granted for the app-process, and afterwards I start a service in separate process, that all permissions would be applied to the new created process.
Thanks in advance for any help!
I created an app and added a java class that extend `NotificationListenerService'.
Everything should work fine, but I just can't get to permission BIND_NOTIFICATION_LISTENER_SERVICE.
I added it on the manifest, but when I check for the permission with:
if (ContextCompat.checkSelfPermission(this,
Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)
!= PackageManager.PERMISSION_GRANTED)
{
Log.d(TAG, "permission denied");
}
else
Log.d(TAG, "permission granted");
but I keep getting permission denied. I know that this permission isn't a "dangerous permission" so I don't have to ask for it from the user.
In the manifest I declared this:
<service android:name=".PcNotification"
android:label="PCNotificationService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
and it still doesn't work.
I also implemented the function onListenerConnected but it never called, so it means that my service never gets connected to the notification manager, probably because I don't have the permission BIND_NOTIFICATION_LISTENER_SERVICE
I just want to know how to grant this permission to my app.
I just can't get to permission BIND_NOTIFICATION_LISTENER_SERVICE
You do not need to request that permission. You need to defend your service with that permission. You are doing that in the <service> element via the android:permission attribute.
I know that this permission isn't a "dangerous permission" so I don't have to ask for it from the user.
Then get rid of the code that is requesting it from the user. It is unnecessary, and it will not work.
This should be the literal answer to the original question.
Nevertheless, if you need the method below, you misunderstood the use of such services (as did I). You should not launch your notification listener yourself, you should simply test if your service is running, and if not, then you already know that the permission was not granted, and you can point the user to the preferences panel at android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS.
Add this method to your NotificationListenerClass, and it will return true if the permission is granted, false if not:
public Boolean VerifyNotificationPermission() {
String theList = android.provider.Settings.Secure.getString(getContentResolver(), "enabled_notification_listeners");
String[] theListList = theList.split(":");
String me = (new ComponentName(this, YourNotificationListenerClass.class)).flattenToString();
for ( String next : theListList ) {
if ( me.equals(next) ) return true;
}
return false;
}
The string "enabled_notification_listeners" seems to be defined under the hood in Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, but I cannot resolve that, so I use the perhaps not so well maintainable literal string. If anyone knows how to get it by its reference, please add it / edit!
This specific permission must be granted manually by the user in android Settings, after that your service will be executed as you bound the permission to your service in AndroidManifest.xml with this:
<service android:name=".PcNotification"
android:label="PCNotificationService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
The problem is that most users won't know where to grant this permission (e.g inside android Settings), so you can open it for them with this:
startActivity(new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS"));
You can run this when the user interacts with your app - e.g clicking a button - and ideally you explain why you need this permission.
I find particularly nice to have a card with the explanation and a button to open the settings so the user can enable.
I'm trying to get the BIND_NOTIFICATION_LISTENER_SERVICE permission granted by the user.
To do that I'm opening the settings app at the correct spot using:
Intent intent = new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS);
startActivity(intent);
What's kinda weird here already is that the Settings are opened twice(if you press the back button once, the same settings screen opens again)
However, in onResume() I then check if the permission has been granted using:
if(ContextCompat.checkSelfPermission(context,Manifest.permission.BIND_NOTIFICATION_LISTENER_SERVICE)== PackageManager.PERMISSION_GRANTED){
//open next activity
}
And now here is the issue: It doesn't matter if the user granted the permission in the settings, because checkSelfPermission() always returns PERMISSION_DENIED.
And now it gets really weird: my NotificationListenerService is instantiated, bound and fully working although the permission hasn't been granted according to checkSelfPermission().
How am I supposed to know if the user granted the permission?
Permission and Service declaration in my Manifest:
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" />
<application>
<service
android:name=".service.NotificationListener"
android:directBootAware="true"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
</application>
My NotificationListenerService:
public class NotificationListener extends NotificationListenerService {
private static final String TAG = NotificationListener.class.getSimpleName();
#Override
public void onNotificationPosted(StatusBarNotification sbn) {
super.onNotificationPosted(sbn);
Log.d(TAG, "onNotificationPosted: "+sbn.getNotification().tickerText + " ;" + sbn.getPackageName());
}
}
What I already tried:
Different devices and API levels (including emulators) -> Everywhere the same issue
PermissionChecker.checkSelfPermission (I don't know what's the difference compared to ContextCompat.checkSelfPermission(), but it returns the same result)
Android Bug Tracker -> no known issues
According to documentation permission BIND_NOTIFICATION_LISTENER_SERVICE has:
Protection level: signature
This means, that only system applications can have it and use it. Asking for permissions by the way you defined is possible only, when the permission has:
Protection level: dangerous
If your app is not a system app and you have not rooted the device to have the possibility to install custom ROM on it with you custom certificate (your app must be signed with it, too), then you just can't get this permission.
I'm currently trying to adapt my application to the new permissions model of Android M.
I'm collecting all the permissions I require, then run
Log.i("Permissions", "Requesting permissions: " + permissions);
requestPermissions(requiredPermissions.toArray(new String[requiredPermissions.size()]), requestCodeForPermissions);
requiredPermissions holds the permissions I need like android.permission.WRITE_EXTERNAL_STORAGE.
That routine is definitely executed as I have the Log line in the logcat:
08-07 12:52:46.469: I/Permissions(1674): Requesting permissions: android.permission.RECEIVE_BOOT_COMPLETED; android.permission.WRITE_EXTERNAL_STORAGE
But the permissions dialog never shows, let alone is onRequestPermissionsResult() called.
What am I doing wrong? Based on some tutorials I found I'm not missing anything.
I only have the emulator for testing, no physical device. This is the about screen from settings:
Image
It might be worth mentioning something else: If I try to open the overview of installed apps from the home screen I only get launcher3 has exited. I'm not sure if that might be related.
Does anybody have an idea why it's not showing?
I experienced the same issue but later I realized I forgot to add the permission to the manifest file. After adding the uses-permission tag, the system showed the dialog. Maybe helps someone.
The original answer helped me.
I fixed by adding tools:remove="android:maxSdkVersion" like this:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" tools:remove="android:maxSdkVersion"/>
I experienced the same issue because I was using the negative value as a REQUEST_CODE.
requestPermissions(new String[]{android.Manifest.permission.CAMERA}, -1)
After using positive value, the system showed the dialog.
Hope it helps someone.
Based on the comment from Hilal (thanks a lot!):
In my case my app is indeed using tabhost and the permissions were requested from an Activity inside the tabhost. After starting a separate activity that requests the permissions it is working.
I just had the same problem.
My issue was that I wrote the permission at the wrong place in the manifest.
Make sure the uses permission is outside of application:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.gms.samples.vision.face.photo"
android:installLocation="auto"
android:versionCode="1"
android:versionName="1" >
<uses-sdk
android:minSdkVersion="9"
android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:hardwareAccelerated="true"
android:label="FacePhotoDemo"
android:allowBackup="true"
android:icon="#drawable/icon">
add
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
in AndroidManifest.xml
Note : The Permission which you want to get. Eg: android.permission.ACCESS_FINE_LOCATION etc.
I have also come across a situation where the permission dialog doesn't appear or the application crashes when using the <uses-permission-sdk23> element, however the cause appears to be a system bug on current 6.0 devices:
https://code.google.com/p/android/issues/detail?id=189841
Crash exception:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.android.packageinstaller/com.android.packageinstaller.permission.ui.GrantPermissionsActivity}: java.lang.NullPointerException: Attempt to get length of null array
I have the same issue and the problem is solved after adding the shouldShowRequestPermissionRationale like this:
if (ActivityCompat.shouldShowRequestPermissionRationale(thisActivity,
Manifest.permission.READ_CONTACTS)) {
} else {
ActivityCompat.requestPermissions(thisActivity,
new String[]{Manifest.permission.READ_CONTACTS},
MY_PERMISSIONS_REQUEST_READ_CONTACTS);
}
To add to #shanraisshan's answer, the REQUEST_CODE actually has to be greater than 0, not just non-negative.
In our code, it was a simple spelling mistake.
We had:
<uses-permission android:name="android.permission.ACCESS_COURSE_LOCATION" />
It should be:
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
I was doing two calls to requestPermissions(), one right after the other. This error message appeared in Logcat (note the misspelling on "request"):
Can reqeust only one set of permissions at a time
requestPermissions() is actually designed to take multiple requests at once; that's what the String array is for.
In my case, the permission I was requesting (WRITE_SETTINGS) was more special and required Settings Activity to launch. So dialog was not showing up.
I had to get its permission using the following code:
Intent intent = new Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS);
intent.setData(Uri.parse("package:" + context.getPackageName()));
startActivityForResult(intent, CODE_WRITE_SETTINGS_PERMISSION);
Yet another cause for not getting the permission dialog to show when requesting a dangerous permission...
I had to do Build -> Clean Project and then Build -> Rebuild Project. I guess Android Studio didn't pick up on the changes I made in the manifest.
I had a similar issue caused by the wrong case of the permission constant in the manifest, I was using read_contacts in lower case:
<uses-permission android:name="android.permission.read_contacts" />
After changing read_contacts to uppercase it started working as expected
<uses-permission android:name="android.permission.READ_CONTACTS" />
Permissions are organised into categories so non-critical ones are granted without the dialog being shown.
I found this the hard way with internet permission, if you're having this issue then changing to a critical permission such as read_contacts will allow you to test your flow and reveal whether the issue is the permission being non-critical or something else.
Normal protection permissions are listed here
In the manifest, I changed
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="22" />
to
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
/>
Maybe that helps.
Add permission in the manifest file as well.
If anyone of you guys has an application that changes/modifies WRITE_SETTINGS and are facing this issue. Check out CommonsWare's Answer to WRITE_SETTINGS Ambiguity
I was facing this issue for 2 weeks and later realised that this issue was due to the fact that requestPermissions doesn't work for requesting WRITE_SETTINGS permission.
Hope this helps :D
In my case I had requested "ACCESS_COARSE_LOCATION" in my manifest file and then request for "ACCESS_FINE_LOCATION" permission in code that's why the Permission Dialog was not opening.
After searching a while it appears it is required to set the value compileSdkVersion to "android-MNC" or as of today to 23. That requires the Gradle build system which then seems to require Android Studio.
At least I couldn't find a single manual about how to set it outside the gradle files.
I did all the things said in above answers but still dialog was not showing and then I changed targetSdkVersion to 23 in gradle and it appeared . Hope this helps someone
#Override
public final void validateRequestPermissionsRequestCode(int requestCode) {
// We use 16 bits of the request code to encode the fragment id when
// requesting permissions from a fragment. Hence, requestPermissions()
// should validate the code against that but we cannot override it as we
// can not then call super and also the ActivityCompat would call back to
// this override. To handle this we use dependency inversion where we are
// the validator of request codes when requesting permissions in
// ActivityCompat.
if (!mRequestedPermissionsFromFragment
&& requestCode != -1 && (requestCode & 0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
}
Had the same issue. Later I realized that we have to declare each and every permission in manifest (even if one is a subclass of another).
In my case I declared
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
in my manifest and was trying to access user's coarse location.
ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
Fixed the problem by adding coarse permission as well in manifest.
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Set your targetSdkVersion to 22.
had the same (i guess) problem and the solution was removing
import <app package>.Manifest;
autoimported by Android Studio at the top of the file and substitute it with
import android.Manifest;
and started working
In my case the solution is the string itself
android.permission.READ_CONTACTS
I did Manifest.permission.READ_CONTACTS which caused silence error (Noting show on the screen).
Make sure that this is correct
My Android targetSDK version is 28.
I don't see any pop ups shown to user requesting for permission which are listed in android Manifest.xml as .
During both installation, installing .apk using USB and installing app from google play store.
So I added below code in my activity it will ask user permission during runtime
ActivityCompat.requestPermissions(MyActivity.this,
new String[]{Manifest.permission.READ_PHONE_STATE}, 1);
I was requesting permission through code, but had missed adding the <uses-permission ..> tag in manifest !
Change the final constant value to 1.
private static final int REQUEST_PERMISSION_WRITE = 1;
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},REQUEST_PERMISSION_WRITE);
I am trying to read Contact names, phone #'s, and emails from the ContactsContract URI, and I am getting a SecurityException when I try to run the program. I have set the permission in the AndroidManifest.xml file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="edu.smumn.cs394"
android:versionCode="1"
android:versionName="1.0">
<uses-sdk android:minSdkVersion="8" />
**<uses-permission android:name="android.pemission.READ_CONTACTS"/>**
<application android:icon="#drawable/icon" android:label="#string/app_name">
<activity android:name=".ReadPhoneNumbers"
android:label="#string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>`
The following is the application code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.contact_list);
ContentResolver resolver = getContentResolver();
Cursor c = resolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
//[...] Work through data here`
I get a security exception on the last line (resolver.query()):
`03-08 07:41:40.812: ERROR/AndroidRuntime(416): FATAL EXCEPTION: main
03-08 07:41:40.812: ERROR/AndroidRuntime(416): java.lang.RuntimeException: Unable to start activity ComponentInfo{edu.smumn.cs394/edu.smumn.cs394.ReadPhoneNumbers}: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/contacts from pid=416, uid=10037 requires android.permission.READ_CONTACTS
[...]
03-08 07:41:40.812: ERROR/AndroidRuntime(416): Caused by: java.lang.SecurityException: Permission Denial: reading com.android.providers.contacts.ContactsProvider2 uri content://com.android.contacts/contacts from pid=416, uid=10037 requires android.permission.READ_CONTACTS
[...]
03-08 07:41:40.812: ERROR/AndroidRuntime(416): at edu.smumn.cs394.ReadPhoneNumbers.onCreate(ReadPhoneNumbers.java:30)
[...]`
I must be missing something, but I can't figure out what.
Requesting Permissions at Run Time
Beginning in Android 6.0 (API level 23), users grant permissions to apps while the app is running, not when they install the app.
If the permission you need to add isn't listed under the normal permissions, you'll need to deal with "Runtime Permissions". Runtime permissions are permissions that are requested as they are needed while the app is running. These permissions will show a dialog to the user, similar to the following one:
The first step when adding a "Runtime Permission" is to add it to the AndroidManifest:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.codepath.androidpermissionsdemo" >
<uses-permission android:name="android.permission.READ_CONTACTS" />
...
</manifest>
Next, you'll need to initiate the permission request and handle the result. The following code shows how to do this in the context of an Activity, but this is also possible from within a Fragment.
// MainActivity.java
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// In an actual app, you'd want to request a permission when the user performs an action
// that requires that permission.
getPermissionToReadUserContacts();
}
// Identifier for the permission request
private static final int READ_CONTACTS_PERMISSIONS_REQUEST = 1;
// Called when the user is performing an action which requires the app to read the
// user's contacts
public void getPermissionToReadUserContacts() {
// 1) Use the support library version ContextCompat.checkSelfPermission(...) to avoid
// checking the build version since Context.checkSelfPermission(...) is only available
// in Marshmallow
// 2) Always check for permission (even if permission has already been granted)
// since the user can revoke permissions at any time through Settings
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) {
// The permission is NOT already granted.
// Check if the user has been asked about this permission already and denied
// it. If so, we want to give more explanation about why the permission is needed.
if (shouldShowRequestPermissionRationale(
Manifest.permission.READ_CONTACTS)) {
// Show our own UI to explain to the user why we need to read the contacts
// before actually requesting the permission and showing the default UI
}
// Fire off an async request to actually get the permission
// This will show the standard permission request dialog UI
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},
READ_CONTACTS_PERMISSIONS_REQUEST);
}
}
// Callback with the request from calling requestPermissions(...)
#Override
public void onRequestPermissionsResult(int requestCode,
#NonNull String permissions[],
#NonNull int[] grantResults) {
// Make sure it's our original READ_CONTACTS request
if (requestCode == READ_CONTACTS_PERMISSIONS_REQUEST) {
if (grantResults.length == 1 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this, "Read Contacts permission granted", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "Read Contacts permission denied", Toast.LENGTH_SHORT).show();
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
}
Make sure you add it outside of the application tag. While developing for a target platform of 2.3.3 using Eclipse on Ubuntu, I had permission failures in the log file that indicated I needed this exact line while working on something similar. It wasn't until I moved the *uses-permission...READ_CONTACTS* line to outside the application tag that things worked.
Hello Steven the debug log trace tells you that you need
... requires android.permission.READ_CONTACTS
so just try something by editing the Manifest.xml like adding another permission, let see if its not correctly readed.
and check this line without **
<uses-permission android:name="android.permission.READ_CONTACTS" />
dan
with the api 23, permission <uses-permission android:name="android.pemission.READ_CONTACTS"/> dont work, change the api level in the emulator for api 22(lollipop) or lower
If the device is running Android 6.0 or higher, and your app's target SDK is 23 or higher: The app has to list the permissions in the manifest, and it must request each dangerous permission it needs while the app is running. The user can grant or deny each permission, and the app can continue to run with limited capabilities even if the user denies a permission request.
EXAMPLE:
//thisActivity is the running activity
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.
}
}
http://developer.android.com/training/permissions/requesting.html