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

Add logs reading for sending clipboard on Android 10

Enable with:
adb -d shell pm grant org.kde.kdeconnect_tp android.permission.READ_LOGS;
adb -d shell appops set org.kde.kdeconnect_tp SYSTEM_ALERT_WINDOW allow;
adb -d shell am force-stop org.kde.kdeconnect_tp;
This commit is contained in:
Ilmaz Gumerov 2022-06-27 16:58:49 -04:00 committed by Simon Redman
parent de1e68d62f
commit edc655da5a
4 changed files with 86 additions and 50 deletions

View File

@ -44,6 +44,8 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" /> <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.READ_LOGS" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

View File

@ -6,6 +6,7 @@
package org.kde.kdeconnect; package org.kde.kdeconnect;
import android.Manifest;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.app.PendingIntent; import android.app.PendingIntent;
@ -14,6 +15,7 @@ import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager; import android.net.ConnectivityManager;
import android.os.Binder; import android.os.Binder;
import android.os.Build; import android.os.Build;
@ -353,10 +355,9 @@ public class BackgroundService extends Service {
notification.setContentText(getString(R.string.foreground_notification_devices, TextUtils.join(", ", connectedDevices))); notification.setContentText(getString(R.string.foreground_notification_devices, TextUtils.join(", ", connectedDevices)));
// Adding an action button to send clipboard manually in Android 10 and later. // Adding an action button to send clipboard manually in Android 10 and later.
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P &&
Intent sendClipboard = new Intent(this, ClipboardFloatingActivity.class); ContextCompat.checkSelfPermission(this, Manifest.permission.READ_LOGS) == PackageManager.PERMISSION_DENIED) {
sendClipboard.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NEW_TASK); Intent sendClipboard = ClipboardFloatingActivity.getIntent(this, true);
sendClipboard.putExtra("connectedDeviceIds", connectedDeviceIds);
PendingIntent sendPendingClipboard = PendingIntent.getActivity(this, 3, sendClipboard, PendingIntent.FLAG_UPDATE_CURRENT); PendingIntent sendPendingClipboard = PendingIntent.getActivity(this, 3, sendClipboard, PendingIntent.FLAG_UPDATE_CURRENT);
notification.addAction(0, getString(R.string.foreground_notification_send_clipboard), sendPendingClipboard); notification.addAction(0, getString(R.string.foreground_notification_send_clipboard), sendPendingClipboard);
} }

View File

@ -1,5 +1,6 @@
/* /*
* SPDX-FileCopyrightText: 2020 Anjani Kumar <anjanik012@gmail.com> * SPDX-FileCopyrightText: 2020 Anjani Kumar <anjanik012@gmail.com>
* SPDX-FileCopyrightText: 2021 Ilmaz Gumerov <ilmaz1309@gmail.com>
* *
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL * 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.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.Toast; import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity; 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 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. 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. 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 https://developer.android.com/reference/android/Manifest.permission#READ_LOGS
This permission can be gained by only from the adb by the user. 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/ 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. Currently this activity is bering triggered from a button in Foreground Notification or quick settings tile.
* */ * */
public class ClipboardFloatingActivity extends AppCompatActivity { public class ClipboardFloatingActivity extends AppCompatActivity {
private ArrayList<Device> 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 @Override
public void onWindowFocusChanged(boolean hasFocus) { public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus); super.onWindowFocusChanged(hasFocus);
if (hasFocus) { if (hasFocus) {
// We are now sure that clipboard can be accessed from here. // We are now sure that clipboard can be accessed from here.
ClipboardManager clipboardManager = ContextCompat.getSystemService(this, ClipboardListener.instance(this).onClipboardChanged();
ClipboardManager.class); if (shouldShowToast()) {
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);
}
}
Toast.makeText(this, R.string.pref_plugin_clipboard_sent, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.pref_plugin_clipboard_sent, Toast.LENGTH_SHORT).show();
} }
finish(); finish();
@ -70,12 +73,10 @@ public class ClipboardFloatingActivity extends AppCompatActivity {
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
getWindow().setAttributes(wlp); getWindow().setAttributes(wlp);
ArrayList<String> connectedDeviceIds = getIntent().getStringArrayListExtra("connectedDeviceIds"); }
if (connectedDeviceIds != null) {
for (String deviceId : connectedDeviceIds) { private boolean shouldShowToast() {
connectedDevices.add(BackgroundService.getInstance().getDevice(deviceId)); return getIntent().getBooleanExtra(KEY_SHOW_TOAST, false);
}
}
} }
} }

View File

@ -1,22 +1,32 @@
/* /*
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com> * SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
* SPDX-FileCopyrightText: 2021 Ilmaz Gumerov <ilmaz1309@gmail.com>
* *
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
*/ */
package org.kde.kdeconnect.Plugins.ClibpoardPlugin; package org.kde.kdeconnect.Plugins.ClibpoardPlugin;
import android.Manifest;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.content.ClipData; import android.content.ClipData;
import android.content.ClipboardManager; import android.content.ClipboardManager;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import androidx.core.content.ContextCompat; 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.HashSet;
import java.util.Locale;
@TargetApi(Build.VERSION_CODES.HONEYCOMB) @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public class ClipboardListener { public class ClipboardListener {
@ -32,7 +42,6 @@ public class ClipboardListener {
private long updateTimestamp; private long updateTimestamp;
private ClipboardManager cm = null; private ClipboardManager cm = null;
private ClipboardManager.OnPrimaryClipChangedListener listener;
private static ClipboardListener _instance = null; private static ClipboardListener _instance = null;
@ -57,28 +66,51 @@ public class ClipboardListener {
new Handler(Looper.getMainLooper()).post(() -> { new Handler(Looper.getMainLooper()).post(() -> {
cm = ContextCompat.getSystemService(context, ClipboardManager.class); cm = ContextCompat.getSystemService(context, ClipboardManager.class);
listener = () -> { cm.addPrimaryClipChangedListener(this::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
}
};
cm.addPrimaryClipChangedListener(listener);
}); });
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() { public String getCurrentContent() {