Members of the KDE Community are recommended to subscribe to the kde-community mailing list at https://mail.kde.org/mailman/listinfo/kde-community to allow them to participate in important discussions and receive other important announcements

Commit 43ae02d6 authored by Nicolas Fella's avatar Nicolas Fella

[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
parent 085fb102
......@@ -257,6 +257,7 @@
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.DeviceSettingsActivity" />
</activity>
<activity android:name="org.kde.kdeconnect.Plugins.PhotoPlugin.PhotoActivity" />
</application>
......
<?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_images">Block images in notifications</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>
</resources>
......@@ -3,4 +3,5 @@
<external-path
name="external_files"
path="." />
<external-path name="images" path="Android/data/org.kde.kdeconnect_tp/files/Pictures" />
</paths>
......@@ -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 class FilesHelper {
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;
}
}
}
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();
}
}
/*
* 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);
}
}
......@@ -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 <http://www.gnu.org/licenses/>.
*/
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Plugins.PingPlugin;
......
......@@ -27,7 +27,6 @@ import android.util.Log;
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;
......
......@@ -40,6 +40,7 @@ import android.provider.MediaStore;
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 @@ public class SharePlugin extends Plugin {
//Read all the data early, as we only have permissions to do it while the activity is alive
final ArrayList<NetworkPacket> 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 @@ 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) {
Bundle extras = intent.getExtras();
if (extras != null) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment