2014-11-16 23:14:06 -08:00
|
|
|
/*
|
2020-08-17 16:17:20 +02:00
|
|
|
* SPDX-FileCopyrightText: 2014 Albert Vaca Cintora <albertvaka@gmail.com>
|
2014-11-16 23:14:06 -08:00
|
|
|
*
|
2020-08-17 16:17:20 +02:00
|
|
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
2018-09-29 20:28:47 +02:00
|
|
|
*/
|
2014-11-16 23:14:06 -08:00
|
|
|
|
2014-06-27 14:44:40 +02:00
|
|
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
|
|
|
|
2017-05-31 15:51:07 +02:00
|
|
|
import android.Manifest;
|
2014-06-27 14:44:40 +02:00
|
|
|
import android.app.Activity;
|
|
|
|
import android.content.ClipboardManager;
|
2023-03-04 12:34:59 +00:00
|
|
|
import android.content.Context;
|
2014-06-27 14:44:40 +02:00
|
|
|
import android.content.Intent;
|
|
|
|
import android.graphics.drawable.Drawable;
|
|
|
|
import android.net.Uri;
|
2023-03-15 22:01:39 +00:00
|
|
|
import android.os.Build;
|
2017-07-29 11:30:26 +02:00
|
|
|
import android.os.Bundle;
|
2018-11-25 17:33:12 +01:00
|
|
|
import android.os.Handler;
|
|
|
|
import android.os.Looper;
|
2014-06-27 14:44:40 +02:00
|
|
|
import android.util.Log;
|
|
|
|
import android.widget.Toast;
|
|
|
|
|
2020-07-07 16:45:02 +05:30
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.WorkerThread;
|
|
|
|
import androidx.core.content.ContextCompat;
|
|
|
|
|
2023-03-15 22:01:39 +00:00
|
|
|
import org.apache.commons.lang3.ArrayUtils;
|
2020-07-09 16:11:22 +05:30
|
|
|
import org.apache.commons.lang3.StringUtils;
|
2019-03-04 23:42:47 +01:00
|
|
|
import org.kde.kdeconnect.Helpers.FilesHelper;
|
2020-04-19 13:26:04 +00:00
|
|
|
import org.kde.kdeconnect.Helpers.IntentHelper;
|
2018-03-04 11:31:37 +01:00
|
|
|
import org.kde.kdeconnect.NetworkPacket;
|
2014-06-27 14:44:40 +02:00
|
|
|
import org.kde.kdeconnect.Plugins.Plugin;
|
2019-02-11 20:04:40 +01:00
|
|
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
2019-01-06 12:26:05 +01:00
|
|
|
import org.kde.kdeconnect.UserInterface.PluginSettingsFragment;
|
2019-03-08 19:07:01 +01:00
|
|
|
import org.kde.kdeconnect.async.BackgroundJob;
|
|
|
|
import org.kde.kdeconnect.async.BackgroundJobHandler;
|
2014-06-27 14:44:40 +02:00
|
|
|
import org.kde.kdeconnect_tp.R;
|
|
|
|
|
2023-04-01 19:49:39 +02:00
|
|
|
import java.net.MalformedURLException;
|
2017-07-29 11:30:26 +02:00
|
|
|
import java.net.URL;
|
2015-09-07 00:09:28 -07:00
|
|
|
import java.util.ArrayList;
|
2023-04-01 19:49:39 +02:00
|
|
|
import java.util.Collections;
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2019-07-21 11:29:14 -04:00
|
|
|
/**
|
|
|
|
* A Plugin for sharing and receiving files and uris.
|
|
|
|
* <p>
|
|
|
|
* All of the associated I/O work is scheduled on background
|
|
|
|
* threads by {@link BackgroundJobHandler}.
|
|
|
|
* </p>
|
|
|
|
*/
|
2019-02-11 20:04:40 +01:00
|
|
|
@PluginFactory.LoadablePlugin
|
2019-01-06 14:06:24 +01:00
|
|
|
public class SharePlugin extends Plugin {
|
2019-03-08 19:07:01 +01:00
|
|
|
final static String ACTION_CANCEL_SHARE = "org.kde.kdeconnect.Plugins.SharePlugin.CancelShare";
|
|
|
|
final static String CANCEL_SHARE_DEVICE_ID_EXTRA = "deviceId";
|
|
|
|
final static String CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA = "backgroundJobId";
|
|
|
|
|
2018-10-26 23:53:58 +02:00
|
|
|
private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
|
2019-04-07 17:54:12 +00:00
|
|
|
final static String PACKET_TYPE_SHARE_REQUEST_UPDATE = "kdeconnect.share.request.update";
|
2016-05-31 17:19:39 +02:00
|
|
|
|
2019-01-06 14:06:24 +01:00
|
|
|
final static String KEY_NUMBER_OF_FILES = "numberOfFiles";
|
|
|
|
final static String KEY_TOTAL_PAYLOAD_SIZE = "totalPayloadSize";
|
|
|
|
|
2023-03-05 22:03:58 +01:00
|
|
|
private final BackgroundJobHandler backgroundJobHandler;
|
2019-01-06 14:06:24 +01:00
|
|
|
private final Handler handler;
|
2019-03-08 19:07:01 +01:00
|
|
|
|
|
|
|
private CompositeReceiveFileJob receiveFileJob;
|
2019-04-07 17:54:12 +00:00
|
|
|
private CompositeUploadFileJob uploadFileJob;
|
2019-03-08 19:07:01 +01:00
|
|
|
private final Callback receiveFileJobCallback;
|
2018-11-25 17:33:12 +01:00
|
|
|
|
|
|
|
public SharePlugin() {
|
2019-03-08 19:07:01 +01:00
|
|
|
backgroundJobHandler = BackgroundJobHandler.newFixedThreadPoolBackgroundJobHander(5);
|
2018-11-25 17:33:12 +01:00
|
|
|
handler = new Handler(Looper.getMainLooper());
|
2019-03-08 19:07:01 +01:00
|
|
|
receiveFileJobCallback = new Callback();
|
2018-11-25 17:33:12 +01:00
|
|
|
}
|
2015-09-11 03:45:31 -07:00
|
|
|
|
2017-07-11 13:50:40 +02:00
|
|
|
@Override
|
2023-03-29 19:01:57 +02:00
|
|
|
protected int getOptionalPermissionExplanation() {
|
|
|
|
return R.string.share_optional_permission_explanation;
|
2017-07-11 13:50:40 +02:00
|
|
|
}
|
|
|
|
|
2014-06-27 14:44:40 +02:00
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String getDisplayName() {
|
2014-06-27 14:44:40 +02:00
|
|
|
return context.getResources().getString(R.string.pref_plugin_sharereceiver);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public Drawable getIcon() {
|
2020-07-08 15:30:19 +05:30
|
|
|
return ContextCompat.getDrawable(context, R.drawable.share_plugin_action_24dp);
|
2014-06-27 14:44:40 +02:00
|
|
|
}
|
|
|
|
|
2014-09-16 15:45:31 +02:00
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String getDescription() {
|
2015-04-12 00:11:30 -07:00
|
|
|
return context.getResources().getString(R.string.pref_plugin_sharereceiver_desc);
|
2014-06-27 14:44:40 +02:00
|
|
|
}
|
|
|
|
|
2015-08-20 00:59:21 -07:00
|
|
|
@Override
|
2023-03-04 12:34:59 +00:00
|
|
|
public boolean hasMainActivity(Context context) {
|
2015-08-20 00:59:21 -07:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String getActionName() {
|
2015-08-20 00:59:21 -07:00
|
|
|
return context.getString(R.string.send_files);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public void startMainActivity(Activity parentActivity) {
|
2015-09-07 00:09:28 -07:00
|
|
|
Intent intent = new Intent(parentActivity, SendFileActivity.class);
|
|
|
|
intent.putExtra("deviceId", device.getDeviceId());
|
|
|
|
parentActivity.startActivity(intent);
|
2015-08-20 00:59:21 -07:00
|
|
|
}
|
|
|
|
|
2014-06-27 14:44:40 +02:00
|
|
|
@Override
|
2015-04-12 00:11:30 -07:00
|
|
|
public boolean hasSettings() {
|
2014-06-27 14:44:40 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2018-11-25 17:33:12 +01:00
|
|
|
@WorkerThread
|
2023-05-26 22:19:21 +02:00
|
|
|
public boolean onPacketReceived(@NonNull NetworkPacket np) {
|
2014-06-27 14:44:40 +02:00
|
|
|
try {
|
2019-01-26 16:59:39 +01:00
|
|
|
if (np.getType().equals(PACKET_TYPE_SHARE_REQUEST_UPDATE)) {
|
2019-03-08 19:07:01 +01:00
|
|
|
if (receiveFileJob != null && receiveFileJob.isRunning()) {
|
|
|
|
receiveFileJob.updateTotals(np.getInt(KEY_NUMBER_OF_FILES), np.getLong(KEY_TOTAL_PAYLOAD_SIZE));
|
2019-01-26 16:59:39 +01:00
|
|
|
} else {
|
|
|
|
Log.d("SharePlugin", "Received update packet but CompositeUploadJob is null or not running");
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2018-12-02 19:33:27 +01:00
|
|
|
if (np.has("filename")) {
|
2023-03-15 22:01:39 +00:00
|
|
|
receiveFile(np);
|
2017-07-11 10:35:53 +02:00
|
|
|
} else if (np.has("text")) {
|
|
|
|
Log.i("SharePlugin", "hasText");
|
|
|
|
receiveText(np);
|
|
|
|
} else if (np.has("url")) {
|
|
|
|
receiveUrl(np);
|
|
|
|
} else {
|
|
|
|
Log.e("SharePlugin", "Error: Nothing attached!");
|
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2017-07-11 10:35:53 +02:00
|
|
|
} catch (Exception e) {
|
2019-04-07 17:54:12 +00:00
|
|
|
Log.e("SharePlugin", "Exception");
|
|
|
|
e.printStackTrace();
|
2017-07-11 10:35:53 +02:00
|
|
|
}
|
2016-12-11 21:03:39 +01:00
|
|
|
|
2017-07-11 10:35:53 +02:00
|
|
|
return true;
|
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
private void receiveUrl(NetworkPacket np) {
|
2017-07-11 10:35:53 +02:00
|
|
|
String url = np.getString("url");
|
|
|
|
|
|
|
|
Log.i("SharePlugin", "hasUrl: " + url);
|
|
|
|
|
|
|
|
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
|
|
|
|
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
|
2023-05-24 19:26:54 +02:00
|
|
|
IntentHelper.startActivityFromBackgroundOrCreateNotification(context, browserIntent, url);
|
2017-07-11 10:35:53 +02:00
|
|
|
}
|
2017-02-20 20:20:06 +01:00
|
|
|
|
2018-03-04 11:31:37 +01:00
|
|
|
private void receiveText(NetworkPacket np) {
|
2017-07-11 10:35:53 +02:00
|
|
|
String text = np.getString("text");
|
2020-07-07 16:45:02 +05:30
|
|
|
ClipboardManager cm = ContextCompat.getSystemService(context, ClipboardManager.class);
|
2018-10-26 22:51:13 +02:00
|
|
|
cm.setText(text);
|
2018-12-07 11:54:00 +01:00
|
|
|
handler.post(() -> Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show());
|
2017-07-11 10:35:53 +02:00
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2018-11-25 17:33:12 +01:00
|
|
|
@WorkerThread
|
2018-03-04 11:31:37 +01:00
|
|
|
private void receiveFile(NetworkPacket np) {
|
2019-03-08 19:07:01 +01:00
|
|
|
CompositeReceiveFileJob job;
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2019-01-06 14:06:24 +01:00
|
|
|
boolean hasNumberOfFiles = np.has(KEY_NUMBER_OF_FILES);
|
2019-10-13 21:33:28 +02:00
|
|
|
boolean isOpen = np.getBoolean("open", false);
|
2015-04-04 13:53:46 -07:00
|
|
|
|
2019-10-13 21:33:28 +02:00
|
|
|
if (hasNumberOfFiles && !isOpen && receiveFileJob != null) {
|
2019-03-08 19:07:01 +01:00
|
|
|
job = receiveFileJob;
|
2018-11-20 21:49:33 +01:00
|
|
|
} else {
|
2019-03-08 19:07:01 +01:00
|
|
|
job = new CompositeReceiveFileJob(device, receiveFileJobCallback);
|
2018-11-25 17:33:12 +01:00
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
|
2019-01-06 14:06:24 +01:00
|
|
|
if (!hasNumberOfFiles) {
|
|
|
|
np.set(KEY_NUMBER_OF_FILES, 1);
|
|
|
|
np.set(KEY_TOTAL_PAYLOAD_SIZE, np.getPayloadSize());
|
2018-12-02 19:33:27 +01:00
|
|
|
}
|
|
|
|
|
2019-03-08 19:07:01 +01:00
|
|
|
job.addNetworkPacket(np);
|
2018-11-20 21:49:33 +01:00
|
|
|
|
2019-03-08 19:07:01 +01:00
|
|
|
if (job != receiveFileJob) {
|
2019-10-13 21:33:28 +02:00
|
|
|
if (hasNumberOfFiles && !isOpen) {
|
2019-03-08 19:07:01 +01:00
|
|
|
receiveFileJob = job;
|
2018-12-02 19:33:27 +01:00
|
|
|
}
|
2019-03-08 19:07:01 +01:00
|
|
|
backgroundJobHandler.runJob(job);
|
2018-12-02 19:33:27 +01:00
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
}
|
|
|
|
|
2016-09-27 20:26:57 +02:00
|
|
|
@Override
|
2019-02-22 20:08:21 +01:00
|
|
|
public PluginSettingsFragment getSettingsFragment(Activity activity) {
|
2022-12-28 19:24:07 +01:00
|
|
|
return ShareSettingsFragment.newInstance(getPluginKey(), R.xml.shareplugin_preferences);
|
2016-09-27 20:26:57 +02:00
|
|
|
}
|
|
|
|
|
2019-04-07 17:54:12 +00:00
|
|
|
void sendUriList(final ArrayList<Uri> uriList) {
|
2019-04-19 23:37:30 +02:00
|
|
|
CompositeUploadFileJob job;
|
2019-04-07 17:54:12 +00:00
|
|
|
|
|
|
|
if (uploadFileJob == null) {
|
|
|
|
job = new CompositeUploadFileJob(device, this.receiveFileJobCallback);
|
|
|
|
} else {
|
|
|
|
job = uploadFileJob;
|
|
|
|
}
|
|
|
|
|
2016-12-08 20:50:37 +01:00
|
|
|
//Read all the data early, as we only have permissions to do it while the activity is alive
|
|
|
|
for (Uri uri : uriList) {
|
2019-03-04 23:42:47 +01:00
|
|
|
NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_SHARE_REQUEST);
|
2018-12-06 16:55:58 +01:00
|
|
|
|
|
|
|
if (np != null) {
|
2019-04-07 17:54:12 +00:00
|
|
|
job.addNetworkPacket(np);
|
2018-12-06 16:55:58 +01:00
|
|
|
}
|
2016-12-08 20:50:37 +01:00
|
|
|
}
|
2016-11-30 16:47:51 +01:00
|
|
|
|
2019-04-07 17:54:12 +00:00
|
|
|
if (job != uploadFileJob) {
|
|
|
|
uploadFileJob = job;
|
|
|
|
backgroundJobHandler.runJob(uploadFileJob);
|
|
|
|
}
|
2016-12-08 20:50:37 +01:00
|
|
|
}
|
|
|
|
|
2018-10-29 15:08:28 +01:00
|
|
|
public void share(Intent intent) {
|
2017-07-29 11:30:26 +02:00
|
|
|
Bundle extras = intent.getExtras();
|
2023-04-01 19:49:39 +02:00
|
|
|
ArrayList<Uri> streams = streamsFromIntent(intent, extras);
|
|
|
|
if (streams != null && !streams.isEmpty()) {
|
|
|
|
sendUriList(streams);
|
|
|
|
} else if (extras != null && extras.containsKey(Intent.EXTRA_TEXT)) {
|
|
|
|
Log.i("SharePlugin", "Intent contains text to share");
|
|
|
|
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 (StringUtils.endsWith(subject, "YouTube")) {
|
|
|
|
int index = text.indexOf(": http://youtu.be/");
|
|
|
|
if (index > 0) {
|
|
|
|
text = text.substring(index + 2); //Skip ": "
|
2017-07-29 11:30:26 +02:00
|
|
|
}
|
|
|
|
}
|
2023-04-01 19:49:39 +02:00
|
|
|
boolean isUrl;
|
|
|
|
try {
|
|
|
|
new URL(text);
|
|
|
|
isUrl = true;
|
|
|
|
} catch (MalformedURLException e) {
|
|
|
|
isUrl = false;
|
|
|
|
}
|
|
|
|
NetworkPacket np = new NetworkPacket(SharePlugin.PACKET_TYPE_SHARE_REQUEST);
|
|
|
|
if (isUrl) {
|
|
|
|
np.set("url", text);
|
|
|
|
} else {
|
|
|
|
np.set("text", text);
|
|
|
|
}
|
|
|
|
device.sendPacket(np);
|
|
|
|
} else {
|
|
|
|
Log.e("SharePlugin", "There's nothing we know how to share");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private ArrayList<Uri> streamsFromIntent(Intent intent, Bundle extras) {
|
|
|
|
if (extras == null || !extras.containsKey(Intent.EXTRA_STREAM)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
Log.i("SharePlugin", "Intent contains streams to share");
|
|
|
|
ArrayList<Uri> uriList;
|
|
|
|
if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
|
|
|
|
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
|
|
|
|
} else {
|
|
|
|
uriList = new ArrayList<>();
|
|
|
|
uriList.add(extras.getParcelable(Intent.EXTRA_STREAM));
|
|
|
|
}
|
|
|
|
uriList.removeAll(Collections.singleton(null));
|
|
|
|
if (uriList.isEmpty()) {
|
|
|
|
Log.w("SharePlugin", "All streams were null");
|
2017-07-29 11:30:26 +02:00
|
|
|
}
|
2023-04-01 19:49:39 +02:00
|
|
|
return uriList;
|
2017-07-29 11:30:26 +02:00
|
|
|
}
|
2016-12-08 20:50:37 +01:00
|
|
|
|
2015-09-08 14:54:04 -07:00
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String[] getSupportedPacketTypes() {
|
2019-01-26 16:59:39 +01:00
|
|
|
return new String[]{PACKET_TYPE_SHARE_REQUEST, PACKET_TYPE_SHARE_REQUEST_UPDATE};
|
2015-09-08 14:54:04 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String[] getOutgoingPacketTypes() {
|
2018-03-04 11:31:37 +01:00
|
|
|
return new String[]{PACKET_TYPE_SHARE_REQUEST};
|
2015-09-08 14:54:04 -07:00
|
|
|
}
|
|
|
|
|
2017-05-31 15:51:07 +02:00
|
|
|
@Override
|
2023-05-26 22:19:21 +02:00
|
|
|
public @NonNull String[] getOptionalPermissions() {
|
2023-03-15 22:01:39 +00:00
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
|
|
return ArrayUtils.EMPTY_STRING_ARRAY;
|
|
|
|
} else {
|
|
|
|
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
|
|
|
|
}
|
2017-05-31 15:51:07 +02:00
|
|
|
}
|
2018-11-25 17:33:12 +01:00
|
|
|
|
2019-04-07 17:54:12 +00:00
|
|
|
private class Callback implements BackgroundJob.Callback<Void> {
|
2019-01-06 14:06:24 +01:00
|
|
|
@Override
|
2019-03-08 19:07:01 +01:00
|
|
|
public void onResult(@NonNull BackgroundJob job, Void result) {
|
|
|
|
if (job == receiveFileJob) {
|
|
|
|
receiveFileJob = null;
|
2019-04-07 17:54:12 +00:00
|
|
|
} else if (job == uploadFileJob) {
|
|
|
|
uploadFileJob = null;
|
2018-11-25 17:33:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2019-03-08 19:07:01 +01:00
|
|
|
public void onError(@NonNull BackgroundJob job, @NonNull Throwable error) {
|
|
|
|
if (job == receiveFileJob) {
|
|
|
|
receiveFileJob = null;
|
2019-04-07 17:54:12 +00:00
|
|
|
} else if (job == uploadFileJob) {
|
|
|
|
uploadFileJob = null;
|
2019-03-08 19:07:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void cancelJob(long jobId) {
|
|
|
|
if (backgroundJobHandler.isRunning(jobId)) {
|
|
|
|
BackgroundJob job = backgroundJobHandler.getJob(jobId);
|
|
|
|
|
|
|
|
if (job != null) {
|
|
|
|
job.cancel();
|
|
|
|
|
|
|
|
if (job == receiveFileJob) {
|
|
|
|
receiveFileJob = null;
|
2019-04-07 17:54:12 +00:00
|
|
|
} else if (job == uploadFileJob) {
|
|
|
|
uploadFileJob = null;
|
2019-03-08 19:07:01 +01:00
|
|
|
}
|
2018-11-25 17:33:12 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-06-27 14:44:40 +02:00
|
|
|
}
|