/* Copyright (C) 2024 José Rebelo This file is part of Gadgetbridge. Gadgetbridge is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Gadgetbridge is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ package nodomain.freeyourgadget.gadgetbridge.devices.garmin; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Bundle; import android.text.InputFilter; import android.text.InputType; import android.widget.ArrayAdapter; import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.fragment.app.FragmentActivity; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.DialogPreference; import androidx.preference.EditTextPreference; import androidx.preference.ListPreference; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import androidx.preference.SwitchPreferenceCompat; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.protobuf.InvalidProtocolBufferException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import nodomain.freeyourgadget.gadgetbridge.BuildConfig; import nodomain.freeyourgadget.gadgetbridge.GBApplication; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSettingsService; import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; import nodomain.freeyourgadget.gadgetbridge.util.StringUtils; import nodomain.freeyourgadget.gadgetbridge.util.XDatePreference; import nodomain.freeyourgadget.gadgetbridge.util.XTimePreference; import nodomain.freeyourgadget.gadgetbridge.util.preferences.MinMaxTextWatcher; public class GarminRealtimeSettingsFragment extends AbstractPreferenceFragment { private static final Logger LOG = LoggerFactory.getLogger(GarminRealtimeSettingsFragment.class); public static final String EXTRA_SCREEN_ID = "screenId"; public static final String PREF_DEBUG = "garmin_rt_debug_mode"; public static final int ROOT_SCREEN_ID = 36352; static final String FRAGMENT_TAG = "GARMIN_REALTIME_SETTINGS_FRAGMENT"; private GBDevice device; private int screenId = ROOT_SCREEN_ID; private GdiSettingsService.ScreenDefinition screenDefinition; private GdiSettingsService.ScreenState screenState; public static final String EXTRA_PROTOBUF = "protobuf"; public static final String ACTION_SCREEN_DEFINITION = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.screen_definition"; public static final String ACTION_SCREEN_STATE = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.screen_state"; public static final String ACTION_CHANGE = "nodomain.freeyourgadget.gadgetbridge.garmin.realtime_settings.change"; private final BroadcastReceiver mReceiver = new BroadcastReceiver() { @Override public void onReceive(final Context context, final Intent intent) { final String action = intent.getAction(); if (action == null) { LOG.error("Got null action"); return; } switch (action) { case ACTION_SCREEN_DEFINITION: final GdiSettingsService.ScreenDefinition incomingScreen; try { incomingScreen = GdiSettingsService.ScreenDefinition.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF)); } catch (final InvalidProtocolBufferException e) { // should never happen LOG.error("Failed to parse protobuf for screen definition on {}", screenId, e); return; } if (incomingScreen.getScreenId() != screenId) { return; } LOG.debug("Got screen definition for screenId={}", screenId); screenDefinition = incomingScreen; break; case ACTION_SCREEN_STATE: final GdiSettingsService.ScreenState incomingState; try { incomingState = GdiSettingsService.ScreenState.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF)); } catch (final InvalidProtocolBufferException e) { // should never happen LOG.error("Failed to parse protobuf for screen state on {}", screenId, e); return; } if (incomingState.getScreenId() != screenId) { return; } LOG.debug("Got screen state for screenId={}", screenId); screenState = incomingState; break; case ACTION_CHANGE: final GdiSettingsService.ChangeResponse incomingChange; try { incomingChange = GdiSettingsService.ChangeResponse.parseFrom(intent.getByteArrayExtra(EXTRA_PROTOBUF)); } catch (final InvalidProtocolBufferException e) { // should never happen LOG.error("Failed to parse protobuf for change", e); return; } if (incomingChange.getState().getScreenId() != screenId) { return; } if (incomingChange.getShouldReturn()) { LOG.debug("Returning from {}", screenId); requireActivity().finish(); return; } LOG.debug("Got screen change for screenId={}", screenId); GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId); return; default: LOG.error("Unknown action {}", action); return; } reload(); } }; private void setDevice(final GBDevice device) { final Bundle args = getArguments() != null ? getArguments() : new Bundle(); args.putParcelable("device", device); setArguments(args); } private void setScreenId(final int screenId) { final Bundle args = getArguments() != null ? getArguments() : new Bundle(); args.putInt("screenId", screenId); setArguments(args); } @Override public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { final Bundle arguments = getArguments(); if (arguments == null) { return; } this.device = arguments.getParcelable(GBDevice.EXTRA_DEVICE); if (device == null) { return; } this.screenId = arguments.getInt(EXTRA_SCREEN_ID, ROOT_SCREEN_ID); if (screenId == 0) { return; } LOG.info("Opened realtime preferences screen for {}", screenId); getPreferenceManager().setSharedPreferencesName("garmin_rt_" + device.getAddress()); setPreferencesFromResource(R.xml.garmin_realtime_settings, rootKey); final IntentFilter filter = new IntentFilter(); filter.addAction(ACTION_SCREEN_DEFINITION); filter.addAction(ACTION_SCREEN_STATE); filter.addAction(ACTION_CHANGE); LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mReceiver, filter); GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId); } @Override public void onResume() { super.onResume(); reload(); } @Override public void onDestroyView() { LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mReceiver); super.onDestroyView(); } static GarminRealtimeSettingsFragment newInstance(final GBDevice device, final int screenId) { final GarminRealtimeSettingsFragment fragment = new GarminRealtimeSettingsFragment(); fragment.setDevice(device); fragment.setScreenId(screenId); return fragment; } void refreshFromDevice() { screenDefinition = null; screenState = null; reload(); GBApplication.deviceService(device).onReadConfiguration("screenId:" + screenId); } void reload() { final boolean debug = GBApplication.getDevicePrefs(device).getBoolean(PREF_DEBUG, BuildConfig.DEBUG); final FragmentActivity activity = getActivity(); if (activity == null) { LOG.error("Activity is null"); return; } final PreferenceScreen prefScreen = findPreference(GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS); if (prefScreen == null) { LOG.error("Preference screen for {} is null", GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS); activity.finish(); return; } if (screenDefinition == null || screenState == null) { ((GarminRealtimeSettingsActivity) activity).setActionBarTitle(activity.getString(R.string.loading)); // Disable all existing preferences while loading for (int i = 0; i < prefScreen.getPreferenceCount(); i++) { prefScreen.getPreference(i).setEnabled(false); } return; } prefScreen.removeAll(); if (debug) { final Preference pref = new PreferenceCategory(activity); pref.setIconSpaceReserved(false); pref.setTitle("Screen ID: " + screenId); pref.setPersistent(false); pref.setKey("rt_pref_header_" + screenId); prefScreen.addPreference(pref); } // Update the screen title, if any if (screenDefinition.hasTitle()) { final String title = screenDefinition.getTitle().getText(); ((GarminRealtimeSettingsActivity) activity).setActionBarTitle(title); } final Map stateById = new HashMap<>(); for (final GdiSettingsService.EntryState state : screenState.getStateList()) { stateById.put(state.getId(), state); } for (final GdiSettingsService.ScreenEntry entry : screenDefinition.getEntryList()) { final GdiSettingsService.EntryState state = stateById.get(entry.getId()); final Preference pref; boolean supported = true; if (entry.hasTarget()) { switch (entry.getTarget().getType()) { case 0: // subscreen case 9: // subscreen with options for a specific preference pref = new Preference(activity); pref.setOnPreferenceClickListener(preference -> { final Intent newIntent = new Intent(requireContext(), GarminRealtimeSettingsActivity.class); newIntent.putExtra(GBDevice.EXTRA_DEVICE, device); newIntent.putExtra(GarminRealtimeSettingsActivity.EXTRA_SCREEN_ID, entry.getTarget().getSubscreen()); activity.startActivityForResult(newIntent, 0); return true; }); break; case 1: // list preference pref = new ListPreference(activity); final CharSequence[] entries = new String[entry.getTarget().getOptions().getOptionList().size()]; int optionIndex = 0; for (final GdiSettingsService.TargetOptionEntry option : entry.getTarget().getOptions().getOptionList()) { entries[optionIndex++] = option.getTitle().getText(); } final ListPreference listPreference = (ListPreference) pref; listPreference.setEntries(entries); listPreference.setEntryValues(entries); listPreference.setValue(entries[Objects.requireNonNull(state).getSummary().getValueList().getIndex()].toString()); listPreference.setOnPreferenceChangeListener((preference, newValue) -> { int newValueIdx = -1; for (int i = 0; i < entries.length; i++) { if (entries[i].equals(newValue.toString())) { newValueIdx = i; break; } } if (newValueIdx < 0) { LOG.error("Failed to find index for {}", newValue); return false; } pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setOption(GdiSettingsService.ChangeRequest.Option.newBuilder() .setIndex(newValueIdx) ) ); return true; }); break; case 3: // time pref = new XTimePreference(activity, null); ((XTimePreference) pref).setValue( Objects.requireNonNull(state).getSummary().getValueTime().getSeconds() / 3600, (Objects.requireNonNull(state).getSummary().getValueTime().getSeconds() % 3600) / 60 ); if (state.getSummary().getValueTime().hasTimeFormat()) { final int timeFormat = state.getSummary().getValueTime().getTimeFormat(); switch (timeFormat) { case 0: // 12h ((XTimePreference) pref).setFormat(XTimePreference.Format.FORMAT_12H); break; case 1: // 24h ((XTimePreference) pref).setFormat(XTimePreference.Format.FORMAT_24H); break; } } pref.setSummary(state.getSummary().getValueDate().getSubtitle().getText()); pref.setOnPreferenceChangeListener((preference, newValue) -> { final String[] pieces = newValue.toString().split(":"); final int hour = Integer.parseInt(pieces[0]); final int minute = Integer.parseInt(pieces[1]); pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setTime(GdiSettingsService.ChangeRequest.Time.newBuilder() .setSeconds(hour * 3600 + minute * 60) ) ); return true; }); break; case 5: // number picker pref = new EditTextPreference(activity); ((EditTextPreference) pref).setText(String.valueOf(state.getSummary().getValueNumber().getValue())); ((EditTextPreference) pref).setSummary(state.getSummary().getValueNumber().getSubtitle().getText()); ((EditTextPreference) pref).setOnBindEditTextListener(p -> { p.setInputType(InputType.TYPE_CLASS_NUMBER); int minValue = Integer.MIN_VALUE; int maxValue = Integer.MAX_VALUE; if (entry.getTarget().getNumberPicker().hasMin()) { minValue = entry.getTarget().getNumberPicker().getMin(); } if (entry.getTarget().getNumberPicker().hasMax()) { maxValue = entry.getTarget().getNumberPicker().getMax(); } p.addTextChangedListener(new MinMaxTextWatcher(p, minValue, maxValue)); p.setSelection(p.getText().length()); }); ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> { final int newValueInt = Integer.parseInt(newValue.toString()); pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setNumber(GdiSettingsService.ChangeRequest.Number.newBuilder() .setValue(newValueInt) ) ); return true; }); break; case 6: // activity switch (entry.getTarget().getActivity()) { case 2: // garmin pay case 7: // text responses case 8: // music providers case 17: // Solar Intensity case 29: // Set Up ECG App case 30: // ECG pref = new Preference(activity); pref.setVisible(debug); pref.setEnabled(false); break; default: supported = false; pref = new Preference(activity); break; } break; case 7: // hidden? pref = new Preference(activity); pref.setVisible(debug); pref.setEnabled(false); break; case 10: // date picker pref = new XDatePreference(activity, null); ((XDatePreference) pref).setValue( Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getYear(), Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getMonth(), Objects.requireNonNull(state).getSummary().getValueDate().getCurrentDate().getDay() ); if (state.getSummary().getValueDate().hasMinDate()) { final Calendar calendar = GregorianCalendar.getInstance(); calendar.set( state.getSummary().getValueDate().getMinDate().getYear(), state.getSummary().getValueDate().getMinDate().getMonth() - 1, state.getSummary().getValueDate().getMinDate().getDay() ); ((XDatePreference) pref).setMinDate(calendar.getTimeInMillis()); } if (state.getSummary().getValueDate().hasMaxDate()) { final Calendar calendar = GregorianCalendar.getInstance(); calendar.set( state.getSummary().getValueDate().getMaxDate().getYear(), state.getSummary().getValueDate().getMaxDate().getMonth() - 1, state.getSummary().getValueDate().getMaxDate().getDay() ); ((XDatePreference) pref).setMaxDate(calendar.getTimeInMillis()); } pref.setSummary(state.getSummary().getValueDate().getSubtitle().getText()); pref.setOnPreferenceChangeListener((preference, newValue) -> { final String[] pieces = newValue.toString().split("-"); final int year = Integer.parseInt(pieces[0]); final int month = Integer.parseInt(pieces[1]); final int day = Integer.parseInt(pieces[2]); pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setNewDate(GdiSettingsService.ChangeRequest.NewDate.newBuilder() .setValue(GdiSettingsService.Date.newBuilder() .setYear(year).setMonth(month).setDay(day) ) ) ); return true; }); break; case 12: // Connect IQ Store pref = new Preference(activity); pref.setVisible(debug); pref.setEnabled(false); break; case 13: // height pref = new EditTextPreference(activity); ((EditTextPreference) pref).setText(String.valueOf(state.getSummary().getValueHeight().getValue())); ((EditTextPreference) pref).setSummary(state.getSummary().getValueHeight().getSubtitle().getText()); if (state.getSummary().getValueHeight().getUnit() == 0) { ((EditTextPreference) pref).setDialogTitle(R.string.activity_prefs_height_cm); ((EditTextPreference) pref).setTitle(R.string.activity_prefs_height_cm); } else { ((EditTextPreference) pref).setDialogTitle(R.string.activity_prefs_height_inches); ((EditTextPreference) pref).setTitle(R.string.activity_prefs_height_inches); } ((EditTextPreference) pref).setOnBindEditTextListener(p -> { p.setInputType(InputType.TYPE_CLASS_NUMBER); p.addTextChangedListener(new MinMaxTextWatcher(p, 0, 300)); p.setSelection(p.getText().length()); }); ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> { final int newValueInt = Integer.parseInt(newValue.toString()); pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setHeight(GdiSettingsService.ChangeRequest.Height.newBuilder() .setValue(newValueInt) .setUnit(state.getSummary().getValueHeight().getUnit()) ) ); return true; }); break; default: supported = false; pref = new Preference(activity); } } else { // No target switch (entry.getType()) { case 0: // notice pref = new Preference(activity); pref.setSummary(entry.getTitle().getText()); break; case 1: // category pref = new PreferenceCategory(activity); break; case 2: // space pref = new PreferenceCategory(activity); pref.setTitle(""); break; case 3: // switch pref = new SwitchPreferenceCompat(activity); pref.setLayoutResource(R.layout.preference_checkbox); ((SwitchPreferenceCompat) pref).setChecked(Objects.requireNonNull(state).getSwitch().getEnabled()); ((SwitchPreferenceCompat) pref).setSummary(Objects.requireNonNull(state).getSwitch().getTitle().getText()); pref.setOnPreferenceChangeListener((preference, newValue) -> { pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setSwitch(GdiSettingsService.ChangeRequest.Switch.newBuilder() .setValue((Boolean) newValue) ) ); return true; }); break; case 4: // single line + optional icon case 5: // double line pref = new Preference(activity); break; case 18: // single line with action (eg. glances) case 7: // single line, normally in list for selection? pref = new Preference(activity); pref.setOnPreferenceClickListener(preference -> { pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) ); return true; }); break; case 8: // device + status? case 9: // finish setup case 10: // find my device case 11: // preferred activity tracker case 13: // help & info case 24: // available accessories? pref = new Preference(activity); pref.setVisible(debug); pref.setEnabled(false); break; case 15: // sortable + delete // Add all sortable items and then continue final String moveUpStr = activity.getString(R.string.widget_move_up); final String moveDownStr = activity.getString(R.string.widget_move_down); final String deleteStr = activity.getString(R.string.appmananger_app_delete); for (int i = 0; i < entry.getSortOptions().getEntriesCount(); i++) { final GdiSettingsService.SortEntry sortEntry = entry.getSortOptions().getEntries(i); final List sortableOptions = new ArrayList<>(3); if (i > 0) { sortableOptions.add(moveUpStr); } if (i < entry.getSortOptions().getEntriesCount() - 1) { sortableOptions.add(moveDownStr); } sortableOptions.add(deleteStr); final ArrayAdapter sortOptionsAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, sortableOptions); final int iFinal = i; final Preference sortPref = new Preference(activity); sortPref.setTitle(sortEntry.getTitle().getText()); sortPref.setPersistent(false); sortPref.setIconSpaceReserved(false); sortPref.setKey("rt_pref_" + screenId + "_" + entry.getId() + "__" + sortEntry.getId()); sortPref.setOnPreferenceClickListener(preference -> { new MaterialAlertDialogBuilder(activity) .setTitle(sortPref.getTitle()) .setAdapter(sortOptionsAdapter, (dialogInterface, j) -> { final String option = sortableOptions.get(j); int moveOffset = 0; if (option.equals(moveUpStr)) { moveOffset = -1; } else if (option.equals(moveDownStr)) { moveOffset = 1; } if (moveOffset != 0) { sortPref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(sortEntry.getId()) .setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder() .setIndex(iFinal + moveOffset) ) ); return; } if (option.equals(deleteStr)) { sortPref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(sortEntry.getId()) .setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder() .setDelete(true) ) ); } }).setNegativeButton(android.R.string.cancel, null) .create().show(); return true; }); prefScreen.addPreference(sortPref); } continue; // We already added all options above, continue case 16: // text pref = new EditTextPreference(activity); ((EditTextPreference) pref).setOnBindEditTextListener(p -> { int maxValue = Integer.MAX_VALUE; if (entry.getTextOption().hasLimits() && entry.getTextOption().getLimits().hasMaxLength()) { p.setFilters(new InputFilter[]{new InputFilter.LengthFilter(entry.getTextOption().getLimits().getMaxLength())}); } p.setSelection(p.getText().length()); }); ((EditTextPreference) pref).setOnPreferenceChangeListener((preference, newValue) -> { if (StringUtils.isNullOrEmpty(newValue.toString())) { return true; } pref.setEnabled(false); sendChangeRequest( GdiSettingsService.ChangeRequest.newBuilder() .setScreenId(screenId) .setEntryId(entry.getId()) .setText(GdiSettingsService.ChangeRequest.Text.newBuilder() .setValue(newValue.toString()) ) ); return true; }); break; default: supported = false; pref = new Preference(activity); } } if (StringUtils.isNullOrEmpty(pref.getTitle()) && entry.getType() != 0 && entry.getType() != 2) { pref.setTitle(!StringUtils.isEmpty(entry.getTitle().getText()) ? entry.getTitle().getText() : activity.getString(R.string.unknown)); if (pref instanceof DialogPreference) { ((DialogPreference) pref).setDialogTitle(pref.getTitle()); } } final int icon = getIcon(entry); if (icon != 0) { pref.setIcon(icon); } else { pref.setIconSpaceReserved(false); } if (state != null && !StringUtils.isEmpty(state.getSummary().getTitle().getText())) { pref.setSummary(state.getSummary().getTitle().getText()); } if (state != null && state.hasState()) { switch (state.getState()) { case 1: pref.setVisible(false); break; case 2: pref.setEnabled(false); break; default: LOG.warn("Unknown state value {}", state.getState()); } } if (!supported) { pref.setEnabled(false); if (StringUtils.isNullOrEmpty(pref.getSummary())) { pref.setSummary(R.string.unsupported); } else { pref.setSummary(activity.getString(R.string.menuitem_unsupported, pref.getSummary())); } } if (debug) { final StringBuilder sb = new StringBuilder(); if (pref.getSummary() != null && pref.getSummary().length() != 0) { sb.append(pref.getSummary()).append("\n"); } sb.append("id=").append(entry.getId()); sb.append(", type=").append(entry.getType()); if (icon == 0 && entry.hasIcon()) { sb.append(", icon=").append(entry.getIcon()); } if (entry.hasTarget()) { sb.append(", targetType=").append(entry.getTarget().getType()); if (entry.getTarget().hasActivity()) { sb.append(", targetActivity=").append(entry.getTarget().getActivity()); } } pref.setSummary(sb.toString()); } pref.setPersistent(false); pref.setKey("rt_pref_" + screenId + "_" + entry.getId()); prefScreen.addPreference(pref); } // If no preferences after the last visible preference category are visible, hide it boolean previousWasVisible = false; PreferenceCategory lastSeenCategory = null; for (int i = prefScreen.getPreferenceCount() - 1; i >= 0; i--) { final Preference pref = prefScreen.getPreference(i); if (pref instanceof PreferenceCategory) { lastSeenCategory = (PreferenceCategory) pref; if (!previousWasVisible) { lastSeenCategory.setVisible(false); } previousWasVisible = false; } else { previousWasVisible |= pref.isVisible(); } } if (!previousWasVisible && lastSeenCategory != null) { lastSeenCategory.setVisible(false); } } @DrawableRes private int getIcon(final GdiSettingsService.ScreenEntry entry) { if (entry.hasIcon()) { switch (entry.getIcon()) { // // Main menu case 20: // Garmin Pay return 0; case 21: // Text Responses return R.drawable.ic_reply; case 4: // Clocks return R.drawable.ic_access_time; case 2: // Glances return R.drawable.ic_widgets; case 3: // Controls return R.drawable.ic_menu; case 1: // Activities / Apps, have the same icon return R.drawable.ic_activity_unknown_small; case 39: // Shortcut return R.drawable.ic_shortcut; case 27: // Notifications & Alerts return R.drawable.ic_notifications; case 30: // Wrist heart rate frequency return R.drawable.ic_heartrate; case 38: // Alarms return R.drawable.ic_access_alarms; case 5: // Sensors & accessories case 46: // Watch Sensors return R.drawable.ic_sensor_calibration; case 47: // Accessories return R.drawable.ic_bluetooth_searching; case 6: // Map return R.drawable.ic_map; case 7: // Music return R.drawable.ic_music_note; case 8: // Phone return R.drawable.ic_phone; case 11: // Connectivity return R.drawable.ic_bluetooth_searching; case 13: // Audio Prompts case 60: // Sound & Vibe return R.drawable.ic_volume_up; case 61: // Display & Brightness return R.drawable.ic_wb_sunny; case 62: // Focus Modes return R.drawable.ic_focus; case 14: // User Profile return R.drawable.ic_person; case 15: // Safety & Tracking return R.drawable.ic_emergency; case 16: // Activity Tracking return R.drawable.ic_activity_unknown_small; case 17: // Navigation return R.drawable.ic_navigation; case 18: // Power manager return R.drawable.ic_battery; case 19: // System return R.drawable.ic_settings; case 26: // Appearance return R.drawable.ic_paint; case 44: // Health & wellness return R.drawable.ic_health; // // Sortable screens (glances, apps, etc) case 33: return R.drawable.ic_add_gray; } } return 0; } void toggleDebug() { final Prefs prefs = GBApplication.getDevicePrefs(device); prefs.getPreferences().edit() .putBoolean(PREF_DEBUG, !prefs.getBoolean(PREF_DEBUG, BuildConfig.DEBUG)) .apply(); reload(); } void shareDebug() { final Intent intent = new Intent(android.content.Intent.ACTION_SEND); intent.setType("text/plain"); final StringBuilder sb = new StringBuilder(); sb.append("screenId: ").append(screenId); sb.append("\n"); sb.append("settingsScreen: "); if (screenDefinition != null) { sb.append(GB.hexdump(screenDefinition.toByteArray())); } else { sb.append("null"); } sb.append("\n"); sb.append("settingsState: "); if (screenState != null) { sb.append(GB.hexdump(screenState.toByteArray())); } else { sb.append("null"); } sb.append("\n"); intent.putExtra(android.content.Intent.EXTRA_SUBJECT, "Garmin Settings Screen " + screenId); intent.putExtra(android.content.Intent.EXTRA_TEXT, sb.toString()); try { startActivity(Intent.createChooser(intent, "Share debug info")); } catch (final ActivityNotFoundException e) { Toast.makeText(requireContext(), "Failed to share text", Toast.LENGTH_LONG).show(); } } private void sendChangeRequest(final GdiSettingsService.ChangeRequest.Builder changeRequest) { screenDefinition = null; screenState = null; final GdiSmartProto.Smart smart = GdiSmartProto.Smart.newBuilder() .setSettingsService(GdiSettingsService.SettingsService.newBuilder() .setChangeRequest(changeRequest) ).build(); GBApplication.deviceService(device).onSendConfiguration("protobuf:" + GB.hexdump(smart.toByteArray())); } }