diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6d93681ba..40cf7aada 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -585,6 +585,9 @@
+
\ No newline at end of file
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java
new file mode 100644
index 000000000..fa78dba20
--- /dev/null
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/AppsManagementActivity.java
@@ -0,0 +1,137 @@
+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.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.widget.PopupMenuCompat;
+import androidx.localbroadcastmanager.content.LocalBroadcastManager;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import nodomain.freeyourgadget.gadgetbridge.GBApplication;
+import nodomain.freeyourgadget.gadgetbridge.R;
+import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.DeviceType;
+import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.QHybridSupport;
+import nodomain.freeyourgadget.gadgetbridge.util.GB;
+
+public class AppsManagementActivity extends AbstractGBActivity {
+ ListView appsListView;
+ String[] appNames;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_qhybrid_apps_management);
+
+ initViews();
+ refreshInstalledApps();
+ }
+
+ private void toast(String data) {
+ GB.toast(data, Toast.LENGTH_LONG, GB.INFO);
+ }
+
+ private void refreshInstalledApps() {
+ try {
+ GBDevice selected = GBApplication.app().getDeviceManager().getSelectedDevice();
+ if (selected.getType() != DeviceType.FOSSILQHYBRID || !selected.isConnected() || !selected.getModel().startsWith("DN") || selected.getState() != GBDevice.State.INITIALIZED) {
+ throw new RuntimeException("Device not connected");
+ }
+ String installedAppsJson = selected.getDeviceInfo("INSTALLED_APPS").getDetails();
+ if (installedAppsJson == null || installedAppsJson.isEmpty()) {
+ throw new RuntimeException("cant get installed apps");
+ }
+ JSONArray apps = new JSONArray(installedAppsJson);
+ appNames = new String[apps.length()];
+ for (int i = 0; i < apps.length(); i++) {
+ appNames[i] = apps.getString(i);
+ }
+ appsListView.setAdapter(new AppsListAdapter(this, appNames));
+ } catch (Exception e) {
+ toast(e.getMessage());
+ finish();
+ return;
+ }
+ }
+
+ class AppsListAdapter extends ArrayAdapter {
+ public AppsListAdapter(@NonNull Context context, @NonNull String[] objects) {
+ super(context, 0, objects);
+ }
+
+ @NonNull
+ @Override
+ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ if (convertView == null) {
+ LayoutInflater inflater = (LayoutInflater.from(getContext()));
+ convertView = inflater.inflate(R.layout.fossil_hr_row_installed_app, null);
+ }
+ TextView nameView = convertView.findViewById(R.id.fossil_hr_row_app_name);
+ nameView.setText(getItem(position));
+ return nameView;
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ LocalBroadcastManager.getInstance(this).unregisterReceiver(deviceUpdateReceiver);
+ finish();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ LocalBroadcastManager.getInstance(this).registerReceiver(deviceUpdateReceiver, new IntentFilter(GBDevice.ACTION_DEVICE_CHANGED));
+ }
+
+ BroadcastReceiver deviceUpdateReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ refreshInstalledApps();
+ }
+ };
+
+ private void initViews() {
+ appsListView = findViewById(R.id.qhybrid_apps_list);
+ appsListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+ @Override
+ public void onItemClick(AdapterView> parent, View view, final int position, long id) {
+ PopupMenu menu = new PopupMenu(AppsManagementActivity.this, view);
+ menu.getMenu()
+ .add("uninstall")
+ .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_UNINSTALL_APP);
+ intent.putExtra("EXTRA_APP_NAME", appNames[position]);
+ LocalBroadcastManager.getInstance(AppsManagementActivity.this).sendBroadcast(intent);
+ return true;
+ }
+ });
+ menu.show();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java
index 4abb67ff3..0862b5ade 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/devices/qhybrid/HRConfigActivity.java
@@ -85,6 +85,7 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
findViewById(R.id.qhybrid_action_add).setOnClickListener(this);
findViewById(R.id.qhybrid_file_management_trigger).setOnClickListener(this);
+ findViewById(R.id.qhybrid_apps_management_trigger).setOnClickListener(this);
findViewById(R.id.calibration_trigger).setOnClickListener(this);
sharedPreferences = GBApplication.getPrefs().getPreferences();
@@ -422,6 +423,9 @@ public class HRConfigActivity extends AbstractGBActivity implements View.OnClick
} else if(v.getId() == R.id.qhybrid_file_management_trigger) {
finish();
startActivity(new Intent(getApplicationContext(), FileManagementActivity.class));
+ } else if(v.getId() == R.id.qhybrid_apps_management_trigger) {
+ finish();
+ startActivity(new Intent(getApplicationContext(), AppsManagementActivity.class));
} else if(v.getId() == R.id.calibration_trigger) {
finish();
startActivity(new Intent(getApplicationContext(), CalibrationActivity.class));
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 50deff187..6d750867b 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
@@ -96,6 +96,7 @@ public class QHybridSupport extends QHybridBaseSupport {
public static final String QHYBRID_COMMAND_SEND_MENU_ITEMS = "nodomain.freeyourgadget.gadgetbridge.Q_SEND_MENU_ITEMS";
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_UNINSTALL_APP = "nodomain.freeyourgadget.gadgetbridge.Q_UNINSTALL_APP";
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";
@@ -160,6 +161,7 @@ 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_UNINSTALL_APP);
commandFilter.addAction(QHYBRID_COMMAND_UPLOAD_FILE);
commandFilter.addAction(QHYBRID_COMMAND_DOWNLOAD_FILE);
commandReceiver = new BroadcastReceiver() {
@@ -286,6 +288,10 @@ public class QHybridSupport extends QHybridBaseSupport {
handleFileUploadIntent(intent);
break;
}
+ case QHYBRID_COMMAND_UNINSTALL_APP:{
+ watchAdapter.uninstallApp(intent.getStringExtra("EXTRA_APP_NAME"));
+ break;
+ }
}
}
};
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 737958119..5d062a635 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
@@ -153,4 +153,7 @@ public abstract class WatchAdapter {
public void factoryReset() {
}
+
+ public void uninstallApp(String appName) {
+ }
}
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 d51678745..ecd1296fb 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
@@ -67,6 +67,7 @@ import nodomain.freeyourgadget.gadgetbridge.entities.HybridHRActivitySample;
import nodomain.freeyourgadget.gadgetbridge.externalevents.NotificationListener;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec;
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec;
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec;
@@ -215,6 +216,19 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
this.initializeAfterAuthentication(success);
}
+ @Override
+ public void uninstallApp(String appName) {
+ for(ApplicationInformation appInfo : this.installedApplications){
+ if(appInfo.getAppName().equals(appName)){
+ byte handle = appInfo.getFileHandle();
+ short fullFileHandle = (short)((FileHandle.APP_CODE.getMajorHandle()) << 8 | handle);
+ queueWrite(new FileDeleteRequest(fullFileHandle));
+ listApplications();
+ break;
+ }
+ }
+ }
+
private void setVibrationStrength() {
Prefs prefs = new Prefs(getDeviceSpecificPreferences());
int vibrationStrengh = prefs.getInt(DeviceSettingsPreferenceConst.PREF_VIBRATION_STRENGH_PERCENTAGE, 2);
@@ -633,6 +647,10 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
LocalBroadcastManager.getInstance(getContext()).sendBroadcast(resultIntent);
}
});
+
+ if(handle == FileHandle.APP_CODE){
+ listApplications();
+ }
} catch (Exception e) {
e.printStackTrace();
resultIntent.putExtra("EXTRA_SUCCESS", false);
@@ -1159,19 +1177,20 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
// configs.add(new ButtonConfiguration("bottom_double_click", prefs.getString(DeviceSettingsPreferenceConst.PREF_BUTTON_3_FUNCTION_DOUBLE, "musicApp")));
// filter out all apps not installed on watch
+ ArrayList availableConfigs = new ArrayList<>();
outerLoop: for (ButtonConfiguration config : configs){
for(ApplicationInformation installedApp : installedApplications){
if(installedApp.getAppName().equals(config.getAction())){
+ availableConfigs.add(config);
continue outerLoop;
}
}
- configs.remove(config);
}
queueWrite(new ButtonConfigurationPutRequest(
menuItems,
- configs.toArray(new ButtonConfiguration[0]),
+ availableConfigs.toArray(new ButtonConfiguration[0]),
this
));
} catch (JSONException e) {
@@ -1242,7 +1261,7 @@ public class FossilHRWatchAdapter extends FossilWatchAdapter {
logger.info("got event id " + eventId);
try {
String jsonString = new String(value, 3, value.length - 3);
- logger.info(jsonString);
+ // logger.info(jsonString);
JSONObject requestJson = new JSONObject(jsonString);
JSONObject request = requestJson.getJSONObject("req");
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java
index 73f3a261a..6fb50507a 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationInformation.java
@@ -15,4 +15,8 @@ public class ApplicationInformation {
public String getAppName() {
return appName;
}
+
+ public byte getFileHandle() {
+ return fileHandle;
+ }
}
diff --git a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java
index 05f9e162a..5e6122405 100644
--- a/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java
+++ b/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/service/devices/qhybrid/requests/fossil_hr/application/ApplicationsListRequest.java
@@ -2,10 +2,15 @@ package nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fo
import android.content.pm.ApplicationInfo;
+import org.json.JSONArray;
+import org.json.JSONObject;
+
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
+import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
+import nodomain.freeyourgadget.gadgetbridge.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.adapter.fossil_hr.FossilHRWatchAdapter;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.requests.fossil.file.FileLookupAndGetRequest;
import nodomain.freeyourgadget.gadgetbridge.service.devices.qhybrid.file.FileHandle;
@@ -27,6 +32,7 @@ public class ApplicationsListRequest extends FileLookupAndGetRequest{
int nameLength = buffer.get() - 1; // cutting off null byte
byte[] nameBuffer = new byte[nameLength];
buffer.get(nameBuffer);
+ String name = new String(nameBuffer);
buffer.get(); // null byte
byte handle = buffer.get();
int hash = buffer.getInt();
@@ -35,13 +41,20 @@ public class ApplicationsListRequest extends FileLookupAndGetRequest{
buffer.get(), buffer.get(), buffer.get(), buffer.get()
);
applicationInfos.add(new ApplicationInformation(
- new String(nameBuffer),
+ name,
version,
hash,
handle
));
}
((FossilHRWatchAdapter) getAdapter()).setInstalledApplications(applicationInfos);
+ GBDevice device = getAdapter().getDeviceSupport().getDevice();
+ JSONArray array = new JSONArray();
+ for(ApplicationInformation info : applicationInfos){
+ array.put(info.getAppName());
+ }
+ device.addDeviceInfo(new GenericItem("INSTALLED_APPS", array.toString()));
+ device.sendDeviceUpdateIntent(getAdapter().getContext());
}
public void handleFileLookupError(FILE_LOOKUP_ERROR error){
diff --git a/app/src/main/res/layout/activity_qhybrid_apps_management.xml b/app/src/main/res/layout/activity_qhybrid_apps_management.xml
new file mode 100644
index 000000000..058e13597
--- /dev/null
+++ b/app/src/main/res/layout/activity_qhybrid_apps_management.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_qhybrid_hr_settings.xml b/app/src/main/res/layout/activity_qhybrid_hr_settings.xml
index 461306874..97db5de43 100644
--- a/app/src/main/res/layout/activity_qhybrid_hr_settings.xml
+++ b/app/src/main/res/layout/activity_qhybrid_hr_settings.xml
@@ -124,6 +124,12 @@
android:layout_height="wrap_content"
android:text="File management" />
+
+