mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-31 06:05:12 +00:00
Merge branch 'pie'
# Conflicts: # res/values/strings.xml # src/org/kde/kdeconnect/UserInterface/MainActivity.java
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="1841"
|
||||
android:versionName="1.8.4">
|
||||
android:versionCode="1902"
|
||||
android:versionName="1.9 Beta">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
@@ -17,11 +17,10 @@
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!--<uses-permission android:name="android.permission.BLUETOOTH" />-->
|
||||
<!--<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />-->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH" /> -->
|
||||
<!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> -->
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
|
||||
<uses-permission android:name="android.permission.BATTERY_STATS" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||
<uses-permission android:name="android.permission.RECEIVE_SMS" />
|
||||
<uses-permission android:name="android.permission.SEND_SMS" />
|
||||
@@ -31,19 +30,16 @@
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||
<uses-permission android:name="android.permission.BIND_REMOTEVIEWS" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/icon"
|
||||
android:label="KDE Connect"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/KdeConnectTheme">
|
||||
|
||||
<service
|
||||
android:name="org.kde.kdeconnect.BackgroundService"
|
||||
android:enabled="true" />
|
||||
|
||||
<service
|
||||
android:name="org.kde.kdeconnect.Plugins.RemoteKeyboardPlugin.RemoteKeyboardService"
|
||||
android:label="KDE Connect Remote Keyboard"
|
||||
@@ -51,6 +47,7 @@
|
||||
<intent-filter>
|
||||
<action android:name="android.view.InputMethod" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.view.im"
|
||||
android:resource="@xml/remotekeyboardplugin_method" />
|
||||
@@ -62,12 +59,12 @@
|
||||
android:theme="@style/KdeConnectTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.UserInterface.SettingsActivity"
|
||||
android:name="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity"
|
||||
android:label="@string/device_menu_plugins"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
|
||||
<meta-data
|
||||
@@ -93,21 +90,20 @@
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
|
||||
android:label="@string/device_menu_plugins"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.SettingsActivity">
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.SettingsActivity" />
|
||||
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
|
||||
</activity>
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity"
|
||||
android:label="@string/device_menu_plugins"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.SettingsActivity">
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity" />
|
||||
</activity>
|
||||
|
||||
|
||||
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
@@ -140,12 +136,13 @@
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"
|
||||
android:label="@string/remote_control"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
android:launchMode="singleTop">
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<receiver android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisMediaNotificationReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||
@@ -159,44 +156,49 @@
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
|
||||
</activity>
|
||||
|
||||
<activity android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandWidgetDeviceSelector"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="user"
|
||||
android:noHistory="true"
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandWidgetDeviceSelector"
|
||||
android:excludeFromRecents="true"
|
||||
android:label="@string/pref_plugin_runcommand"/>
|
||||
android:label="@string/pref_plugin_runcommand"
|
||||
android:launchMode="singleTask"
|
||||
android:noHistory="true"
|
||||
android:screenOrientation="user"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" />
|
||||
|
||||
<service
|
||||
android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandWidgetDataProviderService"
|
||||
android:exported="false" android:permission="android.permission.BIND_REMOTEVIEWS"/>
|
||||
<receiver android:label="@string/pref_plugin_runcommand" android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandWidget">
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_REMOTEVIEWS" />
|
||||
|
||||
<receiver
|
||||
android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandWidget"
|
||||
android:label="@string/pref_plugin_runcommand">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="RUN_COMMAND_ACTION" />
|
||||
</intent-filter>
|
||||
<meta-data android:name="android.appwidget.provider" android:resource="@xml/remotecommandplugin_widget" />
|
||||
</receiver>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/remotecommandplugin_widget" />
|
||||
</receiver>
|
||||
|
||||
<activity android:name="org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandUrlActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED"></action>
|
||||
<action android:name="android.nfc.action.NDEF_DISCOVERED" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:scheme="kdeconnect" android:host="runcommand"/>
|
||||
|
||||
<data
|
||||
android:host="runcommand"
|
||||
android:scheme="kdeconnect" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
|
||||
android:label="@string/remote_control"
|
||||
@@ -205,7 +207,6 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.PresenterPlugin.PresenterActivity"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
@@ -216,7 +217,6 @@
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareActivity"
|
||||
android:label="KDE Connect">
|
||||
@@ -274,7 +274,6 @@
|
||||
android:value="org.kde.kdeconnect.UserInterface.PluginSettingsActivity" />
|
||||
</activity>
|
||||
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
</manifest>
|
20
build.gradle
20
build.gradle
@@ -11,10 +11,10 @@ buildscript {
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
targetSdkVersion 25
|
||||
minSdkVersion 14
|
||||
targetSdkVersion 28
|
||||
//multiDexEnabled true
|
||||
//testInstrumentationRunner "com.android.test.runner.MultiDexTestRunner"
|
||||
}
|
||||
@@ -25,7 +25,7 @@ android {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
sourceSets {
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = ['src']
|
||||
@@ -44,7 +44,6 @@ android {
|
||||
pickFirst "META-INF/BCKEY.SF"
|
||||
pickFirst "META-INF/BCKEY.DSA"
|
||||
pickFirst "META-INF/INDEX.LIST"
|
||||
pickFirst "META-INF/io.netty.versions.properties"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
@@ -58,7 +57,7 @@ android {
|
||||
release { //keep on 'release', set to 'all' when testing to make sure proguard is not deleting important stuff
|
||||
minifyEnabled true
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,12 +69,13 @@ dependencies {
|
||||
google()
|
||||
}
|
||||
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
implementation 'com.android.support:appcompat-v7:25.4.0'
|
||||
implementation 'com.android.support:design:25.4.0'
|
||||
implementation 'com.android.support:support-media-compat:28.0.0'
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
implementation 'com.android.support:preference-v7:28.0.0'
|
||||
implementation 'com.android.support:design:28.0.0'
|
||||
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps
|
||||
|
||||
implementation 'org.apache.sshd:sshd-core:0.8.0' //0.9 seems to fail on Android 6 and 1.+ requires java.nio.file, which doesn't exist in Android
|
||||
implementation 'org.apache.sshd:sshd-core:0.14.0' //Newer version requires java.nio.file, which isn't available until Android 8+
|
||||
|
||||
implementation 'com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0' //For SSL certificate generation
|
||||
|
||||
|
5
proguard-rules.pro
vendored
5
proguard-rules.pro
vendored
@@ -37,3 +37,8 @@
|
||||
-keep class org.apache.mina.** {*;}
|
||||
|
||||
-keep class org.kde.kdeconnect.** {*;}
|
||||
|
||||
-dontwarn org.mockito.**
|
||||
-dontwarn sun.reflect.**
|
||||
-dontwarn android.test.**
|
||||
-dontwarn java.lang.management.**
|
||||
|
@@ -8,12 +8,6 @@
|
||||
android:title="@string/refresh"
|
||||
kdeconnect:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_rename"
|
||||
android:orderInCategory="300"
|
||||
android:title="@string/device_rename_title"
|
||||
kdeconnect:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/menu_custom_device_list"
|
||||
android:orderInCategory="900"
|
||||
|
@@ -1,6 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<string name="kde_connect">KDE Connect</string>
|
||||
<string name="foreground_notification_no_devices">Not connected to any device</string>
|
||||
<string name="foreground_notification_devices">Connected to: %s</string>
|
||||
<string name="pref_plugin_telephony">Telephony notifier</string>
|
||||
<string name="pref_plugin_telephony_desc">Send notifications for incoming calls</string>
|
||||
<string name="pref_plugin_battery">Battery report</string>
|
||||
@@ -151,7 +154,7 @@
|
||||
<string name="device">Device</string>
|
||||
<string name="pair_device">Pair device</string>
|
||||
<string name="remote_control">Remote control</string>
|
||||
<string name="settings">KDE Connect Settings</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="mpris_play">Play</string>
|
||||
<string name="mpris_pause">Pause</string>
|
||||
<string name="mpris_previous">Previous</string>
|
||||
@@ -260,7 +263,7 @@
|
||||
<string name="presenter_fullscreen">Fullscreen</string>
|
||||
<string name="presenter_exit">Exit presentation</string>
|
||||
<string name="presenter_lock_tip">You can lock your device to use the volume keys as previous/next buttons</string>
|
||||
|
||||
|
||||
<string name="add_command">Add a command</string>
|
||||
<string name="addcommand_explanation">There are no commands registered</string>
|
||||
<string name="addcommand_explanation2">You can add new commands in the KDE Connect System Settings</string>
|
||||
@@ -269,6 +272,12 @@
|
||||
<string name="pref_plugin_mprisreceiver_desc">Control your phones media players from another device</string>
|
||||
|
||||
<string name="dark_theme">Dark theme</string>
|
||||
|
||||
<string name="notification_channel_default">Other notifications</string>
|
||||
<string name="notification_channel_persistent">Persistent indicator</string>
|
||||
<string name="notification_channel_media_control">Media control</string>
|
||||
<string name="notification_channel_filetransfer">File transfer</string>
|
||||
|
||||
<string name="mpris_stop">Stop the current player</string>
|
||||
<string name="copy_url_to_clipboard">Copy URL to clipboard</string>
|
||||
<string name="clipboard_toast">Copied to clipboard</string>
|
||||
@@ -285,4 +294,14 @@
|
||||
<string name="mute">Mute</string>
|
||||
<string name="all">All</string>
|
||||
|
||||
<string name="devices">Devices</string>
|
||||
|
||||
<string name="settings_rename">Device name</string>
|
||||
<string name="settings_dark_mode">Dark theme</string>
|
||||
<string name="settings_more_settings_title">More settings</string>
|
||||
<string name="settings_more_settings_text">Per-device settings can be found under \'Plugin settings\' from within a device.</string>
|
||||
<string name="setting_persistent_notification">Show persistent notification</string>
|
||||
<string name="setting_persistent_notification_oreo_description">Required by Android since Android 8.0</string>
|
||||
<string name="setting_persistent_notification_pie_description">Since Android 9.0, this notification can only be minimized by long tapping on it</string>
|
||||
|
||||
</resources>
|
||||
|
@@ -20,6 +20,9 @@
|
||||
|
||||
package org.kde.kdeconnect;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
@@ -28,14 +31,19 @@ import android.content.SharedPreferences;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
//import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider;
|
||||
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
@@ -45,6 +53,7 @@ import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
public class BackgroundService extends Service {
|
||||
public static final int FOREGROUND_NOTIFICATION_ID = 1;
|
||||
|
||||
private static BackgroundService instance;
|
||||
|
||||
@@ -128,6 +137,13 @@ public class BackgroundService extends Service {
|
||||
for (DeviceListChangedCallback callback : deviceListChangedCallbacks.values()) {
|
||||
callback.onDeviceListChanged();
|
||||
}
|
||||
|
||||
|
||||
if (NotificationHelper.isPersistentNotificationEnabled(this)) {
|
||||
//Update the foreground notification with the currently connected device list
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
|
||||
}
|
||||
}
|
||||
|
||||
private void loadRememberedDevicesFromSettings() {
|
||||
@@ -259,6 +275,7 @@ public class BackgroundService extends Service {
|
||||
Log.i("KDE/BackgroundService", "Service not started yet, initializing...");
|
||||
|
||||
initializeSecurityParameters();
|
||||
NotificationHelper.initializeChannels(this);
|
||||
loadRememberedDevicesFromSettings();
|
||||
registerLinkProviders();
|
||||
|
||||
@@ -270,6 +287,54 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void changePersistentNotificationVisibility(boolean visible) {
|
||||
NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
if (visible) {
|
||||
nm.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
|
||||
} else {
|
||||
stopForeground(true);
|
||||
Start(this);
|
||||
}
|
||||
}
|
||||
|
||||
private Notification createForegroundNotification() {
|
||||
|
||||
//Why is this needed: https://developer.android.com/guide/components/services#Foreground
|
||||
|
||||
Intent intent = new Intent(this, MainActivity.class);
|
||||
PendingIntent pi = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(this, NotificationHelper.Channels.PERSISTENT);
|
||||
notification
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setOngoing(true)
|
||||
.setContentIntent(pi)
|
||||
.setPriority(NotificationCompat.PRIORITY_MIN) //MIN so it's not shown in the status bar before Oreo, on Oreo it will be bumped to LOW
|
||||
.setShowWhen(false)
|
||||
.setAutoCancel(false);
|
||||
notification.setGroup("BackgroundService");
|
||||
|
||||
ArrayList<String> connectedDevices = new ArrayList<>();
|
||||
for (Device device : getDevices().values()) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
connectedDevices.add(device.getName());
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
//Pre-oreo, the notification will have an empty title line without this
|
||||
notification.setContentTitle("KDE Connect");
|
||||
}
|
||||
|
||||
if (connectedDevices.isEmpty()) {
|
||||
notification.setContentText(getString(R.string.foreground_notification_no_devices));
|
||||
} else {
|
||||
notification.setContentText(getString(R.string.foreground_notification_devices, TextUtils.join(", ", connectedDevices)));
|
||||
}
|
||||
|
||||
return notification.build();
|
||||
}
|
||||
|
||||
void initializeSecurityParameters() {
|
||||
RsaHelper.initialiseRsaKeys(this);
|
||||
SslHelper.initialiseCertificate(this);
|
||||
@@ -277,6 +342,7 @@ public class BackgroundService extends Service {
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
stopForeground(true);
|
||||
for (BaseLinkProvider a : linkProviders) {
|
||||
a.onStop();
|
||||
}
|
||||
@@ -311,6 +377,10 @@ public class BackgroundService extends Service {
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
|
||||
if (NotificationHelper.isPersistentNotificationEnabled(this)) {
|
||||
startForeground(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
|
||||
}
|
||||
return Service.START_STICKY;
|
||||
}
|
||||
|
||||
|
@@ -404,7 +404,9 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
Resources res = getContext().getResources();
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(getContext())
|
||||
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
|
||||
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
|
||||
.setContentText(res.getString(R.string.tap_to_answer))
|
||||
.setContentIntent(pendingIntent)
|
||||
@@ -416,7 +418,6 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
final NotificationManager notificationManager = (NotificationManager) getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationHelper.notifyCompat(notificationManager, notificationId, noti);
|
||||
|
||||
BackgroundService.addGuiInUseCounter(context);
|
||||
|
@@ -1,10 +1,24 @@
|
||||
package org.kde.kdeconnect.Helpers;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class NotificationHelper {
|
||||
|
||||
public static class Channels {
|
||||
public final static String PERSISTENT = "persistent";
|
||||
public final static String DEFAULT = "default";
|
||||
public final static String MEDIA_CONTROL = "media_control";
|
||||
public final static String FILETRANSFER = "filetransfer";
|
||||
}
|
||||
|
||||
public static void notifyCompat(NotificationManager notificationManager, int notificationId, Notification notification) {
|
||||
try {
|
||||
notificationManager.notify(notificationId, notification);
|
||||
@@ -22,4 +36,57 @@ public class NotificationHelper {
|
||||
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
|
||||
}
|
||||
}
|
||||
|
||||
public static void initializeChannels(Context context) {
|
||||
|
||||
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.O) {
|
||||
return;
|
||||
}
|
||||
|
||||
NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationChannel persistentChannel = new NotificationChannel(
|
||||
Channels.PERSISTENT,
|
||||
context.getString(R.string.notification_channel_persistent),
|
||||
NotificationManager.IMPORTANCE_MIN);
|
||||
|
||||
manager.createNotificationChannel(persistentChannel);
|
||||
|
||||
manager.createNotificationChannel(new NotificationChannel(
|
||||
Channels.DEFAULT,
|
||||
context.getString(R.string.notification_channel_default),
|
||||
NotificationManager.IMPORTANCE_DEFAULT)
|
||||
);
|
||||
|
||||
manager.createNotificationChannel(new NotificationChannel(
|
||||
Channels.MEDIA_CONTROL,
|
||||
context.getString(R.string.notification_channel_media_control),
|
||||
NotificationManager.IMPORTANCE_LOW)
|
||||
);
|
||||
|
||||
NotificationChannel fileTransfer = new NotificationChannel(
|
||||
Channels.FILETRANSFER,
|
||||
context.getString(R.string.notification_channel_filetransfer),
|
||||
NotificationManager.IMPORTANCE_LOW);
|
||||
|
||||
fileTransfer.enableVibration(false);
|
||||
|
||||
manager.createNotificationChannel(fileTransfer);
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void setPersistentNotificationEnabled(Context context, boolean enabled) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
prefs.edit().putBoolean("persistentNotification", enabled).apply();
|
||||
}
|
||||
|
||||
public static boolean isPersistentNotificationEnabled(Context context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
return true;
|
||||
}
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getBoolean("persistentNotification", false);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -30,13 +30,15 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.media.MediaMetadataCompat;
|
||||
import android.support.v4.media.session.MediaSessionCompat;
|
||||
import android.support.v4.media.session.PlaybackStateCompat;
|
||||
import android.support.v7.app.NotificationCompat;
|
||||
import android.support.v4.media.app.NotificationCompat.MediaStyle;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.HashSet;
|
||||
@@ -304,8 +306,10 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
iOpenActivity.putExtra("player", notificationPlayer.getPlayer());
|
||||
PendingIntent piOpenActivity = PendingIntent.getActivity(service, 0, iOpenActivity, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
|
||||
//Create the notification
|
||||
final NotificationCompat.Builder notification = new NotificationCompat.Builder(service);
|
||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
NotificationCompat.Builder notification = new NotificationCompat.Builder(context, NotificationHelper.Channels.MEDIA_CONTROL);
|
||||
|
||||
notification
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(piOpenActivity)
|
||||
@@ -377,7 +381,7 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
|
||||
//Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
|
||||
NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle();
|
||||
MediaStyle mediaStyle = new MediaStyle();
|
||||
if (numActions == 1) {
|
||||
mediaStyle.setShowActionsInCompactView(0);
|
||||
} else if (numActions == 2) {
|
||||
@@ -387,6 +391,7 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
}
|
||||
mediaStyle.setMediaSession(mediaSession.getSessionToken());
|
||||
notification.setStyle(mediaStyle);
|
||||
notification.setGroup("MprisMediaSession");
|
||||
|
||||
//Display the notification
|
||||
mediaSession.setActive(true);
|
||||
|
@@ -45,8 +45,8 @@ import android.util.Log;
|
||||
import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
|
||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
@@ -84,7 +84,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreferencesActivity(final SettingsActivity parentActivity) {
|
||||
public void startPreferencesActivity(final DeviceSettingsActivity parentActivity) {
|
||||
if (hasPermission()) {
|
||||
Intent intent = new Intent(parentActivity, NotificationFilterActivity.class);
|
||||
parentActivity.startActivity(intent);
|
||||
|
@@ -79,7 +79,9 @@ public class PingPlugin extends Plugin {
|
||||
id = 42; //A unique id to create only one notification
|
||||
}
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
||||
.setContentTitle(device.getName())
|
||||
.setContentText(message)
|
||||
.setContentIntent(resultPendingIntent)
|
||||
@@ -89,7 +91,6 @@ public class PingPlugin extends Plugin {
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationHelper.notifyCompat(notificationManager, id, noti);
|
||||
|
||||
return true;
|
||||
|
@@ -34,8 +34,8 @@ import android.widget.Button;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
|
||||
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public abstract class Plugin {
|
||||
@@ -126,9 +126,9 @@ public abstract class Plugin {
|
||||
* If hasSettings returns true, this will be called when the user
|
||||
* wants to access this plugin preferences and should launch some
|
||||
* kind of interface. The default implementation will launch a
|
||||
* SettingsActivity with content from "yourplugin"_preferences.xml.
|
||||
* PluginSettingsActivity with content from "yourplugin"_preferences.xml.
|
||||
*/
|
||||
public void startPreferencesActivity(SettingsActivity parentActivity) {
|
||||
public void startPreferencesActivity(DeviceSettingsActivity parentActivity) {
|
||||
Intent intent = new Intent(parentActivity, PluginSettingsActivity.class);
|
||||
intent.putExtra("plugin_display_name", getDisplayName());
|
||||
intent.putExtra("plugin_key", getPluginKey());
|
||||
|
@@ -105,7 +105,10 @@ public class ReceiveNotificationsPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
||||
.setContentTitle(np.getString("appName"))
|
||||
.setContentText(np.getString("ticker"))
|
||||
.setContentIntent(resultPendingIntent)
|
||||
@@ -117,7 +120,6 @@ public class ReceiveNotificationsPlugin extends Plugin {
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationHelper.notifyCompat(notificationManager, "kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
|
||||
|
||||
}
|
||||
|
@@ -51,9 +51,14 @@ public class SftpPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
server.init(context, device);
|
||||
permissionExplanation = R.string.sftp_permission_explanation;
|
||||
return true;
|
||||
try {
|
||||
server.init(context, device);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -27,24 +27,25 @@ import android.util.Log;
|
||||
import org.apache.sshd.SshServer;
|
||||
import org.apache.sshd.common.NamedFactory;
|
||||
import org.apache.sshd.common.Session;
|
||||
import org.apache.sshd.common.file.FileSystemFactory;
|
||||
import org.apache.sshd.common.file.FileSystemView;
|
||||
import org.apache.sshd.common.file.SshFile;
|
||||
import org.apache.sshd.common.file.nativefs.NativeFileSystemView;
|
||||
import org.apache.sshd.common.file.nativefs.NativeSshFile;
|
||||
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
|
||||
import org.apache.sshd.common.util.SecurityUtils;
|
||||
import org.apache.sshd.server.Command;
|
||||
import org.apache.sshd.server.FileSystemFactory;
|
||||
import org.apache.sshd.server.FileSystemView;
|
||||
import org.apache.sshd.server.PasswordAuthenticator;
|
||||
import org.apache.sshd.server.PublickeyAuthenticator;
|
||||
import org.apache.sshd.server.SshFile;
|
||||
import org.apache.sshd.server.command.ScpCommandFactory;
|
||||
import org.apache.sshd.server.filesystem.NativeFileSystemView;
|
||||
import org.apache.sshd.server.filesystem.NativeSshFile;
|
||||
import org.apache.sshd.server.kex.DHG1;
|
||||
import org.apache.sshd.server.kex.DHG14;
|
||||
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
|
||||
import org.apache.sshd.server.session.ServerSession;
|
||||
import org.apache.sshd.server.sftp.SftpSubsystem;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
||||
import org.kde.kdeconnect.Helpers.RandomHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||
|
||||
import java.io.File;
|
||||
@@ -56,6 +57,8 @@ import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.NetworkInterface;
|
||||
import java.net.SocketException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Security;
|
||||
import java.util.Arrays;
|
||||
@@ -81,13 +84,23 @@ class SimpleSftpServer {
|
||||
|
||||
private final SshServer sshd = SshServer.setUpDefaultServer();
|
||||
|
||||
public void init(Context context, Device device) {
|
||||
public void init(Context context, Device device) throws Exception {
|
||||
|
||||
sshd.setKeyExchangeFactories(Arrays.asList(
|
||||
new DHG14.Factory(),
|
||||
new DHG1.Factory()));
|
||||
|
||||
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(context.getFilesDir() + "/sftpd.ser"));
|
||||
//Reuse this device keys for the ssh connection as well
|
||||
final KeyPair keyPair;
|
||||
PrivateKey privateKey = RsaHelper.getPrivateKey(context);
|
||||
PublicKey publicKey = RsaHelper.getPublicKey(context);
|
||||
keyPair = new KeyPair(publicKey, privateKey);
|
||||
sshd.setKeyPairProvider(new AbstractKeyPairProvider() {
|
||||
@Override
|
||||
public Iterable<KeyPair> loadKeys() {
|
||||
return Collections.singletonList(keyPair);
|
||||
}
|
||||
});
|
||||
|
||||
sshd.setFileSystemFactory(new AndroidFileSystemFactory(context));
|
||||
sshd.setCommandFactory(new ScpCommandFactory());
|
||||
@@ -204,7 +217,7 @@ class SimpleSftpServer {
|
||||
@Override
|
||||
protected SshFile getFile(final String dir, final String file) {
|
||||
File fileObj = new File(dir, file);
|
||||
return new AndroidSshFile(fileObj, userName, context);
|
||||
return new AndroidSshFile(this, fileObj, userName, context);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -213,8 +226,8 @@ class SimpleSftpServer {
|
||||
final private Context context;
|
||||
final private File file;
|
||||
|
||||
public AndroidSshFile(final File file, final String userName, Context context) {
|
||||
super(file.getAbsolutePath(), file, userName);
|
||||
public AndroidSshFile(final AndroidFileSystemView view, final File file, final String userName, Context context) {
|
||||
super(view, file.getAbsolutePath(), file, userName);
|
||||
this.context = context;
|
||||
this.file = file;
|
||||
}
|
||||
|
@@ -39,8 +39,9 @@ class NotificationUpdateCallback extends Device.SendPacketStatusCallback {
|
||||
} else {
|
||||
title = res.getString(R.string.outgoing_file_title, device.getName());
|
||||
}
|
||||
notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
builder = new NotificationCompat.Builder(context)
|
||||
|
||||
notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
builder = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_upload)
|
||||
.setAutoCancel(true)
|
||||
.setProgress(100, 0, false)
|
||||
|
@@ -55,7 +55,7 @@ public class ShareNotification {
|
||||
this.filename = filename;
|
||||
notificationId = (int) System.currentTimeMillis();
|
||||
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
builder = new NotificationCompat.Builder(device.getContext())
|
||||
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.FILETRANSFER)
|
||||
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
|
||||
.setContentText(device.getContext().getResources().getString(R.string.incoming_file_text, filename))
|
||||
.setTicker(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
|
||||
@@ -80,7 +80,7 @@ public class ShareNotification {
|
||||
|
||||
public void setFinished(boolean success) {
|
||||
String message = success ? device.getContext().getResources().getString(R.string.received_file_title, device.getName()) : device.getContext().getResources().getString(R.string.received_file_fail_title, device.getName());
|
||||
builder = new NotificationCompat.Builder(device.getContext());
|
||||
builder = new NotificationCompat.Builder(device.getContext(), NotificationHelper.Channels.DEFAULT);
|
||||
builder.setContentTitle(message)
|
||||
.setTicker(message)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
|
@@ -50,7 +50,7 @@ import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.SettingsActivity;
|
||||
import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.File;
|
||||
@@ -159,7 +159,9 @@ public class SharePlugin extends Plugin {
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
);
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context)
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
|
||||
Notification noti = new NotificationCompat.Builder(context, NotificationHelper.Channels.DEFAULT)
|
||||
.setContentTitle(res.getString(R.string.received_url_title, device.getName()))
|
||||
.setContentText(res.getString(R.string.received_url_text, url))
|
||||
.setContentIntent(resultPendingIntent)
|
||||
@@ -169,7 +171,6 @@ public class SharePlugin extends Plugin {
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.build();
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
NotificationHelper.notifyCompat(notificationManager, (int) System.currentTimeMillis(), noti);
|
||||
}
|
||||
}
|
||||
@@ -280,7 +281,7 @@ public class SharePlugin extends Plugin {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startPreferencesActivity(SettingsActivity parentActivity) {
|
||||
public void startPreferencesActivity(DeviceSettingsActivity parentActivity) {
|
||||
Intent intent = new Intent(parentActivity, ShareSettingsActivity.class);
|
||||
intent.putExtra("plugin_display_name", getDisplayName());
|
||||
intent.putExtra("plugin_key", getPluginKey());
|
||||
|
@@ -182,6 +182,7 @@ public class DeviceFragment extends Fragment {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//Plugins button list
|
||||
final Collection<Plugin> plugins = device.getLoadedPlugins().values();
|
||||
for (final Plugin p : plugins) {
|
||||
@@ -195,7 +196,7 @@ public class DeviceFragment extends Fragment {
|
||||
}
|
||||
|
||||
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(menuItem -> {
|
||||
Intent intent = new Intent(mActivity, SettingsActivity.class);
|
||||
Intent intent = new Intent(mActivity, DeviceSettingsActivity.class);
|
||||
intent.putExtra("deviceId", mDeviceId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
|
@@ -29,7 +29,7 @@ import org.kde.kdeconnect.Device;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
public class DeviceSettingsActivity extends AppCompatPreferenceActivity {
|
||||
|
||||
static private String deviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
|
||||
|
||||
@@ -47,12 +47,12 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
BackgroundService.RunCommand(getApplicationContext(), service -> {
|
||||
final Device device = service.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
SettingsActivity.this.runOnUiThread(SettingsActivity.this::finish);
|
||||
DeviceSettingsActivity.this.runOnUiThread(DeviceSettingsActivity.this::finish);
|
||||
return;
|
||||
}
|
||||
List<String> plugins = device.getSupportedPlugins();
|
||||
for (final String pluginKey : plugins) {
|
||||
PluginPreference pref = new PluginPreference(SettingsActivity.this, pluginKey, device);
|
||||
PluginPreference pref = new PluginPreference(DeviceSettingsActivity.this, pluginKey, device);
|
||||
preferenceScreen.addPreference(pref);
|
||||
}
|
||||
});
|
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.Resources;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -24,10 +25,12 @@ import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.SubMenu;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
@@ -36,10 +39,19 @@ import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.jar.Attributes;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
|
||||
private static final String STATE_SELECTED_DEVICE = "selected_device";
|
||||
private static final int MENU_ENTRY_ADD_DEVICE = 1; //0 means no-selection
|
||||
private static final int MENU_ENTRY_SETTINGS = 2;
|
||||
private static final int MENU_ENTRY_DEVICE_FIRST_ID = 1000; //All subsequent ids are devices in the menu
|
||||
private static final int MENU_ENTRY_DEVICE_UNKNOWN = 9999; //It's still a device, but we don't know which one yet
|
||||
|
||||
private static final String STATE_SELECTED_MENU_ENTRY = "selected_entry"; //Saved only in onSaveInstanceState
|
||||
private static final String STATE_SELECTED_DEVICE = "selected_device"; //Saved persistently in preferences
|
||||
|
||||
public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER;
|
||||
|
||||
@@ -51,10 +63,12 @@ public class MainActivity extends AppCompatActivity {
|
||||
private DrawerLayout mDrawerLayout;
|
||||
|
||||
private String mCurrentDevice;
|
||||
private int mCurrentMenuEntry;
|
||||
|
||||
private SharedPreferences preferences;
|
||||
|
||||
private final HashMap<MenuItem, String> mMapMenuToDeviceId = new HashMap<>();
|
||||
private String pairRequestStatus;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -64,11 +78,11 @@ public class MainActivity extends AppCompatActivity {
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mNavigationView = (NavigationView) findViewById(R.id.navigation_drawer);
|
||||
mDrawerLayout = findViewById(R.id.drawer_layout);
|
||||
mNavigationView = findViewById(R.id.navigation_drawer);
|
||||
View mDrawerHeader = mNavigationView.getHeaderView(0);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
Toolbar toolbar = findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
@@ -87,100 +101,111 @@ public class MainActivity extends AppCompatActivity {
|
||||
mDrawerToggle.syncState();
|
||||
|
||||
String deviceName = DeviceHelper.getDeviceName(this);
|
||||
TextView nameView = (TextView) mDrawerHeader.findViewById(R.id.device_name);
|
||||
TextView nameView = mDrawerHeader.findViewById(R.id.device_name);
|
||||
nameView.setText(deviceName);
|
||||
|
||||
View.OnClickListener renameListener = v -> renameDevice();
|
||||
View.OnClickListener renameListener = v -> openRenameDeviceDialog();
|
||||
mDrawerHeader.findViewById(R.id.kdeconnect_label).setOnClickListener(renameListener);
|
||||
mDrawerHeader.findViewById(R.id.device_name).setOnClickListener(renameListener);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
addDarkModeSwitch((ViewGroup) mDrawerHeader);
|
||||
}
|
||||
preferences = getSharedPreferences("stored_menu_selection", Context.MODE_PRIVATE);
|
||||
|
||||
mNavigationView.setNavigationItemSelectedListener(menuItem -> {
|
||||
|
||||
String deviceId = mMapMenuToDeviceId.get(menuItem);
|
||||
onDeviceSelected(deviceId);
|
||||
mCurrentMenuEntry = menuItem.getItemId();
|
||||
switch (mCurrentMenuEntry) {
|
||||
case MENU_ENTRY_ADD_DEVICE:
|
||||
mCurrentDevice = null;
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
|
||||
setContentFragment(new PairingFragment());
|
||||
break;
|
||||
case MENU_ENTRY_SETTINGS:
|
||||
mCurrentDevice = null;
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
|
||||
setContentFragment(new SettingsFragment());
|
||||
break;
|
||||
default:
|
||||
assert(mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID);
|
||||
String deviceId = mMapMenuToDeviceId.get(menuItem);
|
||||
onDeviceSelected(deviceId);
|
||||
break;
|
||||
}
|
||||
|
||||
mDrawerLayout.closeDrawer(mNavigationView);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
preferences = getSharedPreferences(STATE_SELECTED_DEVICE, Context.MODE_PRIVATE);
|
||||
// Decide which menu entry should be selected at start
|
||||
|
||||
String savedDevice;
|
||||
String pairStatus = "";
|
||||
int savedMenuEntry;
|
||||
if (getIntent().hasExtra("forceOverview")) {
|
||||
Log.i("MainActivity", "Requested to start main overview");
|
||||
savedDevice = null;
|
||||
savedMenuEntry = MENU_ENTRY_ADD_DEVICE;
|
||||
} else if (getIntent().hasExtra("deviceId")) {
|
||||
Log.i("MainActivity", "Loading selected device from parameter");
|
||||
savedDevice = getIntent().getStringExtra("deviceId");
|
||||
if (getIntent().hasExtra(PAIR_REQUEST_STATUS)) {
|
||||
pairStatus = getIntent().getStringExtra(PAIR_REQUEST_STATUS);
|
||||
savedMenuEntry = MENU_ENTRY_DEVICE_UNKNOWN;
|
||||
// If pairStatus is not empty, then the user has accepted/reject the pairing from the notification
|
||||
String pairStatus = getIntent().getStringExtra(PAIR_REQUEST_STATUS);
|
||||
if (pairStatus != null) {
|
||||
Log.i("MainActivity", "pair status is " + pairStatus);
|
||||
onPairResultFromNotification(savedDevice, pairStatus);
|
||||
}
|
||||
} else if (savedInstanceState != null) {
|
||||
Log.i("MainActivity", "Loading selected device from saved activity state");
|
||||
savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE);
|
||||
savedMenuEntry = savedInstanceState.getInt(STATE_SELECTED_MENU_ENTRY, MENU_ENTRY_ADD_DEVICE);
|
||||
} else {
|
||||
Log.i("MainActivity", "Loading selected device from persistent storage");
|
||||
savedDevice = preferences.getString(STATE_SELECTED_DEVICE, null);
|
||||
savedMenuEntry = (savedDevice != null)? MENU_ENTRY_DEVICE_UNKNOWN : MENU_ENTRY_ADD_DEVICE;
|
||||
}
|
||||
//if pairStatus is not empty, then the decision has been made...
|
||||
if (!pairStatus.equals("")) {
|
||||
Log.i("MainActivity", "pair status is " + pairStatus);
|
||||
onNewDeviceSelected(savedDevice, pairStatus);
|
||||
}
|
||||
onDeviceSelected(savedDevice);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a {@link SwitchCompat} to the bottom of the navigation header for
|
||||
* toggling dark mode on and off. Call from {@link #onCreate(Bundle)}.
|
||||
* <p>
|
||||
* Only supports android ICS and higher because {@link SwitchCompat}
|
||||
* requires that.
|
||||
* </p>
|
||||
*
|
||||
* @param drawerHeader the layout which should contain the switch
|
||||
*/
|
||||
@RequiresApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
|
||||
private void addDarkModeSwitch(ViewGroup drawerHeader) {
|
||||
getLayoutInflater().inflate(R.layout.nav_dark_mode_switch, drawerHeader);
|
||||
|
||||
SwitchCompat darkThemeSwitch = (SwitchCompat) drawerHeader.findViewById(R.id.dark_theme);
|
||||
darkThemeSwitch.setChecked(ThemeUtil.shouldUseDarkTheme(this));
|
||||
darkThemeSwitch.setOnCheckedChangeListener((darkThemeSwitch1, isChecked) -> {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(MainActivity.this);
|
||||
boolean isDarkAlready = prefs.getBoolean("darkTheme", false);
|
||||
if (isDarkAlready != isChecked) {
|
||||
prefs.edit().putBoolean("darkTheme", isChecked).apply();
|
||||
MainActivity.this.recreate();
|
||||
// Activate the chosen fragment and select the entry in the menu
|
||||
|
||||
if (savedMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID && savedDevice != null) {
|
||||
onDeviceSelected(savedDevice);
|
||||
} else {
|
||||
mCurrentMenuEntry = savedMenuEntry;
|
||||
mNavigationView.setCheckedItem(savedMenuEntry);
|
||||
if (mCurrentMenuEntry == MENU_ENTRY_SETTINGS) {
|
||||
setContentFragment(new SettingsFragment());
|
||||
} else {
|
||||
setContentFragment(new PairingFragment());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//like onNewDeviceSelected but assumes that the new device is simply requesting to be paired
|
||||
//and can't be null
|
||||
private void onNewDeviceSelected(String deviceId, String pairStatus) {
|
||||
|
||||
private void onPairResultFromNotification(String deviceId, String pairStatus) {
|
||||
|
||||
assert(deviceId != null);
|
||||
|
||||
mCurrentDevice = deviceId;
|
||||
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, mCurrentDevice).apply();
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, null).apply();
|
||||
|
||||
for (HashMap.Entry<MenuItem, String> entry : mMapMenuToDeviceId.entrySet()) {
|
||||
boolean selected = TextUtils.equals(entry.getValue(), deviceId); //null-safe
|
||||
entry.getKey().setChecked(selected);
|
||||
}
|
||||
mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice);
|
||||
mNavigationView.setCheckedItem(mCurrentMenuEntry);
|
||||
|
||||
if (pairStatus.equals(PAIRING_ACCEPTED)) {
|
||||
DeviceFragment.acceptPairing(deviceId, this);
|
||||
DeviceFragment.acceptPairing(mCurrentDevice, this);
|
||||
} else {
|
||||
DeviceFragment.rejectPairing(deviceId, this);
|
||||
DeviceFragment.rejectPairing(mCurrentDevice, this);
|
||||
}
|
||||
}
|
||||
|
||||
private int deviceIdToMenuEntryId(String deviceId) {
|
||||
for (HashMap.Entry<MenuItem, String> entry : mMapMenuToDeviceId.entrySet()) {
|
||||
if (TextUtils.equals(entry.getValue(), deviceId)) { //null-safe
|
||||
return entry.getKey().getItemId();
|
||||
}
|
||||
}
|
||||
return MENU_ENTRY_DEVICE_UNKNOWN;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (mDrawerLayout.isDrawerOpen(mNavigationView)) {
|
||||
@@ -200,43 +225,50 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
private void updateComputerList() {
|
||||
|
||||
//Log.e("MainActivity", "UpdateComputerList");
|
||||
private void updateDeviceList() {
|
||||
|
||||
BackgroundService.RunCommand(MainActivity.this, service -> {
|
||||
|
||||
Menu menu = mNavigationView.getMenu();
|
||||
|
||||
menu.clear();
|
||||
mMapMenuToDeviceId.clear();
|
||||
|
||||
int id = 0;
|
||||
SubMenu devicesMenu = menu.addSubMenu(R.string.devices);
|
||||
|
||||
int id = MENU_ENTRY_DEVICE_FIRST_ID;
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
MenuItem item = menu.add(0, id++, 0, device.getName());
|
||||
MenuItem item = devicesMenu.add(Menu.FIRST, id++, 1, device.getName());
|
||||
item.setIcon(device.getIcon());
|
||||
item.setCheckable(true);
|
||||
item.setChecked(device.getDeviceId().equals(mCurrentDevice));
|
||||
mMapMenuToDeviceId.put(item, device.getDeviceId());
|
||||
}
|
||||
}
|
||||
|
||||
MenuItem item = menu.add(99, id++, 0, R.string.pair_new_device);
|
||||
item.setIcon(R.drawable.ic_action_content_add_circle_outline);
|
||||
item.setCheckable(true);
|
||||
item.setChecked(mCurrentDevice == null);
|
||||
mMapMenuToDeviceId.put(item, null);
|
||||
MenuItem addDeviceItem = devicesMenu.add(Menu.FIRST, MENU_ENTRY_ADD_DEVICE, 1000, R.string.pair_new_device);
|
||||
addDeviceItem.setIcon(R.drawable.ic_action_content_add_circle_outline);
|
||||
addDeviceItem.setCheckable(true);
|
||||
|
||||
MenuItem settingsItem = menu.add(Menu.FIRST, MENU_ENTRY_SETTINGS, 1000, R.string.settings);
|
||||
settingsItem.setIcon(R.drawable.ic_action_settings);
|
||||
settingsItem.setCheckable(true);
|
||||
|
||||
//Ids might have changed
|
||||
if (mCurrentMenuEntry >= MENU_ENTRY_DEVICE_FIRST_ID) {
|
||||
mCurrentMenuEntry = deviceIdToMenuEntryId(mCurrentDevice);
|
||||
}
|
||||
mNavigationView.setCheckedItem(mCurrentMenuEntry);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
BackgroundService.addGuiInUseCounter(this, true);
|
||||
BackgroundService.RunCommand(this, service -> service.addDeviceListChangedCallback("MainActivity", this::updateComputerList));
|
||||
updateComputerList();
|
||||
BackgroundService.RunCommand(this, service -> service.addDeviceListChangedCallback("MainActivity", this::updateDeviceList));
|
||||
updateDeviceList();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,27 +278,38 @@ public class MainActivity extends AppCompatActivity {
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
//TODO: Make it accept two parameters, a constant with the type of screen and the device id in
|
||||
//case the screen is for a device, or even three parameters and the third one be the plugin id?
|
||||
//This way we can keep adding more options with null device id (eg: about, help...)
|
||||
private static void uncheckAllMenuItems(Menu menu) {
|
||||
int size = menu.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
MenuItem item = menu.getItem(i);
|
||||
if(item.hasSubMenu()) {
|
||||
uncheckAllMenuItems(item.getSubMenu());
|
||||
} else {
|
||||
item.setChecked(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onDeviceSelected(String deviceId, boolean fromDeviceList) {
|
||||
|
||||
mCurrentDevice = deviceId;
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, deviceId).apply();
|
||||
|
||||
preferences.edit().putString(STATE_SELECTED_DEVICE, mCurrentDevice).apply();
|
||||
|
||||
for (HashMap.Entry<MenuItem, String> entry : mMapMenuToDeviceId.entrySet()) {
|
||||
boolean selected = TextUtils.equals(entry.getValue(), deviceId); //null-safe
|
||||
entry.getKey().setChecked(selected);
|
||||
}
|
||||
|
||||
Fragment fragment;
|
||||
if (deviceId == null) {
|
||||
fragment = new PairingFragment();
|
||||
if (mCurrentDevice != null) {
|
||||
mCurrentMenuEntry = deviceIdToMenuEntryId(deviceId);
|
||||
if (mCurrentMenuEntry == MENU_ENTRY_DEVICE_UNKNOWN) {
|
||||
uncheckAllMenuItems(mNavigationView.getMenu());
|
||||
} else {
|
||||
mNavigationView.setCheckedItem(mCurrentMenuEntry);
|
||||
}
|
||||
setContentFragment(new DeviceFragment(deviceId, fromDeviceList));
|
||||
} else {
|
||||
fragment = new DeviceFragment(deviceId, fromDeviceList);
|
||||
mCurrentMenuEntry = MENU_ENTRY_ADD_DEVICE;
|
||||
mNavigationView.setCheckedItem(mCurrentMenuEntry);
|
||||
setContentFragment(new PairingFragment());
|
||||
}
|
||||
}
|
||||
|
||||
private void setContentFragment(Fragment fragment) {
|
||||
getSupportFragmentManager()
|
||||
.beginTransaction()
|
||||
.replace(R.id.container, fragment)
|
||||
@@ -281,13 +324,7 @@ public class MainActivity extends AppCompatActivity {
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(STATE_SELECTED_DEVICE, mCurrentDevice);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
String savedDevice = savedInstanceState.getString(STATE_SELECTED_DEVICE);
|
||||
onDeviceSelected(savedDevice);
|
||||
outState.putInt(STATE_SELECTED_MENU_ENTRY, mCurrentMenuEntry);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -317,28 +354,45 @@ public class MainActivity extends AppCompatActivity {
|
||||
}
|
||||
}
|
||||
|
||||
public void renameDevice() {
|
||||
final TextView nameView = (TextView) mNavigationView.findViewById(R.id.device_name);
|
||||
final EditText deviceNameEdit = new EditText(MainActivity.this);
|
||||
String deviceName = DeviceHelper.getDeviceName(MainActivity.this);
|
||||
|
||||
interface NameChangeCallback {
|
||||
void onNameChanged(String newName);
|
||||
}
|
||||
|
||||
private Set<NameChangeCallback> nameChangeSubscribers = new HashSet<>();
|
||||
|
||||
public void addNameChangeCallback(NameChangeCallback cb) {
|
||||
nameChangeSubscribers.add(cb);
|
||||
}
|
||||
|
||||
public void removeNameChangeCallback(NameChangeCallback cb) {
|
||||
nameChangeSubscribers.remove(cb);
|
||||
}
|
||||
|
||||
public void openRenameDeviceDialog() {
|
||||
final EditText deviceNameEdit = new EditText(this);
|
||||
String deviceName = DeviceHelper.getDeviceName(this);
|
||||
deviceNameEdit.setText(deviceName);
|
||||
deviceNameEdit.setPadding(
|
||||
((int) (18 * getResources().getDisplayMetrics().density)),
|
||||
((int) (16 * getResources().getDisplayMetrics().density)),
|
||||
((int) (18 * getResources().getDisplayMetrics().density)),
|
||||
((int) (12 * getResources().getDisplayMetrics().density))
|
||||
);
|
||||
new AlertDialog.Builder(MainActivity.this)
|
||||
float dpi = this.getResources().getDisplayMetrics().density;
|
||||
deviceNameEdit.setPadding( ((int) (18 * dpi)), ((int) (16 * dpi)), ((int) (18 * dpi)), ((int) (12 * dpi)) );
|
||||
new AlertDialog.Builder(this)
|
||||
.setView(deviceNameEdit)
|
||||
.setPositiveButton(R.string.device_rename_confirm, (dialog, which) -> {
|
||||
String deviceName1 = deviceNameEdit.getText().toString();
|
||||
DeviceHelper.setDeviceName(MainActivity.this, deviceName1);
|
||||
nameView.setText(deviceName1);
|
||||
BackgroundService.RunCommand(MainActivity.this, BackgroundService::onNetworkChange);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
String newDeviceName = deviceNameEdit.getText().toString();
|
||||
DeviceHelper.setDeviceName(this, newDeviceName);
|
||||
this.updateDeviceNameFromMenu(newDeviceName);
|
||||
BackgroundService.RunCommand(this, BackgroundService::onNetworkChange);
|
||||
for (NameChangeCallback callback : nameChangeSubscribers) {
|
||||
callback.onNameChanged(newDeviceName);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> { })
|
||||
.setTitle(R.string.device_rename_title)
|
||||
.show();
|
||||
}
|
||||
|
||||
public void updateDeviceNameFromMenu(String newDeviceName) {
|
||||
final TextView nameView = mNavigationView.findViewById(R.id.device_name);
|
||||
nameView.setText(newDeviceName);
|
||||
}
|
||||
}
|
||||
|
@@ -237,9 +237,6 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
case R.id.menu_refresh:
|
||||
updateComputerListAction();
|
||||
break;
|
||||
case R.id.menu_rename:
|
||||
mActivity.renameDevice();
|
||||
break;
|
||||
case R.id.menu_custom_device_list:
|
||||
startActivity(new Intent(mActivity, CustomDevicesActivity.class));
|
||||
break;
|
||||
|
@@ -14,7 +14,7 @@ public class PluginPreference extends CheckBoxPreference {
|
||||
final String pluginKey;
|
||||
final View.OnClickListener listener;
|
||||
|
||||
public PluginPreference(final SettingsActivity activity, final String pluginKey, final Device device) {
|
||||
public PluginPreference(final DeviceSettingsActivity activity, final String pluginKey, final Device device) {
|
||||
super(activity);
|
||||
|
||||
setLayoutResource(R.layout.preference_with_button);
|
||||
|
127
src/org/kde/kdeconnect/UserInterface/SettingsFragment.java
Normal file
127
src/org/kde/kdeconnect/UserInterface/SettingsFragment.java
Normal file
@@ -0,0 +1,127 @@
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.v14.preference.SwitchPreference;
|
||||
import android.support.v7.preference.CheckBoxPreference;
|
||||
import android.support.v7.preference.Preference;
|
||||
import android.support.v7.preference.PreferenceFragmentCompat;
|
||||
import android.support.v7.preference.PreferenceScreen;
|
||||
import android.support.v7.preference.TwoStatePreference;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class SettingsFragment extends PreferenceFragmentCompat implements MainActivity.NameChangeCallback {
|
||||
|
||||
MainActivity mainActivity;
|
||||
private Preference renameDevice;
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
mainActivity.removeNameChangeCallback(this);
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
TwoStatePreference createTwoStatePreferenceCompat(Context context) {
|
||||
//SwitchPreference doesn't work on Android 6
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
|
||||
return new SwitchPreference(context);
|
||||
} else {
|
||||
return new CheckBoxPreference(context);
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
|
||||
mainActivity = (MainActivity)getActivity();
|
||||
Context context = mainActivity;
|
||||
|
||||
PreferenceScreen screen = getPreferenceManager().createPreferenceScreen(context);
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
||||
// Rename device
|
||||
mainActivity.addNameChangeCallback(this);
|
||||
renameDevice = new Preference(context);
|
||||
renameDevice.setPersistent(false);
|
||||
renameDevice.setSelectable(true);
|
||||
renameDevice.setOnPreferenceClickListener(preference -> {
|
||||
mainActivity.openRenameDeviceDialog();
|
||||
return true;
|
||||
});
|
||||
String deviceName = DeviceHelper.getDeviceName(context);
|
||||
renameDevice.setTitle(R.string.settings_rename);
|
||||
renameDevice.setSummary(deviceName);
|
||||
screen.addPreference(renameDevice);
|
||||
|
||||
|
||||
//TODO: Trusted wifi networks settings should go here
|
||||
|
||||
|
||||
// Dark mode
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
final TwoStatePreference darkThemeSwitch = createTwoStatePreferenceCompat(context);
|
||||
darkThemeSwitch.setPersistent(false);
|
||||
darkThemeSwitch.setChecked(ThemeUtil.shouldUseDarkTheme(context));
|
||||
darkThemeSwitch.setTitle(R.string.settings_dark_mode);
|
||||
darkThemeSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
boolean isChecked = (Boolean)newValue;
|
||||
boolean isDarkAlready = prefs.getBoolean("darkTheme", false);
|
||||
if (isDarkAlready != isChecked) {
|
||||
prefs.edit().putBoolean("darkTheme", isChecked).apply();
|
||||
if (mainActivity != null) {
|
||||
mainActivity.recreate();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
screen.addPreference(darkThemeSwitch);
|
||||
}
|
||||
|
||||
// Persistent notification toggle
|
||||
final TwoStatePreference notificationSwitch = createTwoStatePreferenceCompat(context);
|
||||
notificationSwitch.setPersistent(false);
|
||||
notificationSwitch.setChecked(NotificationHelper.isPersistentNotificationEnabled(context));
|
||||
notificationSwitch.setTitle(R.string.setting_persistent_notification);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
notificationSwitch.setSummary(R.string.setting_persistent_notification_pie_description);
|
||||
notificationSwitch.setEnabled(false);
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
notificationSwitch.setSummary(R.string.setting_persistent_notification_oreo_description);
|
||||
notificationSwitch.setEnabled(false);
|
||||
}
|
||||
notificationSwitch.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
final boolean isChecked = (Boolean)newValue;
|
||||
NotificationHelper.setPersistentNotificationEnabled(context, isChecked);
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
service.changePersistentNotificationVisibility(isChecked);
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
screen.addPreference(notificationSwitch);
|
||||
|
||||
// More settings text
|
||||
Preference moreSettingsText = new Preference(context);
|
||||
moreSettingsText.setPersistent(false);
|
||||
moreSettingsText.setSelectable(false);
|
||||
moreSettingsText.setTitle(R.string.settings_more_settings_title);
|
||||
moreSettingsText.setSummary(R.string.settings_more_settings_text);
|
||||
screen.addPreference(moreSettingsText);
|
||||
|
||||
setPreferenceScreen(screen);
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNameChanged(String newName) {
|
||||
renameDevice.setSummary(newName);
|
||||
}
|
||||
|
||||
}
|
Reference in New Issue
Block a user