1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-24 10:56:50 +01:00

Fossil Hybrid: added manual file down/upload

This commit is contained in:
Daniel Dakhno 2020-10-16 01:50:44 +02:00
parent 57f6b225d8
commit 3b2a16b6fc
9 changed files with 289 additions and 11 deletions

View File

@ -570,5 +570,8 @@
<activity
android:name=".devices.qhybrid.ImageEditActivity"
android:exported="true" />
<activity
android:name=".devices.qhybrid.FileManagementActivity"
android:exported="true" />
</application>
</manifest>

View File

@ -0,0 +1,124 @@
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ExpandableListView;
import android.widget.Spinner;
import android.widget.Switch;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import java.io.Serializable;
import java.nio.file.attribute.FileTime;
import no.nordicsemi.android.dfu.FileType;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
public class FileManagementActivity extends AbstractGBActivity implements View.OnClickListener {
private final int REQUEST_CODE_PICK_UPLOAD_FILE = 0;
private Spinner fileTypesSpinner;
private Switch encryptedFile;
BroadcastReceiver fileResultReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE)) {
boolean success = intent.getBooleanExtra("EXTRA_SUCCESS", false);
if (!success) {
Toast.makeText(FileManagementActivity.this, "error downloading file, check logcat", Toast.LENGTH_LONG).show();
return;
}
String path = intent.getStringExtra("EXTRA_PATH");
Toast.makeText(FileManagementActivity.this, "downloaded file " + path, Toast.LENGTH_LONG).show();
}else if(intent.getAction().equals(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE)) {
boolean success = intent.getBooleanExtra("EXTRA_SUCCESS", false);
if (!success) {
Toast.makeText(FileManagementActivity.this, "error uploading file, check logcat", Toast.LENGTH_LONG).show();
return;
}
Toast.makeText(FileManagementActivity.this, "uploaded file", Toast.LENGTH_LONG).show();
}
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_qhybrid_file_management);
initViews();
}
@Override
protected void onResume() {
super.onResume();
IntentFilter filter = new IntentFilter();
filter.addAction(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE);
filter.addAction(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
LocalBroadcastManager.getInstance(this).registerReceiver(fileResultReceiver, filter);
}
@Override
protected void onPause() {
super.onPause();
LocalBroadcastManager.getInstance(this).unregisterReceiver(fileResultReceiver);
}
private void initViews() {
FileHandle[] handles = FileHandle.values();
fileTypesSpinner = findViewById(R.id.qhybrid_file_types);
fileTypesSpinner.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, handles));
encryptedFile = findViewById(R.id.qhybrid_switch_encrypted_file);
findViewById(R.id.qhybrid_button_download_file).setOnClickListener(this);
findViewById(R.id.qhybrid_button_upload_file).setOnClickListener(this);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if(requestCode != REQUEST_CODE_PICK_UPLOAD_FILE) return;
if(resultCode != RESULT_OK) return;
Intent callIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_UPLOAD_FILE);
callIntent.putExtra("EXTRA_HANDLE", (FileHandle) fileTypesSpinner.getSelectedItem());
callIntent.putExtra("EXTRA_ENCRYPTED", encryptedFile.isChecked());
callIntent.putExtra("EXTRA_PATH", data.getData().getPath());
// callIntent.setData(data.getData());
LocalBroadcastManager.getInstance(this).sendBroadcast(callIntent);
}
@Override
public void onClick(View v) {
boolean isEncrypted = encryptedFile.isChecked();
if (v.getId() == R.id.qhybrid_button_download_file) {
Intent fileIntent = new Intent();
fileIntent.putExtra("EXTRA_ENCRYPTED", isEncrypted);
fileIntent.setAction(QHybridSupport.QHYBRID_COMMAND_DOWNLOAD_FILE);
fileIntent.putExtra("EXTRA_HANDLE", (FileHandle) fileTypesSpinner.getSelectedItem());
LocalBroadcastManager.getInstance(this).sendBroadcast(fileIntent);
} else if (v.getId() == R.id.qhybrid_button_upload_file) {
Intent chooserIntent = new Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT);
Intent intent = Intent.createChooser(chooserIntent, "Select a file");
startActivityForResult(intent, REQUEST_CODE_PICK_UPLOAD_FILE);
}
}
}

View File

@ -68,6 +68,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
setContentView(R.layout.activity_qhybrid_hr_settings);
findViewById(R.id.qhybrid_action_add).setOnClickListener(this);
findViewById(R.id.qhybrid_file_management_trigger).setOnClickListener(this);
sharedPreferences = GBApplication.getPrefs().getPreferences();
@ -401,6 +402,9 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
.setPositiveButton("ok", this)
.setTitle("create action")
.show();
} else if(v.getId() == R.id.qhybrid_file_management_trigger) {
finish();
startActivity(new Intent(getApplicationContext(), FileManagementActivity.class));
}
}

View File

@ -70,6 +70,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.Watc
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapterFactory;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.DownloadFileRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
@ -91,6 +92,12 @@ public class QHybridSupport extends QHybridBaseSupport {
public static final String QHYBRID_COMMAND_SET_WIDGET_CONTENT = "nodomain.freeyourgadget.gadgetbridge.Q_SET_WIDGET_CONTENT";
public static final String QHYBRID_COMMAND_SET_BACKGROUND_IMAGE = "nodomain.freeyourgadget.gadgetbridge.Q_SET_BACKGROUND_IMAGE";
public static final String QHYBRID_COMMAND_DOWNLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOAD_FILE";
public static final String QHYBRID_COMMAND_UPLOAD_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOAD_FILE";
public static final String QHYBRID_ACTION_DOWNLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_DOWNLOADED_FILE";
public static final String QHYBRID_ACTION_UPLOADED_FILE = "nodomain.freeyourgadget.gadgetbridge.Q_UPLOADED_FILE";
private static final String QHYBRID_ACTION_SET_ACTIVITY_HAND = "nodomain.freeyourgadget.gadgetbridge.Q_SET_ACTIVITY_HAND";
public static final String QHYBRID_EVENT_SETTINGS_UPDATED = "nodomain.freeyourgadget.gadgetbridge.Q_SETTINGS_UPDATED";
@ -146,6 +153,8 @@ public class QHybridSupport extends QHybridBaseSupport {
commandFilter.addAction(QHYBRID_COMMAND_UPDATE_WIDGETS);
commandFilter.addAction(QHYBRID_COMMAND_SEND_MENU_ITEMS);
commandFilter.addAction(QHYBRID_COMMAND_SET_BACKGROUND_IMAGE);
commandFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
commandFilter.addAction(QHYBRID_COMMAND_DOWNLOAD_FILE);
commandReceiver = new BroadcastReceiver() {
@Override
@ -237,6 +246,21 @@ public class QHybridSupport extends QHybridBaseSupport {
watchAdapter.setBackgroundImage(pixels);
break;
}
case QHYBRID_COMMAND_DOWNLOAD_FILE:{
Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE");
if(handleObject == null || !(handleObject instanceof FileHandle)) return;
FileHandle handle = (FileHandle) handleObject;
watchAdapter.downloadFile(handle, intent.getBooleanExtra("EXTRA_ENCRYPTED", false));
break;
}
case QHYBRID_COMMAND_UPLOAD_FILE:{
Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE");
if(handleObject == null || !(handleObject instanceof FileHandle)) return;
FileHandle handle = (FileHandle) handleObject;
String filePath = intent.getStringExtra("EXTRA_PATH");
watchAdapter.uploadFile(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false));
break;
}
}
}
};

View File

@ -30,6 +30,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.misfit.PlayNotificationRequest;
public abstract class WatchAdapter {
@ -138,4 +139,10 @@ public abstract class WatchAdapter {
public void onDeleteNotification(int id) {
}
public void downloadFile(FileHandle handle, boolean fileIsEncrypted) {
}
public void uploadFile(FileHandle handle, String filePath, boolean fileIsEncrypted) {
}
}

View File

@ -46,6 +46,7 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSuppo
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.WatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigFileBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.buttonconfig.ConfigPayload;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.FossilRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
@ -241,7 +242,7 @@ public class FossilWatchAdapter extends WatchAdapter {
ConfigFileBuilder builder = new ConfigFileBuilder(payloads);
FilePutRequest fileUploadRequets = new FilePutRequest((short) 0x0600, builder.build(true), this) {
FilePutRequest fileUploadRequets = new FilePutRequest(FileHandle.SETTINGS_BUTTONS, builder.build(true), this) {
@Override
public void onFilePut(boolean success) {
if (success)
@ -361,7 +362,7 @@ public class FossilWatchAdapter extends WatchAdapter {
@Override
public void onTestNewFunction() {
queueWrite(new FilePutRequest(
(short) 0x0600,
FileHandle.HAND_ACTIONS,
new byte[]{
(byte) 0x01, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x01, (byte) 0x24, (byte) 0x00, (byte) 0x85, (byte) 0x01, (byte) 0x30, (byte) 0x52, (byte) 0xFF, (byte) 0x26, (byte) 0x00, (byte) 0x03, (byte) 0x00, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0xA0, (byte) 0x00, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x05, (byte) 0x00, (byte) 0x93, (byte) 0x00, (byte) 0x02, (byte) 0x09, (byte) 0x04, (byte) 0x01, (byte) 0x03, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x00, (byte) 0x24, (byte) 0x00, (byte) 0x08, (byte) 0x01, (byte) 0x50, (byte) 0x00, (byte) 0x01, (byte) 0x00, (byte) 0x1F, (byte) 0xBE, (byte) 0xB4, (byte) 0x1B
},

View File

@ -3,7 +3,6 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fos
import android.bluetooth.BluetoothGattCharacteristic;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
@ -13,14 +12,19 @@ import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@ -29,11 +33,8 @@ import java.io.InputStream;
import java.nio.BufferOverflowException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.TimeZone;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -60,26 +61,26 @@ import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec;
import nodomain.freeyourgadget.gadgetbridge.service.btle.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil.FossilWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityEntry;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.parser.ActivityFileParser;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.Request;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.RequestMtuRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.SetDeviceStateRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.configuration.ConfigurationPutRequest.TimeConfigItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileDeleteRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayCallNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.notification.PlayTextNotificationRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.activity.ActivityFilesGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.async.ConfirmAppStatusRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.authentication.VerifyPrivateKeyRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfiguration;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.buttons.ButtonConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.configuration.ConfigurationPutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFile;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.AssetFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FileEncryptedGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FilePutRawRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.file.FirmwareFilePutRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImage;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.image.AssetImageFactory;
@ -478,7 +479,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
if (pushFiles.size() > 0) {
queueWrite(new AssetFilePutRequest(
pushFiles.toArray(new AssetImage[0]),
(byte) 0x00,
FileHandle.ASSET_BACKGROUND_IMAGES,
this
));
}
@ -492,6 +493,70 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
}
}
private void handleFileDownload(FileHandle handle, byte[] file){
Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_DOWNLOADED_FILE);
File outputFile = new File(getContext().getExternalFilesDir("download"), handle.name() + "_" + System.currentTimeMillis() + ".bin");
try {
FileOutputStream fos = new FileOutputStream(outputFile);
fos.write(file);
fos.close();
resultIntent.putExtra("EXTRA_SUCCESS", true);
resultIntent.putExtra("EXTRA_PATH", outputFile.getAbsolutePath());
} catch (IOException e) {
e.printStackTrace();
resultIntent.putExtra("EXTRA_SUCCESS", false);
}
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
}
@Override
public void uploadFile(FileHandle handle, String filePath, boolean fileIsEncrypted) {
final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE);
byte[] fileData;
try {
FileInputStream fis = new FileInputStream(filePath);
fileData = new byte[fis.available()];
fis.read(fileData);
fis.close();
} catch (IOException e) {
e.printStackTrace();
resultIntent.putExtra("EXTRA_SUCCESS", false);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
return;
}
queueWrite(new FilePutRawRequest(handle, fileData, this){
@Override
public void onFilePut(boolean success) {
resultIntent.putExtra("EXTRA_SUCCESS", success);
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
}
});
}
@Override
public void downloadFile(final FileHandle handle, boolean fileIsEncrypted) {
if(fileIsEncrypted){
negotiateSymmetricKey();
queueWrite(new FileEncryptedGetRequest(handle, this) {
@Override
public void handleFileData(byte[] fileData) {
logger.debug("downloaded encrypted file");
handleFileDownload(handle, fileData);
}
});
}else{
queueWrite(new FileGetRequest(handle, this) {
@Override
public void handleFileData(byte[] fileData) {
logger.debug("downloaded regular file");
handleFileDownload(handle, fileData);
}
});
}
}
@Override
public void setWidgetContent(String widgetID, String content, boolean renderOnWatch) {
boolean update = false;

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent" >
<Spinner
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/qhybrid_file_types" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:orientation="vertical">
<Switch
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="encrypted file"
android:id="@+id/qhybrid_switch_encrypted_file" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="download file"
android:id="@+id/qhybrid_button_download_file" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="upload file"
android:id="@+id/qhybrid_button_upload_file" />
</LinearLayout>
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -118,4 +118,11 @@
</LinearLayout>
<Button
android:id="@+id/qhybrid_file_management_trigger"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="File management" />
</LinearLayout>