2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

Request notifications permission so we can target Android 13

Starting next month (September 2023) it will be required to target
Android 13 to publish to the Play Store.

On Android 13, we need to request the POST_NOTIFICATIONS permission. 

This change adds the permission as required/optional to the plugins
that do create notifications. It also adds a popup to request it the
first time you open the app.
This commit is contained in:
Albert Vaca Cintora 2023-09-01 03:34:32 +00:00
parent 37db0810aa
commit 4d2efe6b89
19 changed files with 224 additions and 21 deletions

View File

@ -49,6 +49,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:icon="@mipmap/ic_launcher"

View File

@ -41,7 +41,7 @@ android {
compileSdk = 33
defaultConfig {
minSdk = 21
targetSdk = 32
targetSdk = 33
proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
}
buildFeatures {

View File

@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:drawablePadding="8dp"
android:paddingTop="16dp"
android:paddingBottom="12dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/no_notifications"
app:drawableStartCompat="@drawable/ic_warning"
app:drawableTint="?attr/colorControlNormal">
</TextView>

View File

@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2019 Matthijs Tijink <matthijstijink@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:drawablePadding="8dp"
android:paddingTop="16dp"
android:paddingBottom="12dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/no_notifications"
app:drawableStartCompat="@drawable/ic_warning"
app:drawableLeftCompat="@drawable/ic_warning">
</TextView>

View File

@ -252,6 +252,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<item>60000000</item>
<item>120000000</item>
</string-array>
<string name="mpris_notifications_explanation">The notifications permission is needed to show remote media in the notifications drawer</string>
<string name="mpris_notification_settings_title">Show media control notification</string>
<string name="mpris_notification_settings_summary">Allow controlling your media players without opening KDE Connect</string>
<string name="mpris_notification_key" translatable="false">mpris_notification_enabled</string>
@ -331,6 +332,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="optional_permission_explanation">You need to grant extra permissions to enable all functions</string>
<string name="plugins_need_optional_permission">Some plugins have features disabled because of lack of permission (tap for more info):</string>
<string name="share_optional_permission_explanation">To receive files you need to allow storage access</string>
<string name="share_notifications_explanation">To see the progress when sending and receiving files you need to allow notifications</string>
<string name="telepathy_permission_explanation">To read and write SMS from your desktop you need to give permission to SMS</string>
<string name="telephony_permission_explanation">To see phone calls on the desktop you need to give permission to phone call logs and phone state</string>
<string name="telephony_optional_permission_explanation">To see a contact name instead of a phone number you need to give access to the phone\'s contacts</string>
@ -549,4 +551,8 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="enable_udp_broadcast">Enable UDP device discovery</string>
<string name="receive_notifications_permission_explanation">Notifications need to be allowed to receive them from other devices</string>
<string name="findmyphone_notifications_explanation">The notifications permission is needed so the phone can ring when the app is in the background</string>
<string name="no_notifications">Notifications are disabled, you won\'t receive incoming pair notifications.</string>
</resources>

View File

@ -67,8 +67,6 @@ public class BigscreenActivity extends AppCompatActivity {
new PermissionsAlertDialogFragment.Builder()
.setTitle(plugin.getDisplayName())
.setMessage(R.string.bigscreen_optional_permission_explanation)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setPermissions(new String[]{Manifest.permission.RECORD_AUDIO})
.setRequestCode(MainActivity.RESULT_NEEDS_RELOAD)
.create().show(getSupportFragmentManager(), null);

View File

@ -6,6 +6,7 @@
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
import android.Manifest;
import android.app.Activity;
import android.app.NotificationManager;
import android.app.PendingIntent;
@ -120,6 +121,9 @@ public class FindMyPhonePlugin extends Plugin {
intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId());
context.startActivity(intent);
} else {
if (!checkOptionalPermissions()) {
return false;
}
if (powerManager.isInteractive()) {
startPlaying();
showBroadcastNotification();
@ -127,7 +131,6 @@ public class FindMyPhonePlugin extends Plugin {
showActivityNotification();
}
}
return true;
}
@ -219,4 +222,19 @@ public class FindMyPhonePlugin extends Plugin {
public PluginSettingsFragment getSettingsFragment(Activity activity) {
return FindMyPhoneSettingsFragment.newInstance(getPluginKey(), R.xml.findmyphoneplugin_preferences);
}
@NonNull
@Override
protected String[] getRequiredPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return new String[]{Manifest.permission.POST_NOTIFICATIONS};
} else {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
}
@Override
protected int getPermissionExplanation() {
return R.string.findmyphone_notifications_explanation;
}
}

View File

@ -6,11 +6,13 @@
package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.Manifest;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.media.AudioManager;
import android.os.Build;
@ -257,6 +259,14 @@ public class MprisMediaSession implements
*/
private void updateMediaNotification() {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
int permissionResult = ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS);
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
closeMediaNotification();
return;
}
}
//If the user disabled the media notification, do not show it
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {

View File

@ -6,15 +6,18 @@
package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
@ -521,4 +524,19 @@ public class MprisPlugin extends Plugin {
}
return false;
}
@NonNull
@Override
protected String[] getOptionalPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return new String[]{Manifest.permission.POST_NOTIFICATIONS};
} else {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
}
@Override
protected int getOptionalPermissionExplanation() {
return R.string.mpris_notifications_explanation;
}
}

View File

@ -6,12 +6,18 @@
package org.kde.kdeconnect.Plugins.PingPlugin;
import android.Manifest;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
@ -66,6 +72,15 @@ public class PingPlugin extends Plugin {
id = 42; //A unique id to create only one notification
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
int permissionResult = ContextCompat.checkSelfPermission(context, Manifest.permission.POST_NOTIFICATIONS);
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
// If notifications are not allowed, show a toast instead of a notification
new Handler(Looper.getMainLooper()).post(() -> Toast.makeText(context, message, Toast.LENGTH_LONG).show());
return true;
}
}
NotificationManager notificationManager = ContextCompat.getSystemService(context, NotificationManager.class);
Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)

View File

@ -278,8 +278,6 @@ public abstract class Plugin {
return new PermissionsAlertDialogFragment.Builder()
.setTitle(getDisplayName())
.setMessage(reason)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setPermissions(permissions)
.setRequestCode(MainActivity.RESULT_NEEDS_RELOAD)
.create();

View File

@ -6,18 +6,21 @@
package org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin;
import android.Manifest;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin;
@ -122,4 +125,19 @@ public class ReceiveNotificationsPlugin extends Plugin {
public @NonNull String[] getOutgoingPacketTypes() {
return new String[]{PACKET_TYPE_NOTIFICATION_REQUEST};
}
@NonNull
@Override
protected String[] getRequiredPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return new String[]{Manifest.permission.POST_NOTIFICATIONS};
} else {
return ArrayUtils.EMPTY_STRING_ARRAY;
}
}
@Override
protected int getPermissionExplanation() {
return R.string.receive_notifications_permission_explanation;
}
}

View File

@ -75,7 +75,11 @@ public class SharePlugin extends Plugin {
@Override
protected int getOptionalPermissionExplanation() {
return R.string.share_optional_permission_explanation;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return R.string.share_notifications_explanation;
} else {
return R.string.share_optional_permission_explanation;
}
}
@Override
@ -290,7 +294,9 @@ public class SharePlugin extends Plugin {
@Override
public @NonNull String[] getOptionalPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
return new String[]{Manifest.permission.POST_NOTIFICATIONS};
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
return ArrayUtils.EMPTY_STRING_ARRAY;
} else {
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};

View File

@ -22,6 +22,8 @@ import android.widget.TextView
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.ActionBarDrawerToggle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.fragment.app.Fragment
@ -192,6 +194,15 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
else -> setContentFragment(PairingFragment())
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
if (permissionResult != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), RESULT_NOTIFICATIONS_ENABLED)
}
}
}
}
override fun onDestroy() {
@ -324,25 +335,33 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
val uri = data.data
ShareSettingsFragment.saveStorageLocationPreference(this, uri)
}
else -> super.onActivityResult(requestCode, resultCode, data)
}
}
fun isPermissionGranted(permissions: Array<String>, grantResults: IntArray, permission : String) : Boolean {
val index = ArrayUtils.indexOf(permissions, permission)
return index != ArrayUtils.INDEX_NOT_FOUND && grantResults[index] == PackageManager.PERMISSION_GRANTED
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
val permissionsGranted = ArrayUtils.contains(grantResults, PackageManager.PERMISSION_GRANTED)
if (permissionsGranted) {
val i = ArrayUtils.indexOf(permissions, Manifest.permission.WRITE_EXTERNAL_STORAGE)
val writeStoragePermissionGranted = i != ArrayUtils.INDEX_NOT_FOUND &&
grantResults[i] == PackageManager.PERMISSION_GRANTED
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && writeStoragePermissionGranted) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && isPermissionGranted(permissions, grantResults, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
// To get a writeable path manually on Android 10 and later for Share and Receive Plugin.
// Otherwise, Receiving files will keep failing until the user chooses a path manually to receive files.
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(intent, STORAGE_LOCATION_CONFIGURED)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && isPermissionGranted(permissions, grantResults, Manifest.permission.POST_NOTIFICATIONS)) {
// If PairingFragment is active, reload it
if (mCurrentDevice == null) {
setContentFragment(PairingFragment())
}
}
//New permission granted, reload plugins
KdeConnect.getInstance().getDevice(mCurrentDevice)?.reloadPluginsFromSettings()
}
@ -370,6 +389,7 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
const val PAIRING_REJECTED = "rejected"
const val PAIRING_PENDING = "pending"
const val RESULT_NEEDS_RELOAD = RESULT_FIRST_USER
const val RESULT_NOTIFICATIONS_ENABLED = RESULT_FIRST_USER+1
const val FLAG_FORCE_OVERVIEW = "forceOverview"
}

View File

@ -6,10 +6,13 @@
package org.kde.kdeconnect.UserInterface;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.LayoutInflater;
@ -21,6 +24,8 @@ import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import org.kde.kdeconnect.BackgroundService;
@ -34,6 +39,7 @@ import org.kde.kdeconnect_tp.R;
import org.kde.kdeconnect_tp.databinding.DevicesListBinding;
import org.kde.kdeconnect_tp.databinding.PairingExplanationNotTrustedBinding;
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextBinding;
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoNotificationsBinding;
import org.kde.kdeconnect_tp.databinding.PairingExplanationTextNoWifiBinding;
import java.util.ArrayList;
@ -52,6 +58,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
private PairingExplanationNotTrustedBinding pairingExplanationNotTrustedBinding;
private PairingExplanationTextBinding pairingExplanationTextBinding;
private PairingExplanationTextNoWifiBinding pairingExplanationTextNoWifiBinding;
private PairingExplanationTextNoNotificationsBinding pairingExplanationTextNoNotificationsBinding;
private MainActivity mActivity;
@ -59,6 +66,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
private TextView headerText;
private TextView noWifiHeader;
private TextView noNotificationsHeader;
private TextView notTrustedText;
@Override
@ -69,23 +77,35 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
setHasOptionsMenu(true);
devicesListBinding = DevicesListBinding.inflate(inflater, container, false);
pairingExplanationNotTrustedBinding = PairingExplanationNotTrustedBinding.inflate(inflater);
pairingExplanationTextBinding = PairingExplanationTextBinding.inflate(inflater);
pairingExplanationTextNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater);
devicesListBinding.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction);
notTrustedText = pairingExplanationNotTrustedBinding.getRoot();
notTrustedText.setOnClickListener(null);
notTrustedText.setOnLongClickListener(null);
pairingExplanationTextBinding = PairingExplanationTextBinding.inflate(inflater);
headerText = pairingExplanationTextBinding.getRoot();
headerText.setOnClickListener(null);
headerText.setOnLongClickListener(null);
pairingExplanationTextNoWifiBinding = PairingExplanationTextNoWifiBinding.inflate(inflater);
noWifiHeader = pairingExplanationTextNoWifiBinding.getRoot();
noWifiHeader.setOnClickListener(view -> startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS)));
pairingExplanationTextNoNotificationsBinding = PairingExplanationTextNoNotificationsBinding.inflate(inflater);
noNotificationsHeader = pairingExplanationTextNoNotificationsBinding.getRoot();
noNotificationsHeader.setOnClickListener(view -> ActivityCompat.requestPermissions(requireActivity(), new String[]{Manifest.permission.POST_NOTIFICATIONS}, MainActivity.RESULT_NOTIFICATIONS_ENABLED));
noNotificationsHeader.setOnLongClickListener(view -> {
Intent intent = new Intent();
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package", requireContext().getPackageName(), null);
intent.setData(uri);
startActivity(intent);
return true;
});
devicesListBinding.devicesList.addHeaderView(headerText);
devicesListBinding.refreshListLayout.setOnRefreshListener(this::refreshDevicesAction);
return devicesListBinding.getRoot();
}
@ -207,12 +227,17 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
}
}
boolean hasNotificationsPermission = ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED;
devicesListBinding.devicesList.removeHeaderView(headerText);
devicesListBinding.devicesList.removeHeaderView(noWifiHeader);
devicesListBinding.devicesList.removeHeaderView(notTrustedText);
devicesListBinding.devicesList.removeHeaderView(noNotificationsHeader);
if (someDevicesReachable || isConnectedToNonCellularNetwork) {
if (TrustedNetworkHelper.isTrustedNetwork(getContext())) {
if (!hasNotificationsPermission) {
devicesListBinding.devicesList.addHeaderView(noNotificationsHeader);
} else if (TrustedNetworkHelper.isTrustedNetwork(getContext())) {
devicesListBinding.devicesList.addHeaderView(headerText);
} else {
devicesListBinding.devicesList.addHeaderView(notTrustedText);

View File

@ -11,6 +11,8 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import org.kde.kdeconnect_tp.R;
public class PermissionsAlertDialogFragment extends AlertDialogFragment {
private static final String KEY_PERMISSIONS = "Permissions";
private static final String KEY_REQUEST_CODE = "RequestCode";
@ -43,6 +45,12 @@ public class PermissionsAlertDialogFragment extends AlertDialogFragment {
}
public static class Builder extends AlertDialogFragment.AbstractBuilder<Builder, PermissionsAlertDialogFragment> {
public Builder() {
setPositiveButton(R.string.ok);
setNegativeButton(R.string.cancel);
}
@Override
public Builder getThis() {
return this;

View File

@ -75,8 +75,6 @@ public class TrustedNetworksActivity extends AppCompatActivity {
new PermissionsAlertDialogFragment.Builder()
.setTitle(R.string.location_permission_needed_title)
.setMessage(R.string.location_permission_needed_desc)
.setPositiveButton(R.string.ok)
.setNegativeButton(R.string.cancel)
.setPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION})
.setRequestCode(0)
.create().show(getSupportFragmentManager(), null);