Fossil Q: Move button config to device preferences

This commit is contained in:
Arjan Schrijver
2026-04-05 14:18:10 +02:00
committed by Arjan Schrijver
parent 5015e3a7b7
commit aa37f7bdce
9 changed files with 141 additions and 278 deletions
@@ -17,12 +17,8 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.qhybrid;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
@@ -32,11 +28,9 @@ 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.Button;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.RelativeLayout;
@@ -45,14 +39,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.json.JSONArray;
import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -63,12 +51,8 @@ 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.model.GenericItem;
import nodomain.freeyourgadget.gadgetbridge.model.ItemWithDetails;
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.buttonconfig.ConfigPayload;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
import nodomain.freeyourgadget.gadgetbridge.util.NotificationUtils;
@@ -82,9 +66,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
final int REQUEST_CODE_ADD_APP = 0;
private boolean hasControl = false;
SharedPreferences prefs;
private GBDevice device;
@Override
@@ -101,14 +82,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
setContentView(R.layout.activity_qhybrid_settings);
// findViewById(R.id.buttonOverwriteButtons).setOnClickListener(new View.OnClickListener() {
// @Override
// public void onClick(View view) {
// LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS));
// }
// });
setTitle(R.string.preferences_qhybrid_settings);
ListView appList = findViewById(R.id.qhybrid_appList);
@@ -123,21 +96,15 @@ public class QHybridConfigActivity extends AbstractGBActivity {
// null is added to indicate the plus button added handled in PackageAdapter#getView
list.add(null);
appList.setAdapter(adapter = new PackageAdapter(this, R.layout.qhybrid_package_settings_item, list));
appList.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(final AdapterView<?> adapterView, View view, final int i, long l) {
appList.setOnItemLongClickListener((adapterView, view, i, l) -> {
PopupMenu menu = new PopupMenu(QHybridConfigActivity.this, view);
menu.getMenu().add(0, 0, 0, "edit");
menu.getMenu().add(0, 1, 1, "delete");
menu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
menu.setOnMenuItemClickListener(menuItem -> {
switch (menuItem.getItemId()) {
case 0: {
TimePicker picker = new TimePicker(QHybridConfigActivity.this, (NotificationConfiguration) adapterView.getItemAtPosition(i));
picker.finishListener = new TimePicker.OnFinishListener() {
@Override
public void onFinish(boolean success, NotificationConfiguration config) {
picker.finishListener = (success, config) -> {
setControl(false, null);
if (success) {
try {
@@ -150,7 +117,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
}
refreshList();
}
}
};
picker.handsListener = QHybridConfigActivity.this::setHands;
picker.vibrationListener = QHybridConfigActivity.this::vibrate;
@@ -171,28 +137,17 @@ public class QHybridConfigActivity extends AbstractGBActivity {
}
}
return true;
}
});
menu.show();
return true;
}
});
appList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
appList.setOnItemClickListener((adapterView, view, i, l) -> {
Intent notificationIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_NOTIFICATION);
notificationIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
notificationIntent.putExtra("CONFIG", (NotificationConfiguration) adapterView.getItemAtPosition(i));
LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(notificationIntent);
}
});
if (device.getType() == DeviceType.FOSSILQHYBRID && device.isInitialized() && device.getFirmwareVersion().charAt(2) == '0') {
updateSettings();
} else {
setSettingsError(getString(R.string.watch_not_connected));
}
}
@NonNull
@@ -201,86 +156,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
return deviceInfo != null ? deviceInfo.getDetails() : "";
}
private void setSettingsEnabled(boolean enables) {
findViewById(R.id.settingsLayout).setAlpha(enables ? 1f : 0.2f);
}
private void updateSettings() {
runOnUiThread(new Runnable() {
@Override
public void run() {
ItemWithDetails item = device.getDeviceInfo(FossilWatchAdapter.ITEM_BUTTONS);
String buttonJson = null;
if(item != null) {
buttonJson = item.getDetails();
}
try {
JSONArray buttonConfig_;
if (buttonJson == null || buttonJson.isEmpty()) {
buttonConfig_ = new JSONArray(new String[]{"", "", ""});
}else{
buttonConfig_ = new JSONArray(buttonJson);
}
final JSONArray buttonConfig = buttonConfig_;
LinearLayout buttonLayout = findViewById(R.id.buttonConfigLayout);
buttonLayout.removeAllViews();
// findViewById(R.id.buttonOverwriteButtons).setVisibility(View.GONE);
final ConfigPayload[] payloads = ConfigPayload.values();
final String[] names = new String[payloads.length];
for (int i = 0; i < payloads.length; i++)
names[i] = payloads[i].getDescription();
for (int i = 0; i < buttonConfig.length(); i++) {
final int currentIndex = i;
String configName = buttonConfig.getString(i);
TextView buttonTextView = new TextView(QHybridConfigActivity.this);
buttonTextView.setTextSize(20);
try {
ConfigPayload payload = ConfigPayload.valueOf(configName);
buttonTextView.setText("Button " + (i + 1) + ": " + payload.getDescription());
} catch (IllegalArgumentException e) {
buttonTextView.setText("Button " + (i + 1) + ": Unknown");
}
buttonTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog dialog = new MaterialAlertDialogBuilder(QHybridConfigActivity.this)
.setItems(names, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.cancel();
ConfigPayload selected = payloads[which];
try {
buttonConfig.put(currentIndex, selected.toString());
device.addDeviceInfo(new GenericItem(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString()));
updateSettings();
Intent buttonIntent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
buttonIntent.putExtra(GBDevice.EXTRA_DEVICE, device);
buttonIntent.putExtra(FossilWatchAdapter.ITEM_BUTTONS, buttonConfig.toString());
LocalBroadcastManager.getInstance(QHybridConfigActivity.this).sendBroadcast(buttonIntent);
} catch (JSONException e) {
LOG.error("Failed to update button config", e);
}
}
})
.setTitle("Button " + (currentIndex + 1))
.create();
dialog.show();
}
});
buttonLayout.addView(buttonTextView);
}
} catch (JSONException e) {
GB.toast("error parsing button config", Toast.LENGTH_LONG, GB.ERROR, e);
}
}
});
}
private void setControl(boolean control, NotificationConfiguration config) {
if (hasControl == control) return;
Intent intent = new Intent(control ? QHybridSupport.QHYBRID_COMMAND_CONTROL : QHybridSupport.QHYBRID_COMMAND_UNCONTROL);
@@ -326,16 +201,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
protected void onResume() {
super.onResume();
refreshList();
ContextCompat.registerReceiver(this, buttonReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_BUTTON_PRESS), ContextCompat.RECEIVER_NOT_EXPORTED);
LocalBroadcastManager.getInstance(this).registerReceiver(fileReceiver, new IntentFilter(QHybridSupport.QHYBRID_EVENT_FILE_UPLOADED));
}
@Override
protected void onPause() {
super.onPause();
unregisterReceiver(buttonReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(settingsReceiver);
LocalBroadcastManager.getInstance(this).unregisterReceiver(fileReceiver);
}
@Override
@@ -354,17 +219,6 @@ public class QHybridConfigActivity extends AbstractGBActivity {
return super.onOptionsItemSelected(item);
}
private void setSettingsError(final String error) {
runOnUiThread(new Runnable() {
@Override
public void run() {
setSettingsEnabled(false);
findViewById(R.id.settingsErrorText).setVisibility(View.VISIBLE);
((TextView) findViewById(R.id.settingsErrorText)).setText(error);
}
});
}
class PackageAdapter extends ArrayAdapter<NotificationConfiguration> {
PackageAdapter(@NonNull Context context, int resource, @NonNull List<NotificationConfiguration> objects) {
super(context, resource, objects);
@@ -430,31 +284,4 @@ public class QHybridConfigActivity extends AbstractGBActivity {
return view;
}
}
BroadcastReceiver fileReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
boolean error = intent.getBooleanExtra("EXTRA_ERROR", false);
if (error) {
GB.toast(getString(R.string.qhybrid_buttons_overwrite_error), Toast.LENGTH_SHORT, GB.ERROR);
return;
}
GB.toast(getString(R.string.qhybrid_buttons_overwrite_success), Toast.LENGTH_SHORT, GB.INFO);
}
};
BroadcastReceiver buttonReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
GB.toast("Button " + intent.getIntExtra("BUTTON", -1) + " pressed", Toast.LENGTH_SHORT, GB.INFO);
}
};
BroadcastReceiver settingsReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
GB.toast("Setting updated", Toast.LENGTH_SHORT, GB.INFO);
updateSettings();
}
};
}
@@ -10,6 +10,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
@@ -48,6 +49,36 @@ public class QHybridSettingsCustomizer implements DeviceSpecificSettingsCustomiz
});
}
final ListPreference upperButtonPref = handler.findPreference("top_button_function");
if (upperButtonPref != null) {
upperButtonPref.setOnPreferenceChangeListener((preference, newValue) -> {
final Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice());
LocalBroadcastManager.getInstance(handler.getContext()).sendBroadcast(intent);
return true;
});
}
final ListPreference middleButtonPref = handler.findPreference("middle_button_function");
if (middleButtonPref != null) {
middleButtonPref.setOnPreferenceChangeListener((preference, newValue) -> {
final Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice());
LocalBroadcastManager.getInstance(handler.getContext()).sendBroadcast(intent);
return true;
});
}
final ListPreference bottomButtonPref = handler.findPreference("bottom_button_function");
if (bottomButtonPref != null) {
bottomButtonPref.setOnPreferenceChangeListener((preference, newValue) -> {
final Intent intent = new Intent(QHybridSupport.QHYBRID_COMMAND_OVERWRITE_BUTTONS);
intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice());
LocalBroadcastManager.getInstance(handler.getContext()).sendBroadcast(intent);
return true;
});
}
final Preference calibrationPref = handler.findPreference("pref_key_qhybrid_calibration");
if (calibrationPref != null) {
calibrationPref.setOnPreferenceClickListener(preference -> {
@@ -256,8 +256,7 @@ public class QHybridSupport extends QHybridBaseSupport {
break;
}
case QHYBRID_COMMAND_OVERWRITE_BUTTONS: {
String buttonConfig = intent.getStringExtra(FossilWatchAdapter.ITEM_BUTTONS);
watchAdapter.overwriteButtons(buttonConfig);
watchAdapter.overwriteButtons(null);
break;
}
case QHYBRID_COMMAND_NOTIFICATION_CONFIG_CHANGED: {
@@ -109,11 +109,9 @@ public class FossilWatchAdapter extends WatchAdapter {
private int MTU = 23;
private final String ITEM_MTU = "MTU: ";
static public final String ITEM_BUTTONS = "BUTTONS: ";
private final String CONFIG_ITEM_VIBRATION_STRENGTH = "vibration_strength";
private final String CONFIG_ITEM_TIMEZONE_OFFSET = "timezone_offset";
public final String CONFIG_ITEM_BUTTONS = "buttons";
private int lastButtonIndex = -1;
protected boolean saveRawActivityFiles = false;
@@ -227,9 +225,7 @@ public class FossilWatchAdapter extends WatchAdapter {
}
private void syncButtonSettings() {
String buttonConfig = getDeviceSpecificPreferences().getString(CONFIG_ITEM_BUTTONS, null);
getDeviceSupport().getDevice().addDeviceInfo(new GenericItem(ITEM_BUTTONS, buttonConfig));
overwriteButtons(buttonConfig);
overwriteButtons(null);
}
@Override
@@ -360,16 +356,16 @@ public class FossilWatchAdapter extends WatchAdapter {
@Override
public void overwriteButtons(String jsonConfigString) {
try {
if (jsonConfigString == null) return;
getDeviceSpecificPreferences()
.edit()
.putString(CONFIG_ITEM_BUTTONS, jsonConfigString)
.apply();
JSONArray buttonConfigJson = new JSONArray(jsonConfigString);
// JSONArray buttonConfigJson = new JSONArray(getDeviceSupport().getDevice().getDeviceInfo(ITEM_BUTTONS).getDetails());
String upperButtonFunction = getDeviceSpecificPreferences().getString("top_button_function", "");
String middleButtonFunction = getDeviceSpecificPreferences().getString("middle_button_function", "");
String bottomButtonFunction = getDeviceSpecificPreferences().getString("bottom_button_function", "");
JSONArray buttonConfigJson = new JSONArray();
buttonConfigJson.put(upperButtonFunction);
buttonConfigJson.put(middleButtonFunction);
buttonConfigJson.put(bottomButtonFunction);
LOG.info("overwriteButtons: " + buttonConfigJson);
ConfigPayload[] payloads = new ConfigPayload[buttonConfigJson.length()];
for (int i = 0; i < buttonConfigJson.length(); i++) {
try {
payloads[i] = ConfigPayload.valueOf(buttonConfigJson.getString(i));
@@ -380,7 +376,7 @@ public class FossilWatchAdapter extends WatchAdapter {
ConfigFileBuilder builder = new ConfigFileBuilder(payloads);
FilePutRequest fileUploadRequets = new FilePutRequest(FileHandle.SETTINGS_BUTTONS, builder.build(true), this) {
FilePutRequest fileUploadRequest = new FilePutRequest(FileHandle.SETTINGS_BUTTONS, builder.build(true), this) {
@Override
public void onFilePut(boolean success) {
if (success)
@@ -388,7 +384,7 @@ public class FossilWatchAdapter extends WatchAdapter {
else LOG.error("error overwriting button settings");
}
};
queueWrite(fileUploadRequets);
queueWrite(fileUploadRequest);
} catch (JSONException e) {
LOG.error("error", e);
}
@@ -5,52 +5,9 @@
android:layout_height="match_parent"
android:layout_margin="5dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:orientation="vertical"
android:id="@+id/settingsLayout"
android:focusableInTouchMode="true" >
<!-- <Button-->
<!-- android:id="@+id/buttonOverwriteButtons"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:text="@string/qhybrid_overwrite_buttons" />-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/buttonConfigLayout"
android:orientation="vertical"/>
</LinearLayout>
<!-- <ProgressBar
android:id="@+id/vibrationSettingProgressBar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="@id/settingsLayout"
android:layout_alignTop="@id/settingsLayout"
android:layout_centerHorizontal="true"
android:gravity="center" /> -->
<TextView
android:id="@+id/settingsErrorText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@id/settingsLayout"
android:layout_alignBottom="@id/settingsLayout"
android:layout_centerHorizontal="true"
android:visibility="gone"
android:gravity="center" />
<ListView
android:id="@+id/qhybrid_appList"
android:layout_marginTop="50dp"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_below="@id/settingsLayout"
android:layout_alignParentBottom="true"/>
android:layout_height="match_parent" />
</RelativeLayout>
+25
View File
@@ -5438,4 +5438,29 @@
<item>@string/health_connect_initial_sync_all</item>
</string-array>
<string-array name="qhybrid_button_functions">
<item>Forward to phone</item>
<item>Forward to phone (multifunction)</item>
<item>Stopwatch</item>
<item>Date</item>
<item>Last notification</item>
<item>Second timezone</item>
<item>Volume up</item>
<item>Volume down</item>
<item>Step goal progress</item>
</string-array>
<string-array name="qhybrid_button_functions_values">
<!-- These must be identical to the enum keys in ConfigPayload -->
<item>FORWARD_TO_PHONE</item>
<item>FORWARD_TO_PHONE_MULTI</item>
<item>STOPWATCH</item>
<item>DATE</item>
<item>LAST_NOTIFICATION</item>
<item>SECOND_TIMEZONE</item>
<item>VOLUME_UP</item>
<item>VOLUME_DOWN</item>
<item>STEP_GOAL_COMPLETION</item>
</string-array>
</resources>
+3
View File
@@ -4711,4 +4711,7 @@
<string name="health_connect_sleep_session_notes">Sleep data from Gadgetbridge device: %1$s.</string>
<string name="infinitime_filesystem_access_disabled">Filesystem access is disabled in InfiniTime</string>
<string name="infinitime_resource_upload_progress_status">Performing action %d of %d\n%s %s</string>
<string name="pref_title_qhybrid_top_button_function">Top button function</string>
<string name="pref_title_qhybrid_middle_button_function">Middle button function</string>
<string name="pref_title_qhybrid_bottom_button_function">Bottom button function</string>
</resources>
@@ -4,7 +4,7 @@
<Preference
android:title="@string/qhybrid_title_calibration"
android:key="pref_key_qhybrid_calibration"
android:icon="@drawable/ic_sensor_calibration"
android:icon="@drawable/ic_angle_calibrate"
android:summary="@string/qhybrid_summary_calibration">
</Preference>
@@ -1,23 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:key="top_button_function"
android:title="@string/pref_title_qhybrid_top_button_function"
android:icon="@drawable/ic_edit"
android:entries="@array/qhybrid_button_functions"
android:entryValues="@array/qhybrid_button_functions_values"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:key="middle_button_function"
android:title="@string/pref_title_qhybrid_middle_button_function"
android:icon="@drawable/ic_edit"
android:entries="@array/qhybrid_button_functions"
android:entryValues="@array/qhybrid_button_functions_values"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:key="bottom_button_function"
android:title="@string/pref_title_qhybrid_bottom_button_function"
android:icon="@drawable/ic_edit"
android:entries="@array/qhybrid_button_functions"
android:entryValues="@array/qhybrid_button_functions_values"
app:useSimpleSummaryProvider="true" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="use_activity_hand_as_notification_counter"
android:layout="@layout/preference_checkbox"
android:title="@string/qhybrid_use_activity_hand_as_notification_counter"
android:icon="@drawable/ic_timer" />
android:icon="@drawable/ic_notifications" />
<Preference
android:defaultValue="00:00"
android:key="time_offset"
android:title="@string/qhybrid_time_shift"
android:icon="@drawable/ic_timer" />
android:icon="@drawable/ic_access_time" />
<Preference
android:defaultValue="00:00"
android:key="second_tz_offset"
android:title="@string/qhybrid_second_timezone_offset_relative_to_utc"
android:icon="@drawable/ic_timer" />
android:icon="@drawable/ic_access_time" />
</androidx.preference.PreferenceScreen>