Changeset View
Changeset View
Standalone View
Standalone View
src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
Show All 16 Lines | |||||
17 | * You should have received a copy of the GNU General Public License | 17 | * You should have received a copy of the GNU General Public License | ||
18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | 18 | * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
19 | */ | 19 | */ | ||
20 | 20 | | |||
21 | package org.kde.kdeconnect.Plugins.SharePlugin; | 21 | package org.kde.kdeconnect.Plugins.SharePlugin; | ||
22 | 22 | | |||
23 | import android.Manifest; | 23 | import android.Manifest; | ||
24 | import android.app.Activity; | 24 | import android.app.Activity; | ||
25 | import android.app.DownloadManager; | | |||
26 | import android.app.Notification; | 25 | import android.app.Notification; | ||
27 | import android.app.NotificationManager; | 26 | import android.app.NotificationManager; | ||
28 | import android.app.PendingIntent; | 27 | import android.app.PendingIntent; | ||
29 | import android.content.ClipboardManager; | 28 | import android.content.ClipboardManager; | ||
30 | import android.content.ContentResolver; | 29 | import android.content.ContentResolver; | ||
31 | import android.content.Context; | 30 | import android.content.Context; | ||
32 | import android.content.Intent; | 31 | import android.content.Intent; | ||
33 | import android.content.res.Resources; | 32 | import android.content.res.Resources; | ||
34 | import android.database.Cursor; | 33 | import android.database.Cursor; | ||
35 | import android.graphics.drawable.Drawable; | 34 | import android.graphics.drawable.Drawable; | ||
36 | import android.net.Uri; | 35 | import android.net.Uri; | ||
37 | import android.os.Build; | | |||
38 | import android.os.Bundle; | 36 | import android.os.Bundle; | ||
39 | import android.os.Handler; | 37 | import android.os.Handler; | ||
40 | import android.os.Looper; | 38 | import android.os.Looper; | ||
41 | import android.provider.MediaStore; | 39 | import android.provider.MediaStore; | ||
40 | import android.support.annotation.NonNull; | ||||
42 | import android.support.annotation.WorkerThread; | 41 | import android.support.annotation.WorkerThread; | ||
43 | import android.support.v4.app.NotificationCompat; | 42 | import android.support.v4.app.NotificationCompat; | ||
44 | import android.support.v4.content.ContextCompat; | 43 | import android.support.v4.content.ContextCompat; | ||
45 | import android.support.v4.content.FileProvider; | | |||
46 | import android.support.v4.provider.DocumentFile; | | |||
47 | import android.util.Log; | 44 | import android.util.Log; | ||
48 | import android.widget.Toast; | 45 | import android.widget.Toast; | ||
49 | 46 | | |||
50 | import org.kde.kdeconnect.Helpers.FilesHelper; | | |||
51 | import org.kde.kdeconnect.Helpers.MediaStoreHelper; | | |||
52 | import org.kde.kdeconnect.Helpers.NotificationHelper; | 47 | import org.kde.kdeconnect.Helpers.NotificationHelper; | ||
53 | import org.kde.kdeconnect.NetworkPacket; | 48 | import org.kde.kdeconnect.NetworkPacket; | ||
54 | import org.kde.kdeconnect.Plugins.Plugin; | 49 | import org.kde.kdeconnect.Plugins.Plugin; | ||
55 | import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity; | 50 | import org.kde.kdeconnect.UserInterface.DeviceSettingsActivity; | ||
51 | import org.kde.kdeconnect.async.BackgroundJob; | ||||
52 | import org.kde.kdeconnect.async.BackgroundJobHandler; | ||||
56 | import org.kde.kdeconnect_tp.R; | 53 | import org.kde.kdeconnect_tp.R; | ||
57 | 54 | | |||
58 | import java.io.BufferedOutputStream; | | |||
59 | import java.io.File; | 55 | import java.io.File; | ||
60 | import java.io.FileNotFoundException; | | |||
61 | import java.io.InputStream; | 56 | import java.io.InputStream; | ||
62 | import java.net.URL; | 57 | import java.net.URL; | ||
63 | import java.util.ArrayList; | 58 | import java.util.ArrayList; | ||
64 | import java.util.concurrent.ExecutorService; | | |||
65 | import java.util.concurrent.Executors; | | |||
66 | 59 | | |||
67 | public class SharePlugin extends Plugin implements ReceiveFileRunnable.CallBack { | 60 | public class SharePlugin extends Plugin { | ||
61 | final static String ACTION_CANCEL_SHARE = "org.kde.kdeconnect.Plugins.SharePlugin.CancelShare"; | ||||
62 | final static String CANCEL_SHARE_DEVICE_ID_EXTRA = "deviceId"; | ||||
63 | final static String CANCEL_SHARE_BACKGROUND_JOB_ID_EXTRA = "backgroundJobId"; | ||||
68 | 64 | | |||
69 | private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request"; | 65 | private final static String PACKET_TYPE_SHARE_REQUEST = "kdeconnect.share.request"; | ||
70 | 66 | | |||
67 | final static String KEY_NUMBER_OF_FILES = "numberOfFiles"; | ||||
68 | final static String KEY_TOTAL_PAYLOAD_SIZE = "totalPayloadSize"; | ||||
69 | | ||||
71 | private final static boolean openUrlsDirectly = true; | 70 | private final static boolean openUrlsDirectly = true; | ||
72 | private ShareNotification shareNotification; | 71 | private BackgroundJobHandler backgroundJobHandler; | ||
73 | private FinishReceivingRunnable finishReceivingRunnable; | 72 | private final Handler handler; | ||
74 | private ExecutorService executorService; | 73 | private CompositeReceiveFileJob receiveFileJob; | ||
75 | private ShareInfo currentShareInfo; | 74 | private final Callback receiveFileJobCallback; | ||
76 | private Handler handler; | | |||
77 | 75 | | |||
78 | public SharePlugin() { | 76 | public SharePlugin() { | ||
79 | executorService = Executors.newSingleThreadExecutor(); | 77 | backgroundJobHandler = BackgroundJobHandler.newFixedThreadPoolBackgroundJobHander(5); | ||
80 | handler = new Handler(Looper.getMainLooper()); | 78 | handler = new Handler(Looper.getMainLooper()); | ||
79 | receiveFileJobCallback = new Callback(); | ||||
81 | } | 80 | } | ||
82 | 81 | | |||
83 | @Override | 82 | @Override | ||
84 | public boolean onCreate() { | 83 | public boolean onCreate() { | ||
85 | optionalPermissionExplanation = R.string.share_optional_permission_explanation; | 84 | optionalPermissionExplanation = R.string.share_optional_permission_explanation; | ||
86 | return true; | 85 | return true; | ||
87 | } | 86 | } | ||
88 | 87 | | |||
▲ Show 20 Lines • Show All 102 Lines • ▼ Show 20 Line(s) | 189 | private void receiveText(NetworkPacket np) { | |||
191 | String text = np.getString("text"); | 190 | String text = np.getString("text"); | ||
192 | ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); | 191 | ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); | ||
193 | cm.setText(text); | 192 | cm.setText(text); | ||
194 | handler.post(() -> Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show()); | 193 | handler.post(() -> Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show()); | ||
195 | } | 194 | } | ||
196 | 195 | | |||
197 | @WorkerThread | 196 | @WorkerThread | ||
198 | private void receiveFile(NetworkPacket np) { | 197 | private void receiveFile(NetworkPacket np) { | ||
199 | if (finishReceivingRunnable != null) { | 198 | CompositeReceiveFileJob job; | ||
200 | Log.i("SharePlugin", "receiveFile: canceling finishReceivingRunnable"); | | |||
201 | handler.removeCallbacks(finishReceivingRunnable); | | |||
202 | finishReceivingRunnable = null; | | |||
203 | } | | |||
204 | | ||||
205 | ShareInfo info = new ShareInfo(); | | |||
206 | info.currentFileNumber = currentShareInfo == null ? 1 : currentShareInfo.currentFileNumber + 1; | | |||
207 | info.payload = np.getPayload(); | | |||
208 | info.fileSize = np.getPayloadSize(); | | |||
209 | info.fileName = np.getString("filename", Long.toString(System.currentTimeMillis())); | | |||
210 | info.shouldOpen = np.getBoolean("open"); | | |||
211 | info.setNumberOfFiles(np.getInt("numberOfFiles", 1)); | | |||
212 | info.setTotalTransferSize(np.getLong("totalPayloadSize", 1)); | | |||
213 | 199 | | |||
214 | if (currentShareInfo == null) { | 200 | boolean hasNumberOfFiles = np.has(KEY_NUMBER_OF_FILES); | ||
215 | currentShareInfo = info; | 201 | boolean hasOpen = np.has("open"); | ||
216 | } else { | | |||
217 | synchronized (currentShareInfo) { | | |||
218 | currentShareInfo.setNumberOfFiles(info.numberOfFiles()); | | |||
219 | currentShareInfo.setTotalTransferSize(info.totalTransferSize()); | | |||
220 | } | | |||
221 | } | | |||
222 | 202 | | |||
223 | String filename = info.fileName; | 203 | if (hasNumberOfFiles && !hasOpen && receiveFileJob != null) { | ||
224 | final DocumentFile destinationFolderDocument; | 204 | job = receiveFileJob; | ||
225 | | ||||
226 | //We need to check for already existing files only when storing in the default path. | | |||
227 | //User-defined paths use the new Storage Access Framework that already handles this. | | |||
228 | //If the file should be opened immediately store it in the standard location to avoid the FileProvider trouble (See ShareNotification::setURI) | | |||
229 | if (np.getBoolean("open") || !ShareSettingsActivity.isCustomDestinationEnabled(context)) { | | |||
230 | final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath(); | | |||
231 | filename = FilesHelper.findNonExistingNameForNewFile(defaultPath, filename); | | |||
232 | destinationFolderDocument = DocumentFile.fromFile(new File(defaultPath)); | | |||
233 | } else { | 205 | } else { | ||
234 | destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context); | 206 | job = new CompositeReceiveFileJob(device, receiveFileJobCallback); | ||
235 | } | 207 | } | ||
236 | String displayName = FilesHelper.getFileNameWithoutExt(filename); | | |||
237 | String mimeType = FilesHelper.getMimeTypeFromFile(filename); | | |||
238 | 208 | | |||
239 | if ("*/*".equals(mimeType)) { | 209 | if (!hasNumberOfFiles) { | ||
240 | displayName = filename; | 210 | np.set(KEY_NUMBER_OF_FILES, 1); | ||
211 | np.set(KEY_TOTAL_PAYLOAD_SIZE, np.getPayloadSize()); | ||||
241 | } | 212 | } | ||
242 | 213 | | |||
243 | info.fileDocument = destinationFolderDocument.createFile(mimeType, displayName); | 214 | job.addNetworkPacket(np); | ||
244 | assert info.fileDocument != null; | | |||
245 | | ||||
246 | if (shareNotification == null) { | | |||
247 | shareNotification = new ShareNotification(device); | | |||
248 | } | | |||
249 | 215 | | |||
250 | if (info.fileDocument == null) { | 216 | if (job != receiveFileJob) { | ||
251 | onError(info, new RuntimeException(context.getString(R.string.cannot_create_file, filename))); | 217 | if (hasNumberOfFiles && !hasOpen) { | ||
252 | return; | 218 | receiveFileJob = job; | ||
253 | } | | |||
254 | | ||||
255 | shareNotification.setTitle(context.getResources().getQuantityString(R.plurals.incoming_file_title, info.numberOfFiles(), info.numberOfFiles(), device.getName())); | | |||
256 | shareNotification.show(); | | |||
257 | | ||||
258 | if (np.hasPayload()) { | | |||
259 | try { | | |||
260 | info.outputStream = new BufferedOutputStream(context.getContentResolver().openOutputStream(info.fileDocument.getUri())); | | |||
261 | } catch (FileNotFoundException e) { | | |||
262 | e.printStackTrace(); | | |||
263 | return; | | |||
264 | } | 219 | } | ||
265 | 220 | backgroundJobHandler.runJob(job); | |||
266 | ReceiveFileRunnable runnable = new ReceiveFileRunnable(info, this); | | |||
267 | executorService.execute(runnable); | | |||
268 | } else { | | |||
269 | onProgress(info, 100); | | |||
270 | onSuccess(info); | | |||
271 | } | 221 | } | ||
272 | } | 222 | } | ||
273 | 223 | | |||
274 | @Override | 224 | @Override | ||
275 | public void startPreferencesActivity(DeviceSettingsActivity parentActivity) { | 225 | public void startPreferencesActivity(DeviceSettingsActivity parentActivity) { | ||
276 | Intent intent = new Intent(parentActivity, ShareSettingsActivity.class); | 226 | Intent intent = new Intent(parentActivity, ShareSettingsActivity.class); | ||
277 | intent.putExtra("plugin_display_name", getDisplayName()); | 227 | intent.putExtra("plugin_display_name", getDisplayName()); | ||
278 | intent.putExtra("plugin_key", getPluginKey()); | 228 | intent.putExtra("plugin_key", getPluginKey()); | ||
279 | parentActivity.startActivity(intent); | 229 | parentActivity.startActivity(intent); | ||
280 | } | 230 | } | ||
281 | 231 | | |||
282 | void queuedSendUriList(final ArrayList<Uri> uriList) { | 232 | void queuedSendUriList(final ArrayList<Uri> uriList) { | ||
283 | | ||||
284 | //Read all the data early, as we only have permissions to do it while the activity is alive | 233 | //Read all the data early, as we only have permissions to do it while the activity is alive | ||
285 | final ArrayList<NetworkPacket> toSend = new ArrayList<>(); | 234 | final ArrayList<NetworkPacket> toSend = new ArrayList<>(); | ||
286 | for (Uri uri : uriList) { | 235 | for (Uri uri : uriList) { | ||
287 | NetworkPacket np = uriToNetworkPacket(context, uri); | 236 | NetworkPacket np = uriToNetworkPacket(context, uri); | ||
288 | 237 | | |||
289 | if (np != null) { | 238 | if (np != null) { | ||
290 | toSend.add(np); | 239 | toSend.add(np); | ||
291 | } | 240 | } | ||
Show All 17 Lines | 257 | } catch (Exception e) { | |||
309 | e.printStackTrace(); | 258 | e.printStackTrace(); | ||
310 | } | 259 | } | ||
311 | }).start(); | 260 | }).start(); | ||
312 | 261 | | |||
313 | } | 262 | } | ||
314 | 263 | | |||
315 | //Create the network package from the URI | 264 | //Create the network package from the URI | ||
316 | private static NetworkPacket uriToNetworkPacket(final Context context, final Uri uri) { | 265 | private static NetworkPacket uriToNetworkPacket(final Context context, final Uri uri) { | ||
317 | | ||||
318 | try { | 266 | try { | ||
319 | | ||||
320 | ContentResolver cr = context.getContentResolver(); | 267 | ContentResolver cr = context.getContentResolver(); | ||
321 | InputStream inputStream = cr.openInputStream(uri); | 268 | InputStream inputStream = cr.openInputStream(uri); | ||
322 | 269 | | |||
323 | NetworkPacket np = new NetworkPacket(PACKET_TYPE_SHARE_REQUEST); | 270 | NetworkPacket np = new NetworkPacket(PACKET_TYPE_SHARE_REQUEST); | ||
324 | long size = -1; | 271 | long size = -1; | ||
325 | 272 | | |||
326 | if (uri.getScheme().equals("file")) { | 273 | if (uri.getScheme().equals("file")) { | ||
327 | // file:// is a non media uri, so we cannot query the ContentProvider | 274 | // file:// is a non media uri, so we cannot query the ContentProvider | ||
▲ Show 20 Lines • Show All 43 Lines • ▼ Show 20 Line(s) | 316 | } catch (Exception e) { | |||
371 | e.printStackTrace(); | 318 | e.printStackTrace(); | ||
372 | } | 319 | } | ||
373 | } finally { | 320 | } finally { | ||
374 | try { | 321 | try { | ||
375 | cursor.close(); | 322 | cursor.close(); | ||
376 | } catch (Exception e) { | 323 | } catch (Exception e) { | ||
377 | } | 324 | } | ||
378 | } | 325 | } | ||
379 | | ||||
380 | } | 326 | } | ||
381 | 327 | | |||
382 | np.setPayload(new NetworkPacket.Payload(inputStream, size)); | 328 | np.setPayload(new NetworkPacket.Payload(inputStream, size)); | ||
383 | 329 | | |||
384 | return np; | 330 | return np; | ||
385 | } catch (Exception e) { | 331 | } catch (Exception e) { | ||
386 | Log.e("SendFileActivity", "Exception sending files"); | 332 | Log.e("SendFileActivity", "Exception sending files"); | ||
387 | e.printStackTrace(); | 333 | e.printStackTrace(); | ||
Show All 13 Lines | 346 | if (!Intent.ACTION_SEND.equals(intent.getAction())) { | |||
401 | uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); | 347 | uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); | ||
402 | } else { | 348 | } else { | ||
403 | Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); | 349 | Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); | ||
404 | uriList = new ArrayList<>(); | 350 | uriList = new ArrayList<>(); | ||
405 | uriList.add(uri); | 351 | uriList.add(uri); | ||
406 | } | 352 | } | ||
407 | 353 | | |||
408 | queuedSendUriList(uriList); | 354 | queuedSendUriList(uriList); | ||
409 | | ||||
410 | } catch (Exception e) { | 355 | } catch (Exception e) { | ||
411 | Log.e("ShareActivity", "Exception"); | 356 | Log.e("ShareActivity", "Exception"); | ||
412 | e.printStackTrace(); | 357 | e.printStackTrace(); | ||
413 | } | 358 | } | ||
414 | 359 | | |||
415 | } else if (extras.containsKey(Intent.EXTRA_TEXT)) { | 360 | } else if (extras.containsKey(Intent.EXTRA_TEXT)) { | ||
416 | String text = extras.getString(Intent.EXTRA_TEXT); | 361 | String text = extras.getString(Intent.EXTRA_TEXT); | ||
417 | String subject = extras.getString(Intent.EXTRA_SUBJECT); | 362 | String subject = extras.getString(Intent.EXTRA_SUBJECT); | ||
Show All 17 Lines | |||||
435 | if (isUrl) { | 380 | if (isUrl) { | ||
436 | np.set("url", text); | 381 | np.set("url", text); | ||
437 | } else { | 382 | } else { | ||
438 | np.set("text", text); | 383 | np.set("text", text); | ||
439 | } | 384 | } | ||
440 | device.sendPacket(np); | 385 | device.sendPacket(np); | ||
441 | } | 386 | } | ||
442 | } | 387 | } | ||
443 | | ||||
444 | } | 388 | } | ||
445 | 389 | | |||
446 | @Override | 390 | @Override | ||
447 | public String[] getSupportedPacketTypes() { | 391 | public String[] getSupportedPacketTypes() { | ||
448 | return new String[]{PACKET_TYPE_SHARE_REQUEST}; | 392 | return new String[]{PACKET_TYPE_SHARE_REQUEST}; | ||
449 | } | 393 | } | ||
450 | 394 | | |||
451 | @Override | 395 | @Override | ||
452 | public String[] getOutgoingPacketTypes() { | 396 | public String[] getOutgoingPacketTypes() { | ||
453 | return new String[]{PACKET_TYPE_SHARE_REQUEST}; | 397 | return new String[]{PACKET_TYPE_SHARE_REQUEST}; | ||
454 | } | 398 | } | ||
455 | 399 | | |||
456 | @Override | 400 | @Override | ||
457 | public String[] getOptionalPermissions() { | 401 | public String[] getOptionalPermissions() { | ||
458 | return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; | 402 | return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; | ||
459 | } | 403 | } | ||
460 | 404 | | |||
405 | private class Callback implements CompositeReceiveFileJob.Callback<Void> { | ||||
461 | @Override | 406 | @Override | ||
462 | public void onProgress(ShareInfo info, int progress) { | 407 | public void onResult(@NonNull BackgroundJob job, Void result) { | ||
463 | if (progress == 0 && currentShareInfo != info) { | 408 | if (job == receiveFileJob) { | ||
464 | currentShareInfo = info; | 409 | receiveFileJob = null; | ||
465 | } | 410 | } | ||
466 | | ||||
467 | shareNotification.setProgress(progress, context.getResources().getQuantityString(R.plurals.incoming_files_text, info.numberOfFiles(), info.fileName, info.currentFileNumber, info.numberOfFiles())); | | |||
468 | shareNotification.show(); | | |||
469 | } | 411 | } | ||
470 | 412 | | |||
471 | @Override | 413 | @Override | ||
472 | public void onSuccess(ShareInfo info) { | 414 | public void onError(@NonNull BackgroundJob job, @NonNull Throwable error) { | ||
473 | Log.i("SharePlugin", "onSuccess() - Transfer finished for file: " + info.fileDocument.getUri().getPath()); | 415 | if (job == receiveFileJob) { | ||
474 | 416 | receiveFileJob = null; | |||
475 | if (info.shouldOpen) { | | |||
476 | shareNotification.cancel(); | | |||
477 | | ||||
478 | Intent intent = new Intent(Intent.ACTION_VIEW); | | |||
479 | if (Build.VERSION.SDK_INT >= 24) { | | |||
480 | //Nougat and later require "content://" uris instead of "file://" uris | | |||
481 | File file = new File(info.fileDocument.getUri().getPath()); | | |||
482 | Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file); | | |||
483 | intent.setDataAndType(contentUri, info.fileDocument.getType()); | | |||
484 | intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); | | |||
485 | } else { | | |||
486 | intent.setDataAndType(info.fileDocument.getUri(), info.fileDocument.getType()); | | |||
487 | } | | |||
488 | | ||||
489 | context.startActivity(intent); | | |||
490 | } else { | | |||
491 | if (!ShareSettingsActivity.isCustomDestinationEnabled(context)) { | | |||
492 | Log.i("SharePlugin", "Adding to downloads"); | | |||
493 | DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); | | |||
494 | manager.addCompletedDownload(info.fileDocument.getUri().getLastPathSegment(), device.getName(), true, info.fileDocument.getType(), info.fileDocument.getUri().getPath(), info.fileSize, false); | | |||
495 | } else { | | |||
496 | //Make sure it is added to the Android Gallery anyway | | |||
497 | MediaStoreHelper.indexFile(context, info.fileDocument.getUri()); | | |||
498 | } | | |||
499 | | ||||
500 | if (info.numberOfFiles() == 1 || info.currentFileNumber == info.numberOfFiles()) { | | |||
501 | finishReceivingRunnable = new FinishReceivingRunnable(info); | | |||
502 | Log.i("SharePlugin", "onSuccess() - scheduling finishReceivingRunnable"); | | |||
503 | handler.postDelayed(finishReceivingRunnable, 1000); | | |||
504 | } | | |||
505 | } | | |||
506 | } | 417 | } | ||
507 | | ||||
508 | @Override | | |||
509 | public void onError(ShareInfo info, Throwable error) { | | |||
510 | Log.e("SharePlugin", "onError: " + error.getMessage()); | | |||
511 | | ||||
512 | info.fileDocument.delete(); | | |||
513 | | ||||
514 | //TODO: Show error in notification | | |||
515 | int failedFiles = info.numberOfFiles() - (info.currentFileNumber - 1); | | |||
516 | shareNotification.setFinished(context.getResources().getQuantityString(R.plurals.received_files_fail_title, failedFiles, failedFiles, info.numberOfFiles(), device.getName())); | | |||
517 | shareNotification.show(); | | |||
518 | shareNotification = null; | | |||
519 | currentShareInfo = null; | | |||
520 | } | 418 | } | ||
521 | | ||||
522 | private class FinishReceivingRunnable implements Runnable { | | |||
523 | private final ShareInfo info; | | |||
524 | | ||||
525 | private FinishReceivingRunnable(ShareInfo info) { | | |||
526 | this.info = info; | | |||
527 | } | 419 | } | ||
528 | 420 | | |||
529 | @Override | 421 | void cancelJob(long jobId) { | ||
530 | public void run() { | 422 | if (backgroundJobHandler.isRunning(jobId)) { | ||
531 | Log.i("SharePlugin", "FinishReceivingRunnable: Finishing up"); | 423 | BackgroundJob job = backgroundJobHandler.getJob(jobId); | ||
532 | | ||||
533 | if (shareNotification != null) { | | |||
534 | //Update the notification and allow to open the file from it | | |||
535 | shareNotification.setFinished(context.getResources().getQuantityString(R.plurals.received_files_title, info.numberOfFiles(), info.numberOfFiles(), device.getName())); | | |||
536 | 424 | | |||
537 | if (info.numberOfFiles() == 1) { | 425 | if (job != null) { | ||
538 | shareNotification.setURI(info.fileDocument.getUri(), info.fileDocument.getType(), info.fileName); | 426 | job.cancel(); | ||
539 | } | 427 | } | ||
540 | | ||||
541 | shareNotification.show(); | | |||
542 | shareNotification = null; | | |||
543 | } | | |||
544 | | ||||
545 | finishReceivingRunnable = null; | | |||
546 | currentShareInfo = null; | | |||
547 | } | 428 | } | ||
548 | } | 429 | } | ||
549 | } | 430 | } |