File indexing completed on 2024-12-22 04:41:39
0001 /* 0002 * SPDX-FileCopyrightText: 2021 Daniel Weigl <DanielWeigl@gmx.at> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 package org.kde.kdeconnect.Plugins.MousePadPlugin; 0008 0009 import android.content.Intent; 0010 import android.content.SharedPreferences; 0011 import android.os.Build; 0012 import android.os.Bundle; 0013 import android.preference.PreferenceManager; 0014 import android.widget.Toast; 0015 0016 import androidx.annotation.RequiresApi; 0017 import androidx.appcompat.app.AppCompatActivity; 0018 0019 import org.kde.kdeconnect.BackgroundService; 0020 import org.kde.kdeconnect.Device; 0021 import org.kde.kdeconnect.Helpers.SafeTextChecker; 0022 import org.kde.kdeconnect.KdeConnect; 0023 import org.kde.kdeconnect.NetworkPacket; 0024 import org.kde.kdeconnect.UserInterface.List.EntryItemWithIcon; 0025 import org.kde.kdeconnect.UserInterface.List.ListAdapter; 0026 import org.kde.kdeconnect.UserInterface.List.SectionItem; 0027 import org.kde.kdeconnect_tp.R; 0028 import org.kde.kdeconnect_tp.databinding.ActivitySendkeystrokesBinding; 0029 0030 import java.util.ArrayList; 0031 import java.util.Collection; 0032 import java.util.List; 0033 import java.util.Objects; 0034 import java.util.stream.Collectors; 0035 0036 public class SendKeystrokesToHostActivity extends AppCompatActivity { 0037 0038 // text with these length and content can be send without user confirmation. 0039 // more or less chosen arbitrarily, so that we allow short PINS and TANS without interruption (if only one device is connected) 0040 // but also be on the safe side, so that apps cant send any harmful content 0041 public static final int MAX_SAFE_LENGTH = 8; 0042 public static final String SAFE_CHARS = "1234567890"; 0043 0044 0045 private ActivitySendkeystrokesBinding binding; 0046 private boolean contentIsOkay; 0047 0048 @Override 0049 protected void onCreate(Bundle savedInstanceState) { 0050 super.onCreate(savedInstanceState); 0051 0052 binding = ActivitySendkeystrokesBinding.inflate(getLayoutInflater()); 0053 setContentView(binding.getRoot()); 0054 0055 setSupportActionBar(binding.toolbarLayout.toolbar); 0056 Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); 0057 getSupportActionBar().setDisplayShowHomeEnabled(true); 0058 } 0059 0060 0061 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1) // needed for this.getReferrer() 0062 @Override 0063 protected void onStart() { 0064 super.onStart(); 0065 0066 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); 0067 if (!prefs.getBoolean(getString(R.string.pref_sendkeystrokes_enabled), true)) { 0068 Toast.makeText(getApplicationContext(), R.string.sendkeystrokes_disabled_toast, Toast.LENGTH_LONG).show(); 0069 finish(); 0070 } else { 0071 final Intent intent = getIntent(); 0072 String type = intent.getType(); 0073 0074 if ("text/x-keystrokes".equals(type)) { 0075 String toSend = intent.getStringExtra(Intent.EXTRA_TEXT); 0076 binding.textToSend.setText(toSend); 0077 0078 // if the preference send_safe_text_immediately is true, we will check if exactly one 0079 // device is connected and send the text to it without user confirmation, to make sending of 0080 // short and safe text like PINs/TANs very fluent 0081 // 0082 // (contentIsOkay gets used in updateDeviceList again) 0083 if (prefs.getBoolean(getString(R.string.pref_send_safe_text_immediately), true)) { 0084 SafeTextChecker safeTextChecker = new SafeTextChecker(SAFE_CHARS, MAX_SAFE_LENGTH); 0085 contentIsOkay = safeTextChecker.isSafe(toSend); 0086 } else { 0087 contentIsOkay = false; 0088 } 0089 0090 // If we trust the sending app, check if there is only one device paired / reachable... 0091 if (contentIsOkay) { 0092 List<Device> reachableDevices = KdeConnect.getInstance().getDevices().values().stream() 0093 .filter(Device::isReachable) 0094 .limit(2) // we only need the first two; if its more than one, we need to show the user the device-selection 0095 .collect(Collectors.toList()); 0096 0097 // if its exactly one just send the text to it 0098 if (reachableDevices.size() == 1) { 0099 // send the text and close this activity 0100 sendKeys(reachableDevices.get(0)); 0101 this.finish(); 0102 return; 0103 } 0104 } 0105 0106 KdeConnect.getInstance().addDeviceListChangedCallback("SendKeystrokesToHostActivity", () -> runOnUiThread(this::updateDeviceList)); 0107 BackgroundService.ForceRefreshConnections(this); // force a network re-discover 0108 updateDeviceList(); 0109 } else { 0110 Toast.makeText(getApplicationContext(), R.string.sendkeystrokes_wrong_data, Toast.LENGTH_LONG).show(); 0111 finish(); 0112 } 0113 } 0114 } 0115 0116 @Override 0117 protected void onStop() { 0118 KdeConnect.getInstance().removeDeviceListChangedCallback("SendKeystrokesToHostActivity"); 0119 super.onStop(); 0120 } 0121 0122 private void sendKeys(Device deviceId) { 0123 String toSend; 0124 if (binding.textToSend.getText() != null && (toSend = binding.textToSend.getText().toString().trim()).length() > 0) { 0125 final NetworkPacket np = new NetworkPacket(MousePadPlugin.PACKET_TYPE_MOUSEPAD_REQUEST); 0126 np.set("key", toSend); 0127 MousePadPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId.getDeviceId(), MousePadPlugin.class); 0128 if (plugin == null) { 0129 finish(); 0130 return; 0131 } 0132 plugin.sendKeyboardPacket(np); 0133 Toast.makeText( 0134 getApplicationContext(), 0135 getString(R.string.sendkeystrokes_sent_text, toSend, deviceId.getName()), 0136 Toast.LENGTH_SHORT 0137 ).show(); 0138 0139 } 0140 } 0141 0142 0143 private void updateDeviceList() { 0144 Collection<Device> devices = KdeConnect.getInstance().getDevices().values(); 0145 final ArrayList<Device> devicesList = new ArrayList<>(); 0146 final ArrayList<ListAdapter.Item> items = new ArrayList<>(); 0147 0148 SectionItem section = new SectionItem(getString(R.string.sendkeystrokes_send_to)); 0149 items.add(section); 0150 0151 for (Device d : devices) { 0152 if (d.isReachable() && d.isPaired()) { 0153 devicesList.add(d); 0154 items.add(new EntryItemWithIcon(d.getName(), d.getIcon())); 0155 section.isEmpty = false; 0156 } 0157 } 0158 0159 binding.devicesList.setAdapter(new ListAdapter(SendKeystrokesToHostActivity.this, items)); 0160 binding.devicesList.setOnItemClickListener((adapterView, view, i, l) -> { 0161 Device device = devicesList.get(i - 1); // NOTE: -1 because of the title! 0162 sendKeys(device); 0163 this.finish(); // close the activity 0164 }); 0165 0166 // only one device is connected and we trust the text to send -> send it and close the activity. 0167 // Usually we already check it in `onStart` - but if the BackgroundService was not started/connected to the host 0168 // it will not have the deviceList in memory. Use this callback as second chance (but it will flicker a bit, because the activity might 0169 // already been visible and get closed again quickly) 0170 if (devicesList.size() == 1 && contentIsOkay) { 0171 Device device = devicesList.get(0); 0172 sendKeys(device); 0173 this.finish(); // close the activity 0174 } 0175 } 0176 } 0177