2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

SharePlugin refactoring

Summary:
Some refactoring in the Shareplugin:

Extracted some parts into separate methods
Extracted notification into separate class
Some optical tweaks on notifications

Reviewers: #kde_connect, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: albertvaka, #kde_connect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D6613
This commit is contained in:
Nicolas Fella 2017-07-11 10:35:53 +02:00
parent 17740c63ee
commit 45f3311b70
5 changed files with 267 additions and 193 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ gradlew
gradlew.bat gradlew.bat
*.iml *.iml
*.keystore *.keystore
.directory

View File

@ -3,7 +3,7 @@ buildscript {
jcenter() jcenter()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:2.3.2' classpath 'com.android.tools.build:gradle:2.3.3'
} }
} }

View File

@ -117,8 +117,7 @@ class NotificationUpdateCallback extends Device.SendPackageStatusCallback {
.setContentTitle(title) .setContentTitle(title)
.setContentText(text) .setContentText(text)
.setSmallIcon(icon) .setSmallIcon(icon)
.setProgress(progress, progress, false); //setting progress to 0 out of 0 remove the progress bar .setProgress(0, 0, false); //setting progress to 0 out of 0 remove the progress bar
} }
} }

View File

@ -0,0 +1,104 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
/*
* 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/>.
*/
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect_tp.R;
public class ShareNotification {
private final String filename;
private NotificationManager notificationManager;
private int notificationId;
private NotificationCompat.Builder builder;
private Device device;
public ShareNotification(Device device, String filename) {
this.device = device;
this.filename = filename;
notificationId = (int) System.currentTimeMillis();
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(device.getContext())
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
.setContentText(device.getContext().getResources().getString(R.string.incoming_file_text, filename))
.setTicker(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setAutoCancel(true)
.setOngoing(true)
.setProgress(100, 0, true);
}
public void show() {
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
public int getId() {
return notificationId;
}
public void setProgress(int progress) {
builder.setProgress(100, progress, false)
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName())+" ("+progress+"%)");
}
public void setFinished(boolean success) {
String message = success ? device.getContext().getResources().getString(R.string.received_file_title, device.getName()) : device.getContext().getResources().getString(R.string.received_file_fail_title, device.getName());
builder = new NotificationCompat.Builder(device.getContext());
builder.setContentTitle(message)
.setTicker(message)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setAutoCancel(true)
.setOngoing(false);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(device.getContext());
if (prefs.getBoolean("share_notification_preference", true)) {
builder.setDefaults(Notification.DEFAULT_ALL);
}
}
public void setURI(Uri destinationUri, String mimeType) {
// Nougat requires share:// URIs instead of file:// URIs
// TODO use FileProvider for >=Nougat
if (Build.VERSION.SDK_INT < 24) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(destinationUri, mimeType);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(device.getContext());
stackBuilder.addNextIntent(intent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentText(device.getContext().getResources().getString(R.string.received_file_text, filename))
.setContentIntent(resultPendingIntent);
}
}
}

View File

@ -22,7 +22,6 @@ package org.kde.kdeconnect.Plugins.SharePlugin;
import android.Manifest; import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog;
import android.app.DownloadManager; import android.app.DownloadManager;
import android.app.Notification; import android.app.Notification;
import android.app.NotificationManager; import android.app.NotificationManager;
@ -31,14 +30,11 @@ import android.content.ClipboardManager;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources; import android.content.res.Resources;
import android.database.Cursor; 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.preference.PreferenceManager;
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;
@ -57,13 +53,13 @@ import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList; import java.util.ArrayList;
public class SharePlugin extends Plugin { public class SharePlugin extends Plugin {
//public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request"; public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
final static boolean openUrlsDirectly = true; final static boolean openUrlsDirectly = true;
@ -113,200 +109,171 @@ public class SharePlugin extends Plugin {
Log.i("SharePlugin", "hasPayload"); Log.i("SharePlugin", "hasPayload");
int permissionCheck = ContextCompat.checkSelfPermission(context, if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
Manifest.permission.WRITE_EXTERNAL_STORAGE); receiveFile(np);
} else {
if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
} else if(permissionCheck == PackageManager.PERMISSION_DENIED){
// TODO Request Permission for storage
Log.i("SharePlugin", "no Permission for Storage"); Log.i("SharePlugin", "no Permission for Storage");
return false;
} }
final InputStream input = np.getPayload();
final long fileLength = np.getPayloadSize();
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
//We need to check for already existing files only when storing in the default path.
//User-defined paths use the new Storage Access Framework that already handles this.
final boolean customDestination = ShareSettingsActivity.isCustomDestinationEnabled(context);
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
final String filename = customDestination? originalFilename : FilesHelper.findNonExistingNameForNewFile(defaultPath, originalFilename);
String displayName = FilesHelper.getFileNameWithoutExt(filename);
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
if ("*/*".equals(mimeType)) {
displayName = filename;
}
final DocumentFile destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, displayName);
final OutputStream destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
final Uri destinationUri = destinationDocument.getUri();
final int notificationId = (int)System.currentTimeMillis();
Resources res = context.getResources();
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.incoming_file_title, device.getName()))
.setContentText(res.getString(R.string.incoming_file_text, filename))
.setTicker(res.getString(R.string.incoming_file_title, device.getName()))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setAutoCancel(true)
.setOngoing(true)
.setProgress(100,0,true);
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager,notificationId, builder.build());
new Thread(new Runnable() {
@Override
public void run() {
boolean successful = true;
try {
byte data[] = new byte[1024];
long progress = 0, prevProgressPercentage = 0;
int count;
while ((count = input.read(data)) >= 0) {
progress += count;
destinationOutput.write(data, 0, count);
if (fileLength > 0) {
if (progress >= fileLength) break;
long progressPercentage = (progress * 10 / fileLength);
if (progressPercentage != prevProgressPercentage) {
prevProgressPercentage = progressPercentage;
builder.setProgress(100, (int) progressPercentage*10, false);
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
}
//else Log.e("SharePlugin", "Infinite loop? :D");
}
destinationOutput.flush();
} catch (Exception e) {
successful = false;
Log.e("SharePlugin", "Receiver thread exception");
e.printStackTrace();
} finally {
try { destinationOutput.close(); } catch (Exception e) {}
try { input.close(); } catch (Exception e) {}
}
try {
Log.i("SharePlugin", "Transfer finished: "+destinationUri.getPath());
//Update the notification and allow to open the file from it
Resources res = context.getResources();
String message = successful? res.getString(R.string.received_file_title, device.getName()) : res.getString(R.string.received_file_fail_title, device.getName());
builder.setContentTitle(message)
.setTicker(message)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setAutoCancel(true)
.setProgress(100,100,false)
.setOngoing(false);
// Nougat requires share:// URIs instead of file:// URIs
// TODO use FileProvider for >Nougat
if(Build.VERSION.SDK_INT < 24) {
if (successful) {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(destinationUri, mimeType);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(intent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
.setContentIntent(resultPendingIntent);
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
if (prefs.getBoolean("share_notification_preference", true)) {
builder.setDefaults(Notification.DEFAULT_ALL);
}
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
if (successful) {
if (!customDestination && Build.VERSION.SDK_INT >= 12) {
Log.i("SharePlugin","Adding to downloads");
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
} else {
//Make sure it is added to the Android Gallery anyway
MediaStoreHelper.indexFile(context, destinationUri);
}
}
} catch (Exception e) {
Log.e("SharePlugin", "Receiver thread exception");
e.printStackTrace();
}
}
}).start();
} else if (np.has("text")) { } else if (np.has("text")) {
Log.i("SharePlugin", "hasText"); Log.i("SharePlugin", "hasText");
receiveText(np);
String text = np.getString("text");
if(Build.VERSION.SDK_INT >= 11) {
ClipboardManager cm = (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
} else {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
} else if (np.has("url")) { } else if (np.has("url")) {
receiveUrl(np);
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);
if (openUrlsDirectly) {
context.startActivity(browserIntent);
} else {
Resources res = context.getResources();
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(browserIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.received_url_title, device.getName()))
.setContentText(res.getString(R.string.received_url_text, url))
.setContentIntent(resultPendingIntent)
.setTicker(res.getString(R.string.received_url_title, device.getName()))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager, (int) System.currentTimeMillis(), noti);
}
} else { } else {
Log.e("SharePlugin", "Error: Nothing attached!"); Log.e("SharePlugin", "Error: Nothing attached!");
} }
} catch (Exception e) {
} catch(Exception e) { Log.e("SharePlugin", "Exception");
Log.e("SharePlugin","Exception");
e.printStackTrace(); e.printStackTrace();
} }
return true; return true;
} }
private void receiveUrl(NetworkPackage np) {
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);
if (openUrlsDirectly) {
context.startActivity(browserIntent);
} else {
Resources res = context.getResources();
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(browserIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.received_url_title, device.getName()))
.setContentText(res.getString(R.string.received_url_text, url))
.setContentIntent(resultPendingIntent)
.setTicker(res.getString(R.string.received_url_title, device.getName()))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager, (int) System.currentTimeMillis(), noti);
}
}
private void receiveText(NetworkPackage np) {
String text = np.getString("text");
if (Build.VERSION.SDK_INT >= 11) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
} else {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
}
private void receiveFile(NetworkPackage np) {
final InputStream input = np.getPayload();
final long fileLength = np.getPayloadSize();
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
//We need to check for already existing files only when storing in the default path.
//User-defined paths use the new Storage Access Framework that already handles this.
final boolean customDestination = ShareSettingsActivity.isCustomDestinationEnabled(context);
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
final String filename = customDestination ? originalFilename : FilesHelper.findNonExistingNameForNewFile(defaultPath, originalFilename);
String displayName = FilesHelper.getFileNameWithoutExt(filename);
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
if ("*/*".equals(mimeType)) {
displayName = filename;
}
final DocumentFile destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, displayName);
final OutputStream destinationOutput;
try {
destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
final Uri destinationUri = destinationDocument.getUri();
Resources res = context.getResources();
final ShareNotification notification = new ShareNotification(device, filename);
notification.show();
new Thread(new Runnable() {
@Override
public void run() {
boolean successful = true;
try {
byte data[] = new byte[1024];
long progress = 0, prevProgressPercentage = 0;
int count;
while ((count = input.read(data)) >= 0) {
progress += count;
destinationOutput.write(data, 0, count);
if (fileLength > 0) {
if (progress >= fileLength) break;
long progressPercentage = (progress * 100 / fileLength);
if (progressPercentage != prevProgressPercentage) {
prevProgressPercentage = progressPercentage;
notification.setProgress((int) progressPercentage);
notification.show();
}
}
//else Log.e("SharePlugin", "Infinite loop? :D");
}
destinationOutput.flush();
Log.i("SharePlugin", "Transfer finished: " + destinationUri.getPath());
//Update the notification and allow to open the file from it
notification.setFinished(true);
notification.setURI(destinationUri, mimeType);
notification.show();
if (!customDestination && Build.VERSION.SDK_INT >= 12) {
Log.i("SharePlugin", "Adding to downloads");
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
} else {
//Make sure it is added to the Android Gallery anyway
MediaStoreHelper.indexFile(context, destinationUri);
}
} catch (Exception e) {
successful = false;
Log.e("SharePlugin", "Receiver thread exception");
e.printStackTrace();
notification.setFinished(successful);
notification.show();
} finally {
try {
destinationOutput.close();
} catch (Exception e) {
}
try {
input.close();
} catch (Exception e) {
}
}
}
}).start();
}
@Override @Override
public void startPreferencesActivity(SettingsActivity parentActivity) { public void startPreferencesActivity(SettingsActivity parentActivity) {
Intent intent = new Intent(parentActivity, ShareSettingsActivity.class); Intent intent = new Intent(parentActivity, ShareSettingsActivity.class);
@ -335,7 +302,7 @@ public class SharePlugin extends Plugin {
for (NetworkPackage np : toSend) { for (NetworkPackage np : toSend) {
boolean success = device.sendPackageBlocking(np, notificationUpdateCallback); boolean success = device.sendPackageBlocking(np, notificationUpdateCallback);
if (!success) { if (!success) {
Log.e("SharePlugin","Error sending files"); Log.e("SharePlugin", "Error sending files");
return; return;
} }
} }
@ -365,24 +332,24 @@ public class SharePlugin extends Plugin {
try { try {
size = new File(uri.getPath()).length(); size = new File(uri.getPath()).length();
} catch(Exception e) { } catch (Exception e) {
Log.e("SendFileActivity", "Could not obtain file size"); Log.e("SendFileActivity", "Could not obtain file size");
e.printStackTrace(); e.printStackTrace();
} }
}else{ } else {
// Probably a content:// uri, so we query the Media content provider // Probably a content:// uri, so we query the Media content provider
Cursor cursor = null; Cursor cursor = null;
try { try {
String[] proj = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME }; String[] proj = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME};
cursor = cr.query(uri, proj, null, null, null); cursor = cr.query(uri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
cursor.moveToFirst(); cursor.moveToFirst();
String path = cursor.getString(column_index); String path = cursor.getString(column_index);
np.set("filename", Uri.parse(path).getLastPathSegment()); np.set("filename", Uri.parse(path).getLastPathSegment());
size = new File(path).length(); size = new File(path).length();
} catch(Exception unused) { } catch (Exception unused) {
Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media"); Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media");
@ -401,12 +368,15 @@ public class SharePlugin extends Plugin {
cursor.moveToFirst(); cursor.moveToFirst();
//For some reason this size can differ from the actual file size! //For some reason this size can differ from the actual file size!
size = cursor.getInt(column_index); size = cursor.getInt(column_index);
} catch(Exception e) { } catch (Exception e) {
Log.e("SendFileActivity", "Could not obtain file size"); Log.e("SendFileActivity", "Could not obtain file size");
e.printStackTrace(); e.printStackTrace();
} }
} finally { } finally {
try { cursor.close(); } catch (Exception e) { } try {
cursor.close();
} catch (Exception e) {
}
} }
} }