mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 01:51:47 +00:00
Added a Share setting to add received files to the "Downloads" app
Only works for old-school (not-Storage Access Framework) paths, so we have to keep track of the fact that we are using one or the other. Also this requires the permission DOWNLOAD_WITHOUT_NOTIFICATION, but hopefully the play store won't make users confirm this one. This behaviour is enabled by default.
This commit is contained in:
parent
f8dd9bf923
commit
1334dae342
@ -27,6 +27,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -160,6 +160,10 @@
|
|||||||
<string name="custom_device_list">Add devices by IP</string>
|
<string name="custom_device_list">Add devices by IP</string>
|
||||||
<string name="share_notification_preference">Noisy notifications</string>
|
<string name="share_notification_preference">Noisy notifications</string>
|
||||||
<string name="share_notification_preference_summary">Vibrate and play a sound when receiving a file</string>
|
<string name="share_notification_preference_summary">Vibrate and play a sound when receiving a file</string>
|
||||||
|
<string name="share_destination_customize">Customize destination directory</string>
|
||||||
|
<string name="share_destination_customize_summary_disabled">Received files will appear in Downloads</string>
|
||||||
|
<string name="share_destination_customize_summary_enabled">Files will be stored in the directory below</string>
|
||||||
|
<string name="share_destination_folder_preference">Destination directory</string>
|
||||||
<string name="title_activity_notification_filter">Notification filter</string>
|
<string name="title_activity_notification_filter">Notification filter</string>
|
||||||
<string name="filter_apps_info">Notifications will be synchronized for the selected apps.</string>
|
<string name="filter_apps_info">Notifications will be synchronized for the selected apps.</string>
|
||||||
<string name="sftp_internal_storage">Internal storage</string>
|
<string name="sftp_internal_storage">Internal storage</string>
|
||||||
@ -190,8 +194,8 @@
|
|||||||
<string name="findmyphone_title_tablet">Find my tablet</string>
|
<string name="findmyphone_title_tablet">Find my tablet</string>
|
||||||
<string name="findmyphone_description">Rings this device so you can find it</string>
|
<string name="findmyphone_description">Rings this device so you can find it</string>
|
||||||
<string name="findmyphone_found">Found</string>
|
<string name="findmyphone_found">Found</string>
|
||||||
<string name="share_destination_folder_preference">Destination directory</string>
|
|
||||||
|
|
||||||
<string name="open">Open</string>
|
<string name="open">Open</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -3,6 +3,14 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:id="@+id/share_destination_customize"
|
||||||
|
android:key="share_destination_custom"
|
||||||
|
android:title="@string/share_destination_customize"
|
||||||
|
android:summaryOff="@string/share_destination_customize_summary_disabled"
|
||||||
|
android:summaryOn="@string/share_destination_customize_summary_enabled"
|
||||||
|
android:defaultValue="false" />
|
||||||
|
|
||||||
<Preference
|
<Preference
|
||||||
android:id="@+id/share_destination_folder_preference"
|
android:id="@+id/share_destination_folder_preference"
|
||||||
android:key="share_destination_folder_preference"
|
android:key="share_destination_folder_preference"
|
||||||
|
@ -27,17 +27,35 @@ import java.io.File;
|
|||||||
|
|
||||||
public class FilesHelper {
|
public class FilesHelper {
|
||||||
|
|
||||||
public static String getFileExt(String fileName) {
|
public static String getFileExt(String filename) {
|
||||||
//return MimeTypeMap.getFileExtensionFromUrl(fileName);
|
//return MimeTypeMap.getFileExtensionFromUrl(filename);
|
||||||
return fileName.substring((fileName.lastIndexOf(".") + 1), fileName.length());
|
return filename.substring((filename.lastIndexOf(".") + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getFileNameWithoutExt(String filename) {
|
||||||
|
int dot = filename.lastIndexOf(".");
|
||||||
|
return (dot < 0)? filename : filename.substring(0, dot);
|
||||||
|
}
|
||||||
public static String getMimeTypeFromFile(String file) {
|
public static String getMimeTypeFromFile(String file) {
|
||||||
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExt(file));
|
String mime = MimeTypeMap.getSingleton().getMimeTypeFromExtension(getFileExt(file));
|
||||||
if (mime == null) mime = "*/*";
|
if (mime == null) mime = "*/*";
|
||||||
return mime;
|
return mime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String findNonExistingNameForNewFile(String path, String filename) {
|
||||||
|
int dot = filename.lastIndexOf(".");
|
||||||
|
String name = (dot < 0)? filename : filename.substring(0, dot);
|
||||||
|
String ext = (dot < 0)? "" : filename.substring(filename.lastIndexOf("."));
|
||||||
|
|
||||||
|
int num = 1;
|
||||||
|
while (new File(path+"/"+filename).exists()) {
|
||||||
|
filename = name+" ("+num+")"+ext;
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
|
||||||
//Following code from http://activemq.apache.org/maven/5.7.0/kahadb/apidocs/src-html/org/apache/kahadb/util/IOHelper.html
|
//Following code from http://activemq.apache.org/maven/5.7.0/kahadb/apidocs/src-html/org/apache/kahadb/util/IOHelper.html
|
||||||
/**
|
/**
|
||||||
* Converts any string into a string that is safe to use as a file name.
|
* Converts any string into a string that is safe to use as a file name.
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
package org.kde.kdeconnect.Plugins.SharePlugin;
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.DownloadManager;
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@ -110,20 +111,22 @@ public class SharePlugin extends Plugin {
|
|||||||
|
|
||||||
final InputStream input = np.getPayload();
|
final InputStream input = np.getPayload();
|
||||||
final long fileLength = np.getPayloadSize();
|
final long fileLength = np.getPayloadSize();
|
||||||
final String filename = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
||||||
|
|
||||||
int dot = filename.lastIndexOf(".");
|
//We need to check for already existing files only when storing in the default path.
|
||||||
String name = (dot < 0)? filename : filename.substring(0, dot);
|
//User-defined paths use the new Storage Access Framework that already handles this.
|
||||||
String ext = (dot < 0)? "" : filename.substring(filename.lastIndexOf("."));
|
final boolean customDestination = ShareSettingsActivity.isCustomDestinationEnabled(context);
|
||||||
|
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
|
||||||
|
final String filename = customDestination? originalFilename : FilesHelper.findNonExistingNameForNewFile(defaultPath, originalFilename);
|
||||||
|
|
||||||
|
final String nameWithoutExtension = FilesHelper.getFileNameWithoutExt(filename);
|
||||||
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
||||||
|
|
||||||
final DocumentFile destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
|
final DocumentFile destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
|
||||||
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, name);
|
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, nameWithoutExtension);
|
||||||
final OutputStream destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
|
final OutputStream destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
|
||||||
final Uri destinationUri = destinationDocument.getUri();
|
final Uri destinationUri = destinationDocument.getUri();
|
||||||
|
|
||||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
|
||||||
|
|
||||||
final int notificationId = (int)System.currentTimeMillis();
|
final int notificationId = (int)System.currentTimeMillis();
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context)
|
||||||
@ -135,6 +138,7 @@ public class SharePlugin extends Plugin {
|
|||||||
.setOngoing(true)
|
.setOngoing(true)
|
||||||
.setProgress(100,0,true);
|
.setProgress(100,0,true);
|
||||||
|
|
||||||
|
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
NotificationHelper.notifyCompat(notificationManager,notificationId, builder.build());
|
NotificationHelper.notifyCompat(notificationManager,notificationId, builder.build());
|
||||||
|
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@ -172,7 +176,7 @@ public class SharePlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Log.i("SharePlugin", "Transfer finished");
|
Log.i("SharePlugin", "Transfer finished: "+destinationUri.getPath());
|
||||||
|
|
||||||
//Update the notification and allow to open the file from it
|
//Update the notification and allow to open the file from it
|
||||||
Resources res = context.getResources();
|
Resources res = context.getResources();
|
||||||
@ -189,7 +193,7 @@ public class SharePlugin extends Plugin {
|
|||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||||
stackBuilder.addNextIntent(intent);
|
stackBuilder.addNextIntent(intent);
|
||||||
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
builder.setContentText(res.getString(R.string.received_file_text, filename))
|
builder.setContentText(res.getString(R.string.received_file_text, destinationDocument.getName()))
|
||||||
.setContentIntent(resultPendingIntent);
|
.setContentIntent(resultPendingIntent);
|
||||||
}
|
}
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
@ -199,8 +203,14 @@ public class SharePlugin extends Plugin {
|
|||||||
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
|
||||||
|
|
||||||
if (successful) {
|
if (successful) {
|
||||||
//Make sure it is added to the Android Gallery
|
if (!customDestination) {
|
||||||
MediaStoreHelper.indexFile(context, destinationUri);
|
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) {
|
} catch (Exception e) {
|
||||||
|
@ -9,6 +9,7 @@ import android.net.Uri;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.os.Environment;
|
||||||
|
import android.preference.CheckBoxPreference;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.support.v4.provider.DocumentFile;
|
import android.support.v4.provider.DocumentFile;
|
||||||
@ -20,16 +21,28 @@ import java.io.File;
|
|||||||
|
|
||||||
public class ShareSettingsActivity extends PluginSettingsActivity {
|
public class ShareSettingsActivity extends PluginSettingsActivity {
|
||||||
|
|
||||||
|
private final static String PREFERENCE_CUSTOMIZE_DESTINATION = "share_destination_custom";
|
||||||
private final static String PREFERENCE_DESTINATION = "share_destination_folder_uri";
|
private final static String PREFERENCE_DESTINATION = "share_destination_folder_uri";
|
||||||
|
|
||||||
private static final int RESULT_PICKER = 42;
|
private static final int RESULT_PICKER = Activity.RESULT_FIRST_USER;
|
||||||
|
|
||||||
|
private Preference filePicker;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Preference filePicker = findPreference("share_destination_folder_preference");
|
final CheckBoxPreference customDownloads = (CheckBoxPreference) findPreference("share_destination_custom");
|
||||||
|
filePicker = findPreference("share_destination_folder_preference");
|
||||||
|
|
||||||
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) {
|
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)) {
|
||||||
|
customDownloads.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||||
|
@Override
|
||||||
|
public boolean onPreferenceChange(Preference preference, Object newValue) {
|
||||||
|
updateFilePickerStatus((Boolean) newValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
filePicker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
filePicker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public boolean onPreferenceClick(Preference preference) {
|
public boolean onPreferenceClick(Preference preference) {
|
||||||
@ -39,31 +52,45 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
customDownloads.setEnabled(false);
|
||||||
filePicker.setEnabled(false);
|
filePicker.setEnabled(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean customized = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
|
||||||
|
updateFilePickerStatus(customized);
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateFilePickerStatus(boolean enabled) {
|
||||||
|
filePicker.setEnabled(enabled);
|
||||||
String path = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFERENCE_DESTINATION, null);
|
String path = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFERENCE_DESTINATION, null);
|
||||||
if (path != null) {
|
if (enabled && path != null) {
|
||||||
filePicker.setSummary(Uri.parse(path).getPath());
|
filePicker.setSummary(Uri.parse(path).getPath());
|
||||||
} else {
|
} else {
|
||||||
filePicker.setSummary(getDefaultDestinationDirectory().getAbsolutePath());
|
filePicker.setSummary(getDefaultDestinationDirectory().getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File getDefaultDestinationDirectory() {
|
public static File getDefaultDestinationDirectory() {
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isCustomDestinationEnabled(Context context) {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Will return the appropriate directory, whether it is customized or not
|
||||||
public static DocumentFile getDestinationDirectory(Context context) {
|
public static DocumentFile getDestinationDirectory(Context context) {
|
||||||
String path = PreferenceManager.getDefaultSharedPreferences(context).getString(PREFERENCE_DESTINATION, null);
|
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false)) {
|
||||||
if (path != null) {
|
String path = PreferenceManager.getDefaultSharedPreferences(context).getString(PREFERENCE_DESTINATION, null);
|
||||||
//There should be no way to enter here on api level < kitkat
|
if (path != null) {
|
||||||
DocumentFile treeDocumentFile = DocumentFile.fromTreeUri(context, Uri.parse(path));
|
//There should be no way to enter here on api level < kitkat
|
||||||
if (treeDocumentFile.canWrite()) { //Checks for FLAG_DIR_SUPPORTS_CREATE on directories
|
DocumentFile treeDocumentFile = DocumentFile.fromTreeUri(context, Uri.parse(path));
|
||||||
return treeDocumentFile;
|
if (treeDocumentFile.canWrite()) { //Checks for FLAG_DIR_SUPPORTS_CREATE on directories
|
||||||
} else {
|
return treeDocumentFile;
|
||||||
//Maybe permission was revoked
|
} else {
|
||||||
Log.w("SharePlugin", "Share destination is not writable, falling back to default path.");
|
//Maybe permission was revoked
|
||||||
|
Log.w("SharePlugin", "Share destination is not writable, falling back to default path.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return DocumentFile.fromFile(getDefaultDestinationDirectory());
|
return DocumentFile.fromFile(getDefaultDestinationDirectory());
|
||||||
@ -88,13 +115,6 @@ public class ShareSettingsActivity extends PluginSettingsActivity {
|
|||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
prefs.edit().putString(PREFERENCE_DESTINATION, uri.toString()).apply();
|
prefs.edit().putString(PREFERENCE_DESTINATION, uri.toString()).apply();
|
||||||
}
|
}
|
||||||
/* else { // Here to ease debugging. Removes the setting so we use the default url.
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
|
||||||
prefs.edit().remove(PREFERENCE_DESTINATION).apply();
|
|
||||||
Preference filePicker = findPreference("share_destination_folder_preference");
|
|
||||||
filePicker.setSummary(getDefaultDestinationDirectory().getAbsolutePath());
|
|
||||||
} */
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user