Extend offline URL sharing behavior

## Summary

Previously, URLs shared to offline targets via the ShareActivity were
delivered to them once they became online. This change extends this
behavior to direct share targets.

## Test Plan

Test 1

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC unreachable, e.g., close the KDE app on it.
* On the phone, open Chrome and share a couple of webpages to KDE's PC's direct share target.
* Open the KDE app on the PC.
* Observe that the webpages are opened on PC.

Test 2

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC unreachable, e.g., close the KDE app on it.
* On the phone, share a file to KDE's PC's direct share target.
* Open the KDE app on the PC.
* Observe that the file is sent to PC.

Test 3

* Make sure two PC devices are already paired with the phone. Let's say the name of these devices are "PC1" and "PC2".
* Make PC1 and PC2 unreachable, e.g., close the KDE app on them.
* On the phone, open Chrome and share a couple of webpages to KDE's PC1's direct share target.
* Open the KDE app on the PC2.
* Observe that the webpages are NOT opened on PC2.
* Open the KDE app on the PC1.
* Observe that the webpages are opened on PC1.

Test 4

* Make sure a PC device is already paired with the phone. Let's say the name of this device is "PC".
* Make PC reachable, e.g., have the KDE app open on it.
* Try to share a URL from the phone to the KDE's PC's direct share target.
* Observe that the URL opens instantly on the PC.
This commit is contained in:
Vala Zadeh
2025-04-13 00:20:04 +00:00
committed by Albert Vaca Cintora
parent 170bb5e717
commit 63a849b80a
3 changed files with 56 additions and 25 deletions

View File

@@ -264,6 +264,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<string name="mpris_notification_key" translatable="false">mpris_notification_enabled</string>
<string name="share_to">Share to…</string>
<string name="unreachable_device">%s (Unreachable)</string>
<string name="unreachable_device_dynamic_shortcut">%s (✕)</string>
<string name="unreachable_device_url_share_text">URLs shared to an unreachable device will be delivered to it once it becomes reachable.\n\n</string>
<string name="protocol_version">Protocol version:</string>
<string name="protocol_version_newer">This device uses a newer protocol version</string>

View File

@@ -124,7 +124,7 @@ public class ShareActivity extends BaseActivity<ActivityShareBinding> {
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(device.getDeviceId(), SharePlugin.class);
if (intentHasUrl && !device.isReachable()) {
// Store the URL to be delivered once device becomes online
storeUrlForFutureDelivery(device, intent.toUri(0));
storeUrlForFutureDelivery(device, intent.getStringExtra(Intent.EXTRA_TEXT));
} else if (plugin != null) {
plugin.share(intent);
}
@@ -187,6 +187,15 @@ public class ShareActivity extends BaseActivity<ActivityShareBinding> {
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class);
if (plugin != null) {
plugin.share(intent);
} else {
Bundle extras = intent.getExtras();
if (extras != null && extras.containsKey(Intent.EXTRA_TEXT)) {
final Device device = KdeConnect.getInstance().getDevice(deviceId);
if (doesIntentContainUrl(intent) && device != null && !device.isReachable()) {
final String text = extras.getString(Intent.EXTRA_TEXT);
storeUrlForFutureDelivery(device, text);
}
}
}
finish();
} else {

View File

@@ -22,6 +22,7 @@ import android.widget.Toast;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.core.content.ContextCompat;
import androidx.core.content.LocusIdCompat;
@@ -90,23 +91,7 @@ public class SharePlugin extends Plugin {
public boolean onCreate() {
super.onCreate();
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
Intent shortcutIntent = new Intent(context, MainActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, device.getDeviceId());
IconCompat icon = IconCompat.createWithResource(context, device.getDeviceType().toShortcutDrawableId());
ShortcutInfoCompat shortcut = new ShortcutInfoCompat
.Builder(context, device.getDeviceId())
.setIntent(shortcutIntent)
.setIcon(icon)
.setShortLabel(device.getName())
.setCategories(Set.of("org.kde.kdeconnect.category.SHARE_TARGET"))
.setLocusId(new LocusIdCompat(device.getDeviceId()))
.build();
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
createOrUpdateDynamicShortcut(null);
// Deliver URLs previously shared to this device now that it's connected
deliverPreviouslySentIntents();
return true;
@@ -114,10 +99,49 @@ public class SharePlugin extends Plugin {
@Override
public void onDestroy() {
ShortcutManagerCompat.removeLongLivedShortcuts(context, List.of(device.getDeviceId()));
for (ShortcutInfoCompat shortcut : ShortcutManagerCompat.getDynamicShortcuts(context)) {
if (!shortcut.getId().equals(device.getDeviceId())) continue;
if (!device.isReachable() && shortcut.isPinned()) {
// Create an updated shortcut with the same ID
createOrUpdateDynamicShortcut(shortcut);
break;
} else {
ShortcutManagerCompat.removeLongLivedShortcuts(context, List.of(shortcut.getId()));
}
}
super.onDestroy();
}
private void createOrUpdateDynamicShortcut(@Nullable ShortcutInfoCompat shortcutToUpdate) {
final boolean isNewShortcut = shortcutToUpdate == null;
IconCompat icon = IconCompat.createWithResource(
context, device.getDeviceType().toShortcutDrawableId());
Intent shortcutIntent = null;
if (isNewShortcut) {
shortcutIntent = new Intent(context, MainActivity.class);
shortcutIntent.setAction(Intent.ACTION_VIEW);
shortcutIntent.putExtra(MainActivity.EXTRA_DEVICE_ID, device.getDeviceId());
}
ShortcutInfoCompat shortcut = new ShortcutInfoCompat
.Builder(context, device.getDeviceId())
.setIntent(isNewShortcut ? shortcutIntent : shortcutToUpdate.getIntent())
.setIcon(icon)
.setShortLabel(isNewShortcut ? device.getName()
: context.getString(
R.string.unreachable_device_dynamic_shortcut,
shortcutToUpdate.getShortLabel()))
.setCategories(isNewShortcut ? Set.of("org.kde.kdeconnect.category.SHARE_TARGET")
: shortcutToUpdate.getCategories())
.setLocusId(isNewShortcut ? new LocusIdCompat(device.getDeviceId())
: shortcutToUpdate.getLocusId())
.build();
if (isNewShortcut) {
ShortcutManagerCompat.pushDynamicShortcut(context, shortcut);
} else {
ShortcutManagerCompat.updateShortcuts(context, List.of(shortcut));
}
}
private void deliverPreviouslySentIntents() {
Set<String> currentUrlSet = mSharedPrefs.getStringSet(KEY_UNREACHABLE_URL_LIST + device.getDeviceId(), null);
if (currentUrlSet != null) {
@@ -125,6 +149,7 @@ public class SharePlugin extends Plugin {
Intent intent;
try {
intent = Intent.parseUri(url, 0);
intent.putExtra(Intent.EXTRA_TEXT, url);
} catch (URISyntaxException ex) {
Log.e("SharePlugin", "Malformed URI");
continue;
@@ -316,12 +341,8 @@ public class SharePlugin extends Plugin {
isUrl = false;
}
NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST);
if (isUrl) {
np.set("url", text);
} else {
np.set("text", text);
}
getDevice().sendPacket(np);
np.set(isUrl ? "url" : "text", text);
device.sendPacket(np);
} else {
Log.e("SharePlugin", "There's nothing we know how to share");
}