mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Add auth key activity
This commit is contained in:
@@ -664,6 +664,10 @@
|
||||
android:name=".activities.discovery.DiscoveryActivityV2"
|
||||
android:label="@string/title_activity_discovery"
|
||||
android:parentActivityName=".activities.ControlCenterv2" />
|
||||
<activity
|
||||
android:name=".activities.AuthKeyActivity"
|
||||
android:label="@string/pref_title_authkey"
|
||||
android:parentActivityName=".activities.discovery.DiscoveryActivityV2" />
|
||||
<activity
|
||||
android:name=".activities.AndroidPairingActivity"
|
||||
android:label="@string/title_activity_android_pairing" />
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.net.toUri
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst
|
||||
import nodomain.freeyourgadget.gadgetbridge.databinding.ActivityAuthKeyBinding
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
|
||||
class AuthKeyActivity : AbstractGBActivity() {
|
||||
private lateinit var binding: ActivityAuthKeyBinding
|
||||
private var deviceCandidate: GBDeviceCandidate? = null
|
||||
private var coordinator: DeviceCoordinator? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityAuthKeyBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
deviceCandidate = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
intent.getParcelableExtra(EXTRA_DEVICE_CANDIDATE, GBDeviceCandidate::class.java)
|
||||
} else {
|
||||
intent.getParcelableExtra(EXTRA_DEVICE_CANDIDATE)
|
||||
}
|
||||
|
||||
deviceCandidate?.let { candidate ->
|
||||
coordinator = DeviceHelper.getInstance().resolveDeviceType(candidate).deviceCoordinator
|
||||
}
|
||||
|
||||
if (deviceCandidate == null || coordinator == null) {
|
||||
Toast.makeText(this, "Device info missing", Toast.LENGTH_LONG).show()
|
||||
finish()
|
||||
return
|
||||
}
|
||||
|
||||
title = getString(
|
||||
R.string.auth_key_activity_title_for,
|
||||
deviceCandidate?.name ?: getString(R.string.devicetype_unknown)
|
||||
)
|
||||
|
||||
deviceCandidate?.macAddress?.let { macAddress ->
|
||||
val sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(macAddress)
|
||||
val authKey = sharedPrefs.getString(DeviceSettingsPreferenceConst.PREF_AUTH_KEY, "")
|
||||
if (authKey?.isNotEmpty() == true) {
|
||||
binding.authKeyEditText.setText(authKey)
|
||||
}
|
||||
}
|
||||
|
||||
binding.submitAuthKeyButton.setOnClickListener {
|
||||
val authKey = binding.authKeyEditText.text.toString().trim()
|
||||
|
||||
if (authKey.isEmpty()) {
|
||||
binding.authKeyInputLayout.error = getString(R.string.auth_key_required_message)
|
||||
return@setOnClickListener
|
||||
} else {
|
||||
// Clear error
|
||||
binding.authKeyInputLayout.error = null
|
||||
}
|
||||
|
||||
if (coordinator?.validateAuthKey(authKey) == true) {
|
||||
// Save the auth key
|
||||
deviceCandidate?.macAddress?.let { macAddress ->
|
||||
val sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(macAddress)
|
||||
sharedPrefs.edit { putString(DeviceSettingsPreferenceConst.PREF_AUTH_KEY, authKey) }
|
||||
}
|
||||
|
||||
// Return the auth key and candidate to the calling activity
|
||||
val resultIntent = Intent().apply {
|
||||
putExtra(EXTRA_AUTH_KEY_RESULT, authKey)
|
||||
putExtra(EXTRA_DEVICE_CANDIDATE_RESULT, deviceCandidate)
|
||||
}
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
} else {
|
||||
binding.authKeyInputLayout.error = getString(R.string.invalid_auth_key_message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.menu_auth_key, menu)
|
||||
val helpItem = menu.findItem(R.id.auth_key_help)
|
||||
helpItem?.isVisible = coordinator?.authHelp != null
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.auth_key_help -> {
|
||||
coordinator?.authHelp?.let { url ->
|
||||
val intent = Intent(Intent.ACTION_VIEW, coordinator?.authHelp!!.toUri())
|
||||
try {
|
||||
startActivity(intent)
|
||||
} catch (e: Exception) {
|
||||
GB.toast(this, getString(R.string.cannot_open_help_url), Toast.LENGTH_SHORT, GB.ERROR, e)
|
||||
}
|
||||
} ?: GB.toast(this, "No help URL available.", Toast.LENGTH_SHORT, GB.ERROR)
|
||||
true
|
||||
}
|
||||
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val EXTRA_DEVICE_CANDIDATE = "EXTRA_DEVICE_CANDIDATE_FOR_AUTH"
|
||||
const val EXTRA_AUTH_KEY_RESULT = "EXTRA_AUTH_KEY_RESULT"
|
||||
const val EXTRA_DEVICE_CANDIDATE_RESULT = "EXTRA_DEVICE_CANDIDATE_RESULT"
|
||||
|
||||
fun newIntent(context: Context, deviceCandidate: GBDeviceCandidate): Intent {
|
||||
return Intent(context, AuthKeyActivity::class.java).apply {
|
||||
putExtra(EXTRA_DEVICE_CANDIDATE, deviceCandidate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
-16
@@ -85,6 +85,7 @@ import java.util.Set;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AuthKeyActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.DebugActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.DeviceCandidateAdapter;
|
||||
@@ -128,6 +129,8 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
private Button startButton;
|
||||
private boolean scanning;
|
||||
|
||||
private ActivityResultLauncher<Intent> authKeyLauncher;
|
||||
|
||||
private long selectedUnsupportedDeviceKey = DebugActivity.SELECT_DEVICE;
|
||||
|
||||
private final Runnable stopRunnable = () -> {
|
||||
@@ -194,6 +197,22 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
|
||||
checkAndRequestLocationPermission();
|
||||
|
||||
authKeyLauncher = registerForActivityResult(
|
||||
new ActivityResultContracts.StartActivityForResult(),
|
||||
result -> {
|
||||
if (result.getResultCode() == Activity.RESULT_OK) {
|
||||
final Intent data = result.getData();
|
||||
if (data == null) {
|
||||
// Should never happen
|
||||
GB.toast(this, "Auth data is null", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
final GBDeviceCandidate deviceCandidate = data.getParcelableExtra(AuthKeyActivity.EXTRA_DEVICE_CANDIDATE_RESULT);
|
||||
final DeviceType deviceType = DeviceHelper.getInstance().resolveDeviceType(deviceCandidate);
|
||||
startPair(deviceCandidate, deviceType.getDeviceCoordinator());
|
||||
}
|
||||
});
|
||||
|
||||
if (!startDiscovery()) {
|
||||
/* if we couldn't start scanning, go back to the main page.
|
||||
A toast will have been shown explaining what's wrong */
|
||||
@@ -297,7 +316,7 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
try {
|
||||
final Method isConnectedMethod = device.getClass().getMethod("isConnected");
|
||||
final Boolean isConnected = (Boolean) isConnectedMethod.invoke(device);
|
||||
if (isConnected!= null && isConnected) {
|
||||
if (isConnected != null && isConnected) {
|
||||
LOG.debug("Pre-adding already bonded device {}", device.getAddress());
|
||||
deviceFoundProcessor.scheduleProcessing(new GBScanEvent(device, (short) -1, null));
|
||||
}
|
||||
@@ -528,7 +547,8 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
private void showWarnDialog(@StringRes final int message) {
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setMessage(message)
|
||||
.setPositiveButton(R.string.ok, (dialog, whichButton) -> {})
|
||||
.setPositiveButton(R.string.ok, (dialog, whichButton) -> {
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@@ -630,29 +650,25 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
final DeviceCoordinator coordinator = deviceType.getDeviceCoordinator();
|
||||
LOG.info("Using device candidate {} with coordinator {}", deviceCandidate, coordinator.getClass());
|
||||
|
||||
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
|
||||
final SharedPreferences sharedPrefs = GBApplication.getDeviceSpecificSharedPrefs(deviceCandidate.getMacAddress());
|
||||
|
||||
final String authKey = sharedPrefs.getString("authkey", null);
|
||||
if (authKey == null || authKey.isEmpty()) {
|
||||
showWarnDialog(R.string.discovery_need_to_enter_authkey);
|
||||
return;
|
||||
} else if (!coordinator.validateAuthKey(authKey)) {
|
||||
showWarnDialog(R.string.discovery_entered_invalid_authkey);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (coordinator.suggestUnbindBeforePair() && deviceCandidate.isBonded()) {
|
||||
new MaterialAlertDialogBuilder(getContext())
|
||||
.setTitle(R.string.unbind_before_pair_title)
|
||||
.setMessage(R.string.unbind_before_pair_message)
|
||||
.setIcon(R.drawable.ic_warning_gray)
|
||||
.setPositiveButton(R.string.ok, (dialog, whichButton) -> {
|
||||
startPair(deviceCandidate, coordinator);
|
||||
checkAuthKeyAndPair(deviceCandidate, coordinator);
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
} else {
|
||||
checkAuthKeyAndPair(deviceCandidate, coordinator);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkAuthKeyAndPair(final GBDeviceCandidate deviceCandidate, final DeviceCoordinator coordinator) {
|
||||
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
|
||||
final Intent authIntent = AuthKeyActivity.Companion.newIntent(this, deviceCandidate);
|
||||
authKeyLauncher.launch(authIntent);
|
||||
} else {
|
||||
startPair(deviceCandidate, coordinator);
|
||||
}
|
||||
|
||||
-3
@@ -83,9 +83,6 @@ public class DeviceCandidateAdapter extends ArrayAdapter<GBDeviceCandidate> {
|
||||
if (coordinator.isExperimental()) {
|
||||
statusLines.add(getContext().getString(R.string.device_experimental));
|
||||
}
|
||||
if (coordinator.getBondingStyle() == DeviceCoordinator.BONDING_STYLE_REQUIRE_KEY) {
|
||||
statusLines.add(getContext().getString(R.string.device_requires_key));
|
||||
}
|
||||
|
||||
deviceStatus.setText(TextUtils.join("\n", statusLines));
|
||||
return view;
|
||||
|
||||
+5
@@ -1004,6 +1004,11 @@ public abstract class AbstractDeviceCoordinator implements DeviceCoordinator {
|
||||
return !(authKey.getBytes().length < 34 || !authKey.startsWith("0x"));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getAuthHelp() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceCardAction> getCustomActions() {
|
||||
return Collections.emptyList();
|
||||
|
||||
@@ -861,5 +861,8 @@ public interface DeviceCoordinator {
|
||||
|
||||
boolean validateAuthKey(String authKey);
|
||||
|
||||
@Nullable
|
||||
String getAuthHelp();
|
||||
|
||||
List<DeviceCardAction> getCustomActions();
|
||||
}
|
||||
|
||||
+6
@@ -155,6 +155,12 @@ public class CmfWatchProCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return authKeyBytes.length == 32 || (authKey.startsWith("0x") && authKeyBytes.length == 34);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAuthHelp() {
|
||||
return "https://gadgetbridge.org/basics/pairing/nothing-cmf-server/";
|
||||
}
|
||||
|
||||
@Override
|
||||
public int[] getSupportedDeviceSpecificAuthenticationSettings() {
|
||||
return new int[]{R.xml.devicesettings_pairingkey};
|
||||
|
||||
+6
@@ -98,6 +98,12 @@ public abstract class HuamiCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAuthHelp() {
|
||||
return "https://gadgetbridge.org/basics/pairing/huami-xiaomi-server/";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void deleteDevice(@NonNull final GBDevice gbDevice,
|
||||
@NonNull final Device device,
|
||||
|
||||
+7
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
import android.net.Uri;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.apache.commons.lang3.ArrayUtils;
|
||||
|
||||
@@ -700,4 +701,10 @@ public abstract class ZeppOsCoordinator extends HuamiCoordinator {
|
||||
final byte[] authKeyBytes = authKey.trim().getBytes();
|
||||
return authKeyBytes.length == 32 || (authKey.trim().startsWith("0x") && authKeyBytes.length == 34);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAuthHelp() {
|
||||
return "https://gadgetbridge.org/basics/pairing/huami-xiaomi-server/";
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -315,6 +315,12 @@ public class QHybridCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
};
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAuthHelp() {
|
||||
return "https://gadgetbridge.org/basics/pairing/fossil-server/";
|
||||
}
|
||||
|
||||
@Deprecated // we should use the isHybridHR(GBDevice) instead of iterating every single device
|
||||
private boolean isHybridHR() {
|
||||
List<GBDevice> devices = GBApplication.app().getDeviceManager().getSelectedDevices();
|
||||
|
||||
+6
@@ -99,6 +99,12 @@ public abstract class XiaomiCoordinator extends AbstractBLEDeviceCoordinator {
|
||||
|| AUTH_KEY_PATTERN.matcher(authKey.trim()).matches();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getAuthHelp() {
|
||||
return "https://gadgetbridge.org/basics/pairing/huami-xiaomi-server/";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public InstallHandler findInstallHandler(final Uri uri, final Context context) {
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="16dp"
|
||||
tools:context=".activities.AuthKeyActivity">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/auth_key_explanation_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:autoLink="all"
|
||||
android:linksClickable="true"
|
||||
android:text="@string/auth_key_explanation"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/auth_key_input_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/auth_key_explanation_text">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/auth_key_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/pref_title_authkey"
|
||||
android:inputType="textVisiblePassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<Button
|
||||
android:id="@+id/submit_auth_key_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="24dp"
|
||||
android:text="@string/submit_button_text"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/auth_key_input_layout" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/auth_key_help"
|
||||
android:icon="@drawable/ic_help_outline"
|
||||
android:title="@string/help"
|
||||
app:iconTint="?attr/actionmenu_icon_color"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
||||
@@ -2445,7 +2445,6 @@
|
||||
<string name="app_crash_notification_title">%1s has crashed</string>
|
||||
<string name="app_crash_share_stacktrace">Share error</string>
|
||||
<string name="device_is_currently_bonded">ALREADY BONDED</string>
|
||||
<string name="device_requires_key">KEY REQUIRED, LONG PRESS TO ENTER</string>
|
||||
<string name="device_unsupported">UNSUPPORTED</string>
|
||||
<string name="device_experimental">EXPERIMENTAL</string>
|
||||
<string name="error_background_service_reason">Starting the background service failed because of an exception - click here for more information.\n\nError:</string>
|
||||
@@ -4189,4 +4188,11 @@
|
||||
<string name="error_queue_is_dead">GB\'s processing queue is dead</string>
|
||||
<string name="error_sender_is_dead">GB\'s sender thread is dead</string>
|
||||
<string name="error_receiver_is_dead">GB\'s receiver thread is dead</string>
|
||||
<string name="submit_button_text">Submit</string>
|
||||
<string name="auth_key_required_message">Auth key cannot be empty</string>
|
||||
<string name="invalid_auth_key_message">Invalid auth key - please try again</string>
|
||||
<string name="auth_key_activity_title_for">Auth key for %1$s</string>
|
||||
<string name="auth_key_explanation">This device requires an authentication key for pairing.</string>
|
||||
<string name="help">Help</string>
|
||||
<string name="cannot_open_help_url">Could not open URL</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user