mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-02 07:05:09 +00:00
Implement saving to SD card using SAF
This commit is contained in:
committed by
Albert Vaca
parent
036737deae
commit
e2e40863f8
@@ -83,6 +83,16 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.kde.kdeconnect.UserInterface.SettingsActivity" />
|
android:value="org.kde.kdeconnect.UserInterface.SettingsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity"
|
||||||
|
android:label="@string/device_menu_plugins"
|
||||||
|
android:parentActivityName="org.kde.kdeconnect.UserInterface.SettingsActivity" >
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.kde.kdeconnect.Plugins.SharePlugin.ShareSettingsActivity" />
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver" >
|
<receiver android:name="org.kde.kdeconnect.KdeConnectBroadcastReceiver" >
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
|
@@ -190,6 +190,9 @@
|
|||||||
<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">Select destination Folder</string>
|
||||||
|
<string name="share_destination_folder_preference_summary">Select destination folder at first use (on SD card...)</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="open">Open</string>
|
<string name="open">Open</string>
|
||||||
<string name="close">Close</string>
|
<string name="close">Close</string>
|
||||||
|
@@ -10,4 +10,12 @@
|
|||||||
android:summary="@string/share_notification_preference_summary"
|
android:summary="@string/share_notification_preference_summary"
|
||||||
android:defaultValue="true" />
|
android:defaultValue="true" />
|
||||||
|
|
||||||
|
<CheckBoxPreference
|
||||||
|
android:id="@+id/share_destination_folder_preference"
|
||||||
|
android:key="share_destination_folder_preference"
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:title="@string/share_destination_folder_preference"
|
||||||
|
android:summary="@string/share_destination_folder_preference_summary" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
@@ -198,6 +198,7 @@ public class ShareActivity extends ActionBarActivity {
|
|||||||
ActionBar actionBar = getSupportActionBar();
|
ActionBar actionBar = getSupportActionBar();
|
||||||
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
|
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
|
||||||
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_list);
|
setContentView(R.layout.activity_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -39,6 +39,7 @@ 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;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
|
import android.support.v4.provider.DocumentFile;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -48,6 +49,7 @@ import org.kde.kdeconnect.Helpers.MediaStoreHelper;
|
|||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
import org.kde.kdeconnect.NetworkPackage;
|
import org.kde.kdeconnect.NetworkPackage;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
|
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;
|
||||||
@@ -100,6 +102,8 @@ public class SharePlugin extends Plugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int READ_REQUEST_CODE = 42;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onPackageReceived(NetworkPackage np) {
|
public boolean onPackageReceived(NetworkPackage np) {
|
||||||
|
|
||||||
@@ -112,19 +116,53 @@ public class SharePlugin extends Plugin {
|
|||||||
final long fileLength = np.getPayloadSize();
|
final long fileLength = np.getPayloadSize();
|
||||||
final String filename = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
final String filename = np.getString("filename", Long.toString(System.currentTimeMillis()));
|
||||||
|
|
||||||
|
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
|
||||||
String deviceDir = FilesHelper.toFileSystemSafeName(device.getName());
|
String deviceDir = FilesHelper.toFileSystemSafeName(device.getName());
|
||||||
//Get the external storage and append "/kdeconnect/DEVICE_NAME/"
|
File destinationFullPath;
|
||||||
String destinationDir = Environment.getExternalStorageDirectory().getPath();
|
DocumentFile destinationDocument;
|
||||||
destinationDir = new File(destinationDir, "kdeconnect").getPath();
|
|
||||||
destinationDir = new File(destinationDir, deviceDir).getPath();
|
final OutputStream destinationOutput;
|
||||||
|
final Uri destinationUri;
|
||||||
|
final String mimeType;
|
||||||
|
|
||||||
|
if (prefs.contains("share_destination_folder_uri")) {
|
||||||
|
Uri folderUri = Uri.parse(prefs.getString("share_destination_folder_uri", null));
|
||||||
|
final DocumentFile destinationFolderDocument = DocumentFile.fromTreeUri(context, folderUri);
|
||||||
|
String name = filename.substring(0, filename.lastIndexOf("."));
|
||||||
|
mimeType = FilesHelper.getMimeTypeFromFile(filename);
|
||||||
|
if (destinationFolderDocument.findFile("kdeconnect") == null) {
|
||||||
|
destinationFolderDocument.createDirectory("kdeconnect");
|
||||||
|
}
|
||||||
|
if (destinationFolderDocument.findFile("kdeconnect").findFile(deviceDir) == null) {
|
||||||
|
destinationFolderDocument.findFile("kdeconnect").createDirectory(deviceDir);
|
||||||
|
}
|
||||||
|
destinationDocument = destinationFolderDocument.findFile("kdeconnect")
|
||||||
|
.findFile(deviceDir)
|
||||||
|
.createFile(mimeType, name);
|
||||||
|
destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
|
||||||
|
destinationUri = destinationDocument.getUri();
|
||||||
|
} else {
|
||||||
|
//Get the external storage and append "/kdeconnect/DEVICE_NAME/"
|
||||||
|
String destinationDir = Environment.getExternalStorageDirectory().getPath();
|
||||||
|
destinationDir = new File(destinationDir, "kdeconnect").getPath();
|
||||||
|
destinationDir = new File(destinationDir, deviceDir).getPath();
|
||||||
|
|
||||||
|
//Create directories if needed
|
||||||
|
new File(destinationDir).mkdirs();
|
||||||
|
|
||||||
|
//Append filename to the destination path
|
||||||
|
|
||||||
|
//Log.e("SharePlugin", "destinationFullPath:" + destinationFullPath);
|
||||||
|
destinationFullPath = new File(destinationDir, filename);
|
||||||
|
destinationOutput = new FileOutputStream(destinationFullPath.getPath());
|
||||||
|
destinationUri = Uri.parse(destinationFullPath.toURI().toString());
|
||||||
|
mimeType = FilesHelper.getMimeTypeFromFile(destinationFullPath.getPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//Create directories if needed
|
|
||||||
new File(destinationDir).mkdirs();
|
|
||||||
|
|
||||||
//Append filename to the destination path
|
|
||||||
final File destinationFullPath = new File(destinationDir, filename);
|
|
||||||
|
|
||||||
//Log.e("SharePlugin", "destinationFullPath:" + destinationFullPath);
|
|
||||||
|
|
||||||
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
final NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||||
|
|
||||||
@@ -144,16 +182,14 @@ public class SharePlugin extends Plugin {
|
|||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
OutputStream output = null;
|
|
||||||
boolean successful = true;
|
boolean successful = true;
|
||||||
try {
|
try {
|
||||||
output = new FileOutputStream(destinationFullPath.getPath());
|
|
||||||
byte data[] = new byte[1024];
|
byte data[] = new byte[1024];
|
||||||
long progress = 0, prevProgressPercentage = 0;
|
long progress = 0, prevProgressPercentage = 0;
|
||||||
int count;
|
int count;
|
||||||
while ((count = input.read(data)) >= 0) {
|
while ((count = input.read(data)) >= 0) {
|
||||||
progress += count;
|
progress += count;
|
||||||
output.write(data, 0, count);
|
destinationOutput.write(data, 0, count);
|
||||||
if (fileLength > 0) {
|
if (fileLength > 0) {
|
||||||
if (progress >= fileLength) break;
|
if (progress >= fileLength) break;
|
||||||
long progressPercentage = (progress * 100 / fileLength);
|
long progressPercentage = (progress * 100 / fileLength);
|
||||||
@@ -166,14 +202,14 @@ public class SharePlugin extends Plugin {
|
|||||||
//else Log.e("SharePlugin", "Infinite loop? :D");
|
//else Log.e("SharePlugin", "Infinite loop? :D");
|
||||||
}
|
}
|
||||||
|
|
||||||
output.flush();
|
destinationOutput.flush();
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
successful = false;
|
successful = false;
|
||||||
Log.e("SharePlugin", "Receiver thread exception");
|
Log.e("SharePlugin", "Receiver thread exception");
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
} finally {
|
} finally {
|
||||||
try { output.close(); } catch (Exception e) {}
|
try { destinationOutput.close(); } catch (Exception e) {}
|
||||||
try { input.close(); } catch (Exception e) {}
|
try { input.close(); } catch (Exception e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +217,11 @@ public class SharePlugin extends Plugin {
|
|||||||
Log.i("SharePlugin", "Transfer finished");
|
Log.i("SharePlugin", "Transfer finished");
|
||||||
|
|
||||||
//Make sure it is added to the Android Gallery
|
//Make sure it is added to the Android Gallery
|
||||||
MediaStoreHelper.indexFile(context, Uri.fromFile(destinationFullPath));
|
MediaStoreHelper.indexFile(context, destinationUri);
|
||||||
|
|
||||||
//Update the notification and allow to open the file from it
|
//Update the notification and allow to open the file from it
|
||||||
Intent intent = new Intent(Intent.ACTION_VIEW);
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.setDataAndType(Uri.fromFile(destinationFullPath), FilesHelper.getMimeTypeFromFile(destinationFullPath.getPath()));
|
intent.setDataAndType(destinationUri, mimeType);
|
||||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
|
||||||
stackBuilder.addNextIntent(intent);
|
stackBuilder.addNextIntent(intent);
|
||||||
@@ -208,7 +244,7 @@ public class SharePlugin extends Plugin {
|
|||||||
.setContentIntent(resultPendingIntent);
|
.setContentIntent(resultPendingIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
|
|
||||||
if (prefs.getBoolean("share_notification_preference", true)) {
|
if (prefs.getBoolean("share_notification_preference", true)) {
|
||||||
builder.setDefaults(Notification.DEFAULT_ALL);
|
builder.setDefaults(Notification.DEFAULT_ALL);
|
||||||
}
|
}
|
||||||
@@ -281,6 +317,14 @@ public class SharePlugin extends Plugin {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void startPreferencesActivity(SettingsActivity parentActivity) {
|
||||||
|
Intent intent = new Intent(parentActivity, ShareSettingsActivity.class);
|
||||||
|
intent.putExtra("plugin_display_name", getDisplayName());
|
||||||
|
intent.putExtra("plugin_key", getPluginKey());
|
||||||
|
parentActivity.startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void queuedSendUriList(Context context, final Device device, final ArrayList<Uri> uriList) {
|
static void queuedSendUriList(Context context, final Device device, final ArrayList<Uri> uriList) {
|
||||||
|
|
||||||
|
@@ -0,0 +1,102 @@
|
|||||||
|
package org.kde.kdeconnect.Plugins.SharePlugin;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.v4.provider.DocumentFile;
|
||||||
|
import android.view.View;
|
||||||
|
import android.widget.Toast;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
|
||||||
|
import org.kde.kdeconnect_tp.R;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import static android.R.attr.id;
|
||||||
|
|
||||||
|
|
||||||
|
public class ShareSettingsActivity extends PluginSettingsActivity implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||||
|
SharedPreferences prefs;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
//addPreferencesFromResource(R.xml.fw_preferences); //deprecated
|
||||||
|
prefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
prefs.registerOnSharedPreferenceChangeListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static final int READ_REQUEST_CODE = 42;
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||||
|
if (!(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
|
||||||
|
// Sorry, only KitKat and up!
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.equals("share_destination_folder_preference"))
|
||||||
|
if (sharedPreferences.getBoolean("share_destination_folder_preference", false)) {
|
||||||
|
// ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file
|
||||||
|
// browser.
|
||||||
|
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
|
||||||
|
|
||||||
|
// Filter to only show results that can be "opened", such as a
|
||||||
|
// file (as opposed to a list of contacts or timezones)
|
||||||
|
//intent.addCategory(Intent.CATEGORY_OPENABLE);
|
||||||
|
|
||||||
|
// Filter to show only images, using the image MIME data type.
|
||||||
|
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
|
||||||
|
// To search for all documents available via installed storage providers,
|
||||||
|
// it would be "*/*".
|
||||||
|
//intent.setType("*/*");
|
||||||
|
|
||||||
|
startActivityForResult(intent, READ_REQUEST_CODE);
|
||||||
|
} else {
|
||||||
|
prefs.edit().remove("share_destination_folder_preference").apply();
|
||||||
|
}
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode,
|
||||||
|
Intent resultData) {
|
||||||
|
|
||||||
|
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
|
||||||
|
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
|
||||||
|
// response to some other intent, and the code below shouldn't run at all.
|
||||||
|
|
||||||
|
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
||||||
|
// The document selected by the user won't be returned in the intent.
|
||||||
|
// Instead, a URI to that document will be contained in the return intent
|
||||||
|
// provided to this method as a parameter.
|
||||||
|
// Pull that URI using resultData.getData().
|
||||||
|
Uri uri;
|
||||||
|
if (resultData != null) {
|
||||||
|
uri = resultData.getData();
|
||||||
|
|
||||||
|
|
||||||
|
// Check for the freshest data.
|
||||||
|
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
||||||
|
|
||||||
|
DocumentFile df = DocumentFile.fromTreeUri(getApplicationContext(),uri);
|
||||||
|
df.listFiles();
|
||||||
|
|
||||||
|
prefs.edit().putString("share_destination_folder_uri", uri.toString()).apply();
|
||||||
|
prefs.contains("share_destination_folder_uri");
|
||||||
|
|
||||||
|
|
||||||
|
//owImage(uri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Reference in New Issue
Block a user