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

Compare commits

...

40 Commits

Author SHA1 Message Date
Albert Vaca
b1dcc3c40f Bumped version to release 2018-05-15 00:55:28 +02:00
Albert Vaca
f5254e504d Hopefully fixed crash in Android 8.1 2018-05-15 00:55:28 +02:00
Albert Vaca
55a0ad657e Bumped version to release 2018-05-15 00:55:28 +02:00
l10n daemon script
8add0c4fbe GIT_SILENT made messages (after extraction) 2018-05-14 03:35:55 +02:00
l10n daemon script
187e47aec2 GIT_SILENT made messages (after extraction) 2018-05-13 03:25:31 +02:00
l10n daemon script
27b390a9bd GIT_SILENT made messages (after extraction) 2018-05-12 03:31:41 +02:00
Nicolas Fella
9bf5a9c489 Fix SharePlugin devices list
Summary: D12212 broke the devices list in the SharePlugin. No devices was shown even when there were devices available.

Test Plan: Open up share devices list with 0/1 devices available

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: kdeconnect, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12334
2018-05-11 21:18:58 +02:00
l10n daemon script
a907c02ecf GIT_SILENT made messages (after extraction) 2018-05-11 03:42:17 +02:00
Nicolas Fella
4deeb5e061 Send Runcommand setup package from phone
Summary: Also remove some leftover code from the prevoius attempt

Test Plan: Open command list, click Floating Action Button, look for KCM window on desktop.

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: albertvaka, kdeconnect, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12332
2018-05-10 19:47:15 +02:00
Nicolas Fella
ff2354a7a6 Update gitignore 2018-05-10 19:01:32 +02:00
Yoann Laissus
5463be96a4 Add detection of Android TV devices with a proper icon
KDE app :  https://phabricator.kde.org/D12802

Test Plan: Tested with an Android TV device (Nvidia Shield)

Reviewers: #kde_connect, nicolasfella

Reviewed By: #kde_connect, nicolasfella

Subscribers: tfella, nicolasfella, kdeconnect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12803
2018-05-10 17:02:45 +02:00
Nicolas Fella
0c6b584d57 [WIP] Add MprisReceiverPlugin (Android)
Summary:
Exposes media sessions to the desktop for use with the MprisRemote plugin and the app.
Still contains some Logs, will remove them when its time.

Test Plan:
What works:
- Displaying and updating title, artist and album.
- PlayPause, next and previous
- Playing state
- multiple sessions

What doesn't work:
- volume control
- length and position
- Album art
- can* abilities are not exposed, e.g. canGoNext, not sure if possible at all

Reviewers: #kde_connect, mtijink

Reviewed By: #kde_connect, mtijink

Subscribers: kdeconnect, mtijink, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12559
2018-05-09 15:30:06 +02:00
Nicolas Fella
e712c69e15 Use lambdas where possible
Summary: Let Android Studio replace anonymous types with lambdas. No manual code change.

Test Plan: Compile and superficial behaviour test

Reviewers: #kde_connect, philipc

Reviewed By: #kde_connect, philipc

Subscribers: philipc, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12229
2018-05-09 14:03:08 +02:00
l10n daemon script
7536eb7427 GIT_SILENT made messages (after extraction) 2018-05-05 03:26:35 +02:00
l10n daemon script
04e5f6b451 GIT_SILENT made messages (after extraction) 2018-05-04 03:29:32 +02:00
l10n daemon script
f75bf2129a GIT_SILENT made messages (after extraction) 2018-05-03 03:36:08 +02:00
Albert Vaca
acd98da83a Upgraded gradle 2018-05-01 19:30:32 +02:00
Matthijs Tijink
d6c3803ea2 Add assertions and checks to album art fetch code
Summary:
Add a couple of assertions and checks to the album art fetch code. Hopefully fixes bug https://bugs.kde.org/show_bug.cgi?id=393274, or otherwise at least gives a bit more information.

BUG 393274

Test Plan: Fixes the bug, according to the reporter.

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: albertvaka, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12618
2018-05-01 19:27:24 +02:00
Albert Vaca
d688621f27 Set hardcoded 'Dark theme' switch to be translatable 2018-05-01 19:09:31 +02:00
l10n daemon script
774759746d GIT_SILENT made messages (after extraction) 2018-04-30 03:34:10 +02:00
Nicolas Fella
d65b3153ae Bump gradle version
Reviewers: #kde_connect, philipc

Reviewed By: #kde_connect, philipc

Subscribers: philipc, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12501
2018-04-27 21:23:07 +02:00
Nicolas Fella
8a2ea77030 Add marginEnd for RTL layouts
Reviewers: #kde_connect, mtijink

Reviewed By: #kde_connect, mtijink

Subscribers: mtijink, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12345
2018-04-24 19:58:48 +02:00
Philip Cohn-Cort
a1f1693d0b Add a dark theme
Summary:
BUG: 375376

This revision adds dark mode support to the app ( T7044 ). It does so by injecting
theme information into each activity, and making sure that all Views
define their colors by reference to theme attributes.

In order to make this work, all of the buttons with images (like the list
of available devices) now are tinted according to the theme.

While all versions of android support the theme, only devices running
Android ICS or higher will have a toggle button in the drawer.

Test Plan: Open all the screens, both with and without the dark theme on.

Reviewers: #kde_connect, mtijink, #vdg, nicolasfella

Reviewed By: #kde_connect, mtijink, nicolasfella

Subscribers: apol, ngraham, nicolasfella, mtijink

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D11694
2018-04-23 18:35:43 +02:00
l10n daemon script
b228fb2377 GIT_SILENT made messages (after extraction) 2018-04-23 03:30:04 +02:00
Nicolas Fella
bc330018e1 Rename list layout
Summary: Give it a proper name

Reviewers: #kde_connect, mtijink

Reviewed By: #kde_connect, mtijink

Subscribers: mtijink, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12289
2018-04-17 20:50:23 +02:00
Nicolas Fella
9837c89f35 Hide Available devices section if no devices are available
Summary:
Before:
{F5807970}

After:
{F5807971}

Reviewers: #kde_connect, mtijink

Reviewed By: #kde_connect, mtijink

Subscribers: mtijink, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12212
2018-04-17 20:48:08 +02:00
Simon Redman
4d2357f016 Allow JSONObject in a NetworkPacket
Summary: Allow a mapping of fields to be a field of a NetworkPacket

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: albertvaka, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D11870
2018-04-17 19:03:46 +02:00
Karl Heinz
d4d9e8ba6d Update DeviceHelper.java 2018-04-16 16:55:31 +02:00
Matthijs Tijink
2cd7d4b7a9 Fix default Find My Phone ringtone
Summary:
Previously, if the user hadn't set a ringtone, the code raised an exception (because an empty string isn't an uri). Thus, no sound was played.

This fixes it.

BUG: 392853

Test Plan: Without having set a ringtone, it works. Otherwise, it works too.

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: nicolasfella, sredman, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D12070
2018-04-09 19:00:55 +02:00
l10n daemon script
8bbd3f55b1 GIT_SILENT made messages (after extraction) 2018-04-09 03:23:13 +02:00
Albert Vaca
1bf96454cf required is not part of the uses-permission tag
BUG: 392856
2018-04-08 17:20:48 +02:00
l10n daemon script
6883ed2847 GIT_SILENT made messages (after extraction) 2018-04-07 03:23:18 +02:00
l10n daemon script
91be51ea5a GIT_SILENT made messages (after extraction) 2018-04-06 03:31:04 +02:00
l10n daemon script
3bb54816ed GIT_SILENT made messages (after extraction) 2018-04-04 03:26:25 +02:00
l10n daemon script
810f53449a GIT_SILENT made messages (after extraction) 2018-04-03 03:28:03 +02:00
l10n daemon script
efa4668448 GIT_SILENT made messages (after extraction) 2018-04-01 03:29:58 +02:00
l10n daemon script
b70370005b GIT_SILENT made messages (after extraction) 2018-03-31 03:22:14 +02:00
l10n daemon script
9631cacb69 GIT_SILENT made messages (after extraction) 2018-03-30 03:25:50 +02:00
Matthijs Tijink
94ef1294c5 Add share action button to received file notification
Summary: You can share the just-received file. It doesn't work with a custom download directory (just like opening such files doesn't work yet). It also won't close the notification once shared.

Test Plan: Sharing to other apps works.

Reviewers: #kde_connect, nicolasfella

Reviewed By: #kde_connect, nicolasfella

Subscribers: nicolasfella, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D11680
2018-03-28 22:58:27 +02:00
Matthijs Tijink
7c8301a7cc Disables album art download on metered connections (e.g. 4G)
Summary: Won't typically be a problem (you're usually connected via wifi with KDE Connect anyway), but shouldn't hurt.

Test Plan: Works with the bluetooth backend on 4G (i.e. no album art is downloaded). Still downloads album art otherwise, as expected.

Reviewers: #kde_connect, nicolasfella

Reviewed By: #kde_connect, nicolasfella

Subscribers: #kde_connect

Differential Revision: https://phabricator.kde.org/D11682
2018-03-27 21:23:08 +02:00
83 changed files with 2112 additions and 1817 deletions

1
.gitignore vendored
View File

@@ -13,3 +13,4 @@ gradlew.bat
*.iml
*.keystore
.directory
GPUCache/

View File

@@ -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="1820"
android:versionName="1.8.2">
android:versionCode="1840"
android:versionName="1.8.4">
<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" />

View File

@@ -4,14 +4,14 @@ buildscript {
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
@@ -23,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 {

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 B

View 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>

View 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>

View File

@@ -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" />

View 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"
/>

View File

@@ -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"
@@ -67,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"

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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"

View File

@@ -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

View File

@@ -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" />

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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
View 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>

View File

@@ -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>

View 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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -221,59 +221,49 @@ 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);
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 {
new Thread(() -> {
try {
synchronized (this) {
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);
//}
}
} 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);
//}
}
}).start();
} else {
@@ -298,7 +288,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
* @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 {
private void addLink(final NetworkPacket identityPacket, Socket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
String deviceId = identityPacket.getString("deviceId");
LanLink currentLink = visibleComputers.get(deviceId);
@@ -331,23 +321,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;
}
@@ -356,21 +343,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) {
@@ -401,51 +385,48 @@ 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();
}

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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);

View File

@@ -42,6 +42,7 @@ import org.spongycastle.operator.jcajce.JcaContentSignerBuilder;
import java.io.IOException;
import java.math.BigInteger;
import java.net.Socket;
import java.net.SocketException;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.security.PrivateKey;
@@ -201,7 +202,7 @@ public class SslHelper {
}
public static void configureSslSocket(SSLSocket socket, boolean isDeviceTrusted, boolean isClient) {
public static void configureSslSocket(SSLSocket socket, boolean isDeviceTrusted, boolean isClient) throws SocketException {
socket.setEnabledProtocols(new String[]{"TLSv1"}); //Newer TLS versions are only supported on API 16+
@@ -223,6 +224,8 @@ public class SslHelper {
}
socket.setEnabledCipherSuites(supportedCiphers.toArray(new String[supportedCiphers.size()]));
socket.setSoTimeout(1000);
if (isClient) {
socket.setUseClientMode(true);
} else {

View File

@@ -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());

View File

@@ -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) {

View File

@@ -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);
});
}

View File

@@ -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

View File

@@ -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();

View File

@@ -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

View File

@@ -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);
});
}

View File

@@ -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);
});
}

View File

@@ -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,6 +63,10 @@ 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.
@@ -100,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);
}
/**
@@ -217,6 +225,12 @@ 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) || isFetchingList.contains(url)) {
@@ -256,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) {
@@ -419,6 +436,9 @@ public final class AlbumArtCache {
} catch (IOException ignored) {}
return;
}
if (payload == null) {
return;
}
URL url;
try {

View File

@@ -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,105 +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);
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);
}
});
}
});
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);
});
}
});
});
}
@@ -195,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) {
@@ -314,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");
@@ -325,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
@@ -409,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);
@@ -452,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);
});
}

View File

@@ -211,190 +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());
}
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());
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());
});
}

View File

@@ -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);
}
}

View File

@@ -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();
}
}

View File

@@ -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()));
}
}

View File

@@ -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++) {

View File

@@ -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();

View File

@@ -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();
}

View File

@@ -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) {

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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);
});
}
}

View File

@@ -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);
}
}

View File

@@ -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);
});
}
}

View File

@@ -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();
}

View File

@@ -119,20 +119,34 @@ public class ShareNotification {
}
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());
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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;
});
}
@@ -334,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);
@@ -346,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() {
@@ -407,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();
});
}
@@ -439,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();
});
}

View File

@@ -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;
}

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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_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_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_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

View File

@@ -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);
});
}

View File

@@ -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);
}
});
}

View 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);
}
}

View File

@@ -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);