mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-01 22:55:10 +00:00
[Android] Add photo plugin
Summary: When a request arrive open the camera and send the taken pic back. Reviewers: #kde_connect, broulik, albertvaka Reviewed By: #kde_connect, albertvaka Subscribers: albertvaka, ngraham, kdeconnect Tags: #kde_connect Differential Revision: https://phabricator.kde.org/D18142
This commit is contained in:
@@ -257,6 +257,7 @@
|
|||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
|
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity android:name="org.kde.kdeconnect.Plugins.PhotoPlugin.PhotoActivity" />
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
|
13
res/drawable/ic_camera.xml
Normal file
13
res/drawable/ic_camera.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M15.2 12A3.2 3.2 0 0 1 12 15.2 3.2 3.2 0 0 1 8.8 12 3.2 3.2 0 0 1 12 8.8 3.2 3.2 0 0 1 15.2 12Z"
|
||||||
|
android:fillColor="#000000" />
|
||||||
|
<path
|
||||||
|
android:pathData="M9 2L7.17 4 4 4C2.9 4 2 4.9 2 6l0 12c0 1.1 0.9 2 2 2l16 0c1.1 0 2 -0.9 2 -2L22 6C22 4.9 21.1 4 20 4L16.83 4 15 2 9 2Zm3 15C9.24 17 7 14.76 7 12 7 9.24 9.24 7 12 7c2.76 0 5 2.24 5 5 0 2.76 -2.24 5 -5 5z"
|
||||||
|
android:fillColor="#000000" />
|
||||||
|
</vector>
|
@@ -326,6 +326,8 @@
|
|||||||
<string name="block_contents">Block contents of notifications</string>
|
<string name="block_contents">Block contents of notifications</string>
|
||||||
<string name="block_images">Block images in notifications</string>
|
<string name="block_images">Block images in notifications</string>
|
||||||
<string name="notification_channel_receivenotification">Notifications from other devices</string>
|
<string name="notification_channel_receivenotification">Notifications from other devices</string>
|
||||||
|
<string name="take_picture">Take picture</string>
|
||||||
|
<string name="plugin_photo_desc">Take a picture and send it to another device</string>
|
||||||
|
|
||||||
<string name="findmyphone_preference_key_ringtone" translatable="false">findmyphone_ringtone</string>
|
<string name="findmyphone_preference_key_ringtone" translatable="false">findmyphone_ringtone</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -3,4 +3,5 @@
|
|||||||
<external-path
|
<external-path
|
||||||
name="external_files"
|
name="external_files"
|
||||||
path="." />
|
path="." />
|
||||||
|
<external-path name="images" path="Android/data/org.kde.kdeconnect_tp/files/Pictures" />
|
||||||
</paths>
|
</paths>
|
||||||
|
@@ -20,10 +20,18 @@
|
|||||||
|
|
||||||
package org.kde.kdeconnect.Helpers;
|
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.util.Log;
|
||||||
import android.webkit.MimeTypeMap;
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
public class FilesHelper {
|
public class FilesHelper {
|
||||||
|
|
||||||
@@ -100,4 +108,82 @@ public class FilesHelper {
|
|||||||
public static void LogOpenFileCount() {
|
public static void LogOpenFileCount() {
|
||||||
Log.e("KDE/FileCount", "" + GetOpenFileCount());
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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();
|
||||||
|
}
|
||||||
|
}
|
90
src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoPlugin.java
Normal file
90
src/org/kde/kdeconnect/Plugins/PhotoPlugin/PhotoPlugin.java
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2019 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@@ -16,7 +16,7 @@
|
|||||||
*
|
*
|
||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.kde.kdeconnect.Plugins.PingPlugin;
|
package org.kde.kdeconnect.Plugins.PingPlugin;
|
||||||
|
|
||||||
|
@@ -27,7 +27,6 @@ import android.util.Log;
|
|||||||
import org.atteo.classindex.ClassIndex;
|
import org.atteo.classindex.ClassIndex;
|
||||||
import org.atteo.classindex.IndexAnnotated;
|
import org.atteo.classindex.IndexAnnotated;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@@ -40,6 +40,7 @@ import android.provider.MediaStore;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Helpers.FilesHelper;
|
||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
@@ -240,7 +241,7 @@ public class SharePlugin extends Plugin {
|
|||||||
//Read all the data early, as we only have permissions to do it while the activity is alive
|
//Read all the data early, as we only have permissions to do it while the activity is alive
|
||||||
final ArrayList<NetworkPacket> toSend = new ArrayList<>();
|
final ArrayList<NetworkPacket> toSend = new ArrayList<>();
|
||||||
for (Uri uri : uriList) {
|
for (Uri uri : uriList) {
|
||||||
NetworkPacket np = uriToNetworkPacket(context, uri);
|
NetworkPacket np = FilesHelper.uriToNetworkPacket(context, uri, PACKET_TYPE_SHARE_REQUEST);
|
||||||
|
|
||||||
if (np != null) {
|
if (np != null) {
|
||||||
toSend.add(np);
|
toSend.add(np);
|
||||||
@@ -268,83 +269,6 @@ public class SharePlugin extends Plugin {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//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) {
|
public void share(Intent intent) {
|
||||||
Bundle extras = intent.getExtras();
|
Bundle extras = intent.getExtras();
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
|
Reference in New Issue
Block a user