mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
Add support for Direct Share targets
As described in https://developer.android.com/training/sharing/direct-share-targets. This makes connected devices with `SharePlugin` enabled show up in Android's Sharesheet and can be directly shared to.
This commit is contained in:
parent
f344586fb6
commit
96ecd620cf
@ -120,6 +120,9 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<meta-data android:name="android.app.shortcuts"
|
||||||
|
android:resource="@xml/shortcuts" />
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
|
android:name="org.kde.kdeconnect.UserInterface.PluginSettingsActivity"
|
||||||
|
13
res/drawable/ic_device_desktop_shortcut.xml
Normal file
13
res/drawable/ic_device_desktop_shortcut.xml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group android:pivotX="12" android:pivotY="12" android:scaleX="0.66" android:scaleY="0.66">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,2L3,2c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h7v2L8,20v2h8v-2h-2v-2h7c1.1,0 2,-0.9 2,-2L23,4c0,-1.1 -0.9,-2 -2,-2zM21,16L3,16L3,4h18v12z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
17
res/drawable/ic_device_laptop_shortcut.xml
Normal file
17
res/drawable/ic_device_laptop_shortcut.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12"
|
||||||
|
android:scaleX="0.66"
|
||||||
|
android:scaleY="0.66">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,18c1.1,0 2,-0.9 2,-2V6c0,-1.1 -0.9,-2 -2,-2H4C2.9,4 2,4.9 2,6v10c0,1.1 0.9,2 2,2H0v2h24v-2H20zM4,6h16v10H4V6z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
18
res/drawable/ic_device_phone_shortcut.xml
Normal file
18
res/drawable/ic_device_phone_shortcut.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12"
|
||||||
|
android:scaleX="0.66"
|
||||||
|
android:scaleY="0.66">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M16,1L8,1C6.34,1 5,2.34 5,4v16c0,1.66 1.34,3 3,3h8c1.66,0 3,-1.34 3,-3L19,4c0,-1.66 -1.34,-3 -3,-3zM14,21h-4v-1h4v1zM17.25,18L6.75,18L6.75,4h10.5v14z" />
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</vector>
|
18
res/drawable/ic_device_tablet_shortcut.xml
Normal file
18
res/drawable/ic_device_tablet_shortcut.xml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12"
|
||||||
|
android:scaleX="0.66"
|
||||||
|
android:scaleY="0.66">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,4L3,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h18c1.1,0 1.99,-0.9 1.99,-2L23,6c0,-1.1 -0.9,-2 -2,-2zM19,18L5,18L5,6h14v12z" />
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</vector>
|
17
res/drawable/ic_device_tv_shortcut.xml
Normal file
17
res/drawable/ic_device_tv_shortcut.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:tint="?attr/colorControlNormal"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<group
|
||||||
|
android:pivotX="12"
|
||||||
|
android:pivotY="12"
|
||||||
|
android:scaleX="0.66"
|
||||||
|
android:scaleY="0.66">
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M21,3L3,3c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h5v2h8v-2h5c1.1,0 1.99,-0.9 1.99,-2L23,5c0,-1.1 -0.9,-2 -2,-2zM21,17L3,17L3,5h18v12z" />
|
||||||
|
</group>
|
||||||
|
</vector>
|
7
res/xml/shortcuts.xml
Normal file
7
res/xml/shortcuts.xml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<share-target android:targetClass="org.kde.kdeconnect.Plugins.SharePlugin.ShareActivity">
|
||||||
|
<data android:mimeType="*/*" />
|
||||||
|
<category android:name="org.kde.kdeconnect.category.SHARE_TARGET" />
|
||||||
|
</share-target>
|
||||||
|
</shortcuts>
|
@ -22,13 +22,13 @@ import java.security.cert.CertificateException
|
|||||||
* DeviceInfo contains all the properties needed to instantiate a Device.
|
* DeviceInfo contains all the properties needed to instantiate a Device.
|
||||||
*/
|
*/
|
||||||
class DeviceInfo(
|
class DeviceInfo(
|
||||||
@JvmField val id : String,
|
@JvmField val id: String,
|
||||||
@JvmField val certificate : Certificate,
|
@JvmField val certificate: Certificate,
|
||||||
@JvmField var name : String,
|
@JvmField var name: String,
|
||||||
@JvmField var type : DeviceType,
|
@JvmField var type: DeviceType,
|
||||||
@JvmField var protocolVersion : Int = 0,
|
@JvmField var protocolVersion: Int = 0,
|
||||||
@JvmField var incomingCapabilities : Set<String>? = null,
|
@JvmField var incomingCapabilities: Set<String>? = null,
|
||||||
@JvmField var outgoingCapabilities : Set<String>? = null,
|
@JvmField var outgoingCapabilities: Set<String>? = null,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,7 +40,7 @@ class DeviceInfo(
|
|||||||
try {
|
try {
|
||||||
val encodedCertificate = Base64.encodeToString(certificate.encoded, 0)
|
val encodedCertificate = Base64.encodeToString(certificate.encoded, 0)
|
||||||
|
|
||||||
with (settings.edit()) {
|
with(settings.edit()) {
|
||||||
putString("certificate", encodedCertificate)
|
putString("certificate", encodedCertificate)
|
||||||
putString("deviceName", name)
|
putString("deviceName", name)
|
||||||
putString("deviceType", type.toString())
|
putString("deviceType", type.toString())
|
||||||
@ -73,7 +73,7 @@ class DeviceInfo(
|
|||||||
*/
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@Throws(CertificateException::class)
|
@Throws(CertificateException::class)
|
||||||
fun loadFromSettings(context : Context, deviceId: String, settings: SharedPreferences) =
|
fun loadFromSettings(context: Context, deviceId: String, settings: SharedPreferences) =
|
||||||
with(settings) {
|
with(settings) {
|
||||||
DeviceInfo(
|
DeviceInfo(
|
||||||
id = deviceId,
|
id = deviceId,
|
||||||
@ -104,8 +104,8 @@ class DeviceInfo(
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isValidIdentityPacket(identityPacket: NetworkPacket): Boolean = with(identityPacket) {
|
fun isValidIdentityPacket(identityPacket: NetworkPacket): Boolean = with(identityPacket) {
|
||||||
type == NetworkPacket.PACKET_TYPE_IDENTITY &&
|
type == NetworkPacket.PACKET_TYPE_IDENTITY &&
|
||||||
DeviceHelper.filterName(getString("deviceName", "")).isNotBlank() &&
|
DeviceHelper.filterName(getString("deviceName", "")).isNotBlank() &&
|
||||||
getString("deviceId", "").isNotBlank()
|
getString("deviceId", "").isNotBlank()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,7 +126,7 @@ enum class DeviceType {
|
|||||||
ContextCompat.getDrawable(context, toDrawableId())!!
|
ContextCompat.getDrawable(context, toDrawableId())!!
|
||||||
|
|
||||||
@DrawableRes
|
@DrawableRes
|
||||||
private fun toDrawableId() =
|
fun toDrawableId() =
|
||||||
when (this) {
|
when (this) {
|
||||||
PHONE -> R.drawable.ic_device_phone_32dp
|
PHONE -> R.drawable.ic_device_phone_32dp
|
||||||
TABLET -> R.drawable.ic_device_tablet_32dp
|
TABLET -> R.drawable.ic_device_tablet_32dp
|
||||||
@ -135,6 +135,15 @@ enum class DeviceType {
|
|||||||
else -> R.drawable.ic_device_desktop_32dp
|
else -> R.drawable.ic_device_desktop_32dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toShortcutDrawableId() =
|
||||||
|
when (this) {
|
||||||
|
PHONE -> R.drawable.ic_device_phone_shortcut
|
||||||
|
TABLET -> R.drawable.ic_device_tablet_shortcut
|
||||||
|
TV -> R.drawable.ic_device_tv_shortcut
|
||||||
|
LAPTOP -> R.drawable.ic_device_laptop_shortcut
|
||||||
|
else -> R.drawable.ic_device_desktop_shortcut
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun fromString(s: String) =
|
fun fromString(s: String) =
|
||||||
|
@ -8,6 +8,7 @@ package org.kde.kdeconnect.Plugins.SharePlugin;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -160,7 +161,10 @@ public class ShareActivity extends AppCompatActivity {
|
|||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final String deviceId = intent.getStringExtra("deviceId");
|
String deviceId = intent.getStringExtra("deviceId");
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && deviceId == null) {
|
||||||
|
deviceId = intent.getStringExtra(Intent.EXTRA_SHORTCUT_ID);
|
||||||
|
}
|
||||||
|
|
||||||
if (deviceId != null) {
|
if (deviceId != null) {
|
||||||
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class);
|
SharePlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId, SharePlugin.class);
|
||||||
|
@ -24,6 +24,10 @@ import androidx.annotation.DrawableRes;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.core.content.LocusIdCompat;
|
||||||
|
import androidx.core.content.pm.ShortcutInfoCompat;
|
||||||
|
import androidx.core.content.pm.ShortcutManagerCompat;
|
||||||
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
import androidx.preference.PreferenceManager;
|
import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
@ -33,6 +37,7 @@ import org.kde.kdeconnect.Helpers.IntentHelper;
|
|||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
|
import org.kde.kdeconnect.UserInterface.MainActivity;
|
||||||
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
||||||
import org.kde.kdeconnect.async.BackgroundJob;
|
import org.kde.kdeconnect.async.BackgroundJob;
|
||||||
import org.kde.kdeconnect.async.BackgroundJobHandler;
|
import org.kde.kdeconnect.async.BackgroundJobHandler;
|
||||||
@ -43,6 +48,7 @@ import java.net.URISyntaxException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,11 +90,34 @@ public class SharePlugin extends Plugin {
|
|||||||
public boolean onCreate() {
|
public boolean onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
mSharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
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);
|
||||||
|
|
||||||
// Deliver URLs previously shared to this device now that it's connected
|
// Deliver URLs previously shared to this device now that it's connected
|
||||||
deliverPreviouslySentIntents();
|
deliverPreviouslySentIntents();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
ShortcutManagerCompat.removeLongLivedShortcuts(context, List.of(device.getDeviceId()));
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
private void deliverPreviouslySentIntents() {
|
private void deliverPreviouslySentIntents() {
|
||||||
Set<String> currentUrlSet = mSharedPrefs.getStringSet(KEY_UNREACHABLE_URL_LIST + device.getDeviceId(), null);
|
Set<String> currentUrlSet = mSharedPrefs.getStringSet(KEY_UNREACHABLE_URL_LIST + device.getDeviceId(), null);
|
||||||
if (currentUrlSet != null) {
|
if (currentUrlSet != null) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user