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.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" /> <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.MANAGE_EXTERNAL_STORAGE" tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

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

View File

@ -1,4 +1,11 @@
<?xml version="1.0" encoding="utf-8"?> <?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" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" 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"?> <?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" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" 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>60000000</item>
<item>120000000</item> <item>120000000</item>
</string-array> </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_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_settings_summary">Allow controlling your media players without opening KDE Connect</string>
<string name="mpris_notification_key" translatable="false">mpris_notification_enabled</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="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="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_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="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_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> <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="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> </resources>

View File

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

View File

@ -6,6 +6,7 @@
package org.kde.kdeconnect.Plugins.FindMyPhonePlugin; package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -120,6 +121,9 @@ public class FindMyPhonePlugin extends Plugin {
intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId()); intent.putExtra(FindMyPhoneActivity.EXTRA_DEVICE_ID, device.getDeviceId());
context.startActivity(intent); context.startActivity(intent);
} else { } else {
if (!checkOptionalPermissions()) {
return false;
}
if (powerManager.isInteractive()) { if (powerManager.isInteractive()) {
startPlaying(); startPlaying();
showBroadcastNotification(); showBroadcastNotification();
@ -127,7 +131,6 @@ public class FindMyPhonePlugin extends Plugin {
showActivityNotification(); showActivityNotification();
} }
} }
return true; return true;
} }
@ -219,4 +222,19 @@ public class FindMyPhonePlugin extends Plugin {
public PluginSettingsFragment getSettingsFragment(Activity activity) { public PluginSettingsFragment getSettingsFragment(Activity activity) {
return FindMyPhoneSettingsFragment.newInstance(getPluginKey(), R.xml.findmyphoneplugin_preferences); 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; package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.Manifest;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.media.AudioManager; import android.media.AudioManager;
import android.os.Build; import android.os.Build;
@ -257,6 +259,14 @@ public class MprisMediaSession implements
*/ */
private void updateMediaNotification() { 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 //If the user disabled the media notification, do not show it
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) { if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {

View File

@ -6,15 +6,18 @@
package org.kde.kdeconnect.Plugins.MprisPlugin; package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.DrawableRes; import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
@ -521,4 +524,19 @@ public class MprisPlugin extends Plugin {
} }
return false; 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; package org.kde.kdeconnect.Plugins.PingPlugin;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; 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.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
@ -66,6 +72,15 @@ public class PingPlugin extends Plugin {
id = 42; //A unique id to create only one notification 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); NotificationManager notificationManager = ContextCompat.getSystemService(context, NotificationManager.class);
Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT) Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)

View File

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

View File

@ -6,18 +6,21 @@
package org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin; package org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin;
import android.Manifest;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import org.apache.commons.lang3.ArrayUtils;
import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
@ -122,4 +125,19 @@ public class ReceiveNotificationsPlugin extends Plugin {
public @NonNull String[] getOutgoingPacketTypes() { public @NonNull String[] getOutgoingPacketTypes() {
return new String[]{PACKET_TYPE_NOTIFICATION_REQUEST}; 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 @Override
protected int getOptionalPermissionExplanation() { 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 @Override
@ -290,7 +294,9 @@ public class SharePlugin extends Plugin {
@Override @Override
public @NonNull String[] getOptionalPermissions() { 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; return ArrayUtils.EMPTY_STRING_ARRAY;
} else { } else {
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};

View File

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

View File

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

View File

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

View File

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