AAWireless: Experimental support

This commit is contained in:
José Rebelo
2025-06-17 21:12:33 +01:00
parent 91ae4bffd2
commit 6b97a53d80
39 changed files with 1964 additions and 103 deletions
+1
View File
@@ -1,6 +1,7 @@
<component name="ProjectDictionaryState">
<dictionary name="t">
<words>
<w>aawireless</w>
<w>abettenburg</w>
<w>adhikary</w>
<w>afanasev</w>
@@ -325,6 +325,12 @@ public class DeviceSettingsPreferenceConst {
public static final String WIFI_HOTSPOT_STOP = "wifi_hotspot_stop";
public static final String WIFI_HOTSPOT_STATUS = "wifi_hotspot_status";
public static final String PREF_WIFI_FREQUENCY = "wifi_frequency";
public static final String PREF_WIFI_CHANNEL_2_4 = "wifi_channel_2_4";
public static final String PREF_WIFI_CHANNEL_5 = "wifi_channel_5";
public static final String PREF_COUNTRY = "country";
public static final String PREF_APP_LOGS_START = "pref_app_logs_start";
public static final String PREF_APP_LOGS_STOP = "pref_app_logs_stop";
public static final String PREF_DEVICE_LOGS_TOGGLE = "device_logs_enabled";
@@ -16,6 +16,7 @@
along with this program. If not, see <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities.devicesettings;
import android.content.Context;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
@@ -24,6 +25,9 @@ import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.MultiSelectListPreference;
import androidx.preference.Preference;
import androidx.preference.SwitchPreferenceCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -241,4 +245,40 @@ public final class DeviceSettingsUtils {
return "";
}
}
public static void addConfirmablePreferenceHandlerFor(final DeviceSpecificSettingsHandler handler,
final String preferenceKey,
final int alertMessage) {
final Preference pref = handler.findPreference(preferenceKey);
if (pref == null) {
return;
}
pref.setOnPreferenceChangeListener((preference, newValue) -> {
final Context context = handler.getContext();
final CharSequence preferenceName = pref.getTitle();
final String title = context.getString(R.string.earfun_change_confirm_title, preferenceName);
final String message = context.getString(alertMessage);
new MaterialAlertDialogBuilder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(handler.getContext().getString(R.string.ok), (dialog, which) -> {
if (pref instanceof EditTextPreference) {
((EditTextPreference) pref).setText(newValue.toString());
} else if (pref instanceof ListPreference) {
((ListPreference) pref).setValue(newValue.toString());
} else if (pref instanceof SwitchPreferenceCompat) {
((SwitchPreferenceCompat) pref).setChecked((Boolean) newValue);
} else {
LOG.error("Unsupported preference type {} for confirmable handler: {}", pref.getClass(), pref);
return;
}
handler.notifyPreferenceChanged(preferenceKey);
})
.setNegativeButton(handler.getContext().getString(R.string.Cancel), (dialog, which) -> dialog.dismiss())
.create()
.show();
return false;
});
}
}
@@ -49,4 +49,13 @@ public interface DeviceSpecificSettingsCustomizer extends Parcelable {
* Keys of preferences which should print its values as a summary below the preference name.
*/
Set<String> getPreferenceKeysWithSummary();
/**
* Called when the associated {@link nodomain.freeyourgadget.gadgetbridge.impl.GBDevice} changes.
*
* @param handler the {@link DeviceSpecificSettingsHandler}
*/
default void onDeviceChanged(final DeviceSpecificSettingsHandler handler) {
// Nothing to do by default
}
}
@@ -38,12 +38,18 @@ import static nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandConst.PR
import static nodomain.freeyourgadget.gadgetbridge.devices.moyoung.MoyoungConstants.PREF_MOYOUNG_DEVICE_VERSION;
import static nodomain.freeyourgadget.gadgetbridge.devices.moyoung.MoyoungConstants.PREF_MOYOUNG_WATCH_FACE;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.os.Bundle;
import android.text.InputType;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
@@ -124,6 +130,42 @@ public class DeviceSpecificSettingsFragment extends AbstractPreferenceFragment i
setArguments(args);
}
private final BroadcastReceiver mDeviceUpdateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(final Context context, final Intent intent) {
if (GBDevice.ACTION_DEVICE_CHANGED.equals(intent.getAction())) {
final GBDevice changedDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
if (changedDevice != null && changedDevice.equals(device)) {
device = changedDevice; // update the state
if (deviceSpecificSettingsCustomizer != null) {
LOG.debug("{} changed, notifying customizer", changedDevice);
deviceSpecificSettingsCustomizer.onDeviceChanged(DeviceSpecificSettingsFragment.this);
}
}
}
}
};
@NonNull
@Override
public View onCreateView(@NonNull final LayoutInflater inflater,
final ViewGroup container,
final Bundle savedInstanceState) {
final View view = super.onCreateView(inflater, container, savedInstanceState);
final IntentFilter commandFilter = new IntentFilter();
commandFilter.addAction(GBDevice.ACTION_DEVICE_CHANGED);
LocalBroadcastManager.getInstance(requireContext()).registerReceiver(mDeviceUpdateReceiver, commandFilter);
return view;
}
@Override
public void onDestroyView() {
LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(mDeviceUpdateReceiver);
super.onDestroyView();
}
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
Bundle arguments = getArguments();
@@ -21,6 +21,7 @@ import androidx.annotation.XmlRes;
import nodomain.freeyourgadget.gadgetbridge.R;
public enum DeviceSpecificSettingsScreen {
ADVANCED_SETTINGS("pref_screen_advanced_settings", R.xml.devicesettings_root_advanced_settings),
ACTIVITY_INFO("pref_screen_activity_info", R.xml.devicesettings_root_activity_info),
AUDIO("pref_screen_audio", R.xml.devicesettings_root_audio),
AUTHENTICATION("pref_screen_authentication", R.xml.devicesettings_root_authentication),
@@ -0,0 +1,61 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.adapter;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import java.util.List;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.model.RunnableListIconItem;
public class SimpleIconListAdapter extends ArrayAdapter<RunnableListIconItem> {
private final Context context;
private final List<RunnableListIconItem> items;
public SimpleIconListAdapter(final Context context, final List<RunnableListIconItem> items) {
super(context, R.layout.simple_list_item_with_icon, items);
this.context = context;
this.items = items;
}
@NonNull
@Override
public View getView(final int position, View convertView, @NonNull final ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(context);
convertView = inflater.inflate(R.layout.simple_list_item_with_icon, parent, false);
}
final ImageView icon = convertView.findViewById(R.id.option_icon);
final TextView title = convertView.findViewById(R.id.option_title);
final RunnableListIconItem item = items.get(position);
icon.setImageResource(item.getIconResId());
title.setText(item.getTitle());
return convertView;
}
}
@@ -0,0 +1,112 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.aawireless;
import androidx.annotation.NonNull;
import java.util.List;
import java.util.regex.Pattern;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.GBException;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettings;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsScreen;
import nodomain.freeyourgadget.gadgetbridge.devices.AbstractBLClassicDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.entities.DaoSession;
import nodomain.freeyourgadget.gadgetbridge.entities.Device;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.BatteryConfig;
import nodomain.freeyourgadget.gadgetbridge.service.DeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.devices.aawireless.AAWirelessPrefs;
import nodomain.freeyourgadget.gadgetbridge.service.devices.aawireless.AAWirelessSupport;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
public class AAWirelessCoordinator extends AbstractBLClassicDeviceCoordinator {
@Override
public boolean isExperimental() {
return true;
}
@Override
protected void deleteDevice(@NonNull final GBDevice gbDevice,
@NonNull final Device device,
@NonNull final DaoSession session) throws GBException {
}
@Override
protected Pattern getSupportedDeviceName() {
// AAW 1 - AAWireless-xUkL1YH0
// AAW 2 - Normal mode: AAWireless-12345abc
// AAW 2 - Dongle mode: AndroidAuto-AAW12345abc
return Pattern.compile("^(AAWireless-|AndroidAuto-AAW)[0-9a-zA-Z]+$");
}
@Override
public String getManufacturer() {
return "AAWireless";
}
@NonNull
@Override
public Class<? extends DeviceSupport> getDeviceSupportClass(final GBDevice device) {
return AAWirelessSupport.class;
}
@Override
public int getDeviceNameResource() {
return R.string.devicetype_aawireless;
}
@Override
public int getBatteryCount(final GBDevice device) {
return 0;
}
@Override
public BatteryConfig[] getBatteryConfig(final GBDevice device) {
return new BatteryConfig[0];
}
@Override
public DeviceSpecificSettings getDeviceSpecificSettings(final GBDevice device) {
final DevicePrefs devicePrefs = GBApplication.getDevicePrefs(device);
final DeviceSpecificSettings deviceSpecificSettings = new DeviceSpecificSettings();
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_aawireless_paired_phones);
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_country);
if (devicePrefs.getBoolean(AAWirelessPrefs.PREF_HAS_BUTTON, false)) {
deviceSpecificSettings.addRootScreen(R.xml.devicesettings_aawireless_button);
}
// TODO deviceSpecificSettings.addRootScreen(R.xml.devicesettings_aawireless_auto_standby);
final List<Integer> advanced = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.ADVANCED_SETTINGS);
advanced.add(R.xml.devicesettings_aawireless_advanced);
final List<Integer> connection = deviceSpecificSettings.addRootScreen(DeviceSpecificSettingsScreen.CONNECTION);
connection.add(R.xml.devicesettings_wifi_frequency_channel);
return deviceSpecificSettings;
}
@Override
public DeviceSpecificSettingsCustomizer getDeviceSpecificSettingsCustomizer(final GBDevice device) {
return new AAWirelessSettingsCustomizer();
}
}
@@ -0,0 +1,332 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.devices.aawireless;
import android.content.Context;
import android.content.Intent;
import android.os.Parcel;
import android.text.InputType;
import androidx.annotation.NonNull;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceCategory;
import androidx.preference.SwitchPreferenceCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.adapter.SimpleIconListAdapter;
import nodomain.freeyourgadget.gadgetbridge.model.RunnableListIconItem;
import nodomain.freeyourgadget.gadgetbridge.service.devices.aawireless.AAWirelessPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.CountryCodeTextWatcher;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.MinMaxTextWatcher;
public class AAWirelessSettingsCustomizer implements DeviceSpecificSettingsCustomizer {
private static final String PREF_DUMMY_PHONE = "pref_aawireless_dummy_phone_";
@Override
public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) {
}
@Override
public void onDeviceChanged(final DeviceSpecificSettingsHandler handler) {
final PreferenceCategory pairedPhonesHeader = handler.findPreference(AAWirelessPrefs.PREF_HEADER_PAIRED_PHONES);
if (pairedPhonesHeader != null) {
setupPhoneManagement(handler, new AAWirelessPrefs(GBApplication.getDevicePrefs(handler.getDevice()).getPreferences(), handler.getDevice()));
}
setEnabledPreferences(handler);
}
@Override
public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs genericDevicePrefs, final String rootKey) {
final AAWirelessPrefs prefs = new AAWirelessPrefs(genericDevicePrefs.getPreferences(), handler.getDevice());
// Paired phone management
if (AAWirelessPrefs.PREF_SCREEN_PAIRED_PHONES.equals(rootKey)) {
setupPhoneManagement(handler, prefs);
return;
}
// Country
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_COUNTRY);
final EditTextPreference country = handler.findPreference(DeviceSettingsPreferenceConst.PREF_COUNTRY);
if (country != null) {
country.setOnBindEditTextListener(editText -> {
editText.addTextChangedListener(new CountryCodeTextWatcher(editText));
editText.setSelection(editText.getText().length());
});
}
// Auto-standby
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_AUTO_STANDBY_ENABLED);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_AUTO_STANDBY_DEVICE);
// Button modes
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_BUTTON_MODE_SINGLE_CLICK);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_BUTTON_MODE_DOUBLE_CLICK);
appendKnownPhones(handler.findPreference(AAWirelessPrefs.PREF_BUTTON_MODE_SINGLE_CLICK), handler.getContext(), prefs);
appendKnownPhones(handler.findPreference(AAWirelessPrefs.PREF_BUTTON_MODE_DOUBLE_CLICK), handler.getContext(), prefs);
// Wi-Fi frequency and channel
final String currentFrequency = prefs.getString(DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY, "5");
final ListPreference wifiChannel24 = handler.findPreference(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4);
if (wifiChannel24 != null) {
wifiChannel24.setVisible("2.4".equals(currentFrequency));
}
final ListPreference wifiChannel5 = handler.findPreference(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5);
if (wifiChannel5 != null) {
wifiChannel5.setVisible("5".equals(currentFrequency));
}
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY, (preference, newValue) -> {
if (wifiChannel24 != null) {
wifiChannel24.setVisible("2.4".equals(newValue));
}
if (wifiChannel5 != null) {
wifiChannel5.setVisible("5".equals(newValue));
}
return true;
});
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4);
handler.addPreferenceHandlerFor(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5);
// Advanced settings
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(
handler,
AAWirelessPrefs.PREF_DONGLE_MODE,
R.string.pref_aawireless_dongle_confirmation
);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_PASSTHROUGH);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_AUDIO_STUTTER_FIX);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_DPI);
final EditTextPreference dpi = handler.findPreference(AAWirelessPrefs.PREF_DPI);
if (dpi != null) {
dpi.setOnBindEditTextListener(editText -> {
editText.setInputType(InputType.TYPE_CLASS_NUMBER);
editText.addTextChangedListener(new MinMaxTextWatcher(editText, 0, 300, false));
editText.setSelection(editText.getText().length());
});
}
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_DISABLE_MEDIA_SINK);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_DISABLE_TTS_SINK);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_REMOVE_TAP_RESTRICTION);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_VAG_CRASH_FIX);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_START_FIX);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_DEVELOPER_MODE);
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_AUTO_VIDEO_FOCUS);
setEnabledPreferences(handler);
}
private static void setEnabledPreferences(final DeviceSpecificSettingsHandler handler) {
final boolean initialized = handler.getDevice().isInitialized();
setEnabled(handler.findPreference(DeviceSettingsPreferenceConst.PREF_COUNTRY), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_AUTO_STANDBY_ENABLED), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_AUTO_STANDBY_DEVICE), initialized);
setEnabled(handler.findPreference(DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY), initialized);
setEnabled(handler.findPreference(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4), initialized);
setEnabled(handler.findPreference(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_BUTTON_MODE_SINGLE_CLICK), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_BUTTON_MODE_DOUBLE_CLICK), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_DONGLE_MODE), initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_PASSTHROUGH), initialized);
final SwitchPreferenceCompat passthroughPref = handler.findPreference(AAWirelessPrefs.PREF_PASSTHROUGH);
final boolean passthrough = passthroughPref != null && passthroughPref.isChecked();
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_AUDIO_STUTTER_FIX), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_DPI), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_DISABLE_MEDIA_SINK), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_DISABLE_TTS_SINK), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_REMOVE_TAP_RESTRICTION), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_VAG_CRASH_FIX), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_START_FIX), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_DEVELOPER_MODE), !passthrough && initialized);
setEnabled(handler.findPreference(AAWirelessPrefs.PREF_AUTO_VIDEO_FOCUS), !passthrough && initialized);
}
private static void setEnabled(final Preference pref, final boolean enabled) {
if (pref != null) {
pref.setEnabled(enabled);
}
}
private static void setupPhoneManagement(final DeviceSpecificSettingsHandler handler, final AAWirelessPrefs prefs) {
final Context context = handler.getContext();
final int phonesCount = prefs.getPairedPhoneCount();
final Preference preferLastConnected = handler.findPreference(AAWirelessPrefs.PREF_PREFER_LAST_CONNECTED);
if (preferLastConnected != null) {
handler.addPreferenceHandlerFor(AAWirelessPrefs.PREF_PREFER_LAST_CONNECTED);
preferLastConnected.setEnabled(handler.getDevice().isInitialized());
preferLastConnected.setVisible(!prefs.enableDongleMode());
}
final PreferenceCategory pairedPhonesHeader = handler.findPreference(AAWirelessPrefs.PREF_HEADER_PAIRED_PHONES);
final Preference noPairedPhones = handler.findPreference(AAWirelessPrefs.PREF_NO_PAIRED_PHONES);
if (noPairedPhones != null) {
noPairedPhones.setVisible(phonesCount == 0);
}
for (int i = 0; i < pairedPhonesHeader.getPreferenceCount(); i++) {
final Preference preference = pairedPhonesHeader.getPreference(i);
if (preference.getKey().startsWith(PREF_DUMMY_PHONE)) {
pairedPhonesHeader.removePreference(preference);
i--;
}
}
for (int i = 0; i < phonesCount; i++) {
final int phonePosition = i;
final Preference prefPhone = new Preference(context);
final String phoneName = prefs.getPairedPhoneName(i);
final String phoneMac = prefs.getPairedPhoneMac(i);
prefPhone.setOnPreferenceClickListener(preference -> {
final List<RunnableListIconItem> items = new ArrayList<>(4);
if (!prefs.enableDongleMode()) {
items.add(new RunnableListIconItem(context.getString(R.string.switch_to_phone, phoneName), R.drawable.ic_switch_left, () -> {
final Intent intent = new Intent(AAWirelessPrefs.ACTION_PHONE_SWITCH);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_MAC, phoneMac);
intent.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intent);
}));
}
if (phonePosition > 0) {
items.add(new RunnableListIconItem(context.getString(R.string.widget_move_up), R.drawable.ic_arrow_upward, () -> {
final Intent intent = new Intent(AAWirelessPrefs.ACTION_PHONE_SORT);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_MAC, phoneMac);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_NEW_POSITION, phonePosition - 1);
intent.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intent);
}));
}
if (phonePosition < phonesCount - 1) {
items.add(new RunnableListIconItem(context.getString(R.string.widget_move_down), R.drawable.ic_arrow_downward, () -> {
final Intent intent = new Intent(AAWirelessPrefs.ACTION_PHONE_SORT);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_MAC, phoneMac);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_NEW_POSITION, phonePosition + 1);
intent.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intent);
}));
}
items.add(new RunnableListIconItem(context.getString(R.string.Delete), R.drawable.ic_delete, () -> {
final Intent intent = new Intent(AAWirelessPrefs.ACTION_PHONE_DELETE);
intent.putExtra(AAWirelessPrefs.EXTRA_PHONE_MAC, phoneMac);
intent.setPackage(BuildConfig.APPLICATION_ID);
context.sendBroadcast(intent);
}));
final SimpleIconListAdapter adapter = new SimpleIconListAdapter(context, items);
new MaterialAlertDialogBuilder(context)
.setAdapter(adapter, (dialog, i1) -> items.get(i1).getAction().run())
.setTitle(phoneName)
.setNegativeButton(android.R.string.cancel, (dialogInterface, i1) -> {
})
.create()
.show();
return true;
});
prefPhone.setKey(PREF_DUMMY_PHONE + i);
prefPhone.setIcon(R.drawable.ic_smartphone);
prefPhone.setTitle(phoneName);
prefPhone.setSummary(phoneMac);
prefPhone.setEnabled(handler.getDevice().isInitialized());
pairedPhonesHeader.addPreference(prefPhone);
}
}
private static void appendKnownPhones(final ListPreference listPreference, final Context context, final AAWirelessPrefs prefs) {
if (listPreference == null) {
return;
}
final int phonesCount = prefs.getPairedPhoneCount();
if (phonesCount <= 0) {
return;
}
final CharSequence[] entries = listPreference.getEntries();
final CharSequence[] entryValues = listPreference.getEntryValues();
if (entries == null || entryValues == null || entries.length != entryValues.length) {
return;
}
final CharSequence[] newEntries = new CharSequence[entries.length + phonesCount];
final CharSequence[] newEntryValues = new CharSequence[entries.length + phonesCount];
for (int i = 0; i < entries.length; i++) {
newEntries[i] = entries[i];
newEntryValues[i] = entryValues[i];
}
for (int i = 0; i < phonesCount; i++) {
final String mac = prefs.getPairedPhoneMac(i);
final String name = prefs.getPairedPhoneName(i);
newEntries[entries.length + i] = context.getString(R.string.switch_to_phone, name);
newEntryValues[entries.length + i] = mac;
}
listPreference.setEntries(newEntries);
listPreference.setEntryValues(newEntryValues);
}
@Override
public Set<String> getPreferenceKeysWithSummary() {
return Collections.emptySet();
}
public static final Creator<AAWirelessSettingsCustomizer> CREATOR = new Creator<>() {
@Override
public AAWirelessSettingsCustomizer createFromParcel(final Parcel in) {
return new AAWirelessSettingsCustomizer();
}
@Override
public AAWirelessSettingsCustomizer[] newArray(final int size) {
return new AAWirelessSettingsCustomizer[size];
}
};
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull final Parcel parcel, final int i) {
}
}
@@ -24,7 +24,6 @@ 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;
@@ -56,7 +55,9 @@ 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.adapter.SimpleIconListAdapter;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.model.RunnableListIconItem;
import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSettingsService;
import nodomain.freeyourgadget.gadgetbridge.proto.garmin.GdiSmartProto;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
@@ -577,23 +578,50 @@ public class GarminRealtimeSettingsFragment extends AbstractPreferenceFragment {
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<String> sortableOptions = new ArrayList<>(3);
final Preference sortPref = new Preference(activity);
final int iFinal = i;
final List<RunnableListIconItem> sortableOptions = new ArrayList<>(3);
if (i > 0) {
sortableOptions.add(moveUpStr);
sortableOptions.add(new RunnableListIconItem(activity.getString(R.string.widget_move_up), R.drawable.ic_arrow_upward, () -> {
sortPref.setEnabled(false);
sendChangeRequest(
GdiSettingsService.ChangeRequest.newBuilder()
.setScreenId(screenId)
.setEntryId(sortEntry.getId())
.setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder()
.setIndex(iFinal - 1)
)
);
}));
}
if (i < entry.getSortOptions().getEntriesCount() - 1) {
sortableOptions.add(moveDownStr);
sortableOptions.add(new RunnableListIconItem(activity.getString(R.string.widget_move_down), R.drawable.ic_arrow_downward, () -> {
sortPref.setEnabled(false);
sendChangeRequest(
GdiSettingsService.ChangeRequest.newBuilder()
.setScreenId(screenId)
.setEntryId(sortEntry.getId())
.setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder()
.setIndex(iFinal + 1)
)
);
}));
}
sortableOptions.add(deleteStr);
final ArrayAdapter<String> sortOptionsAdapter = new ArrayAdapter<>(activity, android.R.layout.simple_list_item_1, sortableOptions);
final int iFinal = i;
final Preference sortPref = new Preference(activity);
sortableOptions.add(new RunnableListIconItem(activity.getString(R.string.appmananger_app_delete), R.drawable.ic_delete, () -> {
sortPref.setEnabled(false);
sendChangeRequest(
GdiSettingsService.ChangeRequest.newBuilder()
.setScreenId(screenId)
.setEntryId(sortEntry.getId())
.setPosition(GdiSettingsService.ChangeRequest.Position.newBuilder()
.setDelete(true)
)
);
}));
final SimpleIconListAdapter sortOptionsAdapter = new SimpleIconListAdapter(activity, sortableOptions);
sortPref.setTitle(sortEntry.getTitle().getText());
sortPref.setPersistent(false);
sortPref.setIconSpaceReserved(false);
@@ -601,40 +629,8 @@ public class GarminRealtimeSettingsFragment extends AbstractPreferenceFragment {
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)
.setAdapter(sortOptionsAdapter, (dialogInterface, j) -> sortableOptions.get(j).getAction().run())
.setNegativeButton(android.R.string.cancel, null)
.create().show();
return true;
});
@@ -29,6 +29,7 @@ package nodomain.freeyourgadget.gadgetbridge.model;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.UnknownDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.aawireless.AAWirelessCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.asteroidos.AsteroidOSDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.bandwpseries.BandWPSeriesDeviceCoordinator;
import nodomain.freeyourgadget.gadgetbridge.devices.banglejs.BangleJSCoordinator;
@@ -576,6 +577,7 @@ public enum DeviceType {
GARMIN_VIVOSMART_5(GarminVivosmart5Coordinator.class),
GARMIN_VIVOSPORT(GarminVivosportCoordinator.class),
GREE_AC(GreeAcCoordinator.class),
AAWIRELESS(AAWirelessCoordinator.class),
VIBRATISSIMO(VibratissimoCoordinator.class),
SONY_SWR12(SonySWR12DeviceCoordinator.class),
LIVEVIEW(LiveviewCoordinator.class),
@@ -0,0 +1,46 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.model;
import androidx.annotation.DrawableRes;
public class RunnableListIconItem {
private final String title;
private final int iconResId;
private final Runnable action;
public RunnableListIconItem(final String title,
@DrawableRes final int iconResId,
final Runnable action) {
this.title = title;
this.iconResId = iconResId;
this.action = action;
}
public String getTitle() {
return title;
}
@DrawableRes
public int getIconResId() {
return iconResId;
}
public Runnable getAction() {
return action;
}
}
@@ -50,8 +50,13 @@ public abstract class AbstractBTBRDeviceSupport extends AbstractDeviceSupport im
@Override
public boolean connect() {
final UUID supportedService = getSupportedService();
if (supportedService == null) {
throw new NullPointerException("No supported service UUID specified");
}
if (mQueue == null) {
mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, getSupportedService(), getBufferSize());
mQueue = new BtBRQueue(getBluetoothAdapter(), getDevice(), getContext(), this, supportedService, getBufferSize());
}
return mQueue.connect();
}
@@ -95,7 +95,7 @@ public final class BtBRQueue {
}
};
public BtBRQueue(BluetoothAdapter btAdapter, GBDevice gbDevice, Context context, SocketCallback socketCallback, UUID supportedService, int bufferSize) {
public BtBRQueue(BluetoothAdapter btAdapter, GBDevice gbDevice, Context context, SocketCallback socketCallback, @NonNull UUID supportedService, int bufferSize) {
mBtAdapter = btAdapter;
mGbDevice = gbDevice;
mContext = context;
@@ -0,0 +1,166 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.aawireless;
import android.content.SharedPreferences;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.util.preferences.DevicePrefs;
public class AAWirelessPrefs extends DevicePrefs {
// Auto-standby
public static final String PREF_AUTO_STANDBY_ENABLED = "auto_standby_enabled";
public static final String PREF_AUTO_STANDBY_DEVICE = "auto_standby_device";
// Button modes
public static final String PREF_BUTTON_MODE_SINGLE_CLICK = "button_mode_single_click";
public static final String PREF_BUTTON_MODE_DOUBLE_CLICK = "button_mode_double_click";
public static final String BUTTON_MODE_NONE = "none";
public static final String BUTTON_MODE_NEXT_PHONE = "next_phone";
public static final String BUTTON_MODE_STANDBY_ON_OFF = "standby_on_off";
public static final String PREF_HAS_BUTTON = "aawireless_has_button";
// Advanced settings
public static final String PREF_DONGLE_MODE = "dongle_mode";
public static final String PREF_PASSTHROUGH = "passthrough";
public static final String PREF_AUDIO_STUTTER_FIX = "audio_stutter_fix";
public static final String AUDIO_STUTTER_FIX_OFF = "off";
public static final String AUDIO_STUTTER_FIX_LOW = "low";
public static final String AUDIO_STUTTER_FIX_HIGH = "high";
public static final String AUDIO_STUTTER_FIX_UNLIMITED = "unlimited";
public static final String PREF_DPI = "dpi";
public static final String PREF_DISABLE_MEDIA_SINK = "disable_media_sink";
public static final String PREF_DISABLE_TTS_SINK = "disable_tts_sink";
public static final String PREF_REMOVE_TAP_RESTRICTION = "remove_tap_restriction";
public static final String PREF_VAG_CRASH_FIX = "vag_crash_fix";
public static final String PREF_START_FIX = "start_fix";
public static final String PREF_DEVELOPER_MODE = "developer_mode";
public static final String PREF_AUTO_VIDEO_FOCUS = "auto_video_focus";
// Phone management
public static final String PREF_PREFER_LAST_CONNECTED = "prefer_last_connected";
public static final String PREF_SCREEN_PAIRED_PHONES = "screen_paired_phones";
public static final String PREF_HEADER_PAIRED_PHONES = "paired_phones_header";
public static final String PREF_NO_PAIRED_PHONES = "no_paired_phones";
public static final String PREF_KNOWN_PHONES_COUNT = "aawireless_known_phones_count";
public static final String PREF_KNOWN_PHONES_MAC = "aawireless_known_phones_mac_";
public static final String PREF_KNOWN_PHONES_NAME = "aawireless_known_phones_name_";
// Intents
public static final String ACTION_PHONE_SWITCH = "aawireless_action_phone_switch";
public static final String ACTION_PHONE_SORT = "aawireless_action_phone_sort";
public static final String ACTION_PHONE_DELETE = "aawireless_action_phone_delete";
public static final String EXTRA_PHONE_MAC = "phone_mac";
public static final String EXTRA_PHONE_NEW_POSITION = "phone_position";
public AAWirelessPrefs(final SharedPreferences preferences, final GBDevice gbDevice) {
super(preferences, gbDevice);
}
public String getCountry() {
return getString(DeviceSettingsPreferenceConst.PREF_COUNTRY, "US");
}
public boolean getAutoStandbyEnabled() {
return getBoolean(PREF_AUTO_STANDBY_ENABLED, false);
}
public String getAutoStandbyDevice() {
return getString(PREF_AUTO_STANDBY_DEVICE, "");
}
public String getButtonModeSingleClick() {
return getString(PREF_BUTTON_MODE_SINGLE_CLICK, "none");
}
public String getButtonModeDoubleClick() {
return getString(PREF_BUTTON_MODE_DOUBLE_CLICK, "none");
}
public String getWiFiFrequency() {
return getString(DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY, "5");
}
public int getWiFiChannel() {
return "5".equals(getWiFiFrequency()) ?
getInt(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5, 0) :
getInt(DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4, 0);
}
public boolean enableDongleMode() {
return getBoolean(PREF_DONGLE_MODE, false);
}
public boolean enablePassthrough() {
return getBoolean(PREF_PASSTHROUGH, true);
}
public String getAudioStutterFix() {
// off low high unlimited
return getString(PREF_AUDIO_STUTTER_FIX, "off");
}
public int getDpi() {
// [0-300]
return getInt(PREF_DPI, 0);
}
public boolean disableMediaSink() {
return getBoolean(PREF_DISABLE_MEDIA_SINK, false);
}
public boolean disableTtsSink() {
return getBoolean(PREF_DISABLE_TTS_SINK, false);
}
public boolean removeTapRestriction() {
return getBoolean(PREF_REMOVE_TAP_RESTRICTION, false);
}
public boolean enableVagCrashFix() {
return getBoolean(PREF_VAG_CRASH_FIX, false);
}
public boolean enableStartFix() {
return getBoolean(PREF_START_FIX, false);
}
public boolean enableDeveloperMode() {
return getBoolean(PREF_DEVELOPER_MODE, false);
}
public boolean enableAutoVideoFocus() {
return getBoolean(PREF_AUTO_VIDEO_FOCUS, false);
}
public boolean preferLastConnected() {
return getBoolean(PREF_PREFER_LAST_CONNECTED, false);
}
public int getPairedPhoneCount() {
return getInt(PREF_KNOWN_PHONES_COUNT, 0);
}
public String getPairedPhoneMac(final int position) {
return getString(PREF_KNOWN_PHONES_MAC + position, "");
}
public String getPairedPhoneName(final int position) {
return getString(PREF_KNOWN_PHONES_NAME + position, "");
}
}
@@ -0,0 +1,441 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.service.devices.aawireless;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.widget.Toast;
import androidx.core.content.ContextCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.UUID;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdateDeviceState;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventUpdatePreferences;
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventVersionInfo;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
import nodomain.freeyourgadget.gadgetbridge.proto.aawireless.AAWirelessProto;
import nodomain.freeyourgadget.gadgetbridge.service.btbr.AbstractBTBRDeviceSupport;
import nodomain.freeyourgadget.gadgetbridge.service.btbr.TransactionBuilder;
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
public class AAWirelessSupport extends AbstractBTBRDeviceSupport {
private static final Logger LOG = LoggerFactory.getLogger(AAWirelessSupport.class);
private static final UUID UUID_SERVICE_AAWIRELESS = UUID.fromString("b7b2ee43-c32a-41a6-9ee4-336c1365a1c4");
private static final int MAX_MTU = 2048;
private static final String MAC_ADDR_PATTERN = "^([0-9A-F]{2}:){5}[0-9A-F]{2}$";
private static final short CMD_STATUS_REQUEST = 0x0001;
private static final short CMD_STATUS_RESPONSE = 0x0002;
private static final short CMD_SETTINGS_SET = 0x0003;
private static final short CMD_SETTINGS_ACK = 0x0004;
private static final short CMD_PHONE_DELETE_REQUEST = 0x000b;
private static final short CMD_PHONE_DELETE_ACK = 0x000c;
private static final short CMD_PHONE_POSITION_REQUEST = 0x000d;
private static final short CMD_PHONE_POSITION_ACK = 0x000e;
private static final short CMD_FACTORY_RESET_REQUEST = 0x000f;
private static final short CMD_FACTORY_RESET_ACK = 0x0010;
private static final short CMD_PHONE_SWITCH_REQUEST = 0x0011;
private static final short CMD_PHONE_SWITCH_ACK = 0x0012;
private final ByteBuffer packetBuffer = ByteBuffer.allocate(MAX_MTU).order(ByteOrder.BIG_ENDIAN);
private final BroadcastReceiver commandReceiver = new AAWirelessCommandReceiver();
public AAWirelessSupport() {
super(LOG);
addSupportedService(UUID_SERVICE_AAWIRELESS);
setBufferSize(MAX_MTU);
}
@Override
public boolean useAutoConnect() {
return true;
}
@Override
public AAWirelessPrefs getDevicePrefs() {
return new AAWirelessPrefs(GBApplication.getDeviceSpecificSharedPrefs(gbDevice.getAddress()), gbDevice);
}
@Override
protected TransactionBuilder initializeDevice(final TransactionBuilder builder) {
final IntentFilter commandFilter = new IntentFilter();
commandFilter.addAction(AAWirelessPrefs.ACTION_PHONE_SWITCH);
commandFilter.addAction(AAWirelessPrefs.ACTION_PHONE_SORT);
commandFilter.addAction(AAWirelessPrefs.ACTION_PHONE_DELETE);
ContextCompat.registerReceiver(getContext(), commandReceiver, commandFilter, ContextCompat.RECEIVER_NOT_EXPORTED);
sendCommand(builder, CMD_STATUS_REQUEST, new byte[0]);
return builder;
}
@Override
public void dispose() {
try {
getContext().unregisterReceiver(commandReceiver);
} catch (final Exception e) {
LOG.warn("Failed to unregister receiver", e);
}
super.dispose();
}
@Override
public void onSocketRead(final byte[] data) {
packetBuffer.put(data);
packetBuffer.flip();
while (packetBuffer.hasRemaining()) {
packetBuffer.mark();
if (packetBuffer.remaining() < 6) {
// not enough bytes for min packet
packetBuffer.reset();
break;
}
final int payloadLength = packetBuffer.getInt();
final short command = packetBuffer.getShort();
if (packetBuffer.remaining() < payloadLength) {
// not enough bytes for payload
packetBuffer.reset();
break;
}
final byte[] payload = new byte[payloadLength];
packetBuffer.get(payload);
try {
handleCommand(command, payload);
} catch (final Exception e) {
LOG.error("Failed to handle command", e);
}
}
packetBuffer.compact();
}
private void sendCommand(final TransactionBuilder builder, final short command, final byte[] payload) {
final ByteBuffer buf = ByteBuffer.allocate(payload.length + 6).order(ByteOrder.BIG_ENDIAN);
buf.putInt(payload.length);
buf.putShort(command);
buf.put(payload);
builder.write(buf.array());
}
private void sendCommand(final String taskName, final short command, final byte[] payload) {
final TransactionBuilder builder = createTransactionBuilder(taskName);
sendCommand(builder, command, payload);
builder.queue(getQueue());
}
private void handleCommand(final short command, final byte[] payload) throws Exception {
switch (command) {
case CMD_STATUS_RESPONSE -> {
final AAWirelessProto.Status status = AAWirelessProto.Status.parseFrom(payload);
LOG.info("Got status = {}", status);
handleStatus(status);
}
case CMD_SETTINGS_ACK -> {
final AAWirelessProto.Ack ack = AAWirelessProto.Ack.parseFrom(payload);
LOG.info("Got settings ack, status={}, message={}", ack.getStatus(), ack.getMessage());
if (ack.getStatus() != 0) {
GB.toast("AAW " + ack.getStatus() + ": " + ack.getMessage(), Toast.LENGTH_LONG, GB.WARN);
}
sendCommand("request status", CMD_STATUS_REQUEST, new byte[0]);
}
case CMD_PHONE_DELETE_ACK -> {
final AAWirelessProto.Ack ack = AAWirelessProto.Ack.parseFrom(payload);
LOG.info("Got Phone delete ack, status={}, message={}", ack.getStatus(), ack.getMessage());
if (ack.getStatus() != 0) {
GB.toast("AAW " + ack.getStatus() + ": " + ack.getMessage(), Toast.LENGTH_LONG, GB.WARN);
}
sendCommand("request status", CMD_STATUS_REQUEST, new byte[0]);
}
case CMD_PHONE_POSITION_ACK -> {
final AAWirelessProto.Ack ack = AAWirelessProto.Ack.parseFrom(payload);
LOG.info("Got phone position ack, status={}, message={}", ack.getStatus(), ack.getMessage());
if (ack.getStatus() != 0) {
GB.toast("AAW " + ack.getStatus() + ": " + ack.getMessage(), Toast.LENGTH_LONG, GB.WARN);
}
sendCommand("request status", CMD_STATUS_REQUEST, new byte[0]);
}
case CMD_FACTORY_RESET_ACK -> {
final AAWirelessProto.Ack ack = AAWirelessProto.Ack.parseFrom(payload);
LOG.info("Got factory reset ack, status={}, message={}", ack.getStatus(), ack.getMessage());
if (ack.getStatus() != 0) {
GB.toast("AAW " + ack.getStatus() + ": " + ack.getMessage(), Toast.LENGTH_LONG, GB.WARN);
}
}
case CMD_PHONE_SWITCH_ACK -> {
final AAWirelessProto.Ack ack = AAWirelessProto.Ack.parseFrom(payload);
LOG.info("Got phone switch ack, status={}, message={}", ack.getStatus(), ack.getMessage());
if (ack.getStatus() != 0) {
GB.toast("AAW " + ack.getStatus() + ": " + ack.getMessage(), Toast.LENGTH_LONG, GB.WARN);
}
}
default -> LOG.warn(
"Got unknown command {}, payload={}",
String.format("0x%04x", command),
GB.hexdump(payload)
);
}
}
private void handleStatus(final AAWirelessProto.Status status) {
// FW / HW
final GBDeviceEventVersionInfo versionInfoEvent = new GBDeviceEventVersionInfo();
versionInfoEvent.hwVersion = status.getHardwareModel();
versionInfoEvent.fwVersion = status.getFirmwareVersion();
evaluateGBDeviceEvent(versionInfoEvent);
// Mark as initialized
evaluateGBDeviceEvent(new GBDeviceEventUpdateDeviceState(GBDevice.State.INITIALIZED));
// Settings
final AAWirelessProto.Settings settings = status.getSettings();
final GBDeviceEventUpdatePreferences preferencesEvent = new GBDeviceEventUpdatePreferences();
preferencesEvent.withPreference(DeviceSettingsPreferenceConst.PREF_COUNTRY, settings.getCountry());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_AUTO_STANDBY_ENABLED, settings.getAutoStandbyEnabled());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_AUTO_STANDBY_DEVICE, settings.getAutoStandbyDevice());
preferencesEvent.withPreference(
DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY,
settings.getWifiFrequency() == AAWirelessProto.WiFiFrequency.FREQ_2_4_GHZ ? "2.4" : "5"
);
switch (settings.getWifiFrequency()) {
case FREQ_5_GHZ -> preferencesEvent.withPreference(
DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5,
String.valueOf(settings.getWifiChannel())
);
case FREQ_2_4_GHZ -> preferencesEvent.withPreference(
DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4,
String.valueOf(settings.getWifiChannel())
);
}
preferencesEvent.withPreference(AAWirelessPrefs.PREF_HAS_BUTTON, settings.hasButtonsConfig());
if (settings.hasButtonsConfig()) {
preferencesEvent.withPreference(AAWirelessPrefs.PREF_BUTTON_MODE_SINGLE_CLICK, buttonModeToPref(settings.getButtonsConfig().getSingle()));
preferencesEvent.withPreference(AAWirelessPrefs.PREF_BUTTON_MODE_DOUBLE_CLICK, buttonModeToPref(settings.getButtonsConfig().getDouble()));
}
preferencesEvent.withPreference(AAWirelessPrefs.PREF_DONGLE_MODE, settings.getDongleMode());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_PASSTHROUGH, settings.getPassthrough());
final String audioStutterFixPrefValue;
if (settings.getAudioStutterFix() == 50) {
audioStutterFixPrefValue = AAWirelessPrefs.AUDIO_STUTTER_FIX_LOW;
} else if (settings.getAudioStutterFix() == 100) {
audioStutterFixPrefValue = AAWirelessPrefs.AUDIO_STUTTER_FIX_HIGH;
} else if (settings.getAudioStutterFix() == 4096) {
audioStutterFixPrefValue = AAWirelessPrefs.AUDIO_STUTTER_FIX_UNLIMITED;
} else {
audioStutterFixPrefValue = AAWirelessPrefs.AUDIO_STUTTER_FIX_OFF;
}
preferencesEvent.withPreference(AAWirelessPrefs.PREF_AUDIO_STUTTER_FIX, audioStutterFixPrefValue);
preferencesEvent.withPreference(AAWirelessPrefs.PREF_DPI, String.valueOf(settings.getDpi()));
preferencesEvent.withPreference(AAWirelessPrefs.PREF_DISABLE_MEDIA_SINK, settings.getDisableMediaSink());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_DISABLE_TTS_SINK, settings.getDisableTtsSink());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_REMOVE_TAP_RESTRICTION, settings.getRemoveTapRestriction());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_VAG_CRASH_FIX, settings.getVagCrashFix());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_START_FIX, settings.getStartFix());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_DEVELOPER_MODE, settings.getDeveloperMode());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_AUTO_VIDEO_FOCUS, settings.getAutoVideoFocus());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_PREFER_LAST_CONNECTED, settings.getPreferLastConnectedPhone());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_KNOWN_PHONES_COUNT, status.getPairedPhoneCount());
for (int i = 0; i < status.getPairedPhoneCount(); i++) {
final AAWirelessProto.Phone phone = status.getPairedPhone(i);
preferencesEvent.withPreference(AAWirelessPrefs.PREF_KNOWN_PHONES_MAC + i, phone.getMacAddress());
preferencesEvent.withPreference(AAWirelessPrefs.PREF_KNOWN_PHONES_NAME + i, phone.getName());
}
evaluateGBDeviceEvent(preferencesEvent);
}
private String buttonModeToPref(final AAWirelessProto.ButtonMode buttonMode) {
if (buttonMode.hasNextPhone()) {
return AAWirelessPrefs.BUTTON_MODE_NEXT_PHONE;
} else if (buttonMode.hasSelectPhone()) {
return buttonMode.getSelectPhone().getMacAddress();
} else if (buttonMode.hasStandbyOnOff()) {
return AAWirelessPrefs.BUTTON_MODE_STANDBY_ON_OFF;
} else {
return AAWirelessPrefs.BUTTON_MODE_NONE;
}
}
private AAWirelessProto.ButtonMode prefToButtonMode(final String prefValue) {
final AAWirelessProto.ButtonMode.Builder builder = AAWirelessProto.ButtonMode.newBuilder();
switch (prefValue) {
case AAWirelessPrefs.BUTTON_MODE_NEXT_PHONE:
builder.setNextPhone("");
break;
case AAWirelessPrefs.BUTTON_MODE_STANDBY_ON_OFF:
builder.setStandbyOnOff("");
break;
case AAWirelessPrefs.BUTTON_MODE_NONE:
break;
default:
if (prefValue.matches(MAC_ADDR_PATTERN)) {
builder.setSelectPhone(AAWirelessProto.Phone.newBuilder().setMacAddress(prefValue));
} else {
LOG.warn("Unexpected button mode preference value {}", prefValue);
}
}
return builder.build();
}
@Override
public void onSendConfiguration(final String config) {
switch (config) {
case DeviceSettingsPreferenceConst.PREF_COUNTRY:
case AAWirelessPrefs.PREF_AUTO_STANDBY_ENABLED:
case AAWirelessPrefs.PREF_AUTO_STANDBY_DEVICE:
case DeviceSettingsPreferenceConst.PREF_WIFI_FREQUENCY:
case DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_2_4:
case DeviceSettingsPreferenceConst.PREF_WIFI_CHANNEL_5:
case AAWirelessPrefs.PREF_BUTTON_MODE_SINGLE_CLICK:
case AAWirelessPrefs.PREF_BUTTON_MODE_DOUBLE_CLICK:
case AAWirelessPrefs.PREF_DONGLE_MODE:
case AAWirelessPrefs.PREF_PASSTHROUGH:
case AAWirelessPrefs.PREF_AUDIO_STUTTER_FIX:
case AAWirelessPrefs.PREF_DPI:
case AAWirelessPrefs.PREF_DISABLE_MEDIA_SINK:
case AAWirelessPrefs.PREF_DISABLE_TTS_SINK:
case AAWirelessPrefs.PREF_REMOVE_TAP_RESTRICTION:
case AAWirelessPrefs.PREF_VAG_CRASH_FIX:
case AAWirelessPrefs.PREF_START_FIX:
case AAWirelessPrefs.PREF_DEVELOPER_MODE:
case AAWirelessPrefs.PREF_AUTO_VIDEO_FOCUS:
sendSettings();
return;
}
super.onSendConfiguration(config);
}
private void sendSettings() {
final AAWirelessPrefs prefs = getDevicePrefs();
final AAWirelessProto.WiFiFrequency wiFiFrequency = switch (prefs.getWiFiFrequency()) {
case "5" -> AAWirelessProto.WiFiFrequency.FREQ_5_GHZ;
case "2.4" -> AAWirelessProto.WiFiFrequency.FREQ_2_4_GHZ;
default -> throw new IllegalArgumentException(
"Unknown wifi frequency " + prefs.getWiFiFrequency()
);
};
final long audioStutterFix = switch (prefs.getAudioStutterFix()) {
case "unlimited" -> 4096;
case "high" -> 100;
case "low" -> 50;
case "off" -> -1; // 18446744073709551615 in uint64
default -> throw new IllegalArgumentException(
"Unknown audio stutter fix " + prefs.getAudioStutterFix()
);
};
final AAWirelessProto.ButtonsConfig buttonsConfig = AAWirelessProto.ButtonsConfig.newBuilder()
.setSingle(prefToButtonMode(prefs.getButtonModeSingleClick()))
.setDouble(prefToButtonMode(prefs.getButtonModeDoubleClick()))
.build();
final AAWirelessProto.Settings.Builder builder = AAWirelessProto.Settings.newBuilder()
.setPassthrough(prefs.enablePassthrough())
.setVagCrashFix(prefs.enableVagCrashFix())
.setDpi(prefs.getDpi())
.setRemoveTapRestriction(prefs.removeTapRestriction())
.setDeveloperMode(prefs.enableDeveloperMode())
.setDisableMediaSink(prefs.disableMediaSink())
.setWifiFrequency(wiFiFrequency)
.setAutoVideoFocus(prefs.enableAutoVideoFocus())
.setWifiChannel(prefs.getWiFiChannel())
.setCountry(prefs.getCountry())
.setPreferLastConnectedPhone(prefs.preferLastConnected())
.setDisableTtsSink(prefs.disableTtsSink())
.setDongleMode(prefs.enableDongleMode())
.setAutoStandbyDevice(prefs.getAutoStandbyDevice())
.setStartFix(prefs.enableStartFix())
.setAudioStutterFix(audioStutterFix)
.setButtonsConfig(buttonsConfig)
.setAutoStandbyEnabled(prefs.getAutoStandbyEnabled());
sendCommand("send settings", CMD_SETTINGS_SET, builder.build().toByteArray());
}
@Override
public void onReset(final int flags) {
if ((flags & GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET) != 0) {
sendCommand("factory reset", CMD_FACTORY_RESET_REQUEST, new byte[0]);
}
}
private class AAWirelessCommandReceiver extends BroadcastReceiver {
@Override
public void onReceive(final Context context, final Intent intent) {
final String action = intent.getAction();
if (action == null) {
return;
}
final String mac = intent.getStringExtra(AAWirelessPrefs.EXTRA_PHONE_MAC);
switch (action) {
case AAWirelessPrefs.ACTION_PHONE_SWITCH: {
LOG.debug("Switch phone to {}", mac);
final AAWirelessProto.Phone payload = AAWirelessProto.Phone.newBuilder().setMacAddress(mac).build();
sendCommand("switch phone", CMD_PHONE_SWITCH_REQUEST, payload.toByteArray());
return;
}
case AAWirelessPrefs.ACTION_PHONE_SORT: {
final int newPosition = intent.getIntExtra(AAWirelessPrefs.EXTRA_PHONE_NEW_POSITION, 0);
LOG.debug("Sort phone {} to {}", mac, newPosition);
final AAWirelessProto.PhonePosition payload = AAWirelessProto.PhonePosition.newBuilder()
.setMacAddress(mac)
.setPosition(newPosition)
.build();
sendCommand("set phone position", CMD_PHONE_POSITION_REQUEST, payload.toByteArray());
return;
}
case AAWirelessPrefs.ACTION_PHONE_DELETE: {
final AAWirelessProto.Phone payload = AAWirelessProto.Phone.newBuilder().setMacAddress(mac).build();
LOG.debug("Delete phone {}", mac);
sendCommand("delete phone", CMD_PHONE_DELETE_REQUEST, payload.toByteArray());
return;
}
}
LOG.warn("Unknown action {}", action);
}
}
}
@@ -8,12 +8,10 @@ import android.os.Parcel;
import android.text.InputFilter;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.SeekBarPreference;
import androidx.preference.SwitchPreferenceCompat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -26,6 +24,7 @@ import java.util.stream.Collectors;
import java.util.stream.IntStream;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsUtils;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer;
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler;
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
@@ -39,7 +38,7 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
this.device = device;
}
public static final Creator<EarFunSettingsCustomizer> CREATOR = new Creator<EarFunSettingsCustomizer>() {
public static final Creator<EarFunSettingsCustomizer> CREATOR = new Creator<>() {
@Override
public EarFunSettingsCustomizer createFromParcel(final Parcel in) {
final GBDevice device = in.readParcelable(EarFunSettingsCustomizer.class.getClassLoader());
@@ -60,7 +59,7 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
@Override
public void customizeSettings(DeviceSpecificSettingsHandler handler, Prefs prefs, String rootKey) {
addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_DEVICE_NAME, R.string.prefs_device_name, R.string.earfun_change_device_name_confirm_message);
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_DEVICE_NAME, R.string.earfun_change_device_name_confirm_message);
handler.addPreferenceHandlerFor(PREF_EARFUN_AMBIENT_SOUND_CONTROL);
handler.addPreferenceHandlerFor(PREF_EARFUN_TRANSPARENCY_MODE);
handler.addPreferenceHandlerFor(PREF_EARFUN_ANC_MODE);
@@ -87,12 +86,12 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
handler.addPreferenceHandlerFor(PREF_EARFUN_EQUALIZER_BAND_16000);
handler.addPreferenceHandlerFor(PREF_EARFUN_IN_EAR_DETECTION_MODE);
handler.addPreferenceHandlerFor(PREF_EARFUN_TOUCH_MODE);
addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_CONNECT_TWO_DEVICES_MODE, R.string.earfun_connect_two_devices, R.string.earfun_reboot_confirm_message);
addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_ADVANCED_AUDIO_MODE, R.string.earfun_advanced_audio_mode, R.string.earfun_reboot_confirm_message);
addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_MICROPHONE_MODE, R.string.earfun_microphone_mode, R.string.earfun_reboot_confirm_message);
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_CONNECT_TWO_DEVICES_MODE, R.string.earfun_reboot_confirm_message);
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_ADVANCED_AUDIO_MODE, R.string.earfun_reboot_confirm_message);
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_MICROPHONE_MODE, R.string.earfun_reboot_confirm_message);
handler.addPreferenceHandlerFor(PREF_EARFUN_FIND_DEVICE);
handler.addPreferenceHandlerFor(PREF_EARFUN_VOICE_PROMPT_VOLUME);
addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_AUDIO_CODEC, R.string.audio_codec, R.string.earfun_reboot_confirm_message);
DeviceSettingsUtils.addConfirmablePreferenceHandlerFor(handler, PREF_EARFUN_AUDIO_CODEC, R.string.earfun_reboot_confirm_message);
EditTextPreference editTextDeviceName = handler.findPreference(PREF_EARFUN_DEVICE_NAME);
if (editTextDeviceName != null) {
@@ -103,42 +102,6 @@ public class EarFunSettingsCustomizer implements DeviceSpecificSettingsCustomize
}
}
protected void addConfirmablePreferenceHandlerFor(DeviceSpecificSettingsHandler handler, final String preferenceKey, int alertPreferenceNAme, int alertMessage) {
Preference pref = handler.findPreference(preferenceKey);
Context context = handler.getContext();
if (pref != null) {
pref.setOnPreferenceChangeListener((preference, newValue) -> {
String preferenceName = context.getString(alertPreferenceNAme);
String title = context.getString(R.string.earfun_change_confirm_title, preferenceName);
String message = context.getString(alertMessage);
new AlertDialog.Builder(context)
.setTitle(title)
.setMessage(message)
.setPositiveButton(handler.getContext().getString(R.string.ok), (dialog, which) -> {
if (pref instanceof EditTextPreference) {
((EditTextPreference) pref).setText(newValue.toString());
}
if (pref instanceof ListPreference) {
((ListPreference) pref).setValue(newValue.toString());
}
if (pref instanceof SwitchPreferenceCompat) {
((SwitchPreferenceCompat) pref).setChecked((Boolean) newValue);
} else {
LOG.error("unsupported preference type for confirmable handler: {}", pref);
}
handler.notifyPreferenceChanged(preferenceKey);
})
.setNegativeButton(handler.getContext().getString(R.string.Cancel), (dialog, which) -> {
dialog.dismiss();
})
.create()
.show();
return false;
});
}
}
protected void initializeEqualizerPresetListPreference(DeviceSpecificSettingsHandler handler,
Equalizer.EqualizerPreset[] equalizerPresets) {
ListPreference equalizerPresetListPreference = handler.findPreference(PREF_EARFUN_EQUALIZER_PRESET);
@@ -0,0 +1,63 @@
/* Copyright (C) 2025 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 <https://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.util.preferences;
import android.text.Editable;
import android.text.TextWatcher;
import android.widget.EditText;
public class CountryCodeTextWatcher implements TextWatcher {
private final EditText editText;
public CountryCodeTextWatcher(final EditText editText) {
this.editText = editText;
}
@Override
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
}
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
}
@Override
public void afterTextChanged(final Editable editable) {
editText.removeTextChangedListener(this);
final StringBuilder filtered = new StringBuilder();
for (int i = 0; i < editable.length(); i++) {
final char c = Character.toUpperCase(editable.charAt(i));
if (c >= 'A' && c <= 'Z') {
filtered.append(c);
}
}
String newText = filtered.toString();
if (newText.length() > 2) {
newText = newText.substring(0, 2);
}
if (!newText.equals(editable.toString())) {
editText.setText(newText);
editText.setSelection(newText.length());
}
editText.addTextChangedListener(this);
editText.getRootView().findViewById(android.R.id.button1).setEnabled(editText.getText().length() == 2);
}
}
+93
View File
@@ -0,0 +1,93 @@
syntax = "proto3";
package aawireless;
option java_package = "nodomain.freeyourgadget.gadgetbridge.proto.aawireless";
option java_outer_classname = "AAWirelessProto";
message Status {
optional string macAddress = 1;
optional string firmwareVersion = 2;
optional Settings settings = 3;
optional CarInfo carInfo = 4;
optional string hardwareModel = 5;
repeated Phone pairedPhone = 11;
}
message Phone {
optional string macAddress = 1;
optional string name = 2;
optional uint32 position = 3; // 0,1,2...
optional bool current = 4; // 0/1 - current device? (might not be the connected one)
optional uint32 unk5 = 5; // 0
}
message PhonePosition {
optional string macAddress = 1;
optional uint32 position = 2;
}
message CarInfo {
}
enum WiFiFrequency {
FREQ_5_GHZ = 0;
FREQ_2_4_GHZ = 1;
}
message Settings {
optional uint32 unk1 = 1; // 0
optional uint32 unk2 = 2; // 0
optional bool passthrough = 3;
optional bool vagCrashFix = 4;
optional uint32 dpi = 5; // [0,300]
optional bool removeTapRestriction = 6;
optional bool developerMode = 7;
optional bool disableMediaSink = 8;
optional uint32 unk9 = 9; // 0
optional WiFiFrequency wifiFrequency = 10;
optional bool autoVideoFocus = 11;
optional uint32 unk12 = 12; // 20000
optional uint32 unk13 = 13; // 0
optional string unk14 = 14; // ""
optional string unk15 = 15; // ""
optional uint32 wifiChannel = 16;
optional uint32 unk17 = 17; // 0
optional string country = 18; // 2-letter
optional bool preferLastConnectedPhone = 19;
optional bool disableTtsSink = 20;
optional bool dongleMode = 21;
optional string autoStandbyDevice = 22; // mac address
optional uint32 unk23 = 23; // 0
optional uint32 unk24 = 24; // 0
optional bool startFix = 25;
// 26 is some byte array?
optional uint32 unk27 = 27; // 0
optional uint32 unk28 = 28; // 0
optional uint32 unk29 = 29; // 0
optional uint32 unk30 = 30; // 0
optional uint64 audioStutterFix = 31; // off 18446744073709551615, low 50, high 100, unlimited 4096
optional uint32 unk32 = 32; // 0
optional uint32 unk33 = 33; // 1
optional uint32 unk34 = 34; // 0
optional ButtonsConfig buttonsConfig = 35;
optional bool autoStandbyEnabled = 37;
}
message ButtonsConfig {
optional ButtonMode single = 1;
optional ButtonMode double = 2;
optional ButtonMode unk4 = 4; // { 1: "" }
}
message ButtonMode {
// only the desired one is set, nothing set for no action
optional string nextPhone = 2; // ""
optional Phone selectPhone = 3; // only mac address
optional string standbyOnOff = 4; // ""
}
message Ack {
optional uint32 status = 1; // 0 for success?
optional string message = 2; // "" - error msg?
}
+1 -1
View File
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
+1 -1
View File
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
@@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="@android:color/white"
android:pathData="M440,647L440,200Q440,183 451.5,171.5Q463,160 480,160Q497,160 508.5,171.5Q520,183 520,200L520,647L716,451Q728,439 744,439.5Q760,440 772,452Q783,464 783.5,480Q784,496 772,508L508,772Q502,778 495,780.5Q488,783 480,783Q472,783 465,780.5Q458,778 452,772L188,508Q177,497 177,480.5Q177,464 188,452Q200,440 216.5,440Q233,440 245,452L440,647Z" />
</vector>
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<path
+1 -1
View File
@@ -1,7 +1,7 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="#7E7E7E"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="24"
android:viewportHeight="24">
<!--
Licensed under public domain
https://www.svgrepo.com/svg/467868/frequency-2
-->
<path
android:pathData="M21,12l-4,0l-2,4l-4,-8l-3,7l-2,-3l-3,0"
android:strokeWidth="2"
android:strokeColor="#FFFFFF"
android:strokeLineCap="round"
android:strokeLineJoin="round" />
</vector>
@@ -0,0 +1,17 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="512"
android:viewportHeight="512">
<!--
Licensed under the public domain.
https://www.svgrepo.com/svg/478225/smartphone-material
-->
<path
android:fillColor="#FFFFFF"
android:pathData="M355.3,0H156.69C125.38,0 100,25.38 100,56.69v398.63C100,486.63 125.38,512 156.69,512h198.61C386.61,512 412,486.63 412,455.31V56.69C412,25.38 386.61,0 355.3,0zM256,483.98c-8.09,0 -14.67,-6.56 -14.67,-14.67c0,-8.09 6.58,-14.66 14.67,-14.66s14.67,6.56 14.67,14.66C270.67,477.42 264.09,483.98 256,483.98zM364,424H148V64h216V424z" />
</vector>
@@ -0,0 +1,29 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:tint="?attr/colorControlNormal"
android:viewportWidth="512"
android:viewportHeight="512">
<!--
Licensed under public domain
https://www.svgrepo.com/svg/478147/usb-free-material
-->
<path
android:fillColor="#FFFFFF"
android:pathData="M10.46,295.6c-14.02,14.02 -13.93,36.91 0.18,51.02l154.73,154.74c6.86,6.86 15.98,10.64 25.68,10.64h0c9.59,0 18.59,-3.71 25.34,-10.46L407.64,310.29L201.7,104.36L10.46,295.6zM100.48,296.98c-3.31,0 -6.42,-1.29 -8.76,-3.63l-1.78,-1.78c-2.34,-2.34 -3.63,-5.45 -3.63,-8.76c0,-3.31 1.29,-6.43 3.63,-8.77l97.64,-97.64c2.34,-2.34 5.45,-3.63 8.77,-3.63c3.31,0 6.43,1.29 8.77,3.63l1.77,1.77c4.83,4.83 4.83,12.7 0,17.53l-97.64,97.64C106.9,295.69 103.79,296.98 100.48,296.98z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M508.68,153.91L358.09,3.32C355.95,1.18 353.11,0 350.1,0c-3.02,0 -5.86,1.18 -8,3.32L240.69,104.73l23.6,23.59l85.81,-85.81l119.38,119.38l-85.81,85.81l23.6,23.59l101.41,-101.41c2.13,-2.13 3.31,-4.96 3.32,-7.97C512.01,158.91 510.83,156.06 508.68,153.91z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M335.78,156.91l-34.38,-34.38l34.16,-34.16l34.38,34.38z" />
<path
android:fillColor="#FFFFFF"
android:pathData="M423.56,176.05l-34.15,-34.15l-34.16,34.15l34.16,34.16z" />
</vector>
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<ImageView
android:id="@+id/option_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/app_name" />
<TextView
android:id="@+id/option_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?attr/textAppearanceListItem" />
</LinearLayout>
+89
View File
@@ -230,6 +230,95 @@
<item>buttons_on_right</item>
</string-array>
<string-array name="wifi_frequency_names">
<item>@string/wifi_frequency_2_4_ghz</item>
<item>@string/wifi_frequency_5_ghz</item>
</string-array>
<string-array name="wifi_frequency_values">
<item>2.4</item>
<item>5</item>
</string-array>
<string-array name="aawireless_audio_stutter_fix_names">
<item>@string/off</item>
<item>@string/pref_aawireless_audio_stutter_fix_low</item>
<item>@string/pref_aawireless_audio_stutter_fix_high</item>
<item>@string/pref_aawireless_audio_stutter_fix_unlimited</item>
</string-array>
<string-array name="aawireless_audio_stutter_fix_values">
<item>off</item>
<item>low</item>
<item>high</item>
<item>unlimited</item>
</string-array>
<string-array name="aawireless_click_names">
<item>@string/none</item>
<item>@string/switch_to_next_phone</item>
<item>@string/toggle_standby_on_off</item>
</string-array>
<string-array name="aawireless_click_values">
<item>none</item>
<item>next_phone</item>
<item>standby_on_off</item>
</string-array>
<string-array name="wifi_channels_2_4_ghz">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
<item>9</item>
<item>10</item>
<item>11</item>
<item>12</item>
<item>13</item>
</string-array>
<string-array name="wifi_channels_5_ghz">
<item>32</item>
<item>36</item>
<item>40</item>
<item>44</item>
<item>48</item>
<item>52</item>
<item>56</item>
<item>60</item>
<item>64</item>
<item>68</item>
<item>72</item>
<item>76</item>
<item>80</item>
<item>84</item>
<item>88</item>
<item>92</item>
<item>96</item>
<item>100</item>
<item>104</item>
<item>108</item>
<item>112</item>
<item>116</item>
<item>120</item>
<item>124</item>
<item>128</item>
<item>132</item>
<item>136</item>
<item>140</item>
<item>144</item>
<item>149</item>
<item>153</item>
<item>157</item>
<item>161</item>
<item>165</item>
<item>169</item>
<item>173</item>
<item>177</item>
</string-array>
<string-array name="wearmode">
<item>@string/wearmode_band</item>
<item>@string/wearmode_pebble</item>
+48 -1
View File
@@ -2008,6 +2008,7 @@
<string name="devicetype_redmi_watch_4">Redmi Watch 4</string>
<string name="devicetype_redmi_watch_5_active">Redmi Watch 5 Active</string>
<string name="devicetype_redmi_watch_5_lite">Redmi Watch 5 Lite</string>
<string name="devicetype_aawireless">AAWireless</string>
<string name="devicetype_colmi_r02">Colmi R02</string>
<string name="devicetype_colmi_r03">Colmi R03</string>
<string name="devicetype_colmi_r06">Colmi R06</string>
@@ -4009,5 +4010,51 @@
<string name="even_realities_frame_description">%1$s %2$s (%3$s: %4$s)</string>
<string name="even_realities_left_lens">Left Lens</string>
<string name="even_realities_right_lens">Right Lens</string>
<string name="pref_aawireless_dongle_confirmation">After toggling dongle mode, you will need to pair the device again. Please wait at least 30 seconds for the setting to be applied.</string>
<string name="switch_to_next_phone">Next phone</string>
<string name="toggle_standby_on_off">Standby on/off</string>
<string name="advanced_settings">Advanced settings</string>
<string name="country">Country</string>
<string name="developer_mode">Developer mode</string>
<string name="double_click">Double click</string>
<string name="dpi_dots_per_inch">Screen DPI</string>
<string name="factory_reset">Factory reset</string>
<string name="long_press_10_seconds">Long press (10 sec.)</string>
<string name="long_press_2_seconds">Long press (2 sec.)</string>
<string name="no_paired_phones">There are no paired phones</string>
<string name="paired_phones">Paired phones</string>
<string name="pairing_mode">Pairing mode</string>
<string name="pref_aawireless_audio_stutter_fix_high">High</string>
<string name="pref_aawireless_audio_stutter_fix_low">Low</string>
<string name="pref_aawireless_audio_stutter_fix_summary">Override the audio buffer to fix audio stutter</string>
<string name="pref_aawireless_audio_stutter_fix_title">Audio stutter fix</string>
<string name="pref_aawireless_audio_stutter_fix_unlimited">Unlimited</string>
<string name="pref_aawireless_auto_standby_enabled">Auto standby enabled</string>
<string name="pref_aawireless_auto_standby_device">Auto standby device</string>
<string name="pref_aawireless_auto_standby_summary">Automatically enable standby mode when the selected device is not connected to the phone. Enable this if the USB port in your car has power when the car is off.</string>
<string name="pref_aawireless_auto_video_focus_summary">Automatically open Android Auto when connected, skipping the manual screen prompt. Might not work on all cars.</string>
<string name="pref_aawireless_auto_video_focus_title">Auto-start Android Auto</string>
<string name="pref_aawireless_developer_mode_summary">Enable for app development</string>
<string name="pref_aawireless_disable_media_sink_summary">Allow media playback through a different Bluetooth device</string>
<string name="pref_aawireless_disable_media_sink_title">Disable media sink</string>
<string name="pref_aawireless_disable_tts_sink_summary">Allow speech instructions to through a different Bluetooth device</string>
<string name="pref_aawireless_disable_tts_sink_title">Disable TTS sink</string>
<string name="pref_aawireless_dongle_mode_summary">Use the native dongle logic for Android Auto. This may help with bluetooth hands-free connectivity issues. When enabled, manual phone switching is not possible.</string>
<string name="pref_aawireless_dongle_mode_title">Dongle mode</string>
<string name="pref_aawireless_dpi_summary">Set to 0 to use the car\'s default value</string>
<string name="pref_aawireless_passthrough_summary">Pass data directly, without any processing</string>
<string name="pref_aawireless_passthrough_title">Passthrough</string>
<string name="pref_aawireless_prefer_last">Prefer last connected phone</string>
<string name="pref_aawireless_remove_tap_restriction_summary">Allow over 6 taps in succession. May impact GPS accuracy.</string>
<string name="pref_aawireless_remove_tap_restriction_title">Remove tap restriction</string>
<string name="pref_aawireless_start_fix_summary">Workaround - enable this if the LED is solid blue, but Android Auto is not starting</string>
<string name="pref_aawireless_start_fix_title">Start fix</string>
<string name="pref_aawireless_vag_crash_fix_summary">Fix crash on VAG Technisat units during song skip/pause. Do not enable on other cars, may cause glitches.</string>
<string name="pref_aawireless_vag_crash_fix_title">VAG crash fix</string>
<string name="single_click">Single click</string>
<string name="switch_to_phone">Switch to %1$s</string>
<string name="wifi_channel">Wi-Fi Channel</string>
<string name="wifi_frequency_2_4_ghz">2.4 GHz</string>
<string name="wifi_frequency_5_ghz">5 GHz</string>
<string name="wifi_frequency">Wi-Fi Frequency</string>
</resources>
@@ -0,0 +1,103 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="dongle_mode"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_dongle_mode_summary"
android:title="@string/pref_aawireless_dongle_mode_title"
app:icon="@drawable/ic_usb_dongle" />
<SwitchPreferenceCompat
android:defaultValue="true"
android:key="passthrough"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_passthrough_summary"
android:title="@string/pref_aawireless_passthrough_title"
app:disableDependentsState="true"
app:icon="@drawable/ic_arrow_depth" />
<ListPreference
android:defaultValue="off"
android:dependency="passthrough"
android:entries="@array/aawireless_audio_stutter_fix_names"
android:entryValues="@array/aawireless_audio_stutter_fix_values"
android:key="audio_stutter_fix"
android:summary="@string/pref_aawireless_audio_stutter_fix_summary"
android:title="@string/pref_aawireless_audio_stutter_fix_title"
app:icon="@drawable/ic_volume_up" />
<EditTextPreference
android:defaultValue="0"
android:dependency="passthrough"
android:key="dpi"
android:summary="@string/pref_aawireless_dpi_summary"
android:title="@string/dpi_dots_per_inch"
app:icon="@drawable/ic_font_size" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="disable_media_sink"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_disable_media_sink_summary"
android:title="@string/pref_aawireless_disable_media_sink_title"
app:icon="@drawable/ic_music_note" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="disable_tts_sink"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_disable_tts_sink_summary"
android:title="@string/pref_aawireless_disable_tts_sink_title"
app:icon="@drawable/ic_voice" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="remove_tap_restriction"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_remove_tap_restriction_summary"
android:title="@string/pref_aawireless_remove_tap_restriction_title"
app:icon="@drawable/ic_touch" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="vag_crash_fix"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_vag_crash_fix_summary"
android:title="@string/pref_aawireless_vag_crash_fix_title"
app:icon="@drawable/ic_extension" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="start_fix"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_start_fix_summary"
android:title="@string/pref_aawireless_start_fix_title"
app:icon="@drawable/ic_extension" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="developer_mode"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_developer_mode_summary"
android:title="@string/developer_mode"
app:icon="@drawable/ic_developer_mode" />
<SwitchPreferenceCompat
android:defaultValue="false"
android:dependency="passthrough"
android:key="auto_video_focus"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_auto_video_focus_summary"
android:title="@string/pref_aawireless_auto_video_focus_title"
app:icon="@drawable/ic_focus" />
</androidx.preference.PreferenceScreen>
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="auto_standby_enabled"
android:layout="@layout/preference_checkbox"
android:summary="@string/pref_aawireless_auto_standby_summary"
android:title="@string/pref_aawireless_auto_standby_enabled"
app:icon="@drawable/ic_activity_sleep" />
<ListPreference
android:defaultValue=""
android:dependency="auto_standby_enabled"
android:entries="@array/empty_array"
android:entryValues="@array/empty_array"
android:key="auto_standby_device"
android:title="@string/pref_aawireless_auto_standby_device"
app:icon="@drawable/ic_devices_other"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
android:icon="@drawable/ic_touch"
android:key="screen_button_actions"
android:persistent="false"
android:summary="@string/mi2_prefs_button_actions_summary"
android:title="@string/mi2_prefs_button_actions">
<ListPreference
android:defaultValue="none"
android:entries="@array/aawireless_click_names"
android:entryValues="@array/aawireless_click_values"
android:icon="@drawable/ic_filter_1"
android:key="button_mode_single_click"
android:title="@string/single_click"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:defaultValue="none"
android:entries="@array/aawireless_click_names"
android:entryValues="@array/aawireless_click_values"
android:icon="@drawable/ic_filter_2"
android:key="button_mode_double_click"
android:title="@string/double_click"
app:useSimpleSummaryProvider="true" />
<Preference
android:enabled="false"
android:icon="@drawable/ic_horizontal_rule"
android:key="button_mode_long_press_2s"
android:shouldDisableView="false"
android:summary="@string/pairing_mode"
android:title="@string/long_press_2_seconds" />
<Preference
android:enabled="false"
android:icon="@drawable/ic_horizontal_rule"
android:key="button_mode_long_press_10s"
android:shouldDisableView="false"
android:summary="@string/factory_reset"
android:title="@string/long_press_10_seconds" />
</PreferenceScreen>
</androidx.preference.PreferenceScreen>
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<PreferenceScreen
android:icon="@drawable/ic_smartphone"
android:key="screen_paired_phones"
android:persistent="false"
android:title="@string/paired_phones">
<SwitchPreferenceCompat
android:defaultValue="false"
android:key="prefer_last_connected"
android:layout="@layout/preference_checkbox"
android:title="@string/pref_aawireless_prefer_last"
app:icon="@drawable/ic_bluetooth_connected" />
<PreferenceCategory
android:key="paired_phones_header"
android:title="@string/paired_phones"
app:iconSpaceReserved="false">
<Preference
android:key="no_paired_phones"
android:summary="@string/no_paired_phones"
app:iconSpaceReserved="false" />
</PreferenceCategory>
</PreferenceScreen>
</androidx.preference.PreferenceScreen>
@@ -7,11 +7,6 @@
android:summary="@string/mi2_prefs_button_actions_summary"
android:title="@string/mi2_prefs_button_actions">
<!-- workaround for missing toolbar -->
<PreferenceCategory android:title="@string/mi2_prefs_button_action" />
public static final String PREF_BUTTON_ACTION_PRESS_DELAY = "button_action_press_count_delay";
<SwitchPreferenceCompat
android:defaultValue="false"
android:icon="@drawable/ic_smart_button"
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<EditTextPreference
android:defaultValue="US"
android:key="country"
android:title="@string/country"
app:icon="@drawable/ic_language"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceScreen
android:icon="@drawable/ic_warning"
android:key="pref_screen_advanced_settings"
android:persistent="false"
android:title="@string/advanced_settings">
</PreferenceScreen>
</androidx.preference.PreferenceScreen>
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<ListPreference
android:defaultValue="5"
android:entries="@array/wifi_frequency_names"
android:entryValues="@array/wifi_frequency_values"
android:icon="@drawable/ic_wifi_tethering"
android:key="wifi_frequency"
android:title="@string/wifi_frequency"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:entries="@array/wifi_channels_2_4_ghz"
android:entryValues="@array/wifi_channels_2_4_ghz"
android:icon="@drawable/ic_frequency"
android:key="wifi_channel_2_4"
android:title="@string/wifi_channel"
app:useSimpleSummaryProvider="true" />
<ListPreference
android:entries="@array/wifi_channels_5_ghz"
android:entryValues="@array/wifi_channels_5_ghz"
android:icon="@drawable/ic_frequency"
android:key="wifi_channel_5"
android:title="@string/wifi_channel"
app:useSimpleSummaryProvider="true" />
</androidx.preference.PreferenceScreen>