diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 46a58a5c..70471ac8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -44,6 +44,8 @@ + + diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index efb7a58a..e08c3516 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -6,6 +6,7 @@ package org.kde.kdeconnect; +import android.Manifest; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -14,6 +15,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; +import android.content.pm.PackageManager; import android.net.ConnectivityManager; import android.os.Binder; import android.os.Build; @@ -353,10 +355,9 @@ public class BackgroundService extends Service { notification.setContentText(getString(R.string.foreground_notification_devices, TextUtils.join(", ", connectedDevices))); // Adding an action button to send clipboard manually in Android 10 and later. - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { - Intent sendClipboard = new Intent(this, ClipboardFloatingActivity.class); - sendClipboard.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); - sendClipboard.putExtra("connectedDeviceIds", connectedDeviceIds); + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P && + ContextCompat.checkSelfPermission(this, Manifest.permission.READ_LOGS) == PackageManager.PERMISSION_DENIED) { + Intent sendClipboard = ClipboardFloatingActivity.getIntent(this, true); PendingIntent sendPendingClipboard = PendingIntent.getActivity(this, 3, sendClipboard, PendingIntent.FLAG_UPDATE_CURRENT); notification.addAction(0, getString(R.string.foreground_notification_send_clipboard), sendPendingClipboard); } diff --git a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardFloatingActivity.java b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardFloatingActivity.java index 776ec983..bafae7fa 100644 --- a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardFloatingActivity.java +++ b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardFloatingActivity.java @@ -1,5 +1,6 @@ /* * SPDX-FileCopyrightText: 2020 Anjani Kumar + * SPDX-FileCopyrightText: 2021 Ilmaz Gumerov * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ @@ -8,19 +9,16 @@ package org.kde.kdeconnect.Plugins.ClibpoardPlugin; import android.content.ClipData; import android.content.ClipboardManager; +import android.content.Context; +import android.content.Intent; import android.os.Bundle; import android.view.WindowManager; import android.widget.Toast; import androidx.appcompat.app.AppCompatActivity; -import androidx.core.content.ContextCompat; -import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.Device; import org.kde.kdeconnect_tp.R; -import java.util.ArrayList; - /* An activity to access the clipboard on Android 10 and later by raising over other apps. This is invisible and doesn't require any interaction from the user. @@ -30,30 +28,35 @@ import java.util.ArrayList; https://developer.android.com/reference/android/Manifest.permission#READ_LOGS This permission can be gained by only from the adb by the user. https://www.reddit.com/r/AndroidBusters/comments/fh60lt/how_to_solve_a_problem_with_the_clipboard_on/ + Like: + # Enable the READ_LOGS permission. There is no other way to do this for a regular user app. + adb -d shell pm grant org.kde.kdeconnect_tp android.permission.READ_LOGS; + # Allow "Drawing over other apps", also accessible from Settings on the phone. + # Optional, but makes the feature much more reliable. + adb -d shell appops set org.kde.kdeconnect_tp SYSTEM_ALERT_WINDOW allow; + # Kill the app, new permissions take effect on restart. + adb -d shell am force-stop org.kde.kdeconnect_tp; Currently this activity is bering triggered from a button in Foreground Notification or quick settings tile. * */ public class ClipboardFloatingActivity extends AppCompatActivity { - private ArrayList connectedDevices = new ArrayList<>(); + private static final String KEY_SHOW_TOAST = "SHOW_TOAST"; + + public static Intent getIntent(Context context, boolean showToast) { + Intent startIntent = new Intent(context.getApplicationContext(), ClipboardFloatingActivity.class); + startIntent.putExtra(KEY_SHOW_TOAST, showToast); + startIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); + return startIntent; + } @Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { // We are now sure that clipboard can be accessed from here. - ClipboardManager clipboardManager = ContextCompat.getSystemService(this, - ClipboardManager.class); - ClipData.Item item; - if (clipboardManager.hasPrimaryClip()) { - item = clipboardManager.getPrimaryClip().getItemAt(0); - String content = item.coerceToText(this).toString(); - for (Device device : connectedDevices) { - ClipboardPlugin clipboardPlugin = (ClipboardPlugin) device.getPlugin("ClipboardPlugin"); - if (clipboardPlugin != null) { - clipboardPlugin.propagateClipboard(content); - } - } + ClipboardListener.instance(this).onClipboardChanged(); + if (shouldShowToast()) { Toast.makeText(this, R.string.pref_plugin_clipboard_sent, Toast.LENGTH_SHORT).show(); } finish(); @@ -70,12 +73,10 @@ public class ClipboardFloatingActivity extends AppCompatActivity { WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; getWindow().setAttributes(wlp); - ArrayList connectedDeviceIds = getIntent().getStringArrayListExtra("connectedDeviceIds"); - if (connectedDeviceIds != null) { - for (String deviceId : connectedDeviceIds) { - connectedDevices.add(BackgroundService.getInstance().getDevice(deviceId)); - } - } + } + + private boolean shouldShowToast() { + return getIntent().getBooleanExtra(KEY_SHOW_TOAST, false); } } diff --git a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardListener.java b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardListener.java index fb1944b3..ea9a1321 100644 --- a/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardListener.java +++ b/src/org/kde/kdeconnect/Plugins/ClibpoardPlugin/ClipboardListener.java @@ -1,22 +1,32 @@ /* * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora + * SPDX-FileCopyrightText: 2021 Ilmaz Gumerov * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL */ package org.kde.kdeconnect.Plugins.ClibpoardPlugin; +import android.Manifest; import android.annotation.TargetApi; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; import android.os.Looper; import androidx.core.content.ContextCompat; +import org.kde.kdeconnect_tp.BuildConfig; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.HashSet; +import java.util.Locale; @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class ClipboardListener { @@ -32,7 +42,6 @@ public class ClipboardListener { private long updateTimestamp; private ClipboardManager cm = null; - private ClipboardManager.OnPrimaryClipChangedListener listener; private static ClipboardListener _instance = null; @@ -57,28 +66,51 @@ public class ClipboardListener { new Handler(Looper.getMainLooper()).post(() -> { cm = ContextCompat.getSystemService(context, ClipboardManager.class); - listener = () -> { - try { - - ClipData.Item item = cm.getPrimaryClip().getItemAt(0); - String content = item.coerceToText(context).toString(); - - if (content.equals(currentContent)) { - return; - } - updateTimestamp = System.currentTimeMillis(); - currentContent = content; - - for (ClipboardObserver observer : observers) { - observer.clipboardChanged(content); - } - - } catch (Exception e) { - //Probably clipboard was not text - } - }; - cm.addPrimaryClipChangedListener(listener); + cm.addPrimaryClipChangedListener(this::onClipboardChanged); }); + + + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P && + ContextCompat.checkSelfPermission(context, Manifest.permission.READ_LOGS) == PackageManager.PERMISSION_GRANTED) { + new Thread(() -> { + try { + String timeStamp = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US).format(new Date()); + // Listen only ClipboardService errors after now + Process process = Runtime.getRuntime().exec(new String[]{"logcat", "-T", timeStamp, "ClipboardService:E", "*:S"}); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader( + process.getInputStream() + ) + ); + String line; + while ((line = bufferedReader.readLine()) != null) { + if (line.contains(BuildConfig.APPLICATION_ID)) { + context.startActivity(ClipboardFloatingActivity.getIntent(context, false)); + } + } + } catch (Exception ignored) { + } + }).start(); + } + } + + public void onClipboardChanged() { + try { + ClipData.Item item = cm.getPrimaryClip().getItemAt(0); + String content = item.coerceToText(context).toString(); + + if (content.equals(currentContent)) { + return; + } + updateTimestamp = System.currentTimeMillis(); + currentContent = content; + + for (ClipboardObserver observer : observers) { + observer.clipboardChanged(content); + } + } catch (Exception e) { + //Probably clipboard was not text + } } public String getCurrentContent() {