From ec77345632e601bebbecd67cbee17f109fcfa247 Mon Sep 17 00:00:00 2001 From: Daniel Dakhno Date: Sat, 30 Jan 2021 21:10:25 +0100 Subject: [PATCH] Fossil Hybrid: file handle can be passed to service as string --- .../qhybrid/FileManagementActivity.java | 58 ++++++++++-- .../devices/qhybrid/QHybridSupport.java | 37 ++++---- .../devices/qhybrid/adapter/WatchAdapter.java | 5 +- .../fossil_hr/FossilHRWatchAdapter.java | 93 ++++++++++++++----- .../devices/qhybrid/file/FileHandle.java | 9 ++ .../buttons/ButtonConfiguration.java | 4 + .../activity_qhybrid_file_management.xml | 10 +- .../res/xml/devicesettings_fossilhybridhr.xml | 6 ++ 8 files changed, 175 insertions(+), 47 deletions(-) diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java index 755a6bcd3..ebae3ba97 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/FileManagementActivity.java @@ -16,6 +16,7 @@ along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid; +import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -23,6 +24,7 @@ import android.content.IntentFilter; import android.os.Bundle; import android.view.View; import android.widget.ArrayAdapter; +import android.widget.CompoundButton; import android.widget.ExpandableListView; import android.widget.Spinner; import android.widget.Switch; @@ -31,6 +33,9 @@ import android.widget.Toast; import androidx.annotation.Nullable; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; import java.io.Serializable; import java.nio.file.attribute.FileTime; @@ -39,12 +44,16 @@ 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; +import nodomain.freeyourgadget.gadgetbridge.util.GB; public class FileManagementActivity extends AbstractGBActivity implements View.OnClickListener { private final int REQUEST_CODE_PICK_UPLOAD_FILE = 0; private Spinner fileTypesSpinner; private Switch encryptedFile; + private boolean generateFileHeader = false; + + private boolean warningDisplayed = false; BroadcastReceiver fileResultReceiver = new BroadcastReceiver() { @Override @@ -100,6 +109,15 @@ public class FileManagementActivity extends AbstractGBActivity implements View.O findViewById(R.id.qhybrid_button_download_file).setOnClickListener(this); findViewById(R.id.qhybrid_button_upload_file).setOnClickListener(this); + + ((Switch) findViewById(R.id.sqhybrid_switch_generate_file_header)).setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + generateFileHeader = isChecked; + fileTypesSpinner.setClickable(isChecked); + fileTypesSpinner.setAlpha(isChecked ? 1f : 0.2f); + } + }); } @Override @@ -108,13 +126,41 @@ public class FileManagementActivity extends AbstractGBActivity implements View.O 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()); + String path = data.getData().getPath(); - LocalBroadcastManager.getInstance(this).sendBroadcast(callIntent); + try { + if(!warningDisplayed) { + FileInputStream fis = new FileInputStream(path); + short fileHandle = (short) (fis.read() | (fis.read() << 8)); + fis.close(); + + boolean handleFound = FileHandle.fromHandle(fileHandle) != null; + if (handleFound == generateFileHeader) { + warningDisplayed = true; + String text = "File seems to contain file handle. Are you sure you want to generate a potentially already existing header?"; + if(!handleFound) text = "File does not start with a known handle. Are you sure the header is already generated?"; + text += " Repeat to continue anyway."; + new AlertDialog.Builder(this) + .setTitle("warning") + .setMessage(text) + .setPositiveButton("ok", null) + .show(); + 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_GENERATE_FILE_HEADER", generateFileHeader); + callIntent.putExtra("EXTRA_PATH", data.getData().getPath()); + // callIntent.setData(data.getData()); + + LocalBroadcastManager.getInstance(this).sendBroadcast(callIntent); + } catch (IOException e) { + e.printStackTrace(); + GB.toast("cannot open file", Toast.LENGTH_LONG, GB.ERROR); + } } @Override diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java index 39a517c66..50deff187 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/QHybridSupport.java @@ -283,15 +283,7 @@ public class QHybridSupport extends QHybridBaseSupport { break; } case QHYBRID_COMMAND_UPLOAD_FILE:{ - Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE"); - if(handleObject == null)return; - if(handleObject instanceof String){ - handleObject = FileHandle.fromName((String)handleObject); - } - if(!(handleObject instanceof FileHandle)) return; - FileHandle handle = (FileHandle) handleObject; - String filePath = intent.getStringExtra("EXTRA_PATH"); - watchAdapter.uploadFile(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false)); + handleFileUploadIntent(intent); break; } } @@ -369,15 +361,7 @@ public class QHybridSupport extends QHybridBaseSupport { break; } case QHYBRID_COMMAND_UPLOAD_FILE:{ - Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE"); - if(handleObject == null)return; - if(handleObject instanceof String){ - handleObject = FileHandle.fromName((String)handleObject); - } - if(!(handleObject instanceof FileHandle)) return; - FileHandle handle = (FileHandle) handleObject; - String filePath = intent.getStringExtra("EXTRA_PATH"); - watchAdapter.uploadFile(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false)); + handleFileUploadIntent(intent); break; } } @@ -386,6 +370,23 @@ public class QHybridSupport extends QHybridBaseSupport { GBApplication.getContext().registerReceiver(globalCommandReceiver, globalFilter); } + private void handleFileUploadIntent(Intent intent){ + boolean generateHeader = intent.getBooleanExtra("EXTRA_GENERATE_FILE_HEADER", false); + String filePath = intent.getStringExtra("EXTRA_PATH"); + if(!generateHeader){ + watchAdapter.uploadFileIncludesHeader(filePath); + return; + } + Object handleObject = intent.getSerializableExtra("EXTRA_HANDLE"); + if(handleObject == null)return; + if(handleObject instanceof String){ + handleObject = FileHandle.fromName((String)handleObject); + } + if(!(handleObject instanceof FileHandle)) return; + FileHandle handle = (FileHandle) handleObject; + watchAdapter.uploadFileGenerateHeader(handle, filePath, intent.getBooleanExtra("EXTRA_ENCRYPTED", false)); + } + @Override public void onSetCallState(CallSpec callSpec) { super.onSetCallState(callSpec); diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java index ee7c62908..737958119 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/WatchAdapter.java @@ -145,7 +145,10 @@ public abstract class WatchAdapter { public void downloadFile(FileHandle handle, boolean fileIsEncrypted) { } - public void uploadFile(FileHandle handle, String filePath, boolean fileIsEncrypted) { + public void uploadFileGenerateHeader(FileHandle handle, String filePath, boolean fileIsEncrypted) { + } + + public void uploadFileIncludesHeader(String filePath){ } public void factoryReset() { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java index cf2af3460..7c6c7e3cd 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/adapter/fossil_hr/FossilHRWatchAdapter.java @@ -49,6 +49,7 @@ import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -71,6 +72,7 @@ import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec; import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec; import nodomain.freeyourgadget.gadgetbridge.model.Weather; import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec; +import nodomain.freeyourgadget.gadgetbridge.service.btle.Transaction; 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; @@ -83,8 +85,11 @@ import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fos import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileGetRawRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupRequest; import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FilePutRawRequest; +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.application.ApplicationInformation; +import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil_hr.application.ApplicationsListRequest; 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; @@ -141,6 +146,8 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { HashMap appIconCache = new HashMap<>(); String lastPostedApp = null; + List installedApplications; + enum CONNECTION_MODE { NOT_INITIALIZED, AUTHENTICATED, @@ -157,6 +164,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { queueWrite(new RequestMtuRequest(512)); } + listApplications(); getDeviceInfos(); } @@ -173,6 +181,15 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { negotiateSymmetricKey(); } + private void listApplications(){ + queueWrite(new ApplicationsListRequest(this) { + @Override + public void handleApplicationsList(List applications) { + installedApplications = applications; + } + }); + } + private void initializeAfterAuthentication(boolean authenticated) { queueWrite(new SetDeviceStateRequest(GBDevice.State.INITIALIZING)); @@ -186,10 +203,9 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { setVibrationStrength(); syncSettings(); setTime(); - overwriteButtons(null); } - + overwriteButtons(null); loadBackground(); loadWidgets(); // renderWidgets(); @@ -568,7 +584,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { } @Override - public void uploadFile(FileHandle handle, String filePath, boolean fileIsEncrypted) { + public void uploadFileGenerateHeader(FileHandle handle, String filePath, boolean fileIsEncrypted) { final Intent resultIntent = new Intent(QHybridSupport.QHYBRID_ACTION_UPLOADED_FILE); byte[] fileData; @@ -584,7 +600,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { return; } - queueWrite(new FilePutRawRequest(handle, fileData, this) { + queueWrite(new FilePutRequest(handle, fileData, this) { @Override public void onFilePut(boolean success) { resultIntent.putExtra("EXTRA_SUCCESS", success); @@ -593,6 +609,38 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { }); } + @Override + public void uploadFileIncludesHeader(String filePath) { + 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(); + + short handleBytes = (short)(fileData[0] & 0xFF | ((fileData[1] & 0xFF) << 8)); + FileHandle handle = FileHandle.fromHandle(handleBytes); + + if(handle == null){ + throw new RuntimeException("unknown handle"); + } + + queueWrite(new FilePutRawRequest(handle, fileData, this) { + @Override + public void onFilePut(boolean success) { + resultIntent.putExtra("EXTRA_SUCCESS", success); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent); + } + }); + } catch (Exception e) { + e.printStackTrace(); + resultIntent.putExtra("EXTRA_SUCCESS", false); + LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent); + } + } + @Override public void downloadFile(final FileHandle handle, boolean fileIsEncrypted) { if (fileIsEncrypted) { @@ -1081,10 +1129,6 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { @Override public void overwriteButtons(String jsonConfigString) { - if (connectionMode == CONNECTION_MODE.NOT_AUTHENTICATED) { - GB.toast("not available in unauthenticated mode", Toast.LENGTH_LONG, GB.ERROR); - return; - } try { JSONArray jsonArray = new JSONArray( GBApplication.getPrefs().getString(HRConfigActivity.CONFIG_KEY_Q_ACTIONS, "[]") @@ -1104,24 +1148,31 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter { if (version.compareTo(new Version("1.0.2.19")) == -1) singlePressEvent = "single_click"; } + ArrayList configs = new ArrayList<>(5); + configs.add(new ButtonConfiguration("top_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT, "weatherApp"))); + configs.add(new ButtonConfiguration("top_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG, "weatherApp"))); + // configs.add(new ButtonConfiguration("top_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE, "weatherApp"))); + configs.add(new ButtonConfiguration("middle_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT, "commuteApp"))); + // configs.add(new ButtonConfiguration("middle_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG, "commuteApp"))); + // configs.add(new ButtonConfiguration("middle_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE, "commuteApp"))); + configs.add(new ButtonConfiguration("bottom_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT, "musicApp"))); + configs.add(new ButtonConfiguration("bottom_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG, "musicApp"))); + // configs.add(new ButtonConfiguration("bottom_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE, "musicApp"))); - ButtonConfiguration[] buttonConfigurations = new ButtonConfiguration[]{ - new ButtonConfiguration("top_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_SHORT, "weatherApp")), - new ButtonConfiguration("top_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_LONG, "weatherApp")), - new ButtonConfiguration("top_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_1_FUNCTION_DOUBLE, "weatherApp")), + // filter out all apps not installed on watch + outerLoop: for (ButtonConfiguration config : configs){ + for(ApplicationInformation installedApp : installedApplications){ + if(installedApp.getAppName().equals(config.getAction())){ + continue outerLoop; + } + } + configs.remove(config); + } - new ButtonConfiguration("middle_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_SHORT, "commuteApp")), - // new ButtonConfiguration("middle_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_LONG, "commuteApp")), - new ButtonConfiguration("middle_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_2_FUNCTION_DOUBLE, "commuteApp")), - - new ButtonConfiguration("bottom_" + singlePressEvent, prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_SHORT, "musicApp")), - new ButtonConfiguration("bottom_hold", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_LONG, "musicApp")), - new ButtonConfiguration("bottom_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE, "musicApp")), - }; queueWrite(new ButtonConfigurationPutRequest( menuItems, - buttonConfigurations, + configs.toArray(new ButtonConfiguration[0]), this )); } catch (JSONException e) { diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java index d6054fecc..6ad4f3c09 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/file/FileHandle.java @@ -57,6 +57,15 @@ public enum FileHandle { return null; } + public static FileHandle fromHandle(short handleBytes){ + for(FileHandle handle : FileHandle.values()){ + if(handle.getHandle() == handleBytes){ + return handle; + } + } + return null; + } + public short getHandle(){ return (short)((handle << 8) | (subHandle)); } diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java index e320d6ebf..4dd6a31c0 100644 --- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java +++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/buttons/ButtonConfiguration.java @@ -28,6 +28,10 @@ public class ButtonConfiguration { this.action = action; } + public String getAction() { + return action; + } + public JSONObject toJsonObject(){ try { return new JSONObject() diff --git a/app/src/main/res/layout/activity_qhybrid_file_management.xml b/app/src/main/res/layout/activity_qhybrid_file_management.xml index 6fd1fe1f1..2f9968e1d 100644 --- a/app/src/main/res/layout/activity_qhybrid_file_management.xml +++ b/app/src/main/res/layout/activity_qhybrid_file_management.xml @@ -10,7 +10,9 @@ + android:id="@+id/qhybrid_file_types" + android:alpha="0.2" + android:clickable="false"/> + +