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" /> +