mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-01 14:45:08 +00:00
Compare commits
57 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
55a0ad657e | ||
|
8add0c4fbe | ||
|
187e47aec2 | ||
|
27b390a9bd | ||
|
9bf5a9c489 | ||
|
a907c02ecf | ||
|
4deeb5e061 | ||
|
ff2354a7a6 | ||
|
5463be96a4 | ||
|
0c6b584d57 | ||
|
e712c69e15 | ||
|
7536eb7427 | ||
|
04e5f6b451 | ||
|
f75bf2129a | ||
|
acd98da83a | ||
|
d6c3803ea2 | ||
|
d688621f27 | ||
|
774759746d | ||
|
d65b3153ae | ||
|
8a2ea77030 | ||
|
a1f1693d0b | ||
|
b228fb2377 | ||
|
bc330018e1 | ||
|
9837c89f35 | ||
|
4d2357f016 | ||
|
d4d9e8ba6d | ||
|
2cd7d4b7a9 | ||
|
8bbd3f55b1 | ||
|
1bf96454cf | ||
|
6883ed2847 | ||
|
91be51ea5a | ||
|
3bb54816ed | ||
|
810f53449a | ||
|
efa4668448 | ||
|
b70370005b | ||
|
9631cacb69 | ||
|
94ef1294c5 | ||
|
7c8301a7cc | ||
|
64fd08f3ac | ||
|
e67e43efa1 | ||
|
fda08929af | ||
|
2cb025e368 | ||
|
cf808c03ba | ||
|
2b52cd1547 | ||
|
3d4bf643d4 | ||
|
00b6677aa4 | ||
|
bf0cab9ef2 | ||
|
1c3e6f84a7 | ||
|
c8dbbb1fe8 | ||
|
a17b75264d | ||
|
7276e60aa4 | ||
|
7877d2803c | ||
|
b1c4b6e1e9 | ||
|
eb801fa535 | ||
|
ac4aaf1b39 | ||
|
9840a39992 | ||
|
e73c18d2e3 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ gradlew.bat
|
||||
*.iml
|
||||
*.keystore
|
||||
.directory
|
||||
GPUCache/
|
||||
|
@@ -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="1800"
|
||||
android:versionName="1.8.0">
|
||||
android:versionCode="1830"
|
||||
android:versionName="1.8.3">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
@@ -22,18 +22,10 @@
|
||||
<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"
|
||||
android:required="false" />
|
||||
<uses-permission
|
||||
android:name="android.permission.RECEIVE_SMS"
|
||||
android:required="false" />
|
||||
<uses-permission
|
||||
android:name="android.permission.SEND_SMS"
|
||||
android:required="false" />
|
||||
<uses-permission
|
||||
android:name="android.permission.READ_SMS"
|
||||
android:required="false" />
|
||||
<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" />
|
||||
<uses-permission android:name="android.permission.READ_SMS" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
@@ -146,7 +138,8 @@
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"
|
||||
android:label="@string/remote_control"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
android:launchMode="singleTop">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
|
20
build.gradle
20
build.gradle
@@ -1,20 +1,17 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com/'
|
||||
name 'Google'
|
||||
}
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
classpath 'com.android.tools.build:gradle:3.1.2'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
buildToolsVersion '26.0.2'
|
||||
buildToolsVersion '27.0.3'
|
||||
compileSdkVersion 25
|
||||
defaultConfig {
|
||||
minSdkVersion 9
|
||||
@@ -26,10 +23,8 @@ android {
|
||||
javaMaxHeapSize "2g"
|
||||
}
|
||||
compileOptions {
|
||||
// Use Java 1.7, requires minSdk 8
|
||||
//SSHD requires mina when running on JDK < 7
|
||||
sourceCompatibility JavaVersion.VERSION_1_7
|
||||
targetCompatibility JavaVersion.VERSION_1_7
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
@@ -73,10 +68,7 @@ dependencies {
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com/'
|
||||
name 'Google'
|
||||
}
|
||||
google()
|
||||
}
|
||||
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
|
BIN
res/drawable-hdpi/ic_device_tv.png
Normal file
BIN
res/drawable-hdpi/ic_device_tv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 B |
BIN
res/drawable-mdpi/ic_device_tv.png
Normal file
BIN
res/drawable-mdpi/ic_device_tv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 347 B |
BIN
res/drawable-xhdpi/ic_device_tv.png
Normal file
BIN
res/drawable-xhdpi/ic_device_tv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 439 B |
BIN
res/drawable-xxhdpi/ic_device_tv.png
Normal file
BIN
res/drawable-xxhdpi/ic_device_tv.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 433 B |
@@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
|
9
res/drawable/ic_share_white.xml
Normal file
9
res/drawable/ic_share_white.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FFFFFFFF"
|
||||
android:pathData="M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z"/>
|
||||
</vector>
|
5
res/drawable/state_list_drawer_background_dark.xml
Normal file
5
res/drawable/state_list_drawer_background_dark.xml
Normal file
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@color/primary" android:state_checked="true" />
|
||||
<item android:drawable="@color/darkStatusBarBackground" android:state_checked="false" />
|
||||
</selector>
|
@@ -13,6 +13,7 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginRight="25dp"
|
||||
android:layout_marginEnd="25dp"
|
||||
android:layout_weight="1"
|
||||
android:contentDescription="@string/mpris_coverart_description"
|
||||
android:scaleType="fitCenter" />
|
||||
|
18
res/layout-v14/nav_dark_mode_switch.xml
Normal file
18
res/layout-v14/nav_dark_mode_switch.xml
Normal file
@@ -0,0 +1,18 @@
|
||||
<android.support.v7.widget.SwitchCompat
|
||||
android:id="@+id/dark_theme"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:paddingEnd="48dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="48dp"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="4dp"
|
||||
android:text="@string/dark_theme"
|
||||
android:textColor="@android:color/white"
|
||||
app:switchPadding="12dp"
|
||||
tools:background="@drawable/drawer_header"
|
||||
/>
|
@@ -1,5 +1,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
@@ -11,6 +13,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
@@ -66,29 +69,46 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/not_reachable_message"
|
||||
<LinearLayout
|
||||
android:id="@+id/error_message_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawableLeft="@drawable/ic_error_outline_black_48dp"
|
||||
android:drawablePadding="8dip"
|
||||
android:drawableStart="@drawable/ic_error_outline_black_48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/unreachable_description"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone" />
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center"
|
||||
android:visibility="gone" >
|
||||
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/error_message_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:importantForAccessibility="no"
|
||||
android:paddingEnd="8dip"
|
||||
android:paddingLeft="0dip"
|
||||
android:paddingRight="8dip"
|
||||
android:paddingStart="0dip"
|
||||
android:src="@drawable/ic_error_outline_black_48dp"
|
||||
app:tint="?attr/colorHighContrast"
|
||||
tools:ignore="UnusedAttribute"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/not_reachable_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/unreachable_description"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/on_data_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/on_data_message"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/on_data_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:drawableLeft="@drawable/ic_error_outline_black_48dp"
|
||||
android:drawablePadding="8dip"
|
||||
android:drawableStart="@drawable/ic_error_outline_black_48dp"
|
||||
android:gravity="center_vertical"
|
||||
android:text="@string/on_data_message"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/buttons_list"
|
||||
|
@@ -1,10 +0,0 @@
|
||||
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/listView1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:addStatesFromChildren="true"
|
||||
android:orientation="vertical"
|
||||
android:paddingLeft="16dip"
|
||||
android:paddingRight="16dip"
|
||||
tools:context="org.kde.kdeconnect.UserInterface.MainActivity" />
|
@@ -16,9 +16,7 @@
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="8dp"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
|
||||
app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||
|
||||
<FrameLayout
|
||||
@@ -33,10 +31,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
android:background="@drawable/state_list_drawer_background"
|
||||
app:headerLayout="@layout/nav_header"
|
||||
app:itemBackground="@drawable/state_list_drawer_background"
|
||||
app:itemIconTint="@color/state_list_drawer_text"
|
||||
app:itemTextColor="@color/state_list_drawer_text" />
|
||||
style="?attr/mainNavigationViewStyle" />
|
||||
|
||||
</android.support.v4.widget.DrawerLayout>
|
||||
|
@@ -18,7 +18,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/ic_add"
|
||||
android:src="@drawable/ic_action_image_edit"
|
||||
app:backgroundTint="@color/primary"
|
||||
app:layout_anchor="@id/runcommandslist"
|
||||
app:layout_anchorGravity="bottom|end" />
|
||||
|
@@ -1,34 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/command_confirm_needed"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Medium" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/addcommand_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ems="10"
|
||||
android:hint="@string/addcommand_name"
|
||||
android:inputType="text" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/addcommand_command"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:ems="10"
|
||||
android:hint="@string/addcommand_command"
|
||||
android:inputType="text" />
|
||||
|
||||
</LinearLayout>
|
@@ -6,7 +6,7 @@
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/listView1"
|
||||
android:id="@+id/devices_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:addStatesFromChildren="true"
|
@@ -1,5 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/abc_list_selector_holo_dark"
|
||||
@@ -14,12 +16,13 @@
|
||||
android:paddingRight="?android:attr/scrollbarSize"
|
||||
android:paddingStart="12dip">
|
||||
|
||||
<ImageView
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/list_item_entry_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:contentDescription="@string/device_icon_description"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
android:src="@drawable/ic_device_laptop" />
|
||||
|
||||
<LinearLayout
|
||||
|
@@ -137,7 +137,7 @@
|
||||
android:layout_marginTop="8dip"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
<android.support.v7.widget.AppCompatImageView
|
||||
android:id="@+id/imageView"
|
||||
android:layout_width="30dip"
|
||||
android:layout_height="30dip"
|
||||
@@ -146,6 +146,7 @@
|
||||
android:layout_weight="0"
|
||||
android:contentDescription="@string/mpris_volume"
|
||||
android:maxWidth="30dip"
|
||||
app:tint="?attr/colorHighContrast"
|
||||
android:src="@drawable/ic_volume_black" />
|
||||
|
||||
|
||||
|
@@ -16,7 +16,9 @@
|
||||
<!-- Layout for a Preference in a PreferenceActivity. The
|
||||
Preference is able to place a specific widget for its particular
|
||||
type in the "widget_frame" layout. -->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout
|
||||
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"
|
||||
@@ -68,13 +70,14 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<ImageButton
|
||||
<android.support.v7.widget.AppCompatImageButton
|
||||
android:id="@+id/settingsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/abc_btn_borderless_material"
|
||||
android:contentDescription="@string/settings_icon_description"
|
||||
android:padding="8dip"
|
||||
app:tint="?attr/colorControlNormal"
|
||||
android:src="@drawable/ic_action_settings_inverted" />
|
||||
|
||||
</LinearLayout>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Cancel·la</string>
|
||||
<string name="open_settings">Obre l\'arranjament</string>
|
||||
<string name="no_permissions">Us caldrà concedir permís per accedir a les notificacions</string>
|
||||
<string name="no_permission_mprisreceiver">Per a poder controlar els reproductors multimèdia cal atorgar accés a les notificacions</string>
|
||||
<string name="send_ping">Envia un ping</string>
|
||||
<string name="open_mpris_controls">Control multimèdia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Fes servir les tecles remotes només en editar</string>
|
||||
@@ -146,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Els fitxers rebuts apareixeran a Baixades</string>
|
||||
<string name="share_destination_customize_summary_enabled">Els fitxers seran emmagatzemats al directori de sota</string>
|
||||
<string name="share_destination_folder_preference">Directori de destinació</string>
|
||||
<string name="share">Comparteix</string>
|
||||
<string name="share_received_file">Comparteix «%s»</string>
|
||||
<string name="title_activity_notification_filter">Filtre per a les notificacions</string>
|
||||
<string name="filter_apps_info">Les notificacions se sincronitzaran per a les aplicacions seleccionades.</string>
|
||||
<string name="sftp_internal_storage">Emmagatzematge intern</string>
|
||||
@@ -172,8 +175,9 @@
|
||||
<string name="pref_plugin_telepathy">Envia un SMS</string>
|
||||
<string name="pref_plugin_telepathy_desc">Envia missatges de text des de l\'escriptori</string>
|
||||
<string name="plugin_not_supported">Aquest connector no és admès pel dispositiu</string>
|
||||
<string name="findmyphone_title">Cerca el meu telèfon</string>
|
||||
<string name="findmyphone_title_tablet">Cerca la meva tauleta</string>
|
||||
<string name="findmyphone_title">Troba el meu telèfon</string>
|
||||
<string name="findmyphone_title_tablet">Troba la meva tauleta</string>
|
||||
<string name="findmyphone_title_tv">Troba la meva TV</string>
|
||||
<string name="findmyphone_description">Fa sonar aquest dispositiu perquè el pugueu trobar.</string>
|
||||
<string name="findmyphone_found">L\'he trobat</string>
|
||||
<string name="open">Obre</string>
|
||||
@@ -195,9 +199,10 @@
|
||||
<string name="device_icon_description">Icona del dispositiu</string>
|
||||
<string name="settings_icon_description">Icona d\'arranjament</string>
|
||||
<string name="add_command">Afegeix una ordre</string>
|
||||
<string name="addcommand_name">Nom</string>
|
||||
<string name="addcommand_command">Ordre</string>
|
||||
<string name="command_confirm_needed">Cal confirmar l\'ordre a l\'escriptori</string>
|
||||
<string name="addcommand_explanation">No hi ha cap ordre registrada</string>
|
||||
<string name="addcommand_explanation2">Podeu afegir ordres noves a l\'Arranjament del sistema del KDE Connect</string>
|
||||
<string name="add_command_description">Podeu afegir ordres per a l\'escriptori</string>
|
||||
<string name="pref_plugin_mprisreceiver">Control del reproductor multimèdia</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Controla els reproductors multimèdia dels telèfons des d\'un altre dispositiu</string>
|
||||
<string name="dark_theme">Tema fosc</string>
|
||||
</resources>
|
||||
|
@@ -37,6 +37,7 @@
|
||||
<string name="remotekeyboard_connected">Vzdálené připojení klávesnice je aktivní</string>
|
||||
<string name="remotekeyboard_multiple_connections">Je k dispozici více než jedno připojení klávesnice. Vyberte zařízení pro jeho nastavení.</string>
|
||||
<string name="open_mousepad">Vzdálený vstup</string>
|
||||
<string name="mousepad_info">Pohybujte prstem po obrazovce pro pohybování kurzorem myši. Ťukněte pro kliknutí a použijte dva/tři prsty jako pravé a prostřední tlačítko. Použijte 2 prsty pro posunování. Pro přetažení dlouze podržte.</string>
|
||||
<string name="mousepad_double_tap_settings_title">Nastavit činnost pro ťuknutí dvěma prsty</string>
|
||||
<string name="mousepad_triple_tap_settings_title">Nastavit činnost pro ťuknutí třemi prsty</string>
|
||||
<string name="mousepad_sensitivity_settings_title">Nastavit citlivost touchpadu</string>
|
||||
@@ -82,6 +83,12 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Odesílám soubor do %1s</string>
|
||||
<string name="outgoing_files_title">Odesílám soubory do %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Odeslán %1$d soubor</item>
|
||||
<item quantity="few">Odeslány %1$d ze %2$d souborů</item>
|
||||
<item quantity="many">Odesláno %1$d ze %2$d souborů</item>
|
||||
<item quantity="other"/>
|
||||
</plurals>
|
||||
<string name="received_file_title">Přijat soubor od %1s</string>
|
||||
<string name="received_file_fail_title">Selhalo přijímání souboru od %1s</string>
|
||||
<string name="received_file_text">Ťukněte pro otevření \'%1s\'</string>
|
||||
@@ -103,6 +110,7 @@
|
||||
<string name="remote_control">Vzdálené ovládání</string>
|
||||
<string name="settings">Nastavení KDE Connect</string>
|
||||
<string name="mpris_play">Přehrát</string>
|
||||
<string name="mpris_pause">Pozastavit</string>
|
||||
<string name="mpris_previous">Předchozí</string>
|
||||
<string name="mpris_rew">Přetočit zpět</string>
|
||||
<string name="mpris_ff">Rychle vpřed</string>
|
||||
@@ -118,6 +126,8 @@
|
||||
<item>1 minuta</item>
|
||||
<item>2 minuty</item>
|
||||
</string-array>
|
||||
<string name="mpris_notification_settings_title">Obrazit upozornění pro ovládání médií</string>
|
||||
<string name="mpris_notification_settings_summary">Umožní ovládat přehrávače médií bez otevření KDE Connect.</string>
|
||||
<string name="share_to">Sdílet s...</string>
|
||||
<string name="protocol_version_older">Toto zařízení používá starou verzi protokolu</string>
|
||||
<string name="protocol_version_newer">Toto zařízení používá novější verzi protokolu</string>
|
||||
@@ -138,6 +148,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Přijaté soubory se objeví v Downloads</string>
|
||||
<string name="share_destination_customize_summary_enabled">Soubory budou ukládány v adresáři níže</string>
|
||||
<string name="share_destination_folder_preference">Cílový adresář</string>
|
||||
<string name="share">Sdílet</string>
|
||||
<string name="share_received_file">Sdílet \"%s\"</string>
|
||||
<string name="title_activity_notification_filter">Filtr upozornění</string>
|
||||
<string name="filter_apps_info">Upozorňování mezi vybranými aplikacemi bude synchronizováno.</string>
|
||||
<string name="sftp_internal_storage">Interní úložiště</string>
|
||||
@@ -180,4 +192,13 @@
|
||||
<string name="telepathy_permission_explanation">Pro čtení a psaní SMS z počítače musíte udělit oprávnění k SMS</string>
|
||||
<string name="telephony_permission_explanation">Pro zobrazení telefonátů a SMS v počítači musíte udělit oprávnění k telefonování a SMS</string>
|
||||
<string name="telephony_optional_permission_explanation">Pro zobrazení jména kontaktu u telefonního čísla je potřeba udělit oprávnění ke kontaktům v telefonu</string>
|
||||
<string name="select_ringtone">Vybrat vyzváněcí tón</string>
|
||||
<string name="telephony_pref_blocked_title">Blokovaná čísla</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">Nezobrazovat volnání a SMS z těchto čísel. Prosím, zadejte pouze jedno slovo na řádek.</string>
|
||||
<string name="mpris_coverart_description">Obal současného média</string>
|
||||
<string name="device_icon_description">Ikona zařízení</string>
|
||||
<string name="settings_icon_description">Ikona nastavení</string>
|
||||
<string name="add_command">Přidat příkaz</string>
|
||||
<string name="addcommand_explanation">Nejsou registrovány žádné příkazy</string>
|
||||
<string name="addcommand_explanation2">Nové příkazy můžete přidat v nastavení systému KDE Connect</string>
|
||||
</resources>
|
||||
|
@@ -169,12 +169,23 @@
|
||||
<string name="findmyphone_found">Gefunden</string>
|
||||
<string name="open">Öffnen</string>
|
||||
<string name="close">Schließen</string>
|
||||
<string name="no_permissions_storage">Sie müssen die Berechtigung zum Zugriff auf Speicher erteilen</string>
|
||||
<string name="plugins_need_permission">Einige Module benötigen zusätzliche Berechtigungen, tippen Sie für weitere Details:</string>
|
||||
<string name="permission_explanation">Dieses Modul benötigt zusätzliche Berechtigungen</string>
|
||||
<string name="optional_permission_explanation">Es müssen weitere Berechtigungen erteilt werden, um alle Funktionen nutzen zu können</string>
|
||||
<string name="plugins_need_optional_permission">Einige Module haben eingeschränkte Funktionen wegen fehlender Berechtigungen, tippen Sie für weitere Informationen:</string>
|
||||
<string name="sftp_permission_explanation">Um vom Rechner auf den Telefonspeicher zuzugreifen, werden weitere Berechtigungen benötigt</string>
|
||||
<string name="share_optional_permission_explanation">m Dateien zwischen Rechner und Telefon auszutauschen, muss der Zugriff auf den Telefonspeicher gewährt werden</string>
|
||||
<string name="telepathy_permission_explanation">Um SMS vom Rechner aus zu lesen und zu versenden, muss der Zugriff auf die SMS-Funktion gewährt werden</string>
|
||||
<string name="telephony_permission_explanation">Um Telefonate und SMS auf dem Rechner zu sehen, müssen Berechtigungen für Anrufe und SMS erteilt werden</string>
|
||||
<string name="telephony_optional_permission_explanation">Um einen Namen anstelle einer Telefonnummer zu sehen, muss der Zugriff auf das Adressbuch gewährt werden</string>
|
||||
<string name="select_ringtone">Einen Klingelton auswählen</string>
|
||||
<string name="telephony_pref_blocked_title">Unterdrückte Nummern</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">keine Anrufe und SMS von diesen Telefonnummern anzeigen. Geben Sie eine Nummer pro Zeile ein</string>
|
||||
<string name="mpris_coverart_description">Cover des aktuellen Mediums</string>
|
||||
<string name="device_icon_description">Gerätesymbol</string>
|
||||
<string name="settings_icon_description">Einstellungssymbol</string>
|
||||
<string name="add_command">Einen Befehl hinzufügen</string>
|
||||
<string name="addcommand_name">Name</string>
|
||||
<string name="addcommand_command">Befehl</string>
|
||||
<string name="addcommand_explanation">Es sind keine Befehle registriert.</string>
|
||||
<string name="addcommand_explanation2">Sie können neue Befehle in den Systemeinstellungen für KDE-Connect hinzufügen.</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="no_permissions">You need to grant permission to access notifications</string>
|
||||
<string name="no_permission_mprisreceiver">To be able to control your media players you need to grant access to the notifications</string>
|
||||
<string name="send_ping">Send ping</string>
|
||||
<string name="open_mpris_controls">Multimedia control</string>
|
||||
<string name="remotekeyboard_editing_only_title">Handle remote keys only when editing</string>
|
||||
@@ -146,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Received files will appear in Downloads</string>
|
||||
<string name="share_destination_customize_summary_enabled">Files will be stored in the directory below</string>
|
||||
<string name="share_destination_folder_preference">Destination directory</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="share_received_file">Share \"%s\"</string>
|
||||
<string name="title_activity_notification_filter">Notification filter</string>
|
||||
<string name="filter_apps_info">Notifications will be synchronised for the selected apps.</string>
|
||||
<string name="sftp_internal_storage">Internal storage</string>
|
||||
@@ -174,6 +177,7 @@
|
||||
<string name="plugin_not_supported">This plugin is not supported by the device</string>
|
||||
<string name="findmyphone_title">Find my phone</string>
|
||||
<string name="findmyphone_title_tablet">Find my tablet</string>
|
||||
<string name="findmyphone_title_tv">Find my TV</string>
|
||||
<string name="findmyphone_description">Rings this device so you can find it</string>
|
||||
<string name="findmyphone_found">Found</string>
|
||||
<string name="open">Open</string>
|
||||
@@ -195,9 +199,10 @@
|
||||
<string name="device_icon_description">Device icon</string>
|
||||
<string name="settings_icon_description">Settings icon</string>
|
||||
<string name="add_command">Add a command</string>
|
||||
<string name="addcommand_name">Name</string>
|
||||
<string name="addcommand_command">Command</string>
|
||||
<string name="command_confirm_needed">You will need to confirm the command on the desktop</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>
|
||||
<string name="add_command_description">You can add commands on the desktop</string>
|
||||
<string name="pref_plugin_mprisreceiver">Media Player Control</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Control your phones media players from another device</string>
|
||||
<string name="dark_theme">Dark theme</string>
|
||||
</resources>
|
||||
|
@@ -146,6 +146,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Los archivos recibidos aparecerán en Descargas</string>
|
||||
<string name="share_destination_customize_summary_enabled">Los archivos se almacenarán en el directorio indicado más abajo</string>
|
||||
<string name="share_destination_folder_preference">Directorio destino</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="share_received_file">Compartir «%s»</string>
|
||||
<string name="title_activity_notification_filter">Filtro de notificaciones</string>
|
||||
<string name="filter_apps_info">Las notificaciones se sincronizarán en las aplicaciones seleccionadas.</string>
|
||||
<string name="sftp_internal_storage">Almacenamiento interno</string>
|
||||
@@ -195,9 +197,7 @@
|
||||
<string name="device_icon_description">Icono del dispositivo</string>
|
||||
<string name="settings_icon_description">Icono de preferencias</string>
|
||||
<string name="add_command">Añadir una orden</string>
|
||||
<string name="addcommand_name">Nombre</string>
|
||||
<string name="addcommand_command">Orden</string>
|
||||
<string name="command_confirm_needed">Necesitará confirmar la orden desde su escritorio</string>
|
||||
<string name="addcommand_explanation">No hay ninguna orden registrada</string>
|
||||
<string name="addcommand_explanation2">Puede añadir nuevas órdenes en las preferencias del sistema de KDE Connect</string>
|
||||
<string name="dark_theme">Tema oscuro</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="open_settings">Abrir a configuración</string>
|
||||
<string name="no_permissions">Debe conceder permisos para acceder ás notificacións.</string>
|
||||
<string name="no_permission_mprisreceiver">Para poder controlar os seus reprodutores de son e vídeo ten que garantir acceso ás notificacións.</string>
|
||||
<string name="send_ping">Enviar un ping</string>
|
||||
<string name="open_mpris_controls">Control multimedia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Xestionar teclas remotas só ao editar.</string>
|
||||
@@ -51,7 +52,7 @@
|
||||
<item>O máis lento</item>
|
||||
<item>Lento</item>
|
||||
<item>Predeterminado</item>
|
||||
<item>Rápido</item>
|
||||
<item>Por riba do predeterminado</item>
|
||||
<item>O máis rápido</item>
|
||||
</string-array>
|
||||
<string name="category_connected_devices">Dispositivos conectados</string>
|
||||
@@ -83,6 +84,10 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Enviando un ficheiro a %1s</string>
|
||||
<string name="outgoing_files_title">Enviando os ficheiros a %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Enviouse %1$d ficheiro.</item>
|
||||
<item quantity="other">Enviáronse %1$d de %2$d ficheiros.</item>
|
||||
</plurals>
|
||||
<string name="received_file_title">Recibiuse un ficheiro de %1s</string>
|
||||
<string name="received_file_fail_title">A recepción do ficheiro de %1s fallou</string>
|
||||
<string name="received_file_text">Toque para abrir «%1s».</string>
|
||||
@@ -104,6 +109,7 @@
|
||||
<string name="remote_control">Control remoto</string>
|
||||
<string name="settings">Configuración de KDE Connect</string>
|
||||
<string name="mpris_play">Reproducir</string>
|
||||
<string name="mpris_pause">Deter</string>
|
||||
<string name="mpris_previous">Anterior</string>
|
||||
<string name="mpris_rew">Retroceder</string>
|
||||
<string name="mpris_ff">Cara a adiante</string>
|
||||
@@ -119,6 +125,8 @@
|
||||
<item>1 minuto</item>
|
||||
<item>2 minutos</item>
|
||||
</string-array>
|
||||
<string name="mpris_notification_settings_title">Mostrar a notificación de control de reprodución.</string>
|
||||
<string name="mpris_notification_settings_summary">Permite controlar os reprodutores de son e vídeo sen abrir KDE Connect.</string>
|
||||
<string name="share_to">Compartir con…</string>
|
||||
<string name="protocol_version_older">Este dispositivo usa unha versión vella do protocolo.</string>
|
||||
<string name="protocol_version_newer">Este dispositivo usa unha versión máis nova do protocolo.</string>
|
||||
@@ -139,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Os ficheiros recibidos aparecerán en «Descargas».</string>
|
||||
<string name="share_destination_customize_summary_enabled">Os ficheiros almacenaranse no directorio de abaixo.</string>
|
||||
<string name="share_destination_folder_preference">Directorio de destino</string>
|
||||
<string name="share">Compartir</string>
|
||||
<string name="share_received_file">Compartir «%s»</string>
|
||||
<string name="title_activity_notification_filter">Filtro de notificacións</string>
|
||||
<string name="filter_apps_info">As notificacións sincronizaranse para os seguintes aplicativos.</string>
|
||||
<string name="sftp_internal_storage">Almacenamento interno</string>
|
||||
@@ -167,6 +177,7 @@
|
||||
<string name="plugin_not_supported">O dispositivo non é compatíbel con este complemento.</string>
|
||||
<string name="findmyphone_title">Atopar o móbil</string>
|
||||
<string name="findmyphone_title_tablet">Atopar a tableta</string>
|
||||
<string name="findmyphone_title_tv">Atopar o meu televisor</string>
|
||||
<string name="findmyphone_description">Reproduce un son de chamada no dispositivo para que poida atopalo.</string>
|
||||
<string name="findmyphone_found">Atopado</string>
|
||||
<string name="open">Abrir</string>
|
||||
@@ -181,4 +192,17 @@
|
||||
<string name="telepathy_permission_explanation">Para ler e escribir SMS desde o escritorio ten que dar permiso de SMS.</string>
|
||||
<string name="telephony_permission_explanation">Para ver as chamadas de teléfono e os SMS desde o escritorio ten que dar permiso a chamadas de teléfono e a SMS.</string>
|
||||
<string name="telephony_optional_permission_explanation">Para ver o nome dun contacto en vez dun número de teléfono ten que dar acceso aos contactos do teléfono.</string>
|
||||
<string name="select_ringtone">Seleccione un son de chamada</string>
|
||||
<string name="telephony_pref_blocked_title">Números bloqueados</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">Non mostrar chamadas nin SMS destes números. Indique un número por liña.</string>
|
||||
<string name="mpris_coverart_description">Portada da obra actual.</string>
|
||||
<string name="device_icon_description">Icona do dispositivo.</string>
|
||||
<string name="settings_icon_description">Icona da configuración.</string>
|
||||
<string name="add_command">Engadir unha orde</string>
|
||||
<string name="addcommand_explanation">Non hai ordes rexistradas.</string>
|
||||
<string name="addcommand_explanation2">Pode engadir novas ordes desde a configuración do sistema de KDE Connect.</string>
|
||||
<string name="add_command_description">Pode engadir ordes no escritorio.</string>
|
||||
<string name="pref_plugin_mprisreceiver">Control do reprodutor de multimedia</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Controlar os reprodutores do seu móbil desde outro dispositivo.</string>
|
||||
<string name="dark_theme">Tema escuro</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Annulla</string>
|
||||
<string name="open_settings">Apri impostazioni</string>
|
||||
<string name="no_permissions">Devi concedere i permessi per l\'accesso alle notifiche</string>
|
||||
<string name="no_permission_mprisreceiver">Per poter controllare i tuoi lettori multimediali devi accordare l\'accesso alle notifiche</string>
|
||||
<string name="send_ping">Invia ping</string>
|
||||
<string name="open_mpris_controls">Controllo multimediale</string>
|
||||
<string name="remotekeyboard_editing_only_title">Gestisci i tasti remoti solo durante la modifica</string>
|
||||
@@ -83,6 +84,10 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Invio file a %1s</string>
|
||||
<string name="outgoing_files_title">Invio file a %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="one">Inviato %1$d file</item>
|
||||
<item quantity="other">Inviati %1$d di %2$d file</item>
|
||||
</plurals>
|
||||
<string name="received_file_title">File ricevuto da %1s</string>
|
||||
<string name="received_file_fail_title">Ricezione file da %1s non riuscita</string>
|
||||
<string name="received_file_text">Tocca per aprire «%1s»</string>
|
||||
@@ -142,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">I file ricevuti saranno visualizzati in Downloads</string>
|
||||
<string name="share_destination_customize_summary_enabled">I file saranno memorizzati nella cartella seguente</string>
|
||||
<string name="share_destination_folder_preference">Cartella di destinazione</string>
|
||||
<string name="share">Condividi</string>
|
||||
<string name="share_received_file">Condividi «%s»</string>
|
||||
<string name="title_activity_notification_filter">Filtro delle notifiche</string>
|
||||
<string name="filter_apps_info">Le notifiche saranno sincronizzate per le applicazioni selezionate.</string>
|
||||
<string name="sftp_internal_storage">Archiviazione interna</string>
|
||||
@@ -187,4 +194,13 @@
|
||||
<string name="select_ringtone">Seleziona una suoneria</string>
|
||||
<string name="telephony_pref_blocked_title">Numeri bloccati</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">Non mostrare le chiamate e gli SMS da questi numeri. Specifica un numero per riga</string>
|
||||
<string name="mpris_coverart_description">Copertina del media attuale</string>
|
||||
<string name="device_icon_description">Icona dispositivo</string>
|
||||
<string name="settings_icon_description">Icona impostazioni</string>
|
||||
<string name="add_command">Aggiungi un comando</string>
|
||||
<string name="addcommand_explanation">Non ci sono comandi registrati</string>
|
||||
<string name="addcommand_explanation2">Puoi aggiungere nuovi comandi nelle impostazioni di sistema di KDE Connect</string>
|
||||
<string name="pref_plugin_mprisreceiver">Controllo lettore multimediale</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Controlla i lettori multimediali del tuo telefono da un altro dispositivo</string>
|
||||
<string name="dark_theme">Tema scuro</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Annuleren</string>
|
||||
<string name="open_settings">Instellingen openen</string>
|
||||
<string name="no_permissions">U moet toestemming geven voor toegang tot meldingen</string>
|
||||
<string name="no_permission_mprisreceiver">Om in staat te zijn uw mediaspelers te besturen moet u toegan geven tot de meldingen</string>
|
||||
<string name="send_ping">Ping verzenden</string>
|
||||
<string name="open_mpris_controls">Bediening van multimedia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Behandel toetsen op afstand alleen bij bewerken</string>
|
||||
@@ -146,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Ontvangen bestanden zullen in Downloads verschijnen</string>
|
||||
<string name="share_destination_customize_summary_enabled">Bestanden zullen opgeslagen worden in de onderstaande map</string>
|
||||
<string name="share_destination_folder_preference">Bestemmingsmap</string>
|
||||
<string name="share">Delen</string>
|
||||
<string name="share_received_file">\"%s\" delen</string>
|
||||
<string name="title_activity_notification_filter">Filter voor meldingen</string>
|
||||
<string name="filter_apps_info">Meldingen zullen gesynchroniseerd worden voor de geselecteerde apps.</string>
|
||||
<string name="sftp_internal_storage">Interne opslag</string>
|
||||
@@ -174,6 +177,7 @@
|
||||
<string name="plugin_not_supported">Deze plug-in wordt niet ondersteund door het apparaat</string>
|
||||
<string name="findmyphone_title">Zoek mijn telefoon</string>
|
||||
<string name="findmyphone_title_tablet">Zoek mijn tablet</string>
|
||||
<string name="findmyphone_title_tv">Zoek mijn tv</string>
|
||||
<string name="findmyphone_description">Laat dit apparaat bellen zodat u het kunt vinden</string>
|
||||
<string name="findmyphone_found">Gevonden</string>
|
||||
<string name="open">Openen</string>
|
||||
@@ -195,9 +199,10 @@
|
||||
<string name="device_icon_description">Apparaatpictogram</string>
|
||||
<string name="settings_icon_description">Pictogram voor instellingen</string>
|
||||
<string name="add_command">Een commando toevoegen</string>
|
||||
<string name="addcommand_name">Naam</string>
|
||||
<string name="addcommand_command">Commando</string>
|
||||
<string name="command_confirm_needed">U zult het commando moeten bevestigen op het bureaublad</string>
|
||||
<string name="addcommand_explanation">Er zijn geen commando\'s geregistreerd</string>
|
||||
<string name="addcommand_explanation2">U kunt nieuwe commando\'s in de instellingen voor het KDE connect-systeem toevoegen</string>
|
||||
<string name="add_command_description">U kunt commando\'s toevoegen op het bureaublad</string>
|
||||
<string name="pref_plugin_mprisreceiver">Besturing van mediaspeler</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Uw mediaspelers op uw telefoon besturen vanaf een andere apparaat</string>
|
||||
<string name="dark_theme">Donker themea</string>
|
||||
</resources>
|
||||
|
@@ -197,9 +197,6 @@
|
||||
<string name="device_icon_description">Ikona urządzenia</string>
|
||||
<string name="settings_icon_description">Ikona ustawień</string>
|
||||
<string name="add_command">Dodaj polecenie</string>
|
||||
<string name="addcommand_name">Nazwa</string>
|
||||
<string name="addcommand_command">Polecenie</string>
|
||||
<string name="command_confirm_needed">Będziesz musiał potwierdzić polecenie na pulpicie</string>
|
||||
<string name="addcommand_explanation">Nie zarejestrowano żadnych poleceń</string>
|
||||
<string name="addcommand_explanation2">Nowe polecenia można dodawać w ustawieniach systemowych KDE Connect</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Cancelar</string>
|
||||
<string name="open_settings">Abrir a configuração</string>
|
||||
<string name="no_permissions">Precisa de dar permissões de acesso às notificações</string>
|
||||
<string name="no_permission_mprisreceiver">Para poder controlar os seus leitores multimédia, terá de dar acesso às notificações</string>
|
||||
<string name="send_ping">Enviar um pedido de contacto</string>
|
||||
<string name="open_mpris_controls">Comando multimédia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Lidar com as teclas remotas apenas na edição</string>
|
||||
@@ -146,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Os ficheiros recebidos irão aparecer em \'Transferências\'</string>
|
||||
<string name="share_destination_customize_summary_enabled">Os ficheiros serão guardados na pasta abaixo</string>
|
||||
<string name="share_destination_folder_preference">Pasta de destino</string>
|
||||
<string name="share">Partilhar</string>
|
||||
<string name="share_received_file">Partilhar o \"%s\"</string>
|
||||
<string name="title_activity_notification_filter">Filtro de notificações</string>
|
||||
<string name="filter_apps_info">As notificações serão sincronizadas para as aplicações seleccionadas.</string>
|
||||
<string name="sftp_internal_storage">Armazenamento interno</string>
|
||||
@@ -174,6 +177,7 @@
|
||||
<string name="plugin_not_supported">Este \'plugin\' não é suportado pelo dispositivo</string>
|
||||
<string name="findmyphone_title">Descobrir o meu telefone</string>
|
||||
<string name="findmyphone_title_tablet">Descobrir o meu \'tablet\'</string>
|
||||
<string name="findmyphone_title_tv">Descobrir a minha TV</string>
|
||||
<string name="findmyphone_description">Toca este dispositivo para que o possa encontrar</string>
|
||||
<string name="findmyphone_found">Encontrado</string>
|
||||
<string name="open">Abrir</string>
|
||||
@@ -195,9 +199,10 @@
|
||||
<string name="device_icon_description">Ícone do dispositivo</string>
|
||||
<string name="settings_icon_description">Ícone de configuração</string>
|
||||
<string name="add_command">Adicionar um comando</string>
|
||||
<string name="addcommand_name">Nome</string>
|
||||
<string name="addcommand_command">Comando</string>
|
||||
<string name="command_confirm_needed">Terá de confirmar o comando no ambiente de trabalho</string>
|
||||
<string name="addcommand_explanation">Não existem comandos registados</string>
|
||||
<string name="addcommand_explanation2">Poderá adicionar comandos novos na Configuração do Sistema KDE Connect</string>
|
||||
<string name="add_command_description">Poderá adicionar comandos no ambiente de trabalho</string>
|
||||
<string name="pref_plugin_mprisreceiver">Controlo de Leitores Multimédia</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Controle os leitores multimédia dos seus telemóveis a partir de outro dispositivo</string>
|
||||
<string name="dark_theme">Tema escuro</string>
|
||||
</resources>
|
||||
|
@@ -82,8 +82,6 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Шаљем фајл на %1s</string>
|
||||
<string name="outgoing_files_title">Шаљем фајлове на %1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="outgoing_files_text">Послато %1$d од %2$d фајлова</string>
|
||||
<string name="received_file_title">Примљен фајл са %1s</string>
|
||||
<string name="received_file_fail_title">Неуспео пријем фајла са %1s</string>
|
||||
<string name="received_file_text">Тапните да отворите „%1s“</string>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Avbryt</string>
|
||||
<string name="open_settings">Öppna inställningarna</string>
|
||||
<string name="no_permissions">Du måste ge rättighet att komma åt underrättelser</string>
|
||||
<string name="no_permission_mprisreceiver">För att kunna styra mediaspelare måste du ge tillgång till underrättelser</string>
|
||||
<string name="send_ping">Skicka ping</string>
|
||||
<string name="open_mpris_controls">Kontroll av multimedia</string>
|
||||
<string name="remotekeyboard_editing_only_title">Hantera bara externa tangenter vid redigering</string>
|
||||
@@ -146,6 +147,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Mottagna filer hamnar i Nerladdningar</string>
|
||||
<string name="share_destination_customize_summary_enabled">Filer lagras i katalogen nedan</string>
|
||||
<string name="share_destination_folder_preference">Målkatalog</string>
|
||||
<string name="share">Dela</string>
|
||||
<string name="share_received_file">Dela \"%s\"</string>
|
||||
<string name="title_activity_notification_filter">Underrättelsefilter</string>
|
||||
<string name="filter_apps_info">Underrättelser synkroniseras för markerade applikationer.</string>
|
||||
<string name="sftp_internal_storage">Intern lagring</string>
|
||||
@@ -174,6 +177,7 @@
|
||||
<string name="plugin_not_supported">Insticksprogrammet stöds inte av apparaten</string>
|
||||
<string name="findmyphone_title">Hitta min telefon</string>
|
||||
<string name="findmyphone_title_tablet">Hitta min surfplatta</string>
|
||||
<string name="findmyphone_title_tv">Hitta min tv</string>
|
||||
<string name="findmyphone_description">Ringer till apparaten så att du kan hitta den</string>
|
||||
<string name="findmyphone_found">Hittade den</string>
|
||||
<string name="open">Öppna</string>
|
||||
@@ -195,9 +199,10 @@
|
||||
<string name="device_icon_description">Enhetsikon</string>
|
||||
<string name="settings_icon_description">Inställningsikon</string>
|
||||
<string name="add_command">Lägg till ett kommando</string>
|
||||
<string name="addcommand_name">Namn</string>
|
||||
<string name="addcommand_command">Kommando</string>
|
||||
<string name="command_confirm_needed">Du måste bekräfta kommandot på skrivbordet</string>
|
||||
<string name="addcommand_explanation">Det finns inga kommandon registrerade</string>
|
||||
<string name="addcommand_explanation2">Du kan lägga till nya kommandon i KDE-ansluts systeminställningar</string>
|
||||
<string name="add_command_description">Du kan lägga till kommandon på skrivbordet</string>
|
||||
<string name="pref_plugin_mprisreceiver">Styrning av mediaspelare</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Styr telefonens mediaspelare från en annan enhet</string>
|
||||
<string name="dark_theme">Mörkt tema</string>
|
||||
</resources>
|
||||
|
@@ -30,6 +30,7 @@
|
||||
<string name="cancel">Скасувати</string>
|
||||
<string name="open_settings">Відкрити вікно параметрів</string>
|
||||
<string name="no_permissions">Вам слід надати доступ до сповіщень</string>
|
||||
<string name="no_permission_mprisreceiver">Щоб мати змогу керувати вашими програвачами мультимедійних даних, вам слід надати доступ до сповіщень.</string>
|
||||
<string name="send_ping">Надіслати сигнал підтримання зв’язку</string>
|
||||
<string name="open_mpris_controls">Керування відтворенням</string>
|
||||
<string name="remotekeyboard_editing_only_title">Обробляти віддалені клавіші лише під час редагування</string>
|
||||
@@ -148,6 +149,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Отримані файли зберігатимуться до каталогу «Завантаження»</string>
|
||||
<string name="share_destination_customize_summary_enabled">Файли зберігатимуться у вказаному нижче каталозі</string>
|
||||
<string name="share_destination_folder_preference">Каталог призначення</string>
|
||||
<string name="share">Оприлюднити</string>
|
||||
<string name="share_received_file">Оприлюднити «%s»</string>
|
||||
<string name="title_activity_notification_filter">Фільтр сповіщень</string>
|
||||
<string name="filter_apps_info">Сповіщення буде синхронізовано для позначених програм.</string>
|
||||
<string name="sftp_internal_storage">Вбудоване сховище даних</string>
|
||||
@@ -176,6 +179,7 @@
|
||||
<string name="plugin_not_supported">Підтримки цього додатка не передбачено на пристрої</string>
|
||||
<string name="findmyphone_title">Знайти телефон</string>
|
||||
<string name="findmyphone_title_tablet">Знайти планшет</string>
|
||||
<string name="findmyphone_title_tv">Знайти мій телевізор</string>
|
||||
<string name="findmyphone_description">Відтворити дзвінок, щоб цей пристрій було простіше знайти</string>
|
||||
<string name="findmyphone_found">Знайдено</string>
|
||||
<string name="open">Відкрити</string>
|
||||
@@ -197,9 +201,10 @@
|
||||
<string name="device_icon_description">Піктограма пристрою</string>
|
||||
<string name="settings_icon_description">Піктограма параметрів</string>
|
||||
<string name="add_command">Додати команду</string>
|
||||
<string name="addcommand_name">Назва</string>
|
||||
<string name="addcommand_command">Команда</string>
|
||||
<string name="command_confirm_needed">Вам доведеться підтвердити команду на комп\'ютері</string>
|
||||
<string name="addcommand_explanation">Жодних команд не зареєстровано</string>
|
||||
<string name="addcommand_explanation2">Ви можете додавати нові команди за допомогою Системних параметрів KDE Connect</string>
|
||||
<string name="add_command_description">Ви можете додавати команди на комп\'ютері</string>
|
||||
<string name="pref_plugin_mprisreceiver">Керування відтворенням</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Керування вашими програвачами на телефоні з іншого пристрою</string>
|
||||
<string name="dark_theme">Темна тема</string>
|
||||
</resources>
|
||||
|
12
res/values-v21/styles-dark.xml
Normal file
12
res/values-v21/styles-dark.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="KdeConnectTheme.Dark" parent="KdeConnectThemeBase.Dark">
|
||||
<item name="android:colorEdgeEffect">@color/darkGrey</item>
|
||||
</style>
|
||||
<style name="KdeConnectTheme.Dark.NoActionBar" parent="KdeConnectThemeBase.Dark.NoActionBar">
|
||||
<item name="android:windowTranslucentStatus">true</item>
|
||||
<item name="android:colorEdgeEffect">@color/darkGrey</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
@@ -83,6 +83,9 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">正在向%1s发送文件</string>
|
||||
<string name="outgoing_files_title">正在向 %1s 发送文件</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">已发送 %2$d 个文件中的 %1$d 个</item>
|
||||
</plurals>
|
||||
<string name="received_file_title">已从%1s接收文件</string>
|
||||
<string name="received_file_fail_title">未能从%1s接收文件</string>
|
||||
<string name="received_file_text">点击以打开“%1s”</string>
|
||||
@@ -140,6 +143,7 @@
|
||||
<string name="share_destination_customize_summary_disabled">接收的文件会出现在“Downloads”中</string>
|
||||
<string name="share_destination_customize_summary_enabled">文件将会被存储在以下目录中</string>
|
||||
<string name="share_destination_folder_preference">目标目录</string>
|
||||
<string name="share">分享</string>
|
||||
<string name="title_activity_notification_filter">通知过滤器</string>
|
||||
<string name="filter_apps_info">所选软件的通知将会被同步。</string>
|
||||
<string name="sftp_internal_storage">内部存储</string>
|
||||
@@ -181,6 +185,4 @@
|
||||
<string name="telepathy_permission_explanation">从计算机桌面读取、写入短消息需要向应用程序授予 SMS 权限</string>
|
||||
<string name="telephony_permission_explanation">您必须给予访问手机通话和短信的权限才能从桌面计算机查看通话记录和短信</string>
|
||||
<string name="telephony_optional_permission_explanation">要查看联系人姓名而非电话号码,您需要授予访问手机通讯录的权限</string>
|
||||
<string name="addcommand_name">名称</string>
|
||||
<string name="addcommand_command">命令</string>
|
||||
</resources>
|
||||
|
@@ -10,6 +10,8 @@
|
||||
<string name="pref_plugin_clipboard_desc">分享剪貼板的內容</string>
|
||||
<string name="pref_plugin_mousepad">遠端輸入</string>
|
||||
<string name="pref_plugin_mousepad_desc">使用您的智慧型手機或者平板來模擬觸碰板與鍵盤</string>
|
||||
<string name="pref_plugin_remotekeyboard">接收遠端按鍵輸入</string>
|
||||
<string name="pref_plugin_remotekeyboard_desc">從遠端裝置接收按鍵輸入活動</string>
|
||||
<string name="pref_plugin_mpris">多媒體控制</string>
|
||||
<string name="pref_plugin_mpris_desc">成為您多媒體播放器的遙控器</string>
|
||||
<string name="pref_plugin_runcommand">執行指令</string>
|
||||
@@ -30,7 +32,12 @@
|
||||
<string name="no_permissions">您需要授予存取通知的權限</string>
|
||||
<string name="send_ping">傳送Ping回應封包</string>
|
||||
<string name="open_mpris_controls">多媒體控制</string>
|
||||
<string name="remotekeyboard_editing_only_title">當編輯時只處理遠端按鍵</string>
|
||||
<string name="remotekeyboard_not_connected">這裡沒有建立在 kdeconnect 之上的使用中遠端鍵盤連線。</string>
|
||||
<string name="remotekeyboard_connected">遠端鍵盤連線為啟用狀態</string>
|
||||
<string name="remotekeyboard_multiple_connections">這裡有兩個以上的遠端鍵盤連結,選擇一個裝置以設定。</string>
|
||||
<string name="open_mousepad">遠端輸入</string>
|
||||
<string name="mousepad_info">在您的智慧型手機的螢幕上移動手指頭,用來控制電腦螢幕的鼠標。點擊表示滑鼠的左鍵,使用兩隻/三隻手指頭點擊來表示滑鼠的右鍵/中鍵。使用兩隻手指頭捲動。長按則表示要拖拉。</string>
|
||||
<string name="mousepad_double_tap_settings_title">設定兩隻手指頭點擊的動作</string>
|
||||
<string name="mousepad_triple_tap_settings_title">設定三隻手指頭點擊的動作</string>
|
||||
<string name="mousepad_sensitivity_settings_title">設定觸碰板的靈敏度</string>
|
||||
@@ -75,11 +82,16 @@
|
||||
<string name="incoming_file_title">從 %1s 傳來的檔案</string>
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">正在將檔案發送到 %1s</string>
|
||||
<string name="outgoing_files_title">正在將檔案發送到 %1s</string>
|
||||
<plurals name="outgoing_files_text">
|
||||
<item quantity="other">傳送 %1$d 個檔案,共 %2$d 個檔案。</item>
|
||||
</plurals>
|
||||
<string name="received_file_title">已從 %1s 接收檔案</string>
|
||||
<string name="received_file_fail_title">從 %1s 接收檔案失敗</string>
|
||||
<string name="received_file_text">點擊開啟 \'%1s\'</string>
|
||||
<string name="sent_file_title">將檔案傳送到 %1s</string>
|
||||
<string name="sent_file_text">%1s</string>
|
||||
<string name="sent_file_failed_title">傳送到 %1s 的檔案失敗</string>
|
||||
<string name="sent_file_failed_text">%1s</string>
|
||||
<string name="tap_to_answer">點擊即可應答</string>
|
||||
<string name="reconnect">重新連線</string>
|
||||
@@ -95,6 +107,7 @@
|
||||
<string name="remote_control">遠端控制</string>
|
||||
<string name="settings">KDE連線設定</string>
|
||||
<string name="mpris_play">播放</string>
|
||||
<string name="mpris_pause">暫停</string>
|
||||
<string name="mpris_previous">往前</string>
|
||||
<string name="mpris_rew">往後</string>
|
||||
<string name="mpris_ff">快轉</string>
|
||||
@@ -110,6 +123,8 @@
|
||||
<item>1分鐘</item>
|
||||
<item>2分鐘</item>
|
||||
</string-array>
|
||||
<string name="mpris_notification_settings_title">顯示媒體控制項通知</string>
|
||||
<string name="mpris_notification_settings_summary">允許控制您的媒體播放器而不需要開啟 KDE 連線。</string>
|
||||
<string name="share_to">分享給</string>
|
||||
<string name="protocol_version_older">這個裝置使用舊版本的通訊協定</string>
|
||||
<string name="protocol_version_newer">此設備使用較新的通訊協定</string>
|
||||
@@ -126,6 +141,12 @@
|
||||
<string name="custom_device_list">以IP來新增設備</string>
|
||||
<string name="share_notification_preference">通知方式</string>
|
||||
<string name="share_notification_preference_summary">當接收檔案時發出振動以及播放聲音</string>
|
||||
<string name="share_destination_customize">自訂目標路徑</string>
|
||||
<string name="share_destination_customize_summary_disabled">接收到的檔案將會在 Downloads 上顯示。</string>
|
||||
<string name="share_destination_customize_summary_enabled">檔案將會儲存在下方所示的資料夾</string>
|
||||
<string name="share_destination_folder_preference">目標路徑</string>
|
||||
<string name="share">分享</string>
|
||||
<string name="share_received_file">分享「%s」</string>
|
||||
<string name="title_activity_notification_filter">通知過濾器</string>
|
||||
<string name="filter_apps_info">將會以您選擇的App應用程式啟用同步通知</string>
|
||||
<string name="sftp_internal_storage">內部儲存空間</string>
|
||||
@@ -147,6 +168,7 @@
|
||||
<string name="device_rename_confirm">更改名稱</string>
|
||||
<string name="refresh">刷新</string>
|
||||
<string name="unreachable_description">此配對的裝置無法連接。 請確保它連接到與您相同的網域。</string>
|
||||
<string name="on_data_message">看起來你現在正使用行動數據連線。KDE 連線只在區域網路上運作。</string>
|
||||
<string name="no_file_browser">沒有安裝此檔案的瀏覽程式</string>
|
||||
<string name="pref_plugin_telepathy">傳送簡訊</string>
|
||||
<string name="pref_plugin_telepathy_desc">傳送文字簡訊到您的電腦桌面</string>
|
||||
@@ -157,4 +179,24 @@
|
||||
<string name="findmyphone_found">找到</string>
|
||||
<string name="open">開啟</string>
|
||||
<string name="close">關閉</string>
|
||||
<string name="no_permissions_storage">您需要授予儲存權限</string>
|
||||
<string name="plugins_need_permission">部份的附加元件需要權限才能運作(點擊以取得更多資訊)</string>
|
||||
<string name="permission_explanation">這附加元件需要權限以運作</string>
|
||||
<string name="optional_permission_explanation">你需要授予延伸的權限以啟用所有的功能</string>
|
||||
<string name="plugins_need_optional_permission">部份的附加元件因為缺乏權限,而導致功能被停用。(點擊以了解更多資訊):</string>
|
||||
<string name="sftp_permission_explanation">為了要從您的個人電腦存取檔案,這個應用程式需要權限以存取您的手機儲存空間。</string>
|
||||
<string name="share_optional_permission_explanation">為了要在您的手機與電腦之間分享檔案,你需要同意存取手機的儲存空間。</string>
|
||||
<string name="telepathy_permission_explanation">為了要在您的個人電腦上讀取與撰寫簡訊,你需要提供簡訊的權限。</string>
|
||||
<string name="telephony_permission_explanation">為了要在您的電腦上檢視手機通話與簡訊,你需要提供手機通話與簡訊的權限。</string>
|
||||
<string name="telephony_optional_permission_explanation">為了要讓聯絡人名稱取代手機號碼,您需要提供手機通訊錄的權限。</string>
|
||||
<string name="select_ringtone">選擇一個鈴聲</string>
|
||||
<string name="telephony_pref_blocked_title">已封鎖號碼</string>
|
||||
<string name="telephony_pref_blocked_dialog_desc">不顯示這些號碼的來電與簡訊。請在一行指定一個電話號碼。</string>
|
||||
<string name="mpris_coverart_description">目前媒體的專輯圖像</string>
|
||||
<string name="device_icon_description">裝置圖示</string>
|
||||
<string name="settings_icon_description">設定圖示</string>
|
||||
<string name="add_command">增加一行指令</string>
|
||||
<string name="addcommand_explanation">沒有指令被註冊</string>
|
||||
<string name="addcommand_explanation2">您現在可以在 KDE 連線系統設定增加新的指令</string>
|
||||
<string name="dark_theme">暗色主題</string>
|
||||
</resources>
|
||||
|
6
res/values/attrs.xml
Normal file
6
res/values/attrs.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- The style to use on the MainActivity's NavigationView -->
|
||||
<attr name="mainNavigationViewStyle" format="reference" />
|
||||
<!-- A high-contrast color for important visual elements (for less important elements, use colorControlNormal instead) -->
|
||||
<attr name="colorHighContrast" format="color" />
|
||||
</resources>
|
@@ -31,6 +31,7 @@
|
||||
<string name="cancel">Cancel</string>
|
||||
<string name="open_settings">Open settings</string>
|
||||
<string name="no_permissions">You need to grant permission to access notifications</string>
|
||||
<string name="no_permission_mprisreceiver">To be able to control your media players you need to grant access to the notifications</string>
|
||||
<string name="send_ping">Send ping</string>
|
||||
<string name="open_mpris_controls">Multimedia control</string>
|
||||
<string name="remotekeyboard_editing_only" translatable="false">remotekeyboard_editing_only</string>
|
||||
@@ -177,6 +178,8 @@
|
||||
<string name="share_destination_customize_summary_disabled">Received files will appear in Downloads</string>
|
||||
<string name="share_destination_customize_summary_enabled">Files will be stored in the directory below</string>
|
||||
<string name="share_destination_folder_preference">Destination directory</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="share_received_file">Share \"%s\"</string>
|
||||
<string name="title_activity_notification_filter">Notification filter</string>
|
||||
<string name="filter_apps_info">Notifications will be synchronized for the selected apps.</string>
|
||||
<string name="sftp_internal_storage">Internal storage</string>
|
||||
@@ -206,6 +209,7 @@
|
||||
<string name="plugin_not_supported">This plugin is not supported by the device</string>
|
||||
<string name="findmyphone_title">Find my phone</string>
|
||||
<string name="findmyphone_title_tablet">Find my tablet</string>
|
||||
<string name="findmyphone_title_tv">Find my TV</string>
|
||||
<string name="findmyphone_description">Rings this device so you can find it</string>
|
||||
<string name="findmyphone_found">Found</string>
|
||||
|
||||
@@ -230,10 +234,12 @@
|
||||
<string name="settings_icon_description">Settings icon</string>
|
||||
|
||||
<string name="add_command">Add a command</string>
|
||||
<string name="addcommand_name">Name</string>
|
||||
<string name="addcommand_command">Command</string>
|
||||
<string name="command_confirm_needed">You will need to confirm the command on the desktop</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>
|
||||
<string name="add_command_description">You can add commands on the desktop</string>
|
||||
<string name="pref_plugin_mprisreceiver">Media Player Control</string>
|
||||
<string name="pref_plugin_mprisreceiver_desc">Control your phones media players from another device</string>
|
||||
|
||||
<string name="dark_theme">Dark theme</string>
|
||||
|
||||
</resources>
|
||||
|
40
res/values/styles-dark.xml
Normal file
40
res/values/styles-dark.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<resources>
|
||||
|
||||
<color name="darkGrey">#555555</color>
|
||||
<color name="darkToolbarBackground">#222222</color>
|
||||
<color name="darkStatusBarBackground">#333333</color>
|
||||
|
||||
<!-- KdeConnectThemeBase styles must only be defined in the main res/values/ folder -->
|
||||
<style name="KdeConnectThemeBase.Dark" parent="Theme.AppCompat">
|
||||
<item name="colorPrimary">@color/darkGrey</item>
|
||||
<item name="colorPrimaryDark">@color/darkStatusBarBackground</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="android:windowBackground">@android:color/black</item>
|
||||
<item name="toolbarStyle">@style/KdeConnectTheme.Toolbar.Dark</item>
|
||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat</item>
|
||||
<item name="mainNavigationViewStyle">@style/MainNavigationView.Dark</item>
|
||||
<item name="colorHighContrast">@android:color/white</item>
|
||||
<item name="android:textColorPrimary">@android:color/white</item>
|
||||
<item name="android:textColor">@android:color/white</item>
|
||||
</style>
|
||||
|
||||
<style name="KdeConnectThemeBase.Dark.NoActionBar" parent="KdeConnectThemeBase.Dark">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<!-- KdeConnectTheme styles (without the 'Base' on the end) can be redefined in other 'values' folders. like res/values-v21/ -->
|
||||
|
||||
<style name="KdeConnectTheme.Toolbar.Dark">
|
||||
<item name="android:background">@color/darkToolbarBackground</item>
|
||||
</style>
|
||||
<style name="KdeConnectTheme.Dark" parent="KdeConnectThemeBase.Dark" />
|
||||
<style name="KdeConnectTheme.Dark.NoActionBar" parent="KdeConnectThemeBase.Dark.NoActionBar" />
|
||||
|
||||
<style name="MainNavigationView.Dark">
|
||||
<item name="android:background">@drawable/state_list_drawer_background_dark</item>
|
||||
<item name="itemBackground">@drawable/state_list_drawer_background_dark</item>
|
||||
<item name="itemIconTint">@android:color/white</item>
|
||||
<item name="itemTextColor">@android:color/white</item>
|
||||
</style>
|
||||
</resources>
|
@@ -10,6 +10,10 @@
|
||||
<item name="colorPrimary">@color/primary</item>
|
||||
<item name="colorPrimaryDark">@color/primaryDark</item>
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="toolbarStyle">@style/KdeConnectTheme.Toolbar</item>
|
||||
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
|
||||
<item name="mainNavigationViewStyle">@style/MainNavigationView</item>
|
||||
<item name="colorHighContrast">@android:color/black</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
</style>
|
||||
@@ -21,8 +25,18 @@
|
||||
|
||||
<style name="KdeConnectTheme" parent="KdeConnectThemeBase" />
|
||||
|
||||
<style name="KdeConnectTheme.Toolbar" parent="Widget.AppCompat.Toolbar">
|
||||
<item name="android:background">?attr/colorPrimary</item>
|
||||
</style>
|
||||
<style name="KdeConnectTheme.NoActionBar" parent="KdeConnectThemeBase.NoActionBar" />
|
||||
|
||||
<style name="MainNavigationView">
|
||||
<item name="android:background">@drawable/state_list_drawer_background</item>
|
||||
<item name="itemBackground">@drawable/state_list_drawer_background</item>
|
||||
<item name="itemIconTint">@color/state_list_drawer_text</item>
|
||||
<item name="itemTextColor">@color/state_list_drawer_text</item>
|
||||
</style>
|
||||
|
||||
<style name="DisableableButton" parent="ThemeOverlay.AppCompat">
|
||||
<item name="colorButtonNormal">@drawable/disableable_button</item>
|
||||
</style>
|
||||
|
@@ -89,34 +89,31 @@ public class LanLink extends BaseLink {
|
||||
|
||||
//Log.e("LanLink", "Start listening");
|
||||
//Create a thread to take care of incoming data for the new socket
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(newSocket.getInputStream(), StringsHelper.UTF8));
|
||||
while (true) {
|
||||
String packet;
|
||||
try {
|
||||
packet = reader.readLine();
|
||||
} catch (SocketTimeoutException e) {
|
||||
continue;
|
||||
}
|
||||
if (packet == null) {
|
||||
throw new IOException("End of stream");
|
||||
}
|
||||
if (packet.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
NetworkPacket np = NetworkPacket.unserialize(packet);
|
||||
receivedNetworkPacket(np);
|
||||
new Thread(() -> {
|
||||
try {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(newSocket.getInputStream(), StringsHelper.UTF8));
|
||||
while (true) {
|
||||
String packet;
|
||||
try {
|
||||
packet = reader.readLine();
|
||||
} catch (SocketTimeoutException e) {
|
||||
continue;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i("LanLink", "Socket closed: " + newSocket.hashCode() + ". Reason: " + e.getMessage());
|
||||
try { Thread.sleep(300); } catch (InterruptedException ignored) {} // Wait a bit because we might receive a new socket meanwhile
|
||||
boolean thereIsaANewSocket = (newSocket != socket);
|
||||
if (!thereIsaANewSocket) {
|
||||
callback.linkDisconnected(LanLink.this);
|
||||
if (packet == null) {
|
||||
throw new IOException("End of stream");
|
||||
}
|
||||
if (packet.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
NetworkPacket np = NetworkPacket.unserialize(packet);
|
||||
receivedNetworkPacket(np);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.i("LanLink", "Socket closed: " + newSocket.hashCode() + ". Reason: " + e.getMessage());
|
||||
try { Thread.sleep(300); } catch (InterruptedException ignored) {} // Wait a bit because we might receive a new socket meanwhile
|
||||
boolean thereIsaANewSocket = (newSocket != socket);
|
||||
if (!thereIsaANewSocket) {
|
||||
callback.linkDisconnected(LanLink.this);
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
@@ -27,6 +27,7 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
@@ -59,6 +60,13 @@ import javax.net.ssl.HandshakeCompletedEvent;
|
||||
import javax.net.ssl.HandshakeCompletedListener;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
/**
|
||||
* This BaseLinkProvider creates {@link LanLink}s to other devices on the same
|
||||
* WiFi network. The first packet sent over a socket must be an
|
||||
* {@link NetworkPacket#createIdentityPacket(Context)}.
|
||||
*
|
||||
* @see #identityPacketReceived(NetworkPacket, Socket, LanLink.ConnectionStarted)
|
||||
*/
|
||||
public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDisconnectedCallback {
|
||||
|
||||
public static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
|
||||
@@ -157,14 +165,14 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address);
|
||||
e.printStackTrace();
|
||||
if (!reverseConnectionBlackList.contains(address)) {
|
||||
Log.w("KDE/LanLinkProvider","Blacklisting "+address);
|
||||
Log.w("KDE/LanLinkProvider", "Blacklisting " + address);
|
||||
reverseConnectionBlackList.add(address);
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
reverseConnectionBlackList.remove(address);
|
||||
}
|
||||
}, 5*1000);
|
||||
}, 5 * 1000);
|
||||
|
||||
// Try to cause a reverse connection
|
||||
onNetworkChange();
|
||||
@@ -180,6 +188,18 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new 'identity' packet is received. Those are passed here by
|
||||
* {@link #tcpPacketReceived(Socket)} and {@link #udpPacketReceived(DatagramPacket)}.
|
||||
* <p>
|
||||
* If the remote device should be connected, this calls {@link #addLink}.
|
||||
* Otherwise, if there was an Exception, we unpair from that device.
|
||||
* </p>
|
||||
*
|
||||
* @param identityPacket identity of a remote device
|
||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
||||
* @param connectionStarted which side started this connection
|
||||
*/
|
||||
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted) {
|
||||
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
@@ -201,59 +221,47 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
|
||||
//Device paired with and old version, we can't use it as we lack the certificate
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
//Retry as unpaired
|
||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||
}
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
//Retry as unpaired
|
||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||
});
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider","Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:"+isDeviceTrusted);
|
||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
||||
|
||||
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||
sslsocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
|
||||
@Override
|
||||
public void handshakeCompleted(HandshakeCompletedEvent event) {
|
||||
String mode = clientMode? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider","Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPacket, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
}
|
||||
});
|
||||
}
|
||||
sslsocket.addHandshakeCompletedListener(event -> {
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPacket, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.unpair();
|
||||
});
|
||||
}
|
||||
});
|
||||
//Handshake is blocking, so do it on another thread and free this thread to keep receiving new connection
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
sslsocket.startHandshake();
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Handshake failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
sslsocket.startHandshake();
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
|
||||
//String[] ciphers = sslsocket.getSupportedCipherSuites();
|
||||
//for (String cipher : ciphers) {
|
||||
// Log.i("SupportedCiphers","cipher: " + cipher);
|
||||
//}
|
||||
}
|
||||
//String[] ciphers = sslsocket.getSupportedCipherSuites();
|
||||
//for (String cipher : ciphers) {
|
||||
// Log.i("SupportedCiphers","cipher: " + cipher);
|
||||
//}
|
||||
}
|
||||
}).start();
|
||||
} else {
|
||||
@@ -265,6 +273,19 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update a link in the {@link #visibleComputers} map. This method is synchronized, which ensures that only one
|
||||
* link is operated on at a time.
|
||||
* <p>
|
||||
* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in
|
||||
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
|
||||
* </p>
|
||||
*
|
||||
* @param identityPacket representation of remote device
|
||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
||||
* @param connectionOrigin which side started this connection
|
||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(Socket, LanLink.ConnectionStarted)}
|
||||
*/
|
||||
private synchronized void addLink(final NetworkPacket identityPacket, Socket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
|
||||
|
||||
String deviceId = identityPacket.getString("deviceId");
|
||||
@@ -298,23 +319,20 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (listening) {
|
||||
final int bufferSize = 1024 * 512;
|
||||
byte[] data = new byte[bufferSize];
|
||||
DatagramPacket packet = new DatagramPacket(data, bufferSize);
|
||||
try {
|
||||
server.receive(packet);
|
||||
udpPacketReceived(packet);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "UdpReceive exception");
|
||||
}
|
||||
new Thread(() -> {
|
||||
while (listening) {
|
||||
final int bufferSize = 1024 * 512;
|
||||
byte[] data = new byte[bufferSize];
|
||||
DatagramPacket packet = new DatagramPacket(data, bufferSize);
|
||||
try {
|
||||
server.receive(packet);
|
||||
udpPacketReceived(packet);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "UdpReceive exception");
|
||||
}
|
||||
Log.w("UdpListener","Stopping UDP listener");
|
||||
}
|
||||
Log.w("UdpListener", "Stopping UDP listener");
|
||||
}).start();
|
||||
return server;
|
||||
}
|
||||
@@ -323,21 +341,18 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
try {
|
||||
tcpServer = openServerSocketOnFreePort(MIN_PORT);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
while (listening) {
|
||||
try {
|
||||
Socket socket = tcpServer.accept();
|
||||
configureSocket(socket);
|
||||
tcpPacketReceived(socket);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "TcpReceive exception");
|
||||
}
|
||||
new Thread(() -> {
|
||||
while (listening) {
|
||||
try {
|
||||
Socket socket = tcpServer.accept();
|
||||
configureSocket(socket);
|
||||
tcpPacketReceived(socket);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("LanLinkProvider", "TcpReceive exception");
|
||||
}
|
||||
Log.w("TcpListener", "Stopping TCP listener");
|
||||
}
|
||||
Log.w("TcpListener", "Stopping TCP listener");
|
||||
}).start();
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -347,13 +362,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
|
||||
int tcpPort = minPort;
|
||||
while(tcpPort < MAX_PORT) {
|
||||
while (tcpPort < MAX_PORT) {
|
||||
try {
|
||||
ServerSocket candidateServer = new ServerSocket();
|
||||
candidateServer.bind(new InetSocketAddress(tcpPort));
|
||||
Log.i("KDE/LanLink", "Using port "+tcpPort);
|
||||
Log.i("KDE/LanLink", "Using port " + tcpPort);
|
||||
return candidateServer;
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
tcpPort++;
|
||||
}
|
||||
}
|
||||
@@ -368,53 +383,51 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
return;
|
||||
}
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(() -> {
|
||||
|
||||
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(CustomDevicesActivity.KEY_CUSTOM_DEVLIST_PREFERENCE, "");
|
||||
ArrayList<String> iplist = new ArrayList<>();
|
||||
if (!deviceListPrefs.isEmpty()) {
|
||||
iplist = CustomDevicesActivity.deserializeIpList(deviceListPrefs);
|
||||
}
|
||||
iplist.add("255.255.255.255"); //Default: broadcast.
|
||||
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(CustomDevicesActivity.KEY_CUSTOM_DEVLIST_PREFERENCE, "");
|
||||
ArrayList<String> iplist = new ArrayList<>();
|
||||
if (!deviceListPrefs.isEmpty()) {
|
||||
iplist = CustomDevicesActivity.deserializeIpList(deviceListPrefs);
|
||||
}
|
||||
iplist.add("255.255.255.255"); //Default: broadcast.
|
||||
|
||||
NetworkPacket identity = NetworkPacket.createIdentityPacket(context);
|
||||
identity.set("tcpPort", MIN_PORT);
|
||||
DatagramSocket socket = null;
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
socket.setReuseAddress(true);
|
||||
socket.setBroadcast(true);
|
||||
bytes = identity.serialize().getBytes(StringsHelper.UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider","Failed to create DatagramSocket");
|
||||
}
|
||||
NetworkPacket identity = NetworkPacket.createIdentityPacket(context);
|
||||
identity.set("tcpPort", MIN_PORT);
|
||||
DatagramSocket socket = null;
|
||||
byte[] bytes = null;
|
||||
try {
|
||||
socket = new DatagramSocket();
|
||||
socket.setReuseAddress(true);
|
||||
socket.setBroadcast(true);
|
||||
bytes = identity.serialize().getBytes(StringsHelper.UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket");
|
||||
}
|
||||
|
||||
if (bytes != null) {
|
||||
//Log.e("KDE/LanLinkProvider","Sending packet to "+iplist.size()+" ips");
|
||||
for (String ipstr : iplist) {
|
||||
try {
|
||||
InetAddress client = InetAddress.getByName(ipstr);
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT_LEGACY));
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+client);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Sending udp identity package failed. Invalid address? (" + ipstr + ")");
|
||||
}
|
||||
if (bytes != null) {
|
||||
//Log.e("KDE/LanLinkProvider","Sending packet to "+iplist.size()+" ips");
|
||||
for (String ipstr : iplist) {
|
||||
try {
|
||||
InetAddress client = InetAddress.getByName(ipstr);
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT));
|
||||
socket.send(new DatagramPacket(bytes, bytes.length, client, MIN_PORT_LEGACY));
|
||||
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+client);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider", "Sending udp identity package failed. Invalid address? (" + ipstr + ")");
|
||||
}
|
||||
}
|
||||
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (socket != null) {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
//Log.i("KDE/LanLinkProvider", "onStart");
|
||||
@@ -431,7 +444,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
// and newer android versions. Although devices with android version less than ICS cannot connect to other devices who also have android version less
|
||||
// than ICS because server is disabled on both
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
Log.w("KDE/LanLinkProvider","Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
Log.w("KDE/LanLinkProvider", "Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
} else {
|
||||
setupTcpListener();
|
||||
}
|
||||
@@ -451,17 +464,17 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
listening = false;
|
||||
try {
|
||||
tcpServer.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServer.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServerOldPort.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
@@ -87,24 +87,18 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
|
||||
public static void addGuiInUseCounter(final Context activity, final boolean forceNetworkRefresh) {
|
||||
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
boolean refreshed = service.acquireDiscoveryMode(activity);
|
||||
if (!refreshed && forceNetworkRefresh) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
BackgroundService.RunCommand(activity, service -> {
|
||||
boolean refreshed = service.acquireDiscoveryMode(activity);
|
||||
if (!refreshed && forceNetworkRefresh) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void removeGuiInUseCounter(final Context activity) {
|
||||
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
//If no user interface is open, close the connections open to other devices
|
||||
service.releaseDiscoveryMode(activity);
|
||||
}
|
||||
BackgroundService.RunCommand(activity, service -> {
|
||||
//If no user interface is open, close the connections open to other devices
|
||||
service.releaseDiscoveryMode(activity);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -167,13 +161,10 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
|
||||
private void cleanDevices() {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Device d : devices.values()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
|
||||
d.disconnect();
|
||||
}
|
||||
new Thread(() -> {
|
||||
for (Device d : devices.values()) {
|
||||
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
|
||||
d.disconnect();
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -328,20 +319,17 @@ public class BackgroundService extends Service {
|
||||
}
|
||||
|
||||
public static void RunCommand(final Context c, final InstanceCallback callback) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (callback != null) {
|
||||
mutex.lock();
|
||||
try {
|
||||
callbacks.add(callback);
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
new Thread(() -> {
|
||||
if (callback != null) {
|
||||
mutex.lock();
|
||||
try {
|
||||
callbacks.add(callback);
|
||||
} finally {
|
||||
mutex.unlock();
|
||||
}
|
||||
Intent serviceIntent = new Intent(c, BackgroundService.class);
|
||||
c.startService(serviceIntent);
|
||||
}
|
||||
Intent serviceIntent = new Intent(c, BackgroundService.class);
|
||||
c.startService(serviceIntent);
|
||||
}).start();
|
||||
}
|
||||
|
||||
|
@@ -103,11 +103,13 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
public enum DeviceType {
|
||||
Phone,
|
||||
Tablet,
|
||||
Computer;
|
||||
Computer,
|
||||
Tv;
|
||||
|
||||
public static DeviceType FromString(String s) {
|
||||
if ("tablet".equals(s)) return Tablet;
|
||||
if ("phone".equals(s)) return Phone;
|
||||
if ("tv".equals(s)) return Tv;
|
||||
return Computer; //Default
|
||||
}
|
||||
|
||||
@@ -117,6 +119,8 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
return "tablet";
|
||||
case Phone:
|
||||
return "phone";
|
||||
case Tv:
|
||||
return "tv";
|
||||
default:
|
||||
return "desktop";
|
||||
}
|
||||
@@ -195,6 +199,9 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
case Tablet:
|
||||
drawableId = R.drawable.ic_device_tablet;
|
||||
break;
|
||||
case Tv:
|
||||
drawableId = R.drawable.ic_device_tv;
|
||||
break;
|
||||
default:
|
||||
drawableId = R.drawable.ic_device_laptop;
|
||||
}
|
||||
@@ -638,12 +645,7 @@ public class Device implements BaseLink.PacketReceiver {
|
||||
|
||||
//Async
|
||||
public void sendPacket(final NetworkPacket np, final SendPacketStatusCallback callback) {
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
sendPacketBlocking(np, callback);
|
||||
}
|
||||
}).start();
|
||||
new Thread(() -> sendPacketBlocking(np, callback)).start();
|
||||
}
|
||||
|
||||
public boolean sendPacketBlocking(final NetworkPacket np, final SendPacketStatusCallback callback) {
|
||||
|
@@ -29,6 +29,8 @@ import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public class DeviceHelper {
|
||||
@@ -338,6 +340,7 @@ public class DeviceHelper {
|
||||
humanReadableNames.put("SM-G3815", "Samsung Galaxy Express II");
|
||||
humanReadableNames.put("SM-G386T", "Samsung Galaxy Avant");
|
||||
humanReadableNames.put("SM-G386T1", "Samsung Galaxy Avant");
|
||||
humanReadableNames.put("SM-G388F", "Samsung Galaxy Xcover 3");
|
||||
humanReadableNames.put("SM-G7102", "Samsung Galaxy Grand II");
|
||||
humanReadableNames.put("SM-G800F", "Samsung Galaxy S5 Mini");
|
||||
humanReadableNames.put("SM-G860P", "Samsung Galaxy S5 Sport");
|
||||
@@ -366,6 +369,34 @@ public class DeviceHelper {
|
||||
humanReadableNames.put("SM-G925T", "Galaxy S6 Edge");
|
||||
humanReadableNames.put("SM-G925V", "Galaxy S6 Edge");
|
||||
humanReadableNames.put("SM-G925W8", "Galaxy S6 Edge");
|
||||
humanReadableNames.put("SM-G9500", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950F", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950T", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950S", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950K", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950L", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950P", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950A", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G9509", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G9508", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950R4", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950V", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950FD", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G950W8", "Samsung Galaxy S8");
|
||||
humanReadableNames.put("SM-G9550", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955F", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955T", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955S", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955K", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955L", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955P", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955A", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G9559", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G9558", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955R4", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955V", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955FD", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-G955W8", "Samsung Galaxy S8 Plus");
|
||||
humanReadableNames.put("SM-N7505", "Samsung Galaxy Note 3 Neo");
|
||||
humanReadableNames.put("SM-N900", "Samsung Galaxy Note 3");
|
||||
humanReadableNames.put("SM-N9005", "Samsung Galaxy Note 3");
|
||||
@@ -475,12 +506,27 @@ public class DeviceHelper {
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isTablet() {
|
||||
private static boolean isTablet() {
|
||||
Configuration config = Resources.getSystem().getConfiguration();
|
||||
//This assumes that the values for the screen sizes are consecutive, so XXLARGE > XLARGE > LARGE
|
||||
return ((config.screenLayout & Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE);
|
||||
}
|
||||
|
||||
private static boolean isTv(Context context) {
|
||||
int uiMode = context.getResources().getConfiguration().uiMode;
|
||||
return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
|
||||
}
|
||||
|
||||
public static Device.DeviceType getDeviceType(Context context) {
|
||||
if (isTv(context)) {
|
||||
return Device.DeviceType.Tv;
|
||||
} else if (isTablet()) {
|
||||
return Device.DeviceType.Tablet;
|
||||
} else {
|
||||
return Device.DeviceType.Phone;
|
||||
}
|
||||
}
|
||||
|
||||
//It returns getAndroidDeviceName() if no user-defined name has been set with setDeviceName().
|
||||
public static String getDeviceName(Context context) {
|
||||
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
|
@@ -43,41 +43,27 @@ public class KdeConnectBroadcastReceiver extends BroadcastReceiver {
|
||||
Log.i("KdeConnect", "Ignoring, it's not me!");
|
||||
return;
|
||||
}
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
|
||||
}
|
||||
});
|
||||
break;
|
||||
case Intent.ACTION_BOOT_COMPLETED:
|
||||
Log.i("KdeConnect", "KdeConnectBroadcastReceiver");
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
|
||||
}
|
||||
});
|
||||
break;
|
||||
case WifiManager.SUPPLICANT_CONNECTION_CHANGE_ACTION:
|
||||
case WifiManager.WIFI_STATE_CHANGED_ACTION:
|
||||
case ConnectivityManager.CONNECTIVITY_ACTION:
|
||||
Log.i("KdeConnect", "Connection state changed, trying to connect");
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onDeviceListChanged();
|
||||
service.onNetworkChange();
|
||||
}
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
service.onDeviceListChanged();
|
||||
service.onNetworkChange();
|
||||
});
|
||||
break;
|
||||
case Intent.ACTION_SCREEN_ON:
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(context, BackgroundService::onNetworkChange);
|
||||
break;
|
||||
default:
|
||||
Log.i("BroadcastReceiver", "Ignoring broadcast event: " + intent.getAction());
|
||||
|
@@ -159,6 +159,17 @@ public class NetworkPacket {
|
||||
}
|
||||
}
|
||||
|
||||
public JSONObject getJSONObject(String key) {
|
||||
return mBody.optJSONObject(key);
|
||||
}
|
||||
|
||||
public void set(String key, JSONObject value) {
|
||||
try {
|
||||
mBody.put(key, value);
|
||||
} catch (JSONException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getStringSet(String key) {
|
||||
JSONArray jsonArray = mBody.optJSONArray(key);
|
||||
if (jsonArray == null) return null;
|
||||
@@ -264,7 +275,7 @@ public class NetworkPacket {
|
||||
np.mBody.put("deviceId", deviceId);
|
||||
np.mBody.put("deviceName", DeviceHelper.getDeviceName(context));
|
||||
np.mBody.put("protocolVersion", NetworkPacket.ProtocolVersion);
|
||||
np.mBody.put("deviceType", DeviceHelper.isTablet() ? "tablet" : "phone");
|
||||
np.mBody.put("deviceType", DeviceHelper.getDeviceType(context).toString());
|
||||
np.mBody.put("incomingCapabilities", new JSONArray(PluginFactory.getIncomingCapabilities(context)));
|
||||
np.mBody.put("outgoingCapabilities", new JSONArray(PluginFactory.getOutgoingCapabilities(context)));
|
||||
} catch (Exception e) {
|
||||
|
@@ -69,35 +69,29 @@ public class ClipboardListener {
|
||||
return;
|
||||
}
|
||||
|
||||
new Handler(Looper.getMainLooper()).post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
listener = new ClipboardManager.OnPrimaryClipChangedListener() {
|
||||
@Override
|
||||
public void onPrimaryClipChanged() {
|
||||
try {
|
||||
new Handler(Looper.getMainLooper()).post(() -> {
|
||||
cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
listener = () -> {
|
||||
try {
|
||||
|
||||
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
|
||||
String content = item.coerceToText(context).toString();
|
||||
ClipData.Item item = cm.getPrimaryClip().getItemAt(0);
|
||||
String content = item.coerceToText(context).toString();
|
||||
|
||||
if (content.equals(currentContent)) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentContent = content;
|
||||
|
||||
for (ClipboardObserver observer : observers) {
|
||||
observer.clipboardChanged(content);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
//Probably clipboard was not text
|
||||
}
|
||||
if (content.equals(currentContent)) {
|
||||
return;
|
||||
}
|
||||
};
|
||||
cm.addPrimaryClipChangedListener(listener);
|
||||
}
|
||||
|
||||
currentContent = content;
|
||||
|
||||
for (ClipboardObserver observer : observers) {
|
||||
observer.clipboardChanged(content);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
//Probably clipboard was not text
|
||||
}
|
||||
};
|
||||
cm.addPrimaryClipChangedListener(listener);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -53,13 +53,10 @@ public class ClipboardPlugin extends Plugin {
|
||||
return true;
|
||||
}
|
||||
|
||||
private ClipboardListener.ClipboardObserver observer = new ClipboardListener.ClipboardObserver() {
|
||||
@Override
|
||||
public void clipboardChanged(String content) {
|
||||
NetworkPacket np = new NetworkPacket(ClipboardPlugin.PACKET_TYPE_CLIPBOARD);
|
||||
np.set("content", content);
|
||||
device.sendPacket(np);
|
||||
}
|
||||
private ClipboardListener.ClipboardObserver observer = content -> {
|
||||
NetworkPacket np = new NetworkPacket(ClipboardPlugin.PACKET_TYPE_CLIPBOARD);
|
||||
np.set("content", content);
|
||||
device.sendPacket(np);
|
||||
};
|
||||
|
||||
@Override
|
||||
|
@@ -28,11 +28,13 @@ import android.media.MediaPlayer;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class FindMyPhoneActivity extends Activity {
|
||||
@@ -55,6 +57,7 @@ public class FindMyPhoneActivity extends Activity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.activity_find_my_phone);
|
||||
|
||||
audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
|
||||
@@ -64,12 +67,7 @@ public class FindMyPhoneActivity extends Activity {
|
||||
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
|
||||
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||
|
||||
findViewById(R.id.bFindMyPhone).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
finish();
|
||||
}
|
||||
});
|
||||
findViewById(R.id.bFindMyPhone).setOnClickListener(view -> finish());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,10 +80,16 @@ public class FindMyPhoneActivity extends Activity {
|
||||
audioManager.setStreamVolume(AudioManager.STREAM_ALARM, audioManager.getStreamMaxVolume(AudioManager.STREAM_ALARM), 0);
|
||||
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
String ringtone = prefs.getString("select_ringtone", "");
|
||||
Uri ringtone;
|
||||
String ringtoneString = prefs.getString("select_ringtone", "");
|
||||
if (ringtoneString.isEmpty()) {
|
||||
ringtone = Settings.System.DEFAULT_RINGTONE_URI;
|
||||
} else {
|
||||
ringtone = Uri.parse(ringtoneString);
|
||||
}
|
||||
|
||||
mediaPlayer = new MediaPlayer();
|
||||
mediaPlayer.setDataSource(this, Uri.parse(ringtone));
|
||||
mediaPlayer.setDataSource(this, ringtone);
|
||||
mediaPlayer.setAudioStreamType(AudioManager.STREAM_ALARM);
|
||||
mediaPlayer.setLooping(true);
|
||||
mediaPlayer.prepare();
|
||||
|
@@ -22,6 +22,7 @@ package org.kde.kdeconnect.Plugins.FindMyPhonePlugin;
|
||||
|
||||
import android.content.Intent;
|
||||
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
@@ -33,7 +34,16 @@ public class FindMyPhonePlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return DeviceHelper.isTablet() ? context.getString(R.string.findmyphone_title_tablet) : context.getString(R.string.findmyphone_title);
|
||||
switch (DeviceHelper.getDeviceType(context)) {
|
||||
case Tv:
|
||||
return context.getString(R.string.findmyphone_title_tv);
|
||||
case Tablet:
|
||||
return context.getString(R.string.findmyphone_title_tablet);
|
||||
case Phone:
|
||||
return context.getString(R.string.findmyphone_title);
|
||||
default:
|
||||
return context.getString(R.string.findmyphone_title);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -109,14 +109,11 @@ public class KeyListenerView extends View {
|
||||
}
|
||||
|
||||
private void sendKeyPressPacket(final NetworkPacket np) {
|
||||
BackgroundService.RunCommand(getContext(), new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendKeyboardPacket(np);
|
||||
}
|
||||
BackgroundService.RunCommand(getContext(), service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendKeyboardPacket(np);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -37,6 +37,7 @@ import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class MousePadActivity extends AppCompatActivity implements GestureDetector.OnGestureListener, GestureDetector.OnDoubleTapListener, MousePadGestureDetector.OnGestureListener {
|
||||
@@ -80,6 +81,7 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
|
||||
setContentView(R.layout.activity_mousepad);
|
||||
|
||||
@@ -133,27 +135,24 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
|
||||
final View decorView = getWindow().getDecorView();
|
||||
decorView.setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener() {
|
||||
@Override
|
||||
public void onSystemUiVisibilityChange(int visibility) {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
decorView.setOnSystemUiVisibilityChangeListener(visibility -> {
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
||||
|
||||
int fullscreenType = 0;
|
||||
int fullscreenType = 0;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
}
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(fullscreenType);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
fullscreenType |= View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
}
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(fullscreenType);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -212,16 +211,13 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
mCurrentX = event.getX();
|
||||
mCurrentY = event.getY();
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY, mCurrentSensitivity);
|
||||
mPrevX = mCurrentX;
|
||||
mPrevY = mCurrentY;
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendMouseDelta(mCurrentX - mPrevX, mCurrentY - mPrevY, mCurrentSensitivity);
|
||||
mPrevX = mCurrentX;
|
||||
mPrevY = mCurrentY;
|
||||
});
|
||||
break;
|
||||
}
|
||||
@@ -285,14 +281,11 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
|
||||
getWindow().getDecorView().performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleHold();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleHold();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -303,28 +296,22 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapConfirmed(MotionEvent e) {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleClick();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleClick();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDoubleTap(MotionEvent e) {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendDoubleClick();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendDoubleClick();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
@@ -364,50 +351,38 @@ public class MousePadActivity extends AppCompatActivity implements GestureDetect
|
||||
|
||||
|
||||
private void sendMiddleClick() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendMiddleClick();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendMiddleClick();
|
||||
});
|
||||
}
|
||||
|
||||
private void sendRightClick() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendRightClick();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendRightClick();
|
||||
});
|
||||
}
|
||||
|
||||
private void sendSingleHold() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleHold();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendSingleHold();
|
||||
});
|
||||
}
|
||||
|
||||
private void sendScroll(final float y) {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendScroll(0, y);
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(deviceId);
|
||||
MousePadPlugin mousePadPlugin = device.getPlugin(MousePadPlugin.class);
|
||||
if (mousePadPlugin == null) return;
|
||||
mousePadPlugin.sendScroll(0, y);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -25,7 +25,9 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.support.v4.util.LruCache;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -61,11 +63,19 @@ public final class AlbumArtCache {
|
||||
* An on-disk cache for album art bitmaps.
|
||||
*/
|
||||
private static DiskLruCache diskCache;
|
||||
/**
|
||||
* Used to check if the connection is metered
|
||||
*/
|
||||
private static ConnectivityManager connectivityManager;
|
||||
|
||||
/**
|
||||
* A list of urls yet to be fetched.
|
||||
*/
|
||||
private static final ArrayList<URL> fetchUrlList = new ArrayList<>();
|
||||
/**
|
||||
* A list of urls currently being fetched
|
||||
*/
|
||||
private static final ArrayList<URL> isFetchingList = new ArrayList<>();
|
||||
/**
|
||||
* A integer indicating how many fetches are in progress.
|
||||
*/
|
||||
@@ -96,6 +106,8 @@ public final class AlbumArtCache {
|
||||
} catch (IOException e) {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "Could not open the album art disk cache!", e);
|
||||
}
|
||||
|
||||
connectivityManager = (ConnectivityManager) context.getApplicationContext().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,7 +135,7 @@ public final class AlbumArtCache {
|
||||
* @param albumUrl The album art url
|
||||
* @return A bitmap for the album art. Can be null if not (yet) found
|
||||
*/
|
||||
public static Bitmap getAlbumArt(String albumUrl) {
|
||||
public static Bitmap getAlbumArt(String albumUrl, MprisPlugin plugin, String player) {
|
||||
//If the url is invalid, return "no album art"
|
||||
if (albumUrl == null || albumUrl.isEmpty()) {
|
||||
return null;
|
||||
@@ -138,8 +150,8 @@ public final class AlbumArtCache {
|
||||
return null;
|
||||
}
|
||||
|
||||
//We currently only support http(s) urls
|
||||
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
|
||||
//We currently only support http(s) and file urls
|
||||
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https") && !url.getProtocol().equals("file")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -163,11 +175,7 @@ public final class AlbumArtCache {
|
||||
try {
|
||||
DiskLruCache.Snapshot item = diskCache.get(urlToDiskCacheKey(albumUrl));
|
||||
if (item != null) {
|
||||
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
|
||||
decodeOptions.inScaled = false;
|
||||
decodeOptions.inDensity = 1;
|
||||
decodeOptions.inTargetDensity = 1;
|
||||
Bitmap result = BitmapFactory.decodeStream(item.getInputStream(0), null, decodeOptions);
|
||||
Bitmap result = BitmapFactory.decodeStream(item.getInputStream(0));
|
||||
item.close();
|
||||
MemoryCacheItem memItem = new MemoryCacheItem();
|
||||
if (result != null) {
|
||||
@@ -189,7 +197,20 @@ public final class AlbumArtCache {
|
||||
|
||||
/* If not found, we have not tried fetching it (recently), or a fetch is in-progress.
|
||||
Either way, just add it to the fetch queue and starting fetching it if no fetch is running. */
|
||||
fetchUrl(url);
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
//Special-case file, since we need to fetch it from the remote
|
||||
if (isFetchingList.contains(url)) return null;
|
||||
|
||||
if (!plugin.askTransferAlbumArt(albumUrl, player)) {
|
||||
//It doesn't support transferring the art, so mark it as failed in the memory cache
|
||||
MemoryCacheItem cacheItem = new MemoryCacheItem();
|
||||
cacheItem.failedFetch = true;
|
||||
cacheItem.albumArt = null;
|
||||
memoryCache.put(url.toString(), cacheItem);
|
||||
}
|
||||
} else {
|
||||
fetchUrl(url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -204,9 +225,15 @@ public final class AlbumArtCache {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!");
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
|
||||
//Only download art on unmetered networks (wifi etc.)
|
||||
if (connectivityManager.isActiveNetworkMetered()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
//Only fetch an URL if we're not fetching it already
|
||||
if (fetchUrlList.contains(url)) {
|
||||
if (fetchUrlList.contains(url) || isFetchingList.contains(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -243,6 +270,9 @@ public final class AlbumArtCache {
|
||||
*/
|
||||
private boolean openHttp() throws IOException {
|
||||
//Default android behaviour does not follow https -> http urls, so do this manually
|
||||
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
|
||||
throw new AssertionError("Invalid url: not http(s) in background album art fetch");
|
||||
}
|
||||
URL currentUrl = url;
|
||||
HttpURLConnection connection;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
@@ -323,8 +353,8 @@ public final class AlbumArtCache {
|
||||
memoryCache.put(url.toString(), cacheItem);
|
||||
}
|
||||
|
||||
//Remove the url from the to-fetch list
|
||||
fetchUrlList.remove(url);
|
||||
//Remove the url from the fetching list
|
||||
isFetchingList.remove(url);
|
||||
//Fetch the next url (if any)
|
||||
--numFetching;
|
||||
initiateFetch();
|
||||
@@ -338,10 +368,19 @@ public final class AlbumArtCache {
|
||||
if (numFetching >= 2) return;
|
||||
if (fetchUrlList.isEmpty()) return;
|
||||
|
||||
++numFetching;
|
||||
|
||||
//Fetch the last-requested url first, it will probably be needed first
|
||||
URL url = fetchUrlList.get(fetchUrlList.size() - 1);
|
||||
//Remove the url from the to-fetch list
|
||||
fetchUrlList.remove(url);
|
||||
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
throw new AssertionError("Not file urls should be possible here!");
|
||||
}
|
||||
|
||||
//Download the album art ourselves
|
||||
++numFetching;
|
||||
//Add the url to the currently-fetching list
|
||||
isFetchingList.add(url);
|
||||
try {
|
||||
DiskLruCache.Editor cacheItem = diskCache.edit(urlToDiskCacheKey(url.toString()));
|
||||
if (cacheItem == null) {
|
||||
@@ -392,6 +431,12 @@ public final class AlbumArtCache {
|
||||
//We need the disk cache for this
|
||||
if (diskCache == null) {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!");
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
if (payload == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -400,20 +445,46 @@ public final class AlbumArtCache {
|
||||
url = new URL(albumUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
//Shouldn't happen (checked on receival of the url), but just to be sure
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"file".equals(url.getProtocol())) {
|
||||
//Shouldn't happen (otherwise we wouldn't have asked for the payload), but just to be sure
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
//Only fetch the URL if we're not fetching it already
|
||||
if (fetchUrlList.contains(url)) {
|
||||
if (isFetchingList.contains(url)) {
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
fetchUrlList.add(url);
|
||||
//Check if we already have this art
|
||||
try {
|
||||
if (memoryCache.get(albumUrl) != null || diskCache.get(urlToDiskCacheKey(albumUrl)) != null) {
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "Disk cache problem!", e);
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
//Add it to the currently-fetching list
|
||||
isFetchingList.add(url);
|
||||
++numFetching;
|
||||
|
||||
try {
|
||||
@@ -422,6 +493,9 @@ public final class AlbumArtCache {
|
||||
Log.e("KDE/Mpris/AlbumArtCache",
|
||||
"Two disk cache edits happened at the same time, should be impossible!");
|
||||
--numFetching;
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -46,6 +46,7 @@ import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.List;
|
||||
@@ -77,101 +78,94 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
protected void connectToPlugin(final String targetPlayerName) {
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
|
||||
if (mpris == null) {
|
||||
Log.e("MprisActivity", "device has no mpris plugin!");
|
||||
return;
|
||||
}
|
||||
targetPlayer = mpris.getPlayerStatus(targetPlayerName);
|
||||
|
||||
mpris.setPlayerStatusUpdatedHandler("activity", new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updatePlayerStatus(mpris);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
mpris.setPlayerListUpdatedHandler("activity", new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final List<String> playerList = mpris.getPlayerList();
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(MprisActivity.this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
playerList.toArray(new String[playerList.size()])
|
||||
);
|
||||
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.player_spinner);
|
||||
//String prevPlayer = (String)spinner.getSelectedItem();
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
if (playerList.isEmpty()) {
|
||||
findViewById(R.id.no_players).setVisibility(View.VISIBLE);
|
||||
spinner.setVisibility(View.GONE);
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText("");
|
||||
} else {
|
||||
findViewById(R.id.no_players).setVisibility(View.GONE);
|
||||
spinner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> arg0, View arg1, int pos, long id) {
|
||||
|
||||
if (pos >= playerList.size()) return;
|
||||
|
||||
String player = playerList.get(pos);
|
||||
if (targetPlayer != null && player.equals(targetPlayer.getPlayer())) {
|
||||
return; //Player hasn't actually changed
|
||||
}
|
||||
targetPlayer = mpris.getPlayerStatus(player);
|
||||
updatePlayerStatus(mpris);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
targetPlayer = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (targetPlayer == null) {
|
||||
//If no player is selected, try to select a playing player
|
||||
targetPlayer = mpris.getPlayingPlayer();
|
||||
}
|
||||
//Try to select the specified player
|
||||
if (targetPlayer != null) {
|
||||
int targetIndex = adapter.getPosition(targetPlayer.getPlayer());
|
||||
if (targetIndex >= 0) {
|
||||
spinner.setSelection(targetIndex);
|
||||
} else {
|
||||
targetPlayer = null;
|
||||
}
|
||||
}
|
||||
//If no player selected, select the first one (if any)
|
||||
if (targetPlayer == null && !playerList.isEmpty()) {
|
||||
targetPlayer = mpris.getPlayerStatus(playerList.get(0));
|
||||
spinner.setSelection(0);
|
||||
}
|
||||
updatePlayerStatus(mpris);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final MprisPlugin mpris = device.getPlugin(MprisPlugin.class);
|
||||
if (mpris == null) {
|
||||
Log.e("MprisActivity", "device has no mpris plugin!");
|
||||
return;
|
||||
}
|
||||
targetPlayer = mpris.getPlayerStatus(targetPlayerName);
|
||||
|
||||
mpris.setPlayerStatusUpdatedHandler("activity", new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
runOnUiThread(() -> updatePlayerStatus(mpris));
|
||||
}
|
||||
});
|
||||
|
||||
mpris.setPlayerListUpdatedHandler("activity", new Handler() {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
final List<String> playerList = mpris.getPlayerList();
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(MprisActivity.this,
|
||||
android.R.layout.simple_spinner_item,
|
||||
playerList.toArray(new String[playerList.size()])
|
||||
);
|
||||
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
runOnUiThread(() -> {
|
||||
Spinner spinner = (Spinner) findViewById(R.id.player_spinner);
|
||||
//String prevPlayer = (String)spinner.getSelectedItem();
|
||||
spinner.setAdapter(adapter);
|
||||
|
||||
if (playerList.isEmpty()) {
|
||||
findViewById(R.id.no_players).setVisibility(View.VISIBLE);
|
||||
spinner.setVisibility(View.GONE);
|
||||
((TextView) findViewById(R.id.now_playing_textview)).setText("");
|
||||
} else {
|
||||
findViewById(R.id.no_players).setVisibility(View.GONE);
|
||||
spinner.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
spinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(AdapterView<?> arg0, View arg1, int pos, long id) {
|
||||
|
||||
if (pos >= playerList.size()) return;
|
||||
|
||||
String player = playerList.get(pos);
|
||||
if (targetPlayer != null && player.equals(targetPlayer.getPlayer())) {
|
||||
return; //Player hasn't actually changed
|
||||
}
|
||||
targetPlayer = mpris.getPlayerStatus(player);
|
||||
updatePlayerStatus(mpris);
|
||||
|
||||
if (targetPlayer.isPlaying()) {
|
||||
MprisMediaSession.getInstance().playerSelected(targetPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(AdapterView<?> arg0) {
|
||||
targetPlayer = null;
|
||||
}
|
||||
});
|
||||
|
||||
if (targetPlayer == null) {
|
||||
//If no player is selected, try to select a playing player
|
||||
targetPlayer = mpris.getPlayingPlayer();
|
||||
}
|
||||
//Try to select the specified player
|
||||
if (targetPlayer != null) {
|
||||
int targetIndex = adapter.getPosition(targetPlayer.getPlayer());
|
||||
if (targetIndex >= 0) {
|
||||
spinner.setSelection(targetIndex);
|
||||
} else {
|
||||
targetPlayer = null;
|
||||
}
|
||||
}
|
||||
//If no player selected, select the first one (if any)
|
||||
if (targetPlayer == null && !playerList.isEmpty()) {
|
||||
targetPlayer = mpris.getPlayerStatus(playerList.get(0));
|
||||
spinner.setSelection(0);
|
||||
}
|
||||
updatePlayerStatus(mpris);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
@@ -191,12 +185,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.removeConnectionListener(connectionReceiver);
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> service.removeConnectionListener(connectionReceiver));
|
||||
}
|
||||
|
||||
private void updatePlayerStatus(MprisPlugin mpris) {
|
||||
@@ -246,8 +235,17 @@ public class MprisActivity extends AppCompatActivity {
|
||||
findViewById(R.id.volume_layout).setVisibility(playerStatus.isSetVolumeAllowed() ? View.VISIBLE : View.INVISIBLE);
|
||||
findViewById(R.id.rew_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.ff_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.next_button).setVisibility(playerStatus.isGoNextAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.prev_button).setVisibility(playerStatus.isGoPreviousAllowed() ? View.VISIBLE : View.GONE);
|
||||
|
||||
//Show and hide previous/next buttons simultaneously
|
||||
if (playerStatus.isGoPreviousAllowed() || playerStatus.isGoNextAllowed()) {
|
||||
findViewById(R.id.prev_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.prev_button).setEnabled(playerStatus.isGoPreviousAllowed());
|
||||
findViewById(R.id.next_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.next_button).setEnabled(playerStatus.isGoNextAllowed());
|
||||
} else {
|
||||
findViewById(R.id.prev_button).setVisibility(View.GONE);
|
||||
findViewById(R.id.next_button).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,6 +299,7 @@ public class MprisActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.activity_mpris);
|
||||
|
||||
final String targetPlayerName = getIntent().getStringExtra("player");
|
||||
@@ -312,78 +311,33 @@ public class MprisActivity extends AppCompatActivity {
|
||||
getString(R.string.mpris_time_default));
|
||||
final int interval_time = Integer.parseInt(interval_time_str);
|
||||
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.addConnectionListener(connectionReceiver);
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> service.addConnectionListener(connectionReceiver));
|
||||
connectToPlugin(targetPlayerName);
|
||||
|
||||
findViewById(R.id.play_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.playPause();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
findViewById(R.id.play_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.playPause();
|
||||
}));
|
||||
|
||||
findViewById(R.id.prev_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.previous();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
findViewById(R.id.prev_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.previous();
|
||||
}));
|
||||
|
||||
findViewById(R.id.rew_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time * -1);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
findViewById(R.id.rew_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time * -1);
|
||||
}));
|
||||
|
||||
findViewById(R.id.ff_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
findViewById(R.id.ff_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.seek(interval_time);
|
||||
}));
|
||||
|
||||
findViewById(R.id.next_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.next();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
findViewById(R.id.next_button).setOnClickListener(view -> BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.next();
|
||||
}));
|
||||
|
||||
((SeekBar) findViewById(R.id.volume_seek)).setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
|
||||
@Override
|
||||
@@ -396,33 +350,23 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.setVolume(seekBar.getProgress());
|
||||
}
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer == null) return;
|
||||
targetPlayer.setVolume(seekBar.getProgress());
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
positionSeekUpdateRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final SeekBar positionSeek = (SeekBar) findViewById(R.id.positionSeek);
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer != null) {
|
||||
positionSeek.setProgress((int) (targetPlayer.getPosition()));
|
||||
}
|
||||
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
positionSeekUpdateRunnable = () -> {
|
||||
final SeekBar positionSeek = (SeekBar) findViewById(R.id.positionSeek);
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer != null) {
|
||||
positionSeek.setProgress((int) (targetPlayer.getPosition()));
|
||||
}
|
||||
positionSeekUpdateHandler.removeCallbacks(positionSeekUpdateRunnable);
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 1000);
|
||||
});
|
||||
};
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200);
|
||||
|
||||
@@ -439,14 +383,11 @@ public class MprisActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
public void onStopTrackingTouch(final SeekBar seekBar) {
|
||||
BackgroundService.RunCommand(MprisActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (targetPlayer != null) {
|
||||
targetPlayer.setPosition(seekBar.getProgress());
|
||||
}
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200);
|
||||
BackgroundService.RunCommand(MprisActivity.this, service -> {
|
||||
if (targetPlayer != null) {
|
||||
targetPlayer.setPosition(seekBar.getProgress());
|
||||
}
|
||||
positionSeekUpdateHandler.postDelayed(positionSeekUpdateRunnable, 200);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -25,6 +25,7 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
@@ -210,181 +211,187 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
* Update the media control notification
|
||||
*/
|
||||
private void updateMediaNotification() {
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
//If the user disabled the media notification, do not show it
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {
|
||||
closeMediaNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
//Make sure our information is up-to-date
|
||||
updateCurrentPlayer(service);
|
||||
|
||||
//If the player disappeared (and no other playing one found), just remove the notification
|
||||
if (notificationPlayer == null) {
|
||||
closeMediaNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the metadata and playback status
|
||||
if (mediaSession == null) {
|
||||
mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG);
|
||||
mediaSession.setCallback(mediaSessionCallback);
|
||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
}
|
||||
MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
|
||||
|
||||
//Fallback because older KDE connect versions do not support getTitle()
|
||||
if (!notificationPlayer.getTitle().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
|
||||
} else {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong());
|
||||
}
|
||||
if (!notificationPlayer.getArtist().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
|
||||
}
|
||||
if (!notificationPlayer.getAlbum().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
|
||||
}
|
||||
if (notificationPlayer.getLength() > 0) {
|
||||
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
|
||||
}
|
||||
|
||||
mediaSession.setMetadata(metadata.build());
|
||||
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
|
||||
|
||||
if (notificationPlayer.isPlaying()) {
|
||||
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
|
||||
} else {
|
||||
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f);
|
||||
}
|
||||
|
||||
//Create all actions (previous/play/pause/next)
|
||||
Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY);
|
||||
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay);
|
||||
|
||||
Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE);
|
||||
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause);
|
||||
|
||||
Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS);
|
||||
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious);
|
||||
|
||||
Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT);
|
||||
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext);
|
||||
|
||||
Intent iOpenActivity = new Intent(service, MprisActivity.class);
|
||||
iOpenActivity.putExtra("deviceId", notificationDevice);
|
||||
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);
|
||||
notification
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(piOpenActivity)
|
||||
.setSmallIcon(R.drawable.ic_play_white)
|
||||
.setShowWhen(false)
|
||||
.setColor(service.getResources().getColor(R.color.primary))
|
||||
.setVisibility(android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC);
|
||||
|
||||
if (!notificationPlayer.getTitle().isEmpty()) {
|
||||
notification.setContentTitle(notificationPlayer.getTitle());
|
||||
} else {
|
||||
notification.setContentTitle(notificationPlayer.getCurrentSong());
|
||||
}
|
||||
//Only set the notification body text if we have an author and/or album
|
||||
if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else if (!notificationPlayer.getArtist().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else if (!notificationPlayer.getAlbum().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else {
|
||||
notification.setContentText(notificationPlayer.getPlayer());
|
||||
}
|
||||
|
||||
if (!notificationPlayer.isPlaying()) {
|
||||
Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
|
||||
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piCloseNotification = PendingIntent.getActivity(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
notification.setDeleteIntent(piCloseNotification);
|
||||
}
|
||||
|
||||
//Add media control actions
|
||||
int numActions = 0;
|
||||
long playbackActions = 0;
|
||||
if (notificationPlayer.isGoPreviousAllowed()) {
|
||||
notification.addAction(aPrevious.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
|
||||
++numActions;
|
||||
}
|
||||
if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
|
||||
notification.addAction(aPause.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
|
||||
++numActions;
|
||||
}
|
||||
if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
|
||||
notification.addAction(aPlay.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_PLAY;
|
||||
++numActions;
|
||||
}
|
||||
if (notificationPlayer.isGoNextAllowed()) {
|
||||
notification.addAction(aNext.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
|
||||
++numActions;
|
||||
}
|
||||
playbackState.setActions(playbackActions);
|
||||
mediaSession.setPlaybackState(playbackState.build());
|
||||
|
||||
//Only allow deletion if no music is notificationPlayer
|
||||
if (notificationPlayer.isPlaying()) {
|
||||
notification.setOngoing(true);
|
||||
} else {
|
||||
notification.setOngoing(false);
|
||||
}
|
||||
|
||||
//Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
|
||||
NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle();
|
||||
if (numActions == 1) {
|
||||
mediaStyle.setShowActionsInCompactView(0);
|
||||
} else if (numActions == 2) {
|
||||
mediaStyle.setShowActionsInCompactView(0, 1);
|
||||
} else if (numActions >= 3) {
|
||||
mediaStyle.setShowActionsInCompactView(0, 1, 2);
|
||||
}
|
||||
mediaStyle.setMediaSession(mediaSession.getSessionToken());
|
||||
notification.setStyle(mediaStyle);
|
||||
|
||||
//Display the notification
|
||||
mediaSession.setActive(true);
|
||||
final NotificationManager nm = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build());
|
||||
BackgroundService.RunCommand(context, service -> {
|
||||
//If the user disabled the media notification, do not show it
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {
|
||||
closeMediaNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
//Make sure our information is up-to-date
|
||||
updateCurrentPlayer(service);
|
||||
|
||||
//If the player disappeared (and no other playing one found), just remove the notification
|
||||
if (notificationPlayer == null) {
|
||||
closeMediaNotification();
|
||||
return;
|
||||
}
|
||||
|
||||
//Update the metadata and playback status
|
||||
if (mediaSession == null) {
|
||||
mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG);
|
||||
mediaSession.setCallback(mediaSessionCallback);
|
||||
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
|
||||
}
|
||||
MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
|
||||
|
||||
//Fallback because older KDE connect versions do not support getTitle()
|
||||
if (!notificationPlayer.getTitle().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
|
||||
} else {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong());
|
||||
}
|
||||
if (!notificationPlayer.getArtist().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
|
||||
}
|
||||
if (!notificationPlayer.getAlbum().isEmpty()) {
|
||||
metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
|
||||
}
|
||||
if (notificationPlayer.getLength() > 0) {
|
||||
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
|
||||
}
|
||||
|
||||
Bitmap albumArt = notificationPlayer.getAlbumArt();
|
||||
if (albumArt != null) {
|
||||
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
|
||||
}
|
||||
|
||||
mediaSession.setMetadata(metadata.build());
|
||||
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
|
||||
|
||||
if (notificationPlayer.isPlaying()) {
|
||||
playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
|
||||
} else {
|
||||
playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f);
|
||||
}
|
||||
|
||||
//Create all actions (previous/play/pause/next)
|
||||
Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY);
|
||||
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay);
|
||||
|
||||
Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE);
|
||||
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause);
|
||||
|
||||
Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS);
|
||||
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious);
|
||||
|
||||
Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT);
|
||||
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext);
|
||||
|
||||
Intent iOpenActivity = new Intent(service, MprisActivity.class);
|
||||
iOpenActivity.putExtra("deviceId", notificationDevice);
|
||||
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);
|
||||
notification
|
||||
.setAutoCancel(false)
|
||||
.setContentIntent(piOpenActivity)
|
||||
.setSmallIcon(R.drawable.ic_play_white)
|
||||
.setShowWhen(false)
|
||||
.setColor(service.getResources().getColor(R.color.primary))
|
||||
.setVisibility(android.support.v4.app.NotificationCompat.VISIBILITY_PUBLIC);
|
||||
|
||||
if (!notificationPlayer.getTitle().isEmpty()) {
|
||||
notification.setContentTitle(notificationPlayer.getTitle());
|
||||
} else {
|
||||
notification.setContentTitle(notificationPlayer.getCurrentSong());
|
||||
}
|
||||
//Only set the notification body text if we have an author and/or album
|
||||
if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else if (!notificationPlayer.getArtist().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else if (!notificationPlayer.getAlbum().isEmpty()) {
|
||||
notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
|
||||
} else {
|
||||
notification.setContentText(notificationPlayer.getPlayer());
|
||||
}
|
||||
|
||||
if (albumArt != null) {
|
||||
notification.setLargeIcon(albumArt);
|
||||
}
|
||||
|
||||
if (!notificationPlayer.isPlaying()) {
|
||||
Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
|
||||
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
|
||||
iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
|
||||
PendingIntent piCloseNotification = PendingIntent.getActivity(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
notification.setDeleteIntent(piCloseNotification);
|
||||
}
|
||||
|
||||
//Add media control actions
|
||||
int numActions = 0;
|
||||
long playbackActions = 0;
|
||||
if (notificationPlayer.isGoPreviousAllowed()) {
|
||||
notification.addAction(aPrevious.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
|
||||
++numActions;
|
||||
}
|
||||
if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
|
||||
notification.addAction(aPause.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
|
||||
++numActions;
|
||||
}
|
||||
if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
|
||||
notification.addAction(aPlay.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_PLAY;
|
||||
++numActions;
|
||||
}
|
||||
if (notificationPlayer.isGoNextAllowed()) {
|
||||
notification.addAction(aNext.build());
|
||||
playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
|
||||
++numActions;
|
||||
}
|
||||
playbackState.setActions(playbackActions);
|
||||
mediaSession.setPlaybackState(playbackState.build());
|
||||
|
||||
//Only allow deletion if no music is notificationPlayer
|
||||
if (notificationPlayer.isPlaying()) {
|
||||
notification.setOngoing(true);
|
||||
} else {
|
||||
notification.setOngoing(false);
|
||||
}
|
||||
|
||||
//Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
|
||||
NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle();
|
||||
if (numActions == 1) {
|
||||
mediaStyle.setShowActionsInCompactView(0);
|
||||
} else if (numActions == 2) {
|
||||
mediaStyle.setShowActionsInCompactView(0, 1);
|
||||
} else if (numActions >= 3) {
|
||||
mediaStyle.setShowActionsInCompactView(0, 1, 2);
|
||||
}
|
||||
mediaStyle.setMediaSession(mediaSession.getSessionToken());
|
||||
notification.setStyle(mediaStyle);
|
||||
|
||||
//Display the notification
|
||||
mediaSession.setActive(true);
|
||||
final NotificationManager nm = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build());
|
||||
});
|
||||
}
|
||||
|
||||
@@ -405,4 +412,9 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
updateMediaNotification();
|
||||
}
|
||||
|
||||
public void playerSelected(MprisPlugin.MprisPlayer player) {
|
||||
notificationPlayer = player;
|
||||
updateMediaNotification();
|
||||
}
|
||||
}
|
||||
|
@@ -130,7 +130,7 @@ public class MprisPlugin extends Plugin {
|
||||
* @return The album art, or null if not available
|
||||
*/
|
||||
public Bitmap getAlbumArt() {
|
||||
return AlbumArtCache.getAlbumArt(albumArtUrl);
|
||||
return AlbumArtCache.getAlbumArt(albumArtUrl, MprisPlugin.this, player);
|
||||
}
|
||||
|
||||
public boolean isSetVolumeAllowed() {
|
||||
@@ -205,6 +205,7 @@ public class MprisPlugin extends Plugin {
|
||||
public final static String PACKET_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
|
||||
|
||||
private HashMap<String, MprisPlayer> players = new HashMap<>();
|
||||
private boolean supportAlbumArtPayload = false;
|
||||
private HashMap<String, Handler> playerStatusUpdated = new HashMap<>();
|
||||
|
||||
private HashMap<String, Handler> playerListUpdated = new HashMap<>();
|
||||
@@ -231,7 +232,6 @@ public class MprisPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
requestPlayerList();
|
||||
MprisMediaSession.getInstance().onCreate(context.getApplicationContext(), this, device.getDeviceId());
|
||||
|
||||
//Always request the player list so the data is up-to-date
|
||||
@@ -266,6 +266,11 @@ public class MprisPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceived(NetworkPacket np) {
|
||||
if (np.getBoolean("transferringAlbumArt", false)) {
|
||||
AlbumArtCache.payloadToDiskCache(np.getString("albumArtUrl"), np.getPayload());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (np.has("player")) {
|
||||
MprisPlayer playerStatus = players.get(np.getString("player"));
|
||||
if (playerStatus != null) {
|
||||
@@ -306,6 +311,9 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
//Remember if the connected device support album art payloads
|
||||
supportAlbumArtPayload = np.getBoolean("supportAlbumArtPayload", supportAlbumArtPayload);
|
||||
|
||||
List<String> newPlayerList = np.getStringList("playerList");
|
||||
if (newPlayerList != null) {
|
||||
boolean equals = true;
|
||||
@@ -463,4 +471,22 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean askTransferAlbumArt(String url, String playerName) {
|
||||
//First check if the remote supports transferring album art
|
||||
if (!supportAlbumArtPayload) return false;
|
||||
if (url.isEmpty()) return false;
|
||||
|
||||
MprisPlayer player = getPlayerStatus(playerName);
|
||||
if (player == null) return false;
|
||||
|
||||
if (player.albumArtUrl.equals(url)) {
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_MPRIS_REQUEST);
|
||||
np.set("player", player.getPlayer());
|
||||
np.set("albumArtUrl", url);
|
||||
device.sendPacket(np);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.MprisReceiverPlugin;
|
||||
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
class MprisReceiverCallback extends MediaController.Callback {
|
||||
|
||||
private static final String TAG = "MprisReceiver";
|
||||
|
||||
private final MprisReceiverPlayer player;
|
||||
private final MprisReceiverPlugin plugin;
|
||||
|
||||
MprisReceiverCallback(MprisReceiverPlugin plugin, MprisReceiverPlayer player) {
|
||||
this.player = player;
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
@Override
|
||||
public void onPlaybackStateChanged(@NonNull PlaybackState state) {
|
||||
switch (state.getState()) {
|
||||
case PlaybackState.STATE_PLAYING:
|
||||
player.setPlaying(true);
|
||||
plugin.sendPlaying(player);
|
||||
break;
|
||||
case PlaybackState.STATE_PAUSED:
|
||||
player.setPaused(true);
|
||||
plugin.sendPlaying(player);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMetadataChanged(@Nullable MediaMetadata metadata) {
|
||||
if (metadata == null)
|
||||
return;
|
||||
plugin.sendMetadata(player);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.MprisReceiverPlugin;
|
||||
|
||||
import android.media.MediaMetadata;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.PlaybackState;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.RequiresApi;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
class MprisReceiverPlayer {
|
||||
|
||||
private MediaController controller;
|
||||
|
||||
private String name;
|
||||
|
||||
private boolean isPlaying;
|
||||
|
||||
MprisReceiverPlayer(MediaController controller, String name) {
|
||||
|
||||
this.controller = controller;
|
||||
this.name = name;
|
||||
|
||||
if (controller.getPlaybackState() != null) {
|
||||
isPlaying = controller.getPlaybackState().getState() == PlaybackState.STATE_PLAYING;
|
||||
}
|
||||
}
|
||||
|
||||
boolean isPlaying() {
|
||||
return isPlaying;
|
||||
}
|
||||
|
||||
void setPlaying(boolean playing) {
|
||||
isPlaying = playing;
|
||||
}
|
||||
|
||||
boolean isPaused() {
|
||||
return !isPlaying;
|
||||
}
|
||||
|
||||
void setPaused(boolean paused) {
|
||||
isPlaying = !paused;
|
||||
}
|
||||
|
||||
void playPause() {
|
||||
if (isPlaying) {
|
||||
controller.getTransportControls().pause();
|
||||
} else {
|
||||
controller.getTransportControls().play();
|
||||
}
|
||||
}
|
||||
|
||||
String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
String getAlbum() {
|
||||
if (controller.getMetadata() == null)
|
||||
return "";
|
||||
String album = controller.getMetadata().getString(MediaMetadata.METADATA_KEY_ALBUM);
|
||||
return album != null ? album : "";
|
||||
}
|
||||
|
||||
String getArtist() {
|
||||
if (controller.getMetadata() == null)
|
||||
return "";
|
||||
|
||||
String artist = controller.getMetadata().getString(MediaMetadata.METADATA_KEY_ALBUM_ARTIST);
|
||||
return artist != null ? artist : "";
|
||||
}
|
||||
|
||||
String getTitle() {
|
||||
if (controller.getMetadata() == null)
|
||||
return "";
|
||||
String title = controller.getMetadata().getString(MediaMetadata.METADATA_KEY_TITLE);
|
||||
return title != null ? title : "";
|
||||
}
|
||||
|
||||
void previous() {
|
||||
controller.getTransportControls().skipToPrevious();
|
||||
}
|
||||
|
||||
void next() {
|
||||
controller.getTransportControls().skipToNext();
|
||||
}
|
||||
|
||||
int getVolume() {
|
||||
if (controller.getPlaybackInfo() == null)
|
||||
return 0;
|
||||
return 100 * controller.getPlaybackInfo().getCurrentVolume() / controller.getPlaybackInfo().getMaxVolume();
|
||||
}
|
||||
|
||||
long getPosition() {
|
||||
if (controller.getPlaybackState() == null)
|
||||
return 0;
|
||||
return controller.getPlaybackState().getPosition();
|
||||
}
|
||||
}
|
@@ -0,0 +1,237 @@
|
||||
/*
|
||||
* Copyright 2018 Nicolas Fella <nicolas.fella@gmx.de>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of
|
||||
* the License or (at your option) version 3 or any later version
|
||||
* accepted by the membership of KDE e.V. (or its successor approved
|
||||
* by the membership of KDE e.V.), which shall act as a proxy
|
||||
* defined in Section 14 of version 3 of the license.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.MprisReceiverPlugin;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.media.session.MediaController;
|
||||
import android.media.session.MediaSessionManager;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Helpers.AppsHelper;
|
||||
import org.kde.kdeconnect.NetworkPacket;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver;
|
||||
import org.kde.kdeconnect.Plugins.Plugin;
|
||||
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public class MprisReceiverPlugin extends Plugin implements MediaSessionManager.OnActiveSessionsChangedListener {
|
||||
|
||||
private final static String PACKET_TYPE_MPRIS = "kdeconnect.mpris";
|
||||
private final static String PACKET_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
|
||||
|
||||
private static final String TAG = "MprisReceiver";
|
||||
|
||||
private HashMap<String, MprisReceiverPlayer> players;
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
|
||||
if (!hasPermission())
|
||||
return false;
|
||||
|
||||
players = new HashMap<>();
|
||||
try {
|
||||
MediaSessionManager manager = (MediaSessionManager) context.getSystemService(Context.MEDIA_SESSION_SERVICE);
|
||||
if (null == manager)
|
||||
return false;
|
||||
|
||||
manager.addOnActiveSessionsChangedListener(MprisReceiverPlugin.this, new ComponentName(context, NotificationReceiver.class), new Handler(Looper.getMainLooper()));
|
||||
|
||||
createPlayers(manager.getActiveSessions(new ComponentName(context, NotificationReceiver.class)));
|
||||
sendPlayerList();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Exception", e);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void createPlayers(List<MediaController> sessions) {
|
||||
for (MediaController controller : sessions) {
|
||||
createPlayer(controller);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDisplayName() {
|
||||
return context.getResources().getString(R.string.pref_plugin_mprisreceiver);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDescription() {
|
||||
return context.getResources().getString(R.string.pref_plugin_mprisreceiver_desc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceived(NetworkPacket np) {
|
||||
|
||||
if (np.getBoolean("requestPlayerList")) {
|
||||
sendPlayerList();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!np.has("player")) {
|
||||
return false;
|
||||
}
|
||||
MprisReceiverPlayer player = players.get(np.getString("player"));
|
||||
|
||||
if (null == player) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (np.getBoolean("requestNowPlaying", false)) {
|
||||
sendMetadata(player);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (np.has("action")) {
|
||||
String action = np.getString("action");
|
||||
|
||||
switch (action) {
|
||||
case "PlayPause":
|
||||
player.playPause();
|
||||
break;
|
||||
case "Next":
|
||||
player.next();
|
||||
break;
|
||||
case "Previous":
|
||||
player.previous();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedPacketTypes() {
|
||||
return new String[]{PACKET_TYPE_MPRIS_REQUEST};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getOutgoingPacketTypes() {
|
||||
return new String[]{PACKET_TYPE_MPRIS};
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActiveSessionsChanged(@Nullable List<MediaController> controllers) {
|
||||
|
||||
if (null == controllers) {
|
||||
return;
|
||||
}
|
||||
|
||||
players.clear();
|
||||
|
||||
createPlayers(controllers);
|
||||
sendPlayerList();
|
||||
|
||||
}
|
||||
|
||||
private void createPlayer(MediaController controller) {
|
||||
MprisReceiverPlayer player = new MprisReceiverPlayer(controller, AppsHelper.appNameLookup(context, controller.getPackageName()));
|
||||
controller.registerCallback(new MprisReceiverCallback(this, player), new Handler(Looper.getMainLooper()));
|
||||
players.put(player.getName(), player);
|
||||
}
|
||||
|
||||
private void sendPlayerList() {
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_MPRIS);
|
||||
np.set("playerList", players.keySet());
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
void sendPlaying(MprisReceiverPlayer player) {
|
||||
|
||||
NetworkPacket np = new NetworkPacket(MprisReceiverPlugin.PACKET_TYPE_MPRIS);
|
||||
np.set("player", player.getName());
|
||||
np.set("isPlaying", player.isPlaying());
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getMinSdk() {
|
||||
return Build.VERSION_CODES.LOLLIPOP_MR1;
|
||||
}
|
||||
|
||||
public void sendMetadata(MprisReceiverPlayer player) {
|
||||
NetworkPacket np = new NetworkPacket(MprisReceiverPlugin.PACKET_TYPE_MPRIS);
|
||||
np.set("player", player.getName());
|
||||
if (player.getArtist().isEmpty()) {
|
||||
np.set("nowPlaying", player.getTitle());
|
||||
} else {
|
||||
np.set("nowPlaying", player.getArtist() + " - " + player.getTitle());
|
||||
}
|
||||
np.set("title", player.getTitle());
|
||||
np.set("artist", player.getArtist());
|
||||
np.set("album", player.getAlbum());
|
||||
np.set("isPlaying", player.isPlaying());
|
||||
np.set("pos", player.getPosition());
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
public void sendVolume(MprisReceiverPlayer player) {
|
||||
NetworkPacket np = new NetworkPacket(MprisReceiverPlugin.PACKET_TYPE_MPRIS);
|
||||
np.set("player", player.getName());
|
||||
np.set("volume", player.getVolume());
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AlertDialog getErrorDialog(final Activity deviceActivity) {
|
||||
|
||||
return new AlertDialog.Builder(deviceActivity)
|
||||
.setTitle(R.string.pref_plugin_mpris)
|
||||
.setMessage(R.string.no_permission_mprisreceiver)
|
||||
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
deviceActivity.startActivityForResult(intent, MainActivity.RESULT_NEEDS_RELOAD);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
//Do nothing
|
||||
}
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
||||
private boolean hasPermission() {
|
||||
String notificationListenerList = Settings.Secure.getString(context.getContentResolver(), "enabled_notification_listeners");
|
||||
return (notificationListenerList != null && notificationListenerList.contains(context.getPackageName()));
|
||||
}
|
||||
|
||||
}
|
@@ -39,6 +39,7 @@ import android.widget.ListView;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Helpers.StringsHelper;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.Arrays;
|
||||
@@ -93,41 +94,29 @@ public class NotificationFilterActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.activity_notification_filter);
|
||||
appDatabase = new AppDatabase(NotificationFilterActivity.this, false);
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new Thread(() -> {
|
||||
|
||||
PackageManager packageManager = getPackageManager();
|
||||
List<ApplicationInfo> appList = packageManager.getInstalledApplications(0);
|
||||
int count = appList.size();
|
||||
PackageManager packageManager = getPackageManager();
|
||||
List<ApplicationInfo> appList = packageManager.getInstalledApplications(0);
|
||||
int count = appList.size();
|
||||
|
||||
apps = new AppListInfo[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
ApplicationInfo appInfo = appList.get(i);
|
||||
apps[i] = new AppListInfo();
|
||||
apps[i].pkg = appInfo.packageName;
|
||||
apps[i].name = appInfo.loadLabel(packageManager).toString();
|
||||
apps[i].icon = resizeIcon(appInfo.loadIcon(packageManager), 48);
|
||||
apps[i].isEnabled = appDatabase.isEnabled(appInfo.packageName);
|
||||
}
|
||||
|
||||
Arrays.sort(apps, new Comparator<AppListInfo>() {
|
||||
@Override
|
||||
public int compare(AppListInfo lhs, AppListInfo rhs) {
|
||||
return StringsHelper.compare(lhs.name, rhs.name);
|
||||
}
|
||||
});
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
displayAppList();
|
||||
}
|
||||
});
|
||||
apps = new AppListInfo[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
ApplicationInfo appInfo = appList.get(i);
|
||||
apps[i] = new AppListInfo();
|
||||
apps[i].pkg = appInfo.packageName;
|
||||
apps[i].name = appInfo.loadLabel(packageManager).toString();
|
||||
apps[i].icon = resizeIcon(appInfo.loadIcon(packageManager), 48);
|
||||
apps[i].isEnabled = appDatabase.isEnabled(appInfo.packageName);
|
||||
}
|
||||
|
||||
Arrays.sort(apps, (lhs, rhs) -> StringsHelper.compare(lhs.name, rhs.name));
|
||||
|
||||
runOnUiThread(this::displayAppList);
|
||||
}).start();
|
||||
|
||||
}
|
||||
@@ -138,13 +127,10 @@ public class NotificationFilterActivity extends AppCompatActivity {
|
||||
AppListAdapter adapter = new AppListAdapter();
|
||||
listView.setAdapter(adapter);
|
||||
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
boolean checked = listView.isItemChecked(i);
|
||||
appDatabase.setEnabled(apps[i].pkg, checked);
|
||||
apps[i].isEnabled = checked;
|
||||
}
|
||||
listView.setOnItemClickListener((adapterView, view, i, l) -> {
|
||||
boolean checked = listView.isItemChecked(i);
|
||||
appDatabase.setEnabled(apps[i].pkg, checked);
|
||||
apps[i].isEnabled = checked;
|
||||
});
|
||||
|
||||
for (int i = 0; i < apps.length; i++) {
|
||||
|
@@ -108,17 +108,14 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
|
||||
appDatabase = new AppDatabase(context, true);
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
NotificationReceiver.RunCommand(context, service -> {
|
||||
|
||||
service.addListener(NotificationsPlugin.this);
|
||||
service.addListener(NotificationsPlugin.this);
|
||||
|
||||
serviceReady = service.isConnected();
|
||||
serviceReady = service.isConnected();
|
||||
|
||||
if (serviceReady) {
|
||||
sendCurrentNotifications(service);
|
||||
}
|
||||
if (serviceReady) {
|
||||
sendCurrentNotifications(service);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -128,12 +125,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
service.removeListener(NotificationsPlugin.this);
|
||||
}
|
||||
});
|
||||
NotificationReceiver.RunCommand(context, service -> service.removeListener(NotificationsPlugin.this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -461,22 +453,14 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
if (np.getBoolean("request")) {
|
||||
|
||||
if (serviceReady) {
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
sendCurrentNotifications(service);
|
||||
}
|
||||
});
|
||||
NotificationReceiver.RunCommand(context, this::sendCurrentNotifications);
|
||||
}
|
||||
|
||||
} else if (np.has("cancel")) {
|
||||
|
||||
NotificationReceiver.RunCommand(context, new NotificationReceiver.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(NotificationReceiver service) {
|
||||
String dismissedId = np.getString("cancel");
|
||||
cancelNotificationCompat(service, dismissedId);
|
||||
}
|
||||
NotificationReceiver.RunCommand(context, service -> {
|
||||
String dismissedId = np.getString("cancel");
|
||||
cancelNotificationCompat(service, dismissedId);
|
||||
});
|
||||
|
||||
} else if (np.has("requestReplyId") && np.has("message")) {
|
||||
@@ -497,18 +481,12 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
|
||||
return new AlertDialog.Builder(deviceActivity)
|
||||
.setTitle(R.string.pref_plugin_notifications)
|
||||
.setMessage(R.string.no_permissions)
|
||||
.setPositiveButton(R.string.open_settings, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
deviceActivity.startActivityForResult(intent, MainActivity.RESULT_NEEDS_RELOAD);
|
||||
}
|
||||
.setPositiveButton(R.string.open_settings, (dialogInterface, i) -> {
|
||||
Intent intent = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
|
||||
deviceActivity.startActivityForResult(intent, MainActivity.RESULT_NEEDS_RELOAD);
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
//Do nothing
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
||||
//Do nothing
|
||||
})
|
||||
.create();
|
||||
|
||||
|
@@ -202,12 +202,7 @@ public abstract class Plugin {
|
||||
if (!hasMainActivity()) return null;
|
||||
Button b = new Button(activity);
|
||||
b.setText(getActionName());
|
||||
b.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
startMainActivity(activity);
|
||||
}
|
||||
});
|
||||
b.setOnClickListener(view -> startMainActivity(activity));
|
||||
return b;
|
||||
}
|
||||
|
||||
@@ -242,17 +237,9 @@ public abstract class Plugin {
|
||||
return new AlertDialog.Builder(activity)
|
||||
.setTitle(getDisplayName())
|
||||
.setMessage(reason)
|
||||
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
ActivityCompat.requestPermissions(activity, permissions, 0);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
//Do nothing
|
||||
}
|
||||
.setPositiveButton(R.string.ok, (dialogInterface, i) -> ActivityCompat.requestPermissions(activity, permissions, 0))
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> {
|
||||
//Do nothing
|
||||
})
|
||||
.create();
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
|
||||
import org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhonePlugin;
|
||||
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
|
||||
import org.kde.kdeconnect.Plugins.MprisReceiverPlugin.MprisReceiverPlugin;
|
||||
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
|
||||
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
|
||||
import org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin.ReceiveNotificationsPlugin;
|
||||
@@ -128,6 +129,7 @@ public class PluginFactory {
|
||||
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
|
||||
PluginFactory.registerPlugin(RunCommandPlugin.class);
|
||||
PluginFactory.registerPlugin(RemoteKeyboardPlugin.class);
|
||||
PluginFactory.registerPlugin(MprisReceiverPlugin.class);
|
||||
}
|
||||
|
||||
public static PluginInfo getPluginInfo(Context context, String pluginKey) {
|
||||
|
@@ -121,12 +121,7 @@ public class RemoteKeyboardPlugin extends Plugin {
|
||||
releaseInstances();
|
||||
}
|
||||
if (RemoteKeyboardService.instance != null)
|
||||
RemoteKeyboardService.instance.handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RemoteKeyboardService.instance.updateInputView();
|
||||
}
|
||||
});
|
||||
RemoteKeyboardService.instance.handler.post(() -> RemoteKeyboardService.instance.updateInputView());
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -137,12 +132,7 @@ public class RemoteKeyboardPlugin extends Plugin {
|
||||
if (instances.contains(this)) {
|
||||
instances.remove(this);
|
||||
if (instances.size() < 1 && RemoteKeyboardService.instance != null)
|
||||
RemoteKeyboardService.instance.handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
RemoteKeyboardService.instance.updateInputView();
|
||||
}
|
||||
});
|
||||
RemoteKeyboardService.instance.handler.post(() -> RemoteKeyboardService.instance.updateInputView());
|
||||
}
|
||||
} finally {
|
||||
releaseInstances();
|
||||
|
@@ -1,57 +0,0 @@
|
||||
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
public class AddCommandDialog extends DialogFragment {
|
||||
|
||||
private EditText nameField;
|
||||
private EditText commandField;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setTitle(R.string.add_command);
|
||||
|
||||
LayoutInflater inflater = getActivity().getLayoutInflater();
|
||||
|
||||
View view = inflater.inflate(R.layout.addcommanddialog, null);
|
||||
|
||||
nameField = (EditText) view.findViewById(R.id.addcommand_name);
|
||||
commandField = (EditText) view.findViewById(R.id.addcommand_command);
|
||||
|
||||
builder.setView(view);
|
||||
|
||||
builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
|
||||
if (getActivity() instanceof RunCommandActivity) {
|
||||
|
||||
String name = nameField.getText().toString();
|
||||
String command = commandField.getText().toString();
|
||||
|
||||
((RunCommandActivity) getActivity()).dialogResult(name, command);
|
||||
}
|
||||
}
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
}
|
||||
});
|
||||
|
||||
return builder.create();
|
||||
}
|
||||
}
|
@@ -16,13 +16,14 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.RunCommandPlugin;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
@@ -35,6 +36,7 @@ import org.json.JSONObject;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,77 +46,61 @@ import java.util.Comparator;
|
||||
public class RunCommandActivity extends AppCompatActivity {
|
||||
|
||||
private String deviceId;
|
||||
private final RunCommandPlugin.CommandsChangedCallback commandsChangedCallback = new RunCommandPlugin.CommandsChangedCallback() {
|
||||
@Override
|
||||
public void update() {
|
||||
updateView();
|
||||
}
|
||||
};
|
||||
private final RunCommandPlugin.CommandsChangedCallback commandsChangedCallback = this::updateView;
|
||||
|
||||
private void updateView() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
|
||||
runOnUiThread(() -> {
|
||||
ListView view = (ListView) findViewById(R.id.runcommandslist);
|
||||
|
||||
final ArrayList<ListAdapter.Item> commandItems = new ArrayList<>();
|
||||
for (JSONObject obj : plugin.getCommandList()) {
|
||||
try {
|
||||
commandItems.add(new CommandEntry(obj.getString("name"),
|
||||
obj.getString("command"), obj.getString("key")));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ListView view = (ListView) findViewById(R.id.runcommandslist);
|
||||
|
||||
final ArrayList<ListAdapter.Item> commandItems = new ArrayList<>();
|
||||
for (JSONObject obj : plugin.getCommandList()) {
|
||||
try {
|
||||
commandItems.add(new CommandEntry(obj.getString("name"),
|
||||
obj.getString("command"), obj.getString("key")));
|
||||
} catch (JSONException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(commandItems, new Comparator<ListAdapter.Item>() {
|
||||
@Override
|
||||
public int compare(ListAdapter.Item lhs, ListAdapter.Item rhs) {
|
||||
String lName = ((CommandEntry) lhs).getName();
|
||||
String rName = ((CommandEntry) rhs).getName();
|
||||
return lName.compareTo(rName);
|
||||
}
|
||||
});
|
||||
|
||||
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
|
||||
|
||||
view.setAdapter(adapter);
|
||||
view.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(i);
|
||||
plugin.runCommand(entry.getKey());
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
TextView explanation = (TextView) findViewById(R.id.addcomand_explanation);
|
||||
String text = getString(R.string.addcommand_explanation);
|
||||
if (!plugin.canAddCommand()) {
|
||||
text += "\n" + getString(R.string.addcommand_explanation2);
|
||||
}
|
||||
explanation.setText(text);
|
||||
explanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
Collections.sort(commandItems, (lhs, rhs) -> {
|
||||
String lName = ((CommandEntry) lhs).getName();
|
||||
String rName = ((CommandEntry) rhs).getName();
|
||||
return lName.compareTo(rName);
|
||||
});
|
||||
}
|
||||
|
||||
ListAdapter adapter = new ListAdapter(RunCommandActivity.this, commandItems);
|
||||
|
||||
view.setAdapter(adapter);
|
||||
view.setOnItemClickListener((adapterView, view1, i, l) -> {
|
||||
CommandEntry entry = (CommandEntry) commandItems.get(i);
|
||||
plugin.runCommand(entry.getKey());
|
||||
});
|
||||
|
||||
|
||||
TextView explanation = (TextView) findViewById(R.id.addcomand_explanation);
|
||||
String text = getString(R.string.addcommand_explanation);
|
||||
if (!plugin.canAddCommand()) {
|
||||
text += "\n" + getString(R.string.addcommand_explanation2);
|
||||
}
|
||||
explanation.setText(text);
|
||||
explanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.activity_runcommand);
|
||||
|
||||
deviceId = getIntent().getStringExtra("deviceId");
|
||||
@@ -123,12 +109,26 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
|
||||
FloatingActionButton addCommandButton = (FloatingActionButton) findViewById(R.id.add_command_button);
|
||||
addCommandButton.setVisibility(canAddCommands ? View.VISIBLE : View.GONE);
|
||||
addCommandButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
new AddCommandDialog().show(getSupportFragmentManager(), "addcommanddialog");
|
||||
|
||||
addCommandButton.setOnClickListener(view -> BackgroundService.RunCommand(RunCommandActivity.this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
plugin.sendSetupPacket();
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(RunCommandActivity.this)
|
||||
.setTitle(R.string.add_command)
|
||||
.setMessage(R.string.add_command_description)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.create();
|
||||
dialog.show();
|
||||
|
||||
}));
|
||||
|
||||
updateView();
|
||||
}
|
||||
@@ -137,18 +137,15 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.addCommandsUpdatedCallback(commandsChangedCallback);
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.addCommandsUpdatedCallback(commandsChangedCallback);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -156,31 +153,15 @@ public class RunCommandActivity extends AppCompatActivity {
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.removeCommandsUpdatedCallback(commandsChangedCallback);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void dialogResult(final String cmdName, final String cmdCmd) {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(deviceId);
|
||||
RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if(!cmdName.isEmpty() && !cmdCmd.isEmpty()) {
|
||||
plugin.addCommand(cmdName, cmdCmd);
|
||||
}
|
||||
final Device device = service.getDevice(deviceId);
|
||||
final RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class);
|
||||
if (plugin == null) {
|
||||
Log.e("RunCommandActivity", "device has no runcommand plugin!");
|
||||
return;
|
||||
}
|
||||
plugin.removeCommandsUpdatedCallback(commandsChangedCallback);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -39,7 +39,6 @@ public class RunCommandPlugin extends Plugin {
|
||||
|
||||
public final static String PACKET_TYPE_RUNCOMMAND = "kdeconnect.runcommand";
|
||||
public final static String PACKET_TYPE_RUNCOMMAND_REQUEST = "kdeconnect.runcommand.request";
|
||||
public final static String PACKET_TYPE_RUNCOMMAND_ADD = "kdeconnect.runcommand.add";
|
||||
|
||||
private ArrayList<JSONObject> commandList = new ArrayList<>();
|
||||
private ArrayList<CommandsChangedCallback> callbacks = new ArrayList<>();
|
||||
@@ -153,17 +152,14 @@ public class RunCommandPlugin extends Plugin {
|
||||
return context.getString(R.string.pref_plugin_runcommand);
|
||||
}
|
||||
|
||||
public void addCommand(String name, String command){
|
||||
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_RUNCOMMAND_ADD);
|
||||
np.set("name", name);
|
||||
np.set("command", command);
|
||||
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
public boolean canAddCommand(){
|
||||
return canAddCommand;
|
||||
}
|
||||
|
||||
void sendSetupPacket() {
|
||||
NetworkPacket np = new NetworkPacket(RunCommandPlugin.PACKET_TYPE_RUNCOMMAND_REQUEST);
|
||||
np.set("setup", true);
|
||||
device.sendPacket(np);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -32,6 +32,7 @@ import android.widget.Toast;
|
||||
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -44,6 +45,7 @@ public class SendFileActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
|
||||
mDeviceId = getIntent().getStringExtra("deviceId");
|
||||
|
||||
@@ -87,17 +89,14 @@ public class SendFileActivity extends AppCompatActivity {
|
||||
if (uris.isEmpty()) {
|
||||
Log.w("SendFileActivity", "No files to send?");
|
||||
} else {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(mDeviceId);
|
||||
if (device == null) {
|
||||
Log.e("SendFileActivity", "Device is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
SharePlugin.queuedSendUriList(getApplicationContext(), device, uris);
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(mDeviceId);
|
||||
if (device == null) {
|
||||
Log.e("SendFileActivity", "Device is null");
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
SharePlugin.queuedSendUriList(getApplicationContext(), device, uris);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -15,8 +15,8 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||
|
||||
@@ -38,6 +38,7 @@ import org.kde.kdeconnect.Device;
|
||||
import org.kde.kdeconnect.UserInterface.List.EntryItem;
|
||||
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
|
||||
import org.kde.kdeconnect.UserInterface.List.SectionItem;
|
||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -69,28 +70,15 @@ public class ShareActivity extends AppCompatActivity {
|
||||
|
||||
private void updateComputerListAction() {
|
||||
updateComputerList();
|
||||
BackgroundService.RunCommand(ShareActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(ShareActivity.this, BackgroundService::onNetworkChange);
|
||||
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
runOnUiThread(() -> mSwipeRefreshLayout.setRefreshing(false));
|
||||
}).start();
|
||||
}
|
||||
|
||||
@@ -104,58 +92,49 @@ public class ShareActivity extends AppCompatActivity {
|
||||
return;
|
||||
}
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
final ArrayList<Device> devicesList = new ArrayList<>();
|
||||
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
final ArrayList<Device> devicesList = new ArrayList<>();
|
||||
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
|
||||
|
||||
items.add(new SectionItem(getString(R.string.share_to)));
|
||||
SectionItem section = new SectionItem(getString(R.string.share_to));
|
||||
items.add(section);
|
||||
|
||||
for (Device d : devices) {
|
||||
if (d.isReachable() && d.isPaired()) {
|
||||
devicesList.add(d);
|
||||
items.add(new EntryItem(d.getName()));
|
||||
}
|
||||
for (Device d : devices) {
|
||||
if (d.isReachable() && d.isPaired()) {
|
||||
devicesList.add(d);
|
||||
items.add(new EntryItem(d.getName()));
|
||||
section.isEmpty = false;
|
||||
}
|
||||
|
||||
runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ListView list = (ListView) findViewById(R.id.listView1);
|
||||
list.setAdapter(new ListAdapter(ShareActivity.this, items));
|
||||
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||
|
||||
Device device = devicesList.get(i - 1); //NOTE: -1 because of the title!
|
||||
SharePlugin.share(intent, device);
|
||||
finish();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
runOnUiThread(() -> {
|
||||
ListView list = (ListView) findViewById(R.id.devices_list);
|
||||
list.setAdapter(new ListAdapter(ShareActivity.this, items));
|
||||
list.setOnItemClickListener((adapterView, view, i, l) -> {
|
||||
|
||||
Device device = devicesList.get(i - 1); //NOTE: -1 because of the title!
|
||||
SharePlugin.share(intent, device);
|
||||
finish();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_refresh_list);
|
||||
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.devices_list);
|
||||
|
||||
|
||||
ActionBar actionBar = getSupportActionBar();
|
||||
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_list_layout);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(
|
||||
new SwipeRefreshLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
updateComputerListAction();
|
||||
}
|
||||
}
|
||||
this::updateComputerListAction
|
||||
);
|
||||
if (actionBar != null) {
|
||||
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||
@@ -172,32 +151,20 @@ public class ShareActivity extends AppCompatActivity {
|
||||
|
||||
if (deviceId != null) {
|
||||
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Log.d("DirectShare", "sharing to " + service.getDevice(deviceId).getName());
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
SharePlugin.share(intent, device);
|
||||
}
|
||||
finish();
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Log.d("DirectShare", "sharing to " + service.getDevice(deviceId).getName());
|
||||
Device device = service.getDevice(deviceId);
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
SharePlugin.share(intent, device);
|
||||
}
|
||||
finish();
|
||||
});
|
||||
} else {
|
||||
|
||||
BackgroundService.addGuiInUseCounter(this);
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
service.addDeviceListChangedCallback("ShareActivity", new BackgroundService.DeviceListChangedCallback() {
|
||||
@Override
|
||||
public void onDeviceListChanged() {
|
||||
updateComputerList();
|
||||
}
|
||||
});
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
service.onNetworkChange();
|
||||
service.addDeviceListChangedCallback("ShareActivity", this::updateComputerList);
|
||||
});
|
||||
updateComputerList();
|
||||
}
|
||||
@@ -206,12 +173,7 @@ public class ShareActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.removeDeviceListChangedCallback("ShareActivity");
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("ShareActivity"));
|
||||
BackgroundService.removeGuiInUseCounter(this);
|
||||
super.onStop();
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -38,6 +40,7 @@ import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class ShareNotification {
|
||||
|
||||
@@ -99,25 +102,51 @@ public class ShareNotification {
|
||||
* - Proxy to real files (in case of the default download folder)
|
||||
* - Proxy to the underlying content uri (in case of a custom download folder)
|
||||
*/
|
||||
|
||||
//If it's an image, try to show it in the notification
|
||||
if (mimeType.startsWith("image/")) {
|
||||
try {
|
||||
Bitmap image = BitmapFactory.decodeStream(device.getContext().getContentResolver().openInputStream(destinationUri));
|
||||
if (image != null) {
|
||||
builder.setLargeIcon(image);
|
||||
builder.setStyle(new NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(image));
|
||||
}
|
||||
} catch (FileNotFoundException ignored) {}
|
||||
}
|
||||
if (!"file".equals(destinationUri.getScheme())) {
|
||||
return;
|
||||
}
|
||||
|
||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||
Intent shareIntent = new Intent(Intent.ACTION_SEND);
|
||||
shareIntent.setType(mimeType);
|
||||
if (Build.VERSION.SDK_INT >= 24) {
|
||||
//Nougat and later require "content://" uris instead of "file://" uris
|
||||
File file = new File(destinationUri.getPath());
|
||||
Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
|
||||
intent.setDataAndType(contentUri, mimeType);
|
||||
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
|
||||
} else {
|
||||
intent.setDataAndType(destinationUri, mimeType);
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, destinationUri);
|
||||
}
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(device.getContext());
|
||||
stackBuilder.addNextIntent(intent);
|
||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
builder.setContentText(device.getContext().getResources().getString(R.string.received_file_text, filename))
|
||||
.setContentIntent(resultPendingIntent);
|
||||
|
||||
shareIntent = Intent.createChooser(shareIntent,
|
||||
device.getContext().getString(R.string.share_received_file, destinationUri.getLastPathSegment()));
|
||||
PendingIntent sharePendingIntent = PendingIntent.getActivity(device.getContext(), 0,
|
||||
shareIntent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
NotificationCompat.Action.Builder shareAction = new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_share_white, device.getContext().getString(R.string.share), sharePendingIntent);
|
||||
builder.addAction(shareAction.build());
|
||||
}
|
||||
}
|
||||
|
@@ -219,64 +219,61 @@ public class SharePlugin extends Plugin {
|
||||
final ShareNotification notification = new ShareNotification(device, filename);
|
||||
notification.show();
|
||||
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
byte data[] = new byte[4096];
|
||||
long progress = 0, prevProgressPercentage = -1;
|
||||
int count;
|
||||
long lastUpdate = 0;
|
||||
while ((count = input.read(data)) >= 0) {
|
||||
progress += count;
|
||||
destinationOutput.write(data, 0, count);
|
||||
if (fileLength > 0) {
|
||||
if (progress >= fileLength) break;
|
||||
long progressPercentage = (progress * 100 / fileLength);
|
||||
if (progressPercentage != prevProgressPercentage &&
|
||||
System.currentTimeMillis() - lastUpdate > 100) {
|
||||
prevProgressPercentage = progressPercentage;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
new Thread(() -> {
|
||||
try {
|
||||
byte data[] = new byte[4096];
|
||||
long progress = 0, prevProgressPercentage = -1;
|
||||
int count;
|
||||
long lastUpdate = 0;
|
||||
while ((count = input.read(data)) >= 0) {
|
||||
progress += count;
|
||||
destinationOutput.write(data, 0, count);
|
||||
if (fileLength > 0) {
|
||||
if (progress >= fileLength) break;
|
||||
long progressPercentage = (progress * 100 / fileLength);
|
||||
if (progressPercentage != prevProgressPercentage &&
|
||||
System.currentTimeMillis() - lastUpdate > 100) {
|
||||
prevProgressPercentage = progressPercentage;
|
||||
lastUpdate = System.currentTimeMillis();
|
||||
|
||||
notification.setProgress((int) progressPercentage);
|
||||
notification.show();
|
||||
}
|
||||
notification.setProgress((int) progressPercentage);
|
||||
notification.show();
|
||||
}
|
||||
//else Log.e("SharePlugin", "Infinite loop? :D");
|
||||
}
|
||||
//else Log.e("SharePlugin", "Infinite loop? :D");
|
||||
}
|
||||
|
||||
destinationOutput.flush();
|
||||
destinationOutput.flush();
|
||||
|
||||
Log.i("SharePlugin", "Transfer finished: " + destinationUri.getPath());
|
||||
Log.i("SharePlugin", "Transfer finished: " + destinationUri.getPath());
|
||||
|
||||
//Update the notification and allow to open the file from it
|
||||
notification.setFinished(true);
|
||||
notification.setURI(destinationUri, mimeType);
|
||||
notification.show();
|
||||
//Update the notification and allow to open the file from it
|
||||
notification.setFinished(true);
|
||||
notification.setURI(destinationUri, mimeType);
|
||||
notification.show();
|
||||
|
||||
if (!customDestination && Build.VERSION.SDK_INT >= 12) {
|
||||
Log.i("SharePlugin", "Adding to downloads");
|
||||
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
|
||||
} else {
|
||||
//Make sure it is added to the Android Gallery anyway
|
||||
MediaStoreHelper.indexFile(context, destinationUri);
|
||||
}
|
||||
if (!customDestination && Build.VERSION.SDK_INT >= 12) {
|
||||
Log.i("SharePlugin", "Adding to downloads");
|
||||
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
|
||||
} else {
|
||||
//Make sure it is added to the Android Gallery anyway
|
||||
MediaStoreHelper.indexFile(context, destinationUri);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("SharePlugin", "Receiver thread exception");
|
||||
e.printStackTrace();
|
||||
notification.setFinished(false);
|
||||
notification.show();
|
||||
} finally {
|
||||
try {
|
||||
destinationOutput.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
try {
|
||||
input.close();
|
||||
} catch (Exception e) {
|
||||
Log.e("SharePlugin", "Receiver thread exception");
|
||||
e.printStackTrace();
|
||||
notification.setFinished(false);
|
||||
notification.show();
|
||||
} finally {
|
||||
try {
|
||||
destinationOutput.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
try {
|
||||
input.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}).start();
|
||||
@@ -302,21 +299,18 @@ public class SharePlugin extends Plugin {
|
||||
final NotificationUpdateCallback notificationUpdateCallback = new NotificationUpdateCallback(context, device, toSend);
|
||||
|
||||
//Do the sending in background
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//Actually send the files
|
||||
try {
|
||||
for (NetworkPacket np : toSend) {
|
||||
boolean success = device.sendPacketBlocking(np, notificationUpdateCallback);
|
||||
if (!success) {
|
||||
Log.e("SharePlugin", "Error sending files");
|
||||
return;
|
||||
}
|
||||
new Thread(() -> {
|
||||
//Actually send the files
|
||||
try {
|
||||
for (NetworkPacket np : toSend) {
|
||||
boolean success = device.sendPacketBlocking(np, notificationUpdateCallback);
|
||||
if (!success) {
|
||||
Log.e("SharePlugin", "Error sending files");
|
||||
return;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}).start();
|
||||
|
||||
|
@@ -36,20 +36,14 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
|
||||
filePicker = findPreference("share_destination_folder_preference");
|
||||
|
||||
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
|
||||
customDownloads.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||
updateFilePickerStatus((Boolean) newValue);
|
||||
return true;
|
||||
}
|
||||
customDownloads.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
updateFilePickerStatus((Boolean) newValue);
|
||||
return true;
|
||||
});
|
||||
filePicker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
startActivityForResult(intent, RESULT_PICKER);
|
||||
return true;
|
||||
}
|
||||
filePicker.setOnPreferenceClickListener(preference -> {
|
||||
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||
startActivityForResult(intent, RESULT_PICKER);
|
||||
return true;
|
||||
});
|
||||
} else {
|
||||
customDownloads.setEnabled(false);
|
||||
|
@@ -45,6 +45,8 @@ public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
getDelegate().installViewFactory();
|
||||
getDelegate().onCreate(savedInstanceState);
|
||||
// The superclass's onCreate() method calls setContentView, so this ThemeUtil call must be before that
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
super.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
|
@@ -57,6 +57,7 @@ public class CustomDevicesActivity extends AppCompatActivity {
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
initializeDeviceList(this);
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
setContentView(R.layout.custom_ip_list);
|
||||
|
||||
list = (ListView) findViewById(android.R.id.list);
|
||||
@@ -64,66 +65,48 @@ public class CustomDevicesActivity extends AppCompatActivity {
|
||||
|
||||
list.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, ipAddressList));
|
||||
|
||||
findViewById(android.R.id.button1).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
addNewDevice();
|
||||
}
|
||||
});
|
||||
findViewById(android.R.id.button1).setOnClickListener(v -> addNewDevice());
|
||||
|
||||
EditText ipEntryBox = (EditText) findViewById(R.id.ip_edittext);
|
||||
ipEntryBox.setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
addNewDevice();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
ipEntryBox.setOnEditorActionListener((v, actionId, event) -> {
|
||||
if (actionId == EditorInfo.IME_ACTION_SEND) {
|
||||
addNewDevice();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
boolean dialogAlreadyShown = false;
|
||||
private AdapterView.OnItemClickListener onClickListener = new AdapterView.OnItemClickListener() {
|
||||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, final int position, final long id) {
|
||||
private AdapterView.OnItemClickListener onClickListener = (parent, view, position, id) -> {
|
||||
|
||||
if (dialogAlreadyShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove touched item after confirmation
|
||||
DialogInterface.OnClickListener confirmationListener = new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
ipAddressList.remove(position);
|
||||
saveList();
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(CustomDevicesActivity.this);
|
||||
builder.setMessage("Delete " + ipAddressList.get(position) + " ?");
|
||||
builder.setPositiveButton("Yes", confirmationListener);
|
||||
builder.setNegativeButton("No", confirmationListener);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //DismissListener
|
||||
dialogAlreadyShown = true;
|
||||
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
dialogAlreadyShown = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
builder.show();
|
||||
if (dialogAlreadyShown) {
|
||||
return;
|
||||
}
|
||||
|
||||
// remove touched item after confirmation
|
||||
DialogInterface.OnClickListener confirmationListener = (dialog, which) -> {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
ipAddressList.remove(position);
|
||||
saveList();
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(CustomDevicesActivity.this);
|
||||
builder.setMessage("Delete " + ipAddressList.get(position) + " ?");
|
||||
builder.setPositiveButton("Yes", confirmationListener);
|
||||
builder.setNegativeButton("No", confirmationListener);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //DismissListener
|
||||
dialogAlreadyShown = true;
|
||||
builder.setOnDismissListener(dialog -> dialogAlreadyShown = false);
|
||||
}
|
||||
|
||||
builder.show();
|
||||
};
|
||||
|
||||
private void addNewDevice() {
|
||||
|
@@ -110,97 +110,64 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
//Log.e("DeviceFragment", "device: " + deviceId);
|
||||
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(mDeviceId);
|
||||
if (device == null) {
|
||||
Log.e("DeviceFragment", "Trying to display a device fragment but the device is not present");
|
||||
mActivity.onDeviceSelected(null);
|
||||
return;
|
||||
}
|
||||
|
||||
mActivity.getSupportActionBar().setTitle(device.getName());
|
||||
|
||||
device.addPairingCallback(pairingCallback);
|
||||
device.addPluginsChangedListener(pluginsChangedListener);
|
||||
|
||||
refreshUI();
|
||||
|
||||
BackgroundService.RunCommand(mActivity, service -> {
|
||||
device = service.getDevice(mDeviceId);
|
||||
if (device == null) {
|
||||
Log.e("DeviceFragment", "Trying to display a device fragment but the device is not present");
|
||||
mActivity.onDeviceSelected(null);
|
||||
return;
|
||||
}
|
||||
|
||||
mActivity.getSupportActionBar().setTitle(device.getName());
|
||||
|
||||
device.addPairingCallback(pairingCallback);
|
||||
device.addPluginsChangedListener(pluginsChangedListener);
|
||||
|
||||
refreshUI();
|
||||
|
||||
});
|
||||
|
||||
final Button pairButton = (Button) rootView.findViewById(R.id.pair_button);
|
||||
pairButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
pairButton.setVisibility(View.GONE);
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText("");
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.VISIBLE);
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.requestPairing();
|
||||
}
|
||||
});
|
||||
}
|
||||
pairButton.setOnClickListener(view -> {
|
||||
pairButton.setVisibility(View.GONE);
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText("");
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.VISIBLE);
|
||||
BackgroundService.RunCommand(mActivity, service -> {
|
||||
device = service.getDevice(deviceId);
|
||||
if (device == null) return;
|
||||
device.requestPairing();
|
||||
});
|
||||
});
|
||||
|
||||
rootView.findViewById(R.id.accept_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (device != null) {
|
||||
device.acceptPairing();
|
||||
rootView.findViewById(R.id.pairing_buttons).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
});
|
||||
rootView.findViewById(R.id.accept_button).setOnClickListener(view -> BackgroundService.RunCommand(mActivity, service -> {
|
||||
if (device != null) {
|
||||
device.acceptPairing();
|
||||
rootView.findViewById(R.id.pairing_buttons).setVisibility(View.GONE);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
rootView.findViewById(R.id.reject_button).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
if (device != null) {
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
device.rejectPairing();
|
||||
}
|
||||
mActivity.onDeviceSelected(null);
|
||||
}
|
||||
});
|
||||
rootView.findViewById(R.id.reject_button).setOnClickListener(view -> BackgroundService.RunCommand(mActivity, service -> {
|
||||
if (device != null) {
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
device.rejectPairing();
|
||||
}
|
||||
});
|
||||
mActivity.onDeviceSelected(null);
|
||||
}));
|
||||
|
||||
return rootView;
|
||||
}
|
||||
|
||||
final Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
|
||||
@Override
|
||||
public void onPluginsChanged(final Device device) {
|
||||
refreshUI();
|
||||
}
|
||||
};
|
||||
final Device.PluginsChangedListener pluginsChangedListener = device -> refreshUI();
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(mDeviceId);
|
||||
if (device == null) return;
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
}
|
||||
BackgroundService.RunCommand(mActivity, service -> {
|
||||
Device device = service.getDevice(mDeviceId);
|
||||
if (device == null) return;
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
});
|
||||
super.onDestroyView();
|
||||
}
|
||||
@@ -223,63 +190,47 @@ public class DeviceFragment extends Fragment {
|
||||
if (!p.displayInContextMenu()) {
|
||||
continue;
|
||||
}
|
||||
menu.add(p.getActionName()).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem item) {
|
||||
p.startMainActivity(mActivity);
|
||||
return true;
|
||||
}
|
||||
menu.add(p.getActionName()).setOnMenuItemClickListener(item -> {
|
||||
p.startMainActivity(mActivity);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
Intent intent = new Intent(mActivity, SettingsActivity.class);
|
||||
intent.putExtra("deviceId", mDeviceId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(menuItem -> {
|
||||
Intent intent = new Intent(mActivity, SettingsActivity.class);
|
||||
intent.putExtra("deviceId", mDeviceId);
|
||||
startActivity(intent);
|
||||
return true;
|
||||
});
|
||||
|
||||
if (device.isReachable()) {
|
||||
|
||||
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
Context context = mActivity;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(context.getResources().getString(R.string.encryption_info_title));
|
||||
builder.setPositiveButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(menuItem -> {
|
||||
Context context = mActivity;
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
||||
builder.setTitle(context.getResources().getString(R.string.encryption_info_title));
|
||||
builder.setPositiveButton(context.getResources().getString(R.string.ok), (dialog, id) -> dialog.dismiss());
|
||||
|
||||
if (device.certificate == null) {
|
||||
builder.setMessage(R.string.encryption_info_msg_no_ssl);
|
||||
} else {
|
||||
builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n" + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n"
|
||||
+ context.getResources().getString(R.string.remote_device_fingerprint) + "\n" + SslHelper.getCertificateHash(device.certificate));
|
||||
}
|
||||
builder.create().show();
|
||||
return true;
|
||||
if (device.certificate == null) {
|
||||
builder.setMessage(R.string.encryption_info_msg_no_ssl);
|
||||
} else {
|
||||
builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n" + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n"
|
||||
+ context.getResources().getString(R.string.remote_device_fingerprint) + "\n" + SslHelper.getCertificateHash(device.certificate));
|
||||
}
|
||||
builder.create().show();
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
if (device.isPaired()) {
|
||||
|
||||
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
|
||||
@Override
|
||||
public boolean onMenuItemClick(MenuItem menuItem) {
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
device.unpair();
|
||||
mActivity.onDeviceSelected(null);
|
||||
return true;
|
||||
}
|
||||
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(menuItem -> {
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
device.removePluginsChangedListener(pluginsChangedListener);
|
||||
device.removePairingCallback(pairingCallback);
|
||||
device.unpair();
|
||||
mActivity.onDeviceSelected(null);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -291,19 +242,16 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
getView().setFocusableInTouchMode(true);
|
||||
getView().requestFocus();
|
||||
getView().setOnKeyListener(new View.OnKeyListener() {
|
||||
@Override
|
||||
public boolean onKey(View v, int keyCode, KeyEvent event) {
|
||||
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
boolean fromDeviceList = getArguments().getBoolean(ARG_FROM_DEVICE_LIST, false);
|
||||
// Handle back button so we go to the list of devices in case we came from there
|
||||
if (fromDeviceList) {
|
||||
mActivity.onDeviceSelected(null);
|
||||
return true;
|
||||
}
|
||||
getView().setOnKeyListener((v, keyCode, event) -> {
|
||||
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
boolean fromDeviceList = getArguments().getBoolean(ARG_FROM_DEVICE_LIST, false);
|
||||
// Handle back button so we go to the list of devices in case we came from there
|
||||
if (fromDeviceList) {
|
||||
mActivity.onDeviceSelected(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -323,6 +271,7 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
if (device.isPairRequestedByPeer()) {
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.pair_requested);
|
||||
rootView.findViewById(R.id.pairing_buttons).setVisibility(View.VISIBLE);
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_button).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_request).setVisibility(View.VISIBLE);
|
||||
@@ -333,6 +282,7 @@ public class DeviceFragment extends Fragment {
|
||||
boolean onData = NetworkHelper.isOnMobileNetwork(getContext());
|
||||
|
||||
rootView.findViewById(R.id.pairing_buttons).setVisibility(paired ? View.GONE : View.VISIBLE);
|
||||
rootView.findViewById(R.id.error_message_container).setVisibility((paired && !reachable) ? View.VISIBLE : View.GONE);
|
||||
rootView.findViewById(R.id.not_reachable_message).setVisibility((paired && !reachable && !onData) ? View.VISIBLE : View.GONE);
|
||||
rootView.findViewById(R.id.on_data_message).setVisibility((paired && !reachable && onData) ? View.VISIBLE : View.GONE);
|
||||
|
||||
@@ -345,12 +295,7 @@ public class DeviceFragment extends Fragment {
|
||||
if (!p.hasMainActivity()) continue;
|
||||
if (p.displayInContextMenu()) continue;
|
||||
|
||||
pluginListItems.add(new PluginItem(p, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
p.startMainActivity(mActivity);
|
||||
}
|
||||
}));
|
||||
pluginListItems.add(new PluginItem(p, v -> p.startMainActivity(mActivity)));
|
||||
}
|
||||
|
||||
createPluginsList(device.getFailedPlugins(), R.string.plugins_failed_to_load, new PluginClickListener() {
|
||||
@@ -406,31 +351,25 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
@Override
|
||||
public void pairingFailed(final String error) {
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (rootView == null) return;
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(error);
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
|
||||
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
|
||||
refreshUI();
|
||||
}
|
||||
mActivity.runOnUiThread(() -> {
|
||||
if (rootView == null) return;
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(error);
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
|
||||
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
|
||||
refreshUI();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unpaired() {
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (rootView == null) return;
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.device_not_paired);
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
|
||||
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
|
||||
refreshUI();
|
||||
}
|
||||
mActivity.runOnUiThread(() -> {
|
||||
if (rootView == null) return;
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.device_not_paired);
|
||||
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
|
||||
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
|
||||
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
|
||||
refreshUI();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -438,51 +377,47 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
public static void acceptPairing(final String devId, final MainActivity activity) {
|
||||
final DeviceFragment frag = new DeviceFragment(devId, activity);
|
||||
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device dev = service.getDevice(devId);
|
||||
if (dev == null) {
|
||||
Log.w("rejectPairing", "Device no longer exists: " + devId);
|
||||
return;
|
||||
}
|
||||
activity.getSupportActionBar().setTitle(dev.getName());
|
||||
|
||||
dev.addPairingCallback(frag.pairingCallback);
|
||||
dev.addPluginsChangedListener(frag.pluginsChangedListener);
|
||||
|
||||
frag.device = dev;
|
||||
frag.device.acceptPairing();
|
||||
|
||||
frag.refreshUI();
|
||||
|
||||
BackgroundService.RunCommand(activity, service -> {
|
||||
Device dev = service.getDevice(devId);
|
||||
if (dev == null) {
|
||||
Log.w("rejectPairing", "Device no longer exists: " + devId);
|
||||
return;
|
||||
}
|
||||
activity.getSupportActionBar().setTitle(dev.getName());
|
||||
|
||||
dev.addPairingCallback(frag.pairingCallback);
|
||||
dev.addPluginsChangedListener(frag.pluginsChangedListener);
|
||||
|
||||
frag.device = dev;
|
||||
frag.device.acceptPairing();
|
||||
|
||||
frag.refreshUI();
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static void rejectPairing(final String devId, final MainActivity activity) {
|
||||
final DeviceFragment frag = new DeviceFragment(devId, activity);
|
||||
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device dev = service.getDevice(devId);
|
||||
if (dev == null) {
|
||||
Log.w("rejectPairing", "Device no longer exists: " + devId);
|
||||
return;
|
||||
}
|
||||
activity.getSupportActionBar().setTitle(dev.getName());
|
||||
|
||||
dev.addPairingCallback(frag.pairingCallback);
|
||||
dev.addPluginsChangedListener(frag.pluginsChangedListener);
|
||||
|
||||
frag.device = dev;
|
||||
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
frag.device.removePluginsChangedListener(frag.pluginsChangedListener);
|
||||
frag.device.removePairingCallback(frag.pairingCallback);
|
||||
frag.device.rejectPairing();
|
||||
activity.onDeviceSelected(null);
|
||||
|
||||
frag.refreshUI();
|
||||
BackgroundService.RunCommand(activity, service -> {
|
||||
Device dev = service.getDevice(devId);
|
||||
if (dev == null) {
|
||||
Log.w("rejectPairing", "Device no longer exists: " + devId);
|
||||
return;
|
||||
}
|
||||
activity.getSupportActionBar().setTitle(dev.getName());
|
||||
|
||||
dev.addPairingCallback(frag.pairingCallback);
|
||||
dev.addPluginsChangedListener(frag.pluginsChangedListener);
|
||||
|
||||
frag.device = dev;
|
||||
|
||||
//Remove listener so buttons don't show for a while before changing the view
|
||||
frag.device.removePluginsChangedListener(frag.pluginsChangedListener);
|
||||
frag.device.removePairingCallback(frag.pairingCallback);
|
||||
frag.device.rejectPairing();
|
||||
activity.onDeviceSelected(null);
|
||||
|
||||
frag.refreshUI();
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -74,12 +74,7 @@ public class PairingDeviceItem implements ListAdapter.Item {
|
||||
v.findViewById(R.id.list_item_entry_summary).setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
callback.pairingClicked(device);
|
||||
}
|
||||
});
|
||||
v.setOnClickListener(v1 -> callback.pairingClicked(device));
|
||||
|
||||
return v;
|
||||
}
|
||||
|
@@ -29,11 +29,11 @@ import org.kde.kdeconnect_tp.R;
|
||||
public class SectionItem implements ListAdapter.Item {
|
||||
|
||||
private final String title;
|
||||
public boolean isSectionEmpty;
|
||||
public boolean isEmpty;
|
||||
|
||||
public SectionItem(String title) {
|
||||
this.title = title;
|
||||
this.isSectionEmpty = false;
|
||||
this.isEmpty = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,7 +48,7 @@ public class SectionItem implements ListAdapter.Item {
|
||||
TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text);
|
||||
sectionView.setText(title);
|
||||
|
||||
if (isSectionEmpty) {
|
||||
if (isEmpty) {
|
||||
v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
|
@@ -7,8 +7,11 @@ import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.design.widget.NavigationView;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.view.GravityCompat;
|
||||
@@ -16,12 +19,15 @@ import android.support.v4.widget.DrawerLayout;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.ActionBarDrawerToggle;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.support.v7.widget.SwitchCompat;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -55,6 +61,10 @@ public class MainActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
// We need to set this theme before the call to 'setContentView' below
|
||||
ThemeUtil.setUserPreferredTheme(this);
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
|
||||
mNavigationView = (NavigationView) findViewById(R.id.navigation_drawer);
|
||||
@@ -82,26 +92,22 @@ public class MainActivity extends AppCompatActivity {
|
||||
TextView nameView = (TextView) mDrawerHeader.findViewById(R.id.device_name);
|
||||
nameView.setText(deviceName);
|
||||
|
||||
View.OnClickListener renameListener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
renameDevice();
|
||||
}
|
||||
};
|
||||
View.OnClickListener renameListener = v -> renameDevice();
|
||||
mDrawerHeader.findViewById(R.id.kdeconnect_label).setOnClickListener(renameListener);
|
||||
mDrawerHeader.findViewById(R.id.device_name).setOnClickListener(renameListener);
|
||||
|
||||
mNavigationView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(MenuItem menuItem) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
addDarkModeSwitch((ViewGroup) mDrawerHeader);
|
||||
}
|
||||
|
||||
String deviceId = mMapMenuToDeviceId.get(menuItem);
|
||||
onDeviceSelected(deviceId);
|
||||
mNavigationView.setNavigationItemSelectedListener(menuItem -> {
|
||||
|
||||
mDrawerLayout.closeDrawer(mNavigationView);
|
||||
String deviceId = mMapMenuToDeviceId.get(menuItem);
|
||||
onDeviceSelected(deviceId);
|
||||
|
||||
return true;
|
||||
}
|
||||
mDrawerLayout.closeDrawer(mNavigationView);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
preferences = getSharedPreferences(STATE_SELECTED_DEVICE, Context.MODE_PRIVATE);
|
||||
@@ -132,6 +138,36 @@ public class MainActivity extends AppCompatActivity {
|
||||
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(new CompoundButton.OnCheckedChangeListener() {
|
||||
@RequiresApi(Build.VERSION_CODES.HONEYCOMB)
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton darkThemeSwitch, boolean 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();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//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) {
|
||||
@@ -174,33 +210,30 @@ public class MainActivity extends AppCompatActivity {
|
||||
|
||||
//Log.e("MainActivity", "UpdateComputerList");
|
||||
|
||||
BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
BackgroundService.RunCommand(MainActivity.this, service -> {
|
||||
|
||||
Menu menu = mNavigationView.getMenu();
|
||||
Menu menu = mNavigationView.getMenu();
|
||||
|
||||
menu.clear();
|
||||
mMapMenuToDeviceId.clear();
|
||||
menu.clear();
|
||||
mMapMenuToDeviceId.clear();
|
||||
|
||||
int id = 0;
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
MenuItem item = menu.add(0, id++, 0, device.getName());
|
||||
item.setIcon(device.getIcon());
|
||||
item.setCheckable(true);
|
||||
item.setChecked(device.getDeviceId().equals(mCurrentDevice));
|
||||
mMapMenuToDeviceId.put(item, device.getDeviceId());
|
||||
}
|
||||
int id = 0;
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
MenuItem item = menu.add(0, id++, 0, 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 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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -208,29 +241,14 @@ public class MainActivity extends AppCompatActivity {
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
BackgroundService.addGuiInUseCounter(this, true);
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.addDeviceListChangedCallback("MainActivity", new BackgroundService.DeviceListChangedCallback() {
|
||||
@Override
|
||||
public void onDeviceListChanged() {
|
||||
updateComputerList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(this, service -> service.addDeviceListChangedCallback("MainActivity", this::updateComputerList));
|
||||
updateComputerList();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
BackgroundService.removeGuiInUseCounter(this);
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.removeDeviceListChangedCallback("MainActivity");
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(this, service -> service.removeDeviceListChangedCallback("MainActivity"));
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@@ -282,12 +300,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
switch (requestCode) {
|
||||
case RESULT_NEEDS_RELOAD:
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(mCurrentDevice);
|
||||
device.reloadPluginsFromSettings();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(mCurrentDevice);
|
||||
device.reloadPluginsFromSettings();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
@@ -300,12 +315,9 @@ public class MainActivity extends AppCompatActivity {
|
||||
for (int result : grantResults) {
|
||||
if (result == PackageManager.PERMISSION_GRANTED) {
|
||||
//New permission granted, reload plugins
|
||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
Device device = service.getDevice(mCurrentDevice);
|
||||
device.reloadPluginsFromSettings();
|
||||
}
|
||||
BackgroundService.RunCommand(this, service -> {
|
||||
Device device = service.getDevice(mCurrentDevice);
|
||||
device.reloadPluginsFromSettings();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -324,24 +336,13 @@ public class MainActivity extends AppCompatActivity {
|
||||
);
|
||||
new AlertDialog.Builder(MainActivity.this)
|
||||
.setView(deviceNameEdit)
|
||||
.setPositiveButton(R.string.device_rename_confirm, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
String deviceName = deviceNameEdit.getText().toString();
|
||||
DeviceHelper.setDeviceName(MainActivity.this, deviceName);
|
||||
nameView.setText(deviceName);
|
||||
BackgroundService.RunCommand(MainActivity.this, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
}
|
||||
.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, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, (dialog, which) -> {
|
||||
})
|
||||
.setTitle(R.string.device_rename_title)
|
||||
.show();
|
||||
|
@@ -15,8 +15,8 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
@@ -75,16 +75,11 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
|
||||
setHasOptionsMenu(true);
|
||||
|
||||
rootView = inflater.inflate(R.layout.activity_refresh_list, container, false);
|
||||
View listRootView = rootView.findViewById(R.id.listView1);
|
||||
rootView = inflater.inflate(R.layout.devices_list, container, false);
|
||||
View listRootView = rootView.findViewById(R.id.devices_list);
|
||||
mSwipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.refresh_list_layout);
|
||||
mSwipeRefreshLayout.setOnRefreshListener(
|
||||
new SwipeRefreshLayout.OnRefreshListener() {
|
||||
@Override
|
||||
public void onRefresh() {
|
||||
updateComputerListAction();
|
||||
}
|
||||
}
|
||||
this::updateComputerListAction
|
||||
);
|
||||
headerText = new TextView(inflater.getContext());
|
||||
headerText.setText(getString(R.string.pairing_description));
|
||||
@@ -102,138 +97,106 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
|
||||
private void updateComputerListAction() {
|
||||
updateComputerList();
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.onNetworkChange();
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(mActivity, BackgroundService::onNetworkChange);
|
||||
mSwipeRefreshLayout.setRefreshing(true);
|
||||
new Thread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mSwipeRefreshLayout.setRefreshing(false);
|
||||
}
|
||||
});
|
||||
new Thread(() -> {
|
||||
try {
|
||||
Thread.sleep(1500);
|
||||
} catch (InterruptedException ignored) {
|
||||
}
|
||||
mActivity.runOnUiThread(() -> mSwipeRefreshLayout.setRefreshing(false));
|
||||
}).start();
|
||||
}
|
||||
|
||||
private void updateComputerList() {
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(final BackgroundService service) {
|
||||
mActivity.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
if (!isAdded()) {
|
||||
//Fragment is not attached to an activity. We will crash if we try to do anything here.
|
||||
return;
|
||||
}
|
||||
|
||||
if (listRefreshCalledThisFrame) {
|
||||
// This makes sure we don't try to call list.getFirstVisiblePosition()
|
||||
// twice per frame, because the second time the list hasn't been drawn
|
||||
// yet and it would always return 0.
|
||||
return;
|
||||
}
|
||||
listRefreshCalledThisFrame = true;
|
||||
|
||||
headerText.setText(getString(NetworkHelper.isOnMobileNetwork(getContext()) ? R.string.on_data_message : R.string.pairing_description));
|
||||
//Disable tap animation
|
||||
headerText.setOnClickListener(null);
|
||||
headerText.setOnLongClickListener(null);
|
||||
|
||||
try {
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
|
||||
|
||||
SectionItem section;
|
||||
Resources res = getResources();
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && !device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
section.isSectionEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_connected_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
section.isSectionEmpty = false;
|
||||
}
|
||||
}
|
||||
if (section.isSectionEmpty) {
|
||||
items.remove(items.size() - 1); //Remove connected devices section if empty
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_remembered_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
for (Device device : devices) {
|
||||
if (!device.isReachable() && device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
section.isSectionEmpty = false;
|
||||
}
|
||||
}
|
||||
if (section.isSectionEmpty) {
|
||||
items.remove(items.size() - 1); //Remove remembered devices section if empty
|
||||
}
|
||||
|
||||
final ListView list = (ListView) rootView.findViewById(R.id.listView1);
|
||||
|
||||
//Store current scroll
|
||||
int index = list.getFirstVisiblePosition();
|
||||
View v = list.getChildAt(0);
|
||||
int top = (v == null) ? 0 : (v.getTop() - list.getPaddingTop());
|
||||
|
||||
list.setAdapter(new ListAdapter(mActivity, items));
|
||||
|
||||
//Restore scroll
|
||||
list.setSelectionFromTop(index, top);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
//Ignore: The activity was closed while we were trying to update it
|
||||
} finally {
|
||||
listRefreshCalledThisFrame = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(mActivity, service -> mActivity.runOnUiThread(() -> {
|
||||
|
||||
if (!isAdded()) {
|
||||
//Fragment is not attached to an activity. We will crash if we try to do anything here.
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (listRefreshCalledThisFrame) {
|
||||
// This makes sure we don't try to call list.getFirstVisiblePosition()
|
||||
// twice per frame, because the second time the list hasn't been drawn
|
||||
// yet and it would always return 0.
|
||||
return;
|
||||
}
|
||||
listRefreshCalledThisFrame = true;
|
||||
|
||||
headerText.setText(getString(NetworkHelper.isOnMobileNetwork(getContext()) ? R.string.on_data_message : R.string.pairing_description));
|
||||
//Disable tap animation
|
||||
headerText.setOnClickListener(null);
|
||||
headerText.setOnLongClickListener(null);
|
||||
|
||||
try {
|
||||
Collection<Device> devices = service.getDevices().values();
|
||||
final ArrayList<ListAdapter.Item> items = new ArrayList<>();
|
||||
|
||||
SectionItem connectedSection;
|
||||
Resources res = getResources();
|
||||
|
||||
connectedSection = new SectionItem(res.getString(R.string.category_connected_devices));
|
||||
items.add(connectedSection);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
connectedSection.isEmpty = false;
|
||||
}
|
||||
}
|
||||
if (connectedSection.isEmpty) {
|
||||
items.remove(items.size() - 1); //Remove connected devices section if empty
|
||||
}
|
||||
|
||||
SectionItem availableSection = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
||||
items.add(availableSection);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && !device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
availableSection.isEmpty = false;
|
||||
}
|
||||
}
|
||||
if (availableSection.isEmpty && !connectedSection.isEmpty) {
|
||||
items.remove(items.size() - 1); //Remove remembered devices section if empty
|
||||
}
|
||||
|
||||
SectionItem rememberedSection = new SectionItem(res.getString(R.string.category_remembered_devices));
|
||||
items.add(rememberedSection);
|
||||
for (Device device : devices) {
|
||||
if (!device.isReachable() && device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
rememberedSection.isEmpty = false;
|
||||
}
|
||||
}
|
||||
if (rememberedSection.isEmpty) {
|
||||
items.remove(items.size() - 1); //Remove remembered devices section if empty
|
||||
}
|
||||
|
||||
final ListView list = (ListView) rootView.findViewById(R.id.devices_list);
|
||||
|
||||
//Store current scroll
|
||||
int index = list.getFirstVisiblePosition();
|
||||
View v = list.getChildAt(0);
|
||||
int top = (v == null) ? 0 : (v.getTop() - list.getPaddingTop());
|
||||
|
||||
list.setAdapter(new ListAdapter(mActivity, items));
|
||||
|
||||
//Restore scroll
|
||||
list.setSelectionFromTop(index, top);
|
||||
} catch (IllegalStateException e) {
|
||||
e.printStackTrace();
|
||||
//Ignore: The activity was closed while we were trying to update it
|
||||
} finally {
|
||||
listRefreshCalledThisFrame = false;
|
||||
}
|
||||
|
||||
}));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.addDeviceListChangedCallback("PairingFragment", new BackgroundService.DeviceListChangedCallback() {
|
||||
@Override
|
||||
public void onDeviceListChanged() {
|
||||
updateComputerList();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(mActivity, service -> service.addDeviceListChangedCallback("PairingFragment", this::updateComputerList));
|
||||
updateComputerList();
|
||||
}
|
||||
|
||||
@@ -241,12 +204,7 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
mSwipeRefreshLayout.setEnabled(false);
|
||||
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
service.removeDeviceListChangedCallback("PairingFragment");
|
||||
}
|
||||
});
|
||||
BackgroundService.RunCommand(mActivity, service -> service.removeDeviceListChangedCallback("PairingFragment"));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -29,15 +29,12 @@ public class PluginPreference extends CheckBoxPreference {
|
||||
|
||||
Plugin plugin = device.getPlugin(pluginKey, true);
|
||||
if (info.hasSettings() && plugin != null) {
|
||||
this.listener = new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
Plugin plugin = device.getPlugin(pluginKey, true);
|
||||
if (plugin != null) {
|
||||
plugin.startPreferencesActivity(activity);
|
||||
} else { //Could happen if the device is not connected anymore
|
||||
activity.finish(); //End this activity so we go to the "device not reachable" screen
|
||||
}
|
||||
this.listener = v -> {
|
||||
Plugin plugin1 = device.getPlugin(pluginKey, true);
|
||||
if (plugin1 != null) {
|
||||
plugin1.startPreferencesActivity(activity);
|
||||
} else { //Could happen if the device is not connected anymore
|
||||
activity.finish(); //End this activity so we go to the "device not reachable" screen
|
||||
}
|
||||
};
|
||||
} else {
|
||||
@@ -58,14 +55,11 @@ public class PluginPreference extends CheckBoxPreference {
|
||||
button.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
root.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
boolean newState = !device.isPluginEnabled(pluginKey);
|
||||
setChecked(newState); //It actually works on API<14
|
||||
button.setEnabled(newState);
|
||||
device.setPluginEnabled(pluginKey, newState);
|
||||
}
|
||||
root.setOnClickListener(v -> {
|
||||
boolean newState = !device.isPluginEnabled(pluginKey);
|
||||
setChecked(newState); //It actually works on API<14
|
||||
button.setEnabled(newState);
|
||||
device.setPluginEnabled(pluginKey, newState);
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -44,24 +44,16 @@ public class SettingsActivity extends AppCompatPreferenceActivity {
|
||||
deviceId = getIntent().getStringExtra("deviceId");
|
||||
}
|
||||
|
||||
BackgroundService.RunCommand(getApplicationContext(), new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
public void onServiceStart(BackgroundService service) {
|
||||
final Device device = service.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
SettingsActivity.this.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SettingsActivity.this.finish();
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
List<String> plugins = device.getSupportedPlugins();
|
||||
for (final String pluginKey : plugins) {
|
||||
PluginPreference pref = new PluginPreference(SettingsActivity.this, pluginKey, device);
|
||||
preferenceScreen.addPreference(pref);
|
||||
}
|
||||
BackgroundService.RunCommand(getApplicationContext(), service -> {
|
||||
final Device device = service.getDevice(deviceId);
|
||||
if (device == null) {
|
||||
SettingsActivity.this.runOnUiThread(SettingsActivity.this::finish);
|
||||
return;
|
||||
}
|
||||
List<String> plugins = device.getSupportedPlugins();
|
||||
for (final String pluginKey : plugins) {
|
||||
PluginPreference pref = new PluginPreference(SettingsActivity.this, pluginKey, device);
|
||||
preferenceScreen.addPreference(pref);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
46
src/org/kde/kdeconnect/UserInterface/ThemeUtil.java
Normal file
46
src/org/kde/kdeconnect/UserInterface/ThemeUtil.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package org.kde.kdeconnect.UserInterface;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
/**
|
||||
* Utilities for working with android {@link android.content.res.Resources.Theme Themes}.
|
||||
*/
|
||||
public class ThemeUtil {
|
||||
|
||||
/**
|
||||
* This method should be called from the {@code activity}'s onCreate method, before
|
||||
* any calls to {@link Activity#setContentView} or
|
||||
* {@link android.preference.PreferenceActivity#setPreferenceScreen}.
|
||||
*
|
||||
* @param activity any Activity on screen
|
||||
*/
|
||||
public static void setUserPreferredTheme(Activity activity) {
|
||||
boolean useDarkTheme = shouldUseDarkTheme(activity);
|
||||
|
||||
// Only MainActivity sets its own Toolbar as the ActionBar.
|
||||
boolean usesOwnActionBar = activity instanceof MainActivity;
|
||||
|
||||
if (useDarkTheme) {
|
||||
activity.setTheme(usesOwnActionBar ? R.style.KdeConnectTheme_Dark_NoActionBar : R.style.KdeConnectTheme_Dark);
|
||||
} else {
|
||||
activity.setTheme(usesOwnActionBar ? R.style.KdeConnectTheme_NoActionBar : R.style.KdeConnectTheme);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks {@link SharedPreferences} to figure out whether we should use the light
|
||||
* theme or the dark theme. The app defaults to light theme.
|
||||
*
|
||||
* @param context any active context (Activity, Service, Application, etc.)
|
||||
* @return true if the dark theme should be active, false otherwise
|
||||
*/
|
||||
public static boolean shouldUseDarkTheme(Context context) {
|
||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
return prefs.getBoolean("darkTheme", false);
|
||||
}
|
||||
}
|
@@ -204,43 +204,27 @@ public class LanLinkTest extends AndroidTestCase {
|
||||
Mockito.when(sharePacket.getType()).thenReturn("kdeconnect.share");
|
||||
Mockito.when(sharePacket.hasPayload()).thenReturn(true);
|
||||
Mockito.when(sharePacket.hasPayloadTransferInfo()).thenReturn(true);
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
return sharePacketJson.toString();
|
||||
}
|
||||
}).when(sharePacket).serialize();
|
||||
Mockito.doAnswer(invocationOnMock -> sharePacketJson.toString()).when(sharePacket).serialize();
|
||||
Mockito.when(sharePacket.getPayload()).thenReturn(new ByteArrayInputStream(data));
|
||||
Mockito.when(sharePacket.getPayloadSize()).thenReturn((long) data.length);
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
return sharePacketJson.getJSONObject("payloadTransferInfo");
|
||||
}
|
||||
}).when(sharePacket).getPayloadTransferInfo();
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
JSONObject object = (JSONObject) invocationOnMock.getArguments()[0];
|
||||
Mockito.doAnswer(invocationOnMock -> sharePacketJson.getJSONObject("payloadTransferInfo")).when(sharePacket).getPayloadTransferInfo();
|
||||
Mockito.doAnswer(invocationOnMock -> {
|
||||
JSONObject object = (JSONObject) invocationOnMock.getArguments()[0];
|
||||
|
||||
sharePacketJson.put("payloadTransferInfo", object);
|
||||
return null;
|
||||
}
|
||||
sharePacketJson.put("payloadTransferInfo", object);
|
||||
return null;
|
||||
}).when(sharePacket).setPayloadTransferInfo(Mockito.any(JSONObject.class));
|
||||
|
||||
Mockito.doAnswer(new Answer() {
|
||||
@Override
|
||||
public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
|
||||
Mockito.doAnswer(invocationOnMock -> {
|
||||
|
||||
Log.e("LanLinkTest", "Write to stream");
|
||||
String stringNetworkPacket = new String((byte[]) invocationOnMock.getArguments()[0]);
|
||||
final NetworkPacket np = NetworkPacket.unserialize(stringNetworkPacket);
|
||||
Log.e("LanLinkTest", "Write to stream");
|
||||
String stringNetworkPacket = new String((byte[]) invocationOnMock.getArguments()[0]);
|
||||
final NetworkPacket np = NetworkPacket.unserialize(stringNetworkPacket);
|
||||
|
||||
downloader.setNetworkPacket(np);
|
||||
downloader.start();
|
||||
downloader.setNetworkPacket(np);
|
||||
downloader.start();
|
||||
|
||||
return stringNetworkPacket.length();
|
||||
}
|
||||
return stringNetworkPacket.length();
|
||||
}).when(goodOutputStream).write(Mockito.any(byte[].class));
|
||||
|
||||
goodLanLink.sendPacket(sharePacket, callback);
|
||||
|
Reference in New Issue
Block a user