diff --git a/AndroidManifest.xml b/AndroidManifest.xml --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -257,7 +257,8 @@ android:name="android.support.PARENT_ACTIVITY" android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" /> + \ No newline at end of file diff --git a/res/drawable/ic_camera.xml b/res/drawable/ic_camera.xml new file mode 100644 --- /dev/null +++ b/res/drawable/ic_camera.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -326,6 +326,8 @@ Block contents of notifications Block images in notifications Notifications from other devices + Take picture + Take a picture and send it to another device findmyphone_ringtone diff --git a/res/xml/fileprovider_paths.xml b/res/xml/fileprovider_paths.xml --- a/res/xml/fileprovider_paths.xml +++ b/res/xml/fileprovider_paths.xml @@ -3,4 +3,5 @@ + diff --git a/src/org/kde/kdeconnect/Helpers/FilesHelper.java b/src/org/kde/kdeconnect/Helpers/FilesHelper.java --- a/src/org/kde/kdeconnect/Helpers/FilesHelper.java +++ b/src/org/kde/kdeconnect/Helpers/FilesHelper.java @@ -20,10 +20,18 @@ package org.kde.kdeconnect.Helpers; +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.provider.MediaStore; import android.util.Log; import android.webkit.MimeTypeMap; +import org.kde.kdeconnect.NetworkPacket; + import java.io.File; +import java.io.InputStream; public class FilesHelper { @@ -100,4 +108,82 @@ public static void LogOpenFileCount() { Log.e("KDE/FileCount", "" + GetOpenFileCount()); } + + + //Create the network package from the URI + public static NetworkPacket uriToNetworkPacket(final Context context, final Uri uri, String type) { + + try { + + ContentResolver cr = context.getContentResolver(); + InputStream inputStream = cr.openInputStream(uri); + + NetworkPacket np = new NetworkPacket(type); + long size = -1; + + if (uri.getScheme().equals("file")) { + // file:// is a non media uri, so we cannot query the ContentProvider + + np.set("filename", uri.getLastPathSegment()); + + try { + size = new File(uri.getPath()).length(); + } catch (Exception e) { + Log.e("SendFileActivity", "Could not obtain file size"); + e.printStackTrace(); + } + + } else { + // Probably a content:// uri, so we query the Media content provider + + Cursor cursor = null; + try { + String[] proj = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME}; + cursor = cr.query(uri, proj, null, null, null); + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + cursor.moveToFirst(); + String path = cursor.getString(column_index); + np.set("filename", Uri.parse(path).getLastPathSegment()); + size = new File(path).length(); + } catch (Exception unused) { + + Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media"); + + try { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + cursor.moveToFirst(); + String name = cursor.getString(column_index); + np.set("filename", name); + } catch (Exception e) { + e.printStackTrace(); + Log.e("SendFileActivity", "Could not obtain file name"); + } + + try { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE); + cursor.moveToFirst(); + //For some reason this size can differ from the actual file size! + size = cursor.getInt(column_index); + } catch (Exception e) { + Log.e("SendFileActivity", "Could not obtain file size"); + e.printStackTrace(); + } + } finally { + try { + cursor.close(); + } catch (Exception ignored) { + } + } + + } + + np.setPayload(new NetworkPacket.Payload(inputStream, size)); + + return np; + } catch (Exception e) { + Log.e("SendFileActivity", "Exception creating network packet", e); + e.printStackTrace(); + return null; + } + } } diff --git a/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoActivity.java @@ -0,0 +1,72 @@ +package org.kde.kdeconnect.Plugins.PhotoPlugin; + +import android.content.Intent; +import android.net.Uri; +import android.os.Environment; +import android.provider.MediaStore; + +import org.kde.kdeconnect.BackgroundService; + +import java.io.File; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; + +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.FileProvider; + +public class PhotoActivity extends AppCompatActivity { + + private Uri photoURI; + private PhotoPlugin plugin; + + @Override + protected void onStart() { + super.onStart(); + + BackgroundService.runWithPlugin(this, getIntent().getStringExtra("deviceId"), PhotoPlugin.class, plugin -> { + this.plugin = plugin; + }); + + Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + if (takePictureIntent.resolveActivity(getPackageManager()) != null) { + File photoFile = null; + try { + photoFile = createImageFile(); + } catch (IOException ignored) { + } + // Continue only if the File was successfully created + if (photoFile != null) { + photoURI = FileProvider.getUriForFile(this, + "org.kde.kdeconnect_tp.fileprovider", + photoFile); + takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI); + startActivityForResult(takePictureIntent, 1); + } + } + + } + + private File createImageFile() throws IOException { + // Create an image file name + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); + String imageFileName = "JPEG_" + timeStamp + "_"; + File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); + return File.createTempFile( + imageFileName, /* prefix */ + ".jpg", /* suffix */ + storageDir /* directory */ + ); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (resultCode == -1) { + plugin.sendPhoto(photoURI); + } + finish(); + } +} diff --git a/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoPlugin.java b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoPlugin.java new file mode 100644 --- /dev/null +++ b/src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoPlugin.java @@ -0,0 +1,90 @@ +/* + * Copyright 2019 Nicolas Fella + * + * 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 . + */ + +package org.kde.kdeconnect.Plugins.PhotoPlugin; + +import android.content.Intent; +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import org.kde.kdeconnect.Helpers.FilesHelper; +import org.kde.kdeconnect.NetworkPacket; +import org.kde.kdeconnect.Plugins.Plugin; +import org.kde.kdeconnect_tp.R; + +import androidx.core.content.ContextCompat; + + +public class PhotoPlugin extends Plugin { + + private final static String PACKET_TYPE_PHOTO = "kdeconnect.photo"; + private final static String PACKET_TYPE_PHOTO_REQUEST = "kdeconnect.photo.request"; + + @Override + public String getDisplayName() { + return context.getResources().getString(R.string.take_picture); + } + + @Override + public String getDescription() { + return context.getResources().getString(R.string.plugin_photo_desc); + } + + @Override + public boolean onPacketReceived(NetworkPacket np) { + Intent intent = new Intent(context, PhotoActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra("deviceId", device.getDeviceId()); + context.startActivity(intent); + return true; + } + + void sendPhoto(Uri uri) { + NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_PHOTO); + if (np != null) { + device.sendPacket(np); + } + } + + @Override + public boolean hasMainActivity() { + return false; + } + + @Override + public boolean displayInContextMenu() { + return false; + } + + @Override + public String[] getSupportedPacketTypes() { + return new String[]{PACKET_TYPE_PHOTO_REQUEST}; + } + + @Override + public String[] getOutgoingPacketTypes() { + return new String[]{PACKET_TYPE_PHOTO}; + } + + @Override + public Drawable getIcon() { + return ContextCompat.getDrawable(context, R.drawable.ic_camera); + } +} diff --git a/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java b/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java --- a/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java @@ -15,8 +15,8 @@ * 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 . -*/ + * along with this program. If not, see . + */ package org.kde.kdeconnect.Plugins.PingPlugin; diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java --- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java +++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.java @@ -27,7 +27,6 @@ import org.atteo.classindex.ClassIndex; import org.atteo.classindex.IndexAnnotated; import org.kde.kdeconnect.Device; - import java.util.Collections; import java.util.HashSet; import java.util.Map; diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java --- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java +++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java @@ -40,6 +40,7 @@ import android.util.Log; import android.widget.Toast; +import org.kde.kdeconnect.Helpers.FilesHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; @@ -240,7 +241,7 @@ //Read all the data early, as we only have permissions to do it while the activity is alive final ArrayList toSend = new ArrayList<>(); for (Uri uri : uriList) { - NetworkPacket np = uriToNetworkPacket(context, uri); + NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_SHARE_REQUEST); if (np != null) { toSend.add(np); @@ -268,83 +269,6 @@ } - //Create the network package from the URI - private static NetworkPacket uriToNetworkPacket(final Context context, final Uri uri) { - - try { - - ContentResolver cr = context.getContentResolver(); - InputStream inputStream = cr.openInputStream(uri); - - NetworkPacket np = new NetworkPacket(PACKET_TYPE_SHARE_REQUEST); - long size = -1; - - if (uri.getScheme().equals("file")) { - // file:// is a non media uri, so we cannot query the ContentProvider - - np.set("filename", uri.getLastPathSegment()); - - try { - size = new File(uri.getPath()).length(); - } catch (Exception e) { - Log.e("SendFileActivity", "Could not obtain file size"); - e.printStackTrace(); - } - - } else { - // Probably a content:// uri, so we query the Media content provider - - Cursor cursor = null; - try { - String[] proj = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME}; - cursor = cr.query(uri, proj, null, null, null); - int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - cursor.moveToFirst(); - String path = cursor.getString(column_index); - np.set("filename", Uri.parse(path).getLastPathSegment()); - size = new File(path).length(); - } catch (Exception unused) { - - Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media"); - - try { - int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); - cursor.moveToFirst(); - String name = cursor.getString(column_index); - np.set("filename", name); - } catch (Exception e) { - e.printStackTrace(); - Log.e("SendFileActivity", "Could not obtain file name"); - } - - try { - int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE); - cursor.moveToFirst(); - //For some reason this size can differ from the actual file size! - size = cursor.getInt(column_index); - } catch (Exception e) { - Log.e("SendFileActivity", "Could not obtain file size"); - e.printStackTrace(); - } - } finally { - try { - cursor.close(); - } catch (Exception ignored) { - } - } - - } - - np.setPayload(new NetworkPacket.Payload(inputStream, size)); - - return np; - } catch (Exception e) { - Log.e("SendFileActivity", "Exception sending files"); - e.printStackTrace(); - return null; - } - } - public void share(Intent intent) { Bundle extras = intent.getExtras(); if (extras != null) {