mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Direct Share
Summary: Support Android 6 direct share. BUG:362266 BackgroundService is a SIngleton now. This should only be used if strictly necessary. Test Plan: Pick a random app with a share button, look for device entries in Sharing options (might not be there because other sharing options have higher priority) Reviewers: #kde_connect, albertvaka Reviewed By: #kde_connect, albertvaka Subscribers: albertvaka, #kde_connect Tags: #kde_connect Differential Revision: https://phabricator.kde.org/D6743
This commit is contained in:
parent
b68c159275
commit
bea90521c5
3
.arcconfig
Normal file
3
.arcconfig
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"phabricator.uri" : "https://phabricator.kde.org/project/profile/159/"
|
||||||
|
}
|
@ -35,8 +35,8 @@
|
|||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@drawable/icon"
|
android:icon="@drawable/icon"
|
||||||
android:supportsRtl="true"
|
|
||||||
android:label="KDE Connect"
|
android:label="KDE Connect"
|
||||||
|
android:supportsRtl="true"
|
||||||
android:theme="@style/KdeConnectTheme"
|
android:theme="@style/KdeConnectTheme"
|
||||||
>
|
>
|
||||||
|
|
||||||
@ -140,9 +140,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity"
|
android:name="org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhoneActivity"
|
||||||
android:label="@string/findmyphone_title"
|
|
||||||
android:configChanges="orientation|screenSize"
|
android:configChanges="orientation|screenSize"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
|
android:label="@string/findmyphone_title"
|
||||||
android:launchMode="singleInstance">
|
android:launchMode="singleInstance">
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
@ -194,6 +194,9 @@
|
|||||||
|
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<meta-data
|
||||||
|
android:name="android.service.chooser.chooser_target_service"
|
||||||
|
android:value="org.kde.kdeconnect.Plugins.SharePlugin.ShareChooserTargetService" />
|
||||||
</activity>
|
</activity>
|
||||||
<service
|
<service
|
||||||
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver"
|
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver"
|
||||||
@ -202,6 +205,13 @@
|
|||||||
<action android:name="android.service.notification.NotificationListenerService" />
|
<action android:name="android.service.notification.NotificationListenerService" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
<service
|
||||||
|
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareChooserTargetService"
|
||||||
|
android:permission="android.permission.BIND_CHOOSER_TARGET_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.service.chooser.ChooserTargetService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationFilterActivity"
|
android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationFilterActivity"
|
||||||
|
@ -46,6 +46,8 @@ import java.util.concurrent.locks.ReentrantLock;
|
|||||||
|
|
||||||
public class BackgroundService extends Service {
|
public class BackgroundService extends Service {
|
||||||
|
|
||||||
|
private static BackgroundService instance;
|
||||||
|
|
||||||
public interface DeviceListChangedCallback {
|
public interface DeviceListChangedCallback {
|
||||||
void onDeviceListChanged();
|
void onDeviceListChanged();
|
||||||
}
|
}
|
||||||
@ -58,6 +60,10 @@ public class BackgroundService extends Service {
|
|||||||
|
|
||||||
private final HashSet<Object> discoveryModeAcquisitions = new HashSet<>();
|
private final HashSet<Object> discoveryModeAcquisitions = new HashSet<>();
|
||||||
|
|
||||||
|
public static BackgroundService getInstance(){
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean acquireDiscoveryMode(Object key) {
|
public boolean acquireDiscoveryMode(Object key) {
|
||||||
boolean wasEmpty = discoveryModeAcquisitions.isEmpty();
|
boolean wasEmpty = discoveryModeAcquisitions.isEmpty();
|
||||||
discoveryModeAcquisitions.add(key);
|
discoveryModeAcquisitions.add(key);
|
||||||
@ -250,6 +256,8 @@ public class BackgroundService extends Service {
|
|||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
|
||||||
|
instance = this;
|
||||||
|
|
||||||
// Register screen on listener
|
// Register screen on listener
|
||||||
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
|
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);
|
||||||
registerReceiver(new KdeConnectBroadcastReceiver(), filter);
|
registerReceiver(new KdeConnectBroadcastReceiver(), filter);
|
||||||
|
@ -33,6 +33,8 @@ import android.view.MenuItem;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import org.kde.kdeconnect.BackgroundService;
|
import org.kde.kdeconnect.BackgroundService;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
import org.kde.kdeconnect.NetworkPackage;
|
import org.kde.kdeconnect.NetworkPackage;
|
||||||
@ -130,57 +132,7 @@ public class ShareActivity extends AppCompatActivity {
|
|||||||
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
|
||||||
|
|
||||||
Device device = devicesList.get(i - 1); //NOTE: -1 because of the title!
|
Device device = devicesList.get(i - 1); //NOTE: -1 because of the title!
|
||||||
|
SharePlugin.share(intent, device);
|
||||||
Bundle extras = intent.getExtras();
|
|
||||||
if (extras != null) {
|
|
||||||
if (extras.containsKey(Intent.EXTRA_STREAM)) {
|
|
||||||
|
|
||||||
try {
|
|
||||||
|
|
||||||
ArrayList<Uri> uriList;
|
|
||||||
if (!Intent.ACTION_SEND.equals(intent.getAction())) {
|
|
||||||
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
|
||||||
} else {
|
|
||||||
Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
|
|
||||||
uriList = new ArrayList<>();
|
|
||||||
uriList.add(uri);
|
|
||||||
}
|
|
||||||
|
|
||||||
SharePlugin.queuedSendUriList(getApplicationContext(), device, uriList);
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("ShareActivity", "Exception");
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
|
|
||||||
String text = extras.getString(Intent.EXTRA_TEXT);
|
|
||||||
String subject = extras.getString(Intent.EXTRA_SUBJECT);
|
|
||||||
|
|
||||||
//Hack: Detect shared youtube videos, so we can open them in the browser instead of as text
|
|
||||||
if (subject != null && subject.endsWith("YouTube")) {
|
|
||||||
int index = text.indexOf(": http://youtu.be/");
|
|
||||||
if (index > 0) {
|
|
||||||
text = text.substring(index + 2); //Skip ": "
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean isUrl;
|
|
||||||
try {
|
|
||||||
new URL(text);
|
|
||||||
isUrl = true;
|
|
||||||
} catch (Exception e) {
|
|
||||||
isUrl = false;
|
|
||||||
}
|
|
||||||
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE_REQUEST);
|
|
||||||
if (isUrl) {
|
|
||||||
np.set("url", text);
|
|
||||||
} else {
|
|
||||||
np.set("text", text);
|
|
||||||
}
|
|
||||||
device.sendPackage(np);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -215,22 +167,44 @@ public class ShareActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
BackgroundService.addGuiInUseCounter(this);
|
|
||||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
final Intent intent = getIntent();
|
||||||
@Override
|
final String deviceId = intent.getStringExtra("deviceId");
|
||||||
public void onServiceStart(BackgroundService service) {
|
|
||||||
service.onNetworkChange();
|
if (deviceId!=null) {
|
||||||
service.addDeviceListChangedCallback("ShareActivity", new BackgroundService.DeviceListChangedCallback() {
|
|
||||||
@Override
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback(){
|
||||||
public void onDeviceListChanged() {
|
|
||||||
updateComputerList();
|
@Override
|
||||||
|
public void onServiceStart(BackgroundService service) {
|
||||||
|
Log.d("DirectShare", "sharing to "+service.getDevice(deviceId).getName());
|
||||||
|
Device device = service.getDevice(deviceId);
|
||||||
|
if (device.isReachable() && device.isPaired()) {
|
||||||
|
SharePlugin.share(intent, device);
|
||||||
}
|
}
|
||||||
});
|
finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
updateComputerList();
|
} else {
|
||||||
|
|
||||||
|
BackgroundService.addGuiInUseCounter(this);
|
||||||
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||||
|
@Override
|
||||||
|
public void onServiceStart(BackgroundService service) {
|
||||||
|
service.onNetworkChange();
|
||||||
|
service.addDeviceListChangedCallback("ShareActivity", new BackgroundService.DeviceListChangedCallback() {
|
||||||
|
@Override
|
||||||
|
public void onDeviceListChanged() {
|
||||||
|
updateComputerList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
updateComputerList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2017 Nicolas Fella <nicolas.fella@gmx.de>
|
||||||
|
*
|
||||||
|
* This program is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU General Public License as
|
||||||
|
* published by the Free Software Foundation; either version 2 of
|
||||||
|
* the License or (at your option) version 3 or any later version
|
||||||
|
* accepted by the membership of KDE e.V. (or its successor approved
|
||||||
|
* by the membership of KDE e.V.), which shall act as a proxy
|
||||||
|
* defined in Section 14 of version 3 of the license.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.content.ComponentName;
|
||||||
|
import android.content.IntentFilter;
|
||||||
|
import android.graphics.drawable.Icon;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.service.chooser.ChooserTarget;
|
||||||
|
import android.service.chooser.ChooserTargetService;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.BackgroundService;
|
||||||
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@TargetApi(23)
|
||||||
|
public class ShareChooserTargetService extends ChooserTargetService {
|
||||||
|
@Override
|
||||||
|
public List<ChooserTarget> onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
|
||||||
|
Log.d("DirectShare", "invoked");
|
||||||
|
final List<ChooserTarget> targets = new ArrayList<>();
|
||||||
|
for(Device d: BackgroundService.getInstance().getDevices().values()){
|
||||||
|
if(d.isReachable() && d.isPaired()) {
|
||||||
|
Log.d("DirectShare", d.getName());
|
||||||
|
final String targetName = d.getName();
|
||||||
|
final Icon targetIcon = Icon.createWithResource(this, R.drawable.icon);
|
||||||
|
final float targetRanking = 1;
|
||||||
|
final ComponentName targetComponentName = new ComponentName(getPackageName(),
|
||||||
|
ShareActivity.class.getCanonicalName());
|
||||||
|
final Bundle targetExtras = new Bundle();
|
||||||
|
targetExtras.putString("deviceId", d.getDeviceId());
|
||||||
|
targets.add(new ChooserTarget(
|
||||||
|
targetName, targetIcon, targetRanking, targetComponentName, targetExtras
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return targets;
|
||||||
|
}
|
||||||
|
}
|
@ -35,6 +35,7 @@ import android.database.Cursor;
|
|||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
@ -56,6 +57,7 @@ import java.io.File;
|
|||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
public class SharePlugin extends Plugin {
|
public class SharePlugin extends Plugin {
|
||||||
@ -399,6 +401,59 @@ public class SharePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void share(Intent intent, Device device){
|
||||||
|
Bundle extras = intent.getExtras();
|
||||||
|
if (extras != null) {
|
||||||
|
if (extras.containsKey(Intent.EXTRA_STREAM)) {
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
ArrayList<Uri> uriList;
|
||||||
|
if (!Intent.ACTION_SEND.equals(intent.getAction())) {
|
||||||
|
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
||||||
|
} else {
|
||||||
|
Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
|
||||||
|
uriList = new ArrayList<>();
|
||||||
|
uriList.add(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
SharePlugin.queuedSendUriList(device.getContext(), device, uriList);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e("ShareActivity", "Exception");
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
|
||||||
|
String text = extras.getString(Intent.EXTRA_TEXT);
|
||||||
|
String subject = extras.getString(Intent.EXTRA_SUBJECT);
|
||||||
|
|
||||||
|
//Hack: Detect shared youtube videos, so we can open them in the browser instead of as text
|
||||||
|
if (subject != null && subject.endsWith("YouTube")) {
|
||||||
|
int index = text.indexOf(": http://youtu.be/");
|
||||||
|
if (index > 0) {
|
||||||
|
text = text.substring(index + 2); //Skip ": "
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isUrl;
|
||||||
|
try {
|
||||||
|
new URL(text);
|
||||||
|
isUrl = true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
isUrl = false;
|
||||||
|
}
|
||||||
|
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE_REQUEST);
|
||||||
|
if (isUrl) {
|
||||||
|
np.set("url", text);
|
||||||
|
} else {
|
||||||
|
np.set("text", text);
|
||||||
|
}
|
||||||
|
device.sendPackage(np);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getSupportedPackageTypes() {
|
public String[] getSupportedPackageTypes() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user