mirror of
https://codeberg.org/Freeyourgadget/Gadgetbridge.git
synced 2026-07-31 07:44:24 +02:00
Revamp Debug activity
This commit is contained in:
Generated
+3
@@ -116,12 +116,15 @@
|
||||
<w>morpheuz</w>
|
||||
<w>mosenkovs</w>
|
||||
<w>multisport</w>
|
||||
<w>musicspec</w>
|
||||
<w>musicstatespec</w>
|
||||
<w>nack</w>
|
||||
<w>nephiel</w>
|
||||
<w>nodomain</w>
|
||||
<w>nordhøy</w>
|
||||
<w>normano</w>
|
||||
<w>novotny</w>
|
||||
<w>opentracks</w>
|
||||
<w>oraclejdk</w>
|
||||
<w>oukitel</w>
|
||||
<w>paddleboarding</w>
|
||||
|
||||
@@ -664,10 +664,14 @@
|
||||
dialog when changing orientation
|
||||
-->
|
||||
<activity
|
||||
android:name=".activities.DebugActivity"
|
||||
android:name=".activities.debug.DebugActivityV2"
|
||||
android:label="@string/title_activity_debug"
|
||||
android:parentActivityName=".activities.ControlCenterv2"
|
||||
android:screenOrientation="portrait"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.debug.preferences.PreferenceManagerActivity"
|
||||
android:label="@string/menuitem_settings"
|
||||
android:parentActivityName=".activities.debug.DebugActivityV2"
|
||||
android:windowSoftInputMode="stateHidden" />
|
||||
<activity
|
||||
android:name=".activities.AboutActivity"
|
||||
|
||||
@@ -141,7 +141,7 @@ public class GBApplication extends Application {
|
||||
private static final Lock dbLock = new ReentrantLock();
|
||||
private static DeviceService deviceService;
|
||||
private static SharedPreferences sharedPrefs;
|
||||
private static final String PREFS_VERSION = "shared_preferences_version";
|
||||
public static final String PREFS_VERSION = "shared_preferences_version";
|
||||
//if preferences have to be migrated, increment the following and add the migration logic in migratePrefs below; see http://stackoverflow.com/questions/16397848/how-can-i-migrate-android-preferences-with-a-new-version
|
||||
private static final int CURRENT_PREFS_VERSION = 54;
|
||||
|
||||
|
||||
+2
-1
@@ -65,6 +65,7 @@ import java.util.Objects;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.debug.DebugActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryActivityV2;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
@@ -344,7 +345,7 @@ public class ControlCenterv2 extends AppCompatActivity
|
||||
startActivityForResult(settingsIntent, MENU_REFRESH_CODE);
|
||||
return false;
|
||||
} else if (itemId == R.id.action_debug) {
|
||||
final Intent debugIntent = new Intent(this, DebugActivity.class);
|
||||
final Intent debugIntent = new Intent(this, DebugActivityV2.class);
|
||||
startActivity(debugIntent);
|
||||
return false;
|
||||
} else if (itemId == R.id.action_data_management) {
|
||||
|
||||
-162
@@ -19,12 +19,9 @@ package nodomain.freeyourgadget.gadgetbridge.activities;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
@@ -145,81 +142,9 @@ public class DataManagementActivity extends AbstractGBActivity {
|
||||
startActivity(fileManagerIntent);
|
||||
});
|
||||
|
||||
int oldDBVisibility = hasOldActivityDatabase() ? View.VISIBLE : View.GONE;
|
||||
|
||||
TextView deleteOldActivityTitle = findViewById(R.id.mergeOldActivityDataTitle);
|
||||
deleteOldActivityTitle.setVisibility(oldDBVisibility);
|
||||
|
||||
Button deleteOldActivityDBButton = findViewById(R.id.deleteOldActivityDB);
|
||||
deleteOldActivityDBButton.setVisibility(oldDBVisibility);
|
||||
deleteOldActivityDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
deleteOldActivityDbFile();
|
||||
}
|
||||
});
|
||||
|
||||
Button deleteDBButton = findViewById(R.id.emptyDBButton);
|
||||
deleteDBButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
deleteActivityDatabase();
|
||||
}
|
||||
});
|
||||
|
||||
TextView dbPath2 = findViewById(R.id.activity_data_management_path2);
|
||||
dbPath2.setText(getExternalPath());
|
||||
|
||||
Button cleanExportDirectoryButton = findViewById(R.id.cleanExportDirectoryButton);
|
||||
cleanExportDirectoryButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
cleanExportDirectory();
|
||||
}
|
||||
});
|
||||
|
||||
sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
}
|
||||
|
||||
private String getAutoExportLocationPreferenceString() {
|
||||
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_DB_LOCATION, null);
|
||||
if (autoExportLocation == null) {
|
||||
return "";
|
||||
}
|
||||
return autoExportLocation;
|
||||
}
|
||||
|
||||
private String getAutoExportLocationUri() {
|
||||
String autoExportLocation = getAutoExportLocationPreferenceString();
|
||||
if (autoExportLocation == null) {
|
||||
return "";
|
||||
}
|
||||
Uri uri = Uri.parse(autoExportLocation);
|
||||
try {
|
||||
|
||||
return AndroidUtils.getFilePath(getApplicationContext(), uri);
|
||||
} catch (IllegalArgumentException e) {
|
||||
LOG.error("getFilePath did not work, trying to resolve content provider path");
|
||||
try {
|
||||
Cursor cursor = getContentResolver().query(
|
||||
uri,
|
||||
new String[]{DocumentsContract.Document.COLUMN_DISPLAY_NAME},
|
||||
null, null, null, null
|
||||
);
|
||||
if (cursor != null && cursor.moveToFirst()) {
|
||||
return cursor.getString(cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME));
|
||||
}
|
||||
} catch (Exception exception) {
|
||||
LOG.error("Error getting export path", exception);
|
||||
}
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private boolean hasOldActivityDatabase() {
|
||||
return new DBHelper(this).existsDB("ActivityDatabase");
|
||||
}
|
||||
|
||||
private String getExternalPath() {
|
||||
try {
|
||||
return FileUtils.getExternalFilesDir().getAbsolutePath();
|
||||
@@ -351,93 +276,6 @@ public class DataManagementActivity extends AbstractGBActivity {
|
||||
.show();
|
||||
}
|
||||
|
||||
private void deleteActivityDatabase() {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setCancelable(true)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setTitle(R.string.dbmanagementactivity_delete_activity_data_title)
|
||||
.setMessage(R.string.dbmanagementactivity_really_delete_entire_db)
|
||||
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (GBApplication.deleteActivityDatabase(DataManagementActivity.this)) {
|
||||
GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_database_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} else {
|
||||
GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
private void deleteOldActivityDbFile() {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.dbmanagementactivity_delete_old_activity_db)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setMessage(R.string.dbmanagementactivity_delete_old_activitydb_confirmation)
|
||||
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (GBApplication.deleteOldActivityDatabase(DataManagementActivity.this)) {
|
||||
GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_successfully_deleted), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} else {
|
||||
GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_old_activity_db_deletion_failed), Toast.LENGTH_SHORT, GB.INFO);
|
||||
}
|
||||
}
|
||||
});
|
||||
new MaterialAlertDialogBuilder(this).setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
});
|
||||
new MaterialAlertDialogBuilder(this).show();
|
||||
}
|
||||
|
||||
private void cleanExportDirectory() {
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setCancelable(true)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setTitle(R.string.activity_DB_clean_export_directory_warning_title)
|
||||
.setMessage(getString(R.string.activity_DB_clean_export_directory_warning_message))
|
||||
.setPositiveButton(R.string.Delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
try {
|
||||
File externalFilesDir = FileUtils.getExternalFilesDir();
|
||||
String autoexportFile = getAutoExportLocationUri();
|
||||
for (File file : externalFilesDir.listFiles()) {
|
||||
if (file.isFile() &&
|
||||
(!FileUtils.getExtension(file.toString()).toLowerCase().equals("gpx")) && //keep GPX files
|
||||
(!file.toString().equals(autoexportFile)) // do not remove autoexport
|
||||
) {
|
||||
LOG.debug("Deleting file: " + file);
|
||||
try {
|
||||
file.delete();
|
||||
} catch (Exception exception) {
|
||||
LOG.error("Error erasing file: " + exception);
|
||||
}
|
||||
}
|
||||
}
|
||||
GB.toast(getString(R.string.dbmanagementactivity_export_finished), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} catch (Exception ex) {
|
||||
GB.toast(DataManagementActivity.this, getString(R.string.dbmanagementactivity_error_cleaning_export_directory, ex.getLocalizedMessage()), Toast.LENGTH_LONG, GB.ERROR, ex);
|
||||
}
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
|
||||
-1310
File diff suppressed because it is too large
Load Diff
-13
@@ -66,8 +66,6 @@ import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesA
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.discovery.DiscoveryPairingPreferenceActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.maps.MapsSettingsActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.TimeChangeReceiver;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather;
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.weather.WeatherCacheManager;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
@@ -186,17 +184,6 @@ public class SettingsActivity extends AbstractSettingsActivityV2 {
|
||||
}
|
||||
}
|
||||
|
||||
pref = findPreference("cache_weather");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
||||
boolean doEnable = Boolean.TRUE.equals(newVal);
|
||||
|
||||
Weather.initializeCache(new WeatherCacheManager(requireContext().getCacheDir(), doEnable));
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
pref = findPreference("language");
|
||||
if (pref != null) {
|
||||
pref.setOnPreferenceChangeListener((preference, newVal) -> {
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.text.InputType
|
||||
import android.widget.Toast
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceGroup
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractPreferenceFragment
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import java.util.UUID
|
||||
|
||||
abstract class AbstractDebugFragment : AbstractPreferenceFragment() {
|
||||
protected fun runOnDebugDevices(title: String? = null, forceDialog: Boolean = false, function: (GBDevice) -> Unit) {
|
||||
val selectedDevices = GBApplication.app().deviceManager.selectedDevices
|
||||
.filter { it.isInitialized }
|
||||
.sortedBy { it.aliasOrName }
|
||||
|
||||
if (selectedDevices.isEmpty()) {
|
||||
GB.toast(
|
||||
requireContext(),
|
||||
requireContext().getString(R.string.info_no_devices_connected),
|
||||
Toast.LENGTH_LONG,
|
||||
GB.ERROR
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (selectedDevices.size > 1 || forceDialog) {
|
||||
val deviceNames = selectedDevices.map { it.aliasOrName }.toTypedArray()
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(title ?: getString(R.string.choose_device))
|
||||
.setItems(deviceNames) { _, which ->
|
||||
function(selectedDevices[which])
|
||||
}
|
||||
.setNegativeButton(R.string.Cancel, null)
|
||||
.show()
|
||||
} else {
|
||||
function(selectedDevices[0])
|
||||
}
|
||||
}
|
||||
|
||||
protected fun onClick(prefKey: String, function: () -> Unit) {
|
||||
findPreference<Preference>(prefKey)!!.setOnPreferenceClickListener {
|
||||
function.invoke()
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
protected fun setInputTypeNumber(prefKey: String) {
|
||||
findPreference<EditTextPreference>(prefKey)!!.setOnBindEditTextListener {
|
||||
it.setInputType(InputType.TYPE_CLASS_NUMBER)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun setListPreferenceEntries(prefKey: String, entries: Array<String>) {
|
||||
val statePref = findPreference<ListPreference>(prefKey)!!
|
||||
statePref.entries = entries
|
||||
statePref.entryValues = entries
|
||||
}
|
||||
|
||||
protected fun removeDynamicPrefs(group: PreferenceGroup) {
|
||||
var i = 0
|
||||
while (i < group.preferenceCount) {
|
||||
val preference: Preference = group.getPreference(i)
|
||||
if (preference.key.startsWith(PREF_DYNAMIC_PREFIX)) {
|
||||
group.removePreference(preference)
|
||||
i--
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
protected fun addDynamicPref(
|
||||
group: PreferenceGroup? = preferenceScreen,
|
||||
title: String,
|
||||
summary: String = "",
|
||||
icon: Int = 0,
|
||||
onClickFunction: (() -> Unit)? = null
|
||||
): Preference {
|
||||
val pref = Preference(requireContext())
|
||||
pref.setKey("${PREF_DYNAMIC_PREFIX}_${UUID.randomUUID()}")
|
||||
pref.isPersistent = false
|
||||
pref.isSelectable = onClickFunction != null
|
||||
pref.title = title
|
||||
pref.summary = summary
|
||||
if (icon != 0) {
|
||||
pref.setIcon(icon)
|
||||
} else if (!title.isEmpty()) {
|
||||
pref.isIconSpaceReserved = false
|
||||
}
|
||||
if (onClickFunction != null) {
|
||||
pref.setOnPreferenceClickListener {
|
||||
onClickFunction.invoke()
|
||||
return@setOnPreferenceClickListener true
|
||||
}
|
||||
}
|
||||
|
||||
group?.addPreference(pref)
|
||||
|
||||
return pref
|
||||
}
|
||||
|
||||
protected fun goTo(fragment: AbstractDebugFragment) {
|
||||
requireActivity().supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.settings_container, fragment)
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
|
||||
companion object {
|
||||
protected const val PREF_DYNAMIC_PREFIX: String = "pref_debug_dynamic"
|
||||
}
|
||||
}
|
||||
+82
@@ -0,0 +1,82 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.content.edit
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.CallSpec
|
||||
|
||||
class CallsDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun setupPreferences() {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_calls, null)
|
||||
|
||||
onClick(PREF_DEBUG_CALL_SEND) { sendCallSpec() }
|
||||
onClick(PREF_DEBUG_CALL_RESET) { resetPreferences() }
|
||||
|
||||
setListPreferenceEntries(
|
||||
PREF_DEBUG_CALL_COMMAND,
|
||||
CallSpec.Command.entries.map { it.name }.toTypedArray()
|
||||
)
|
||||
}
|
||||
|
||||
private fun sendCallSpec() {
|
||||
val sharedPreferences = preferenceManager.sharedPreferences!!
|
||||
val callSpec = CallSpec()
|
||||
|
||||
callSpec.command =
|
||||
CallSpec.Command.valueOf(sharedPreferences.getString(PREF_DEBUG_CALL_COMMAND, "INCOMING")!!).ordinal
|
||||
callSpec.number = sharedPreferences.getString(PREF_DEBUG_CALL_NUMBER, "6365553226")
|
||||
callSpec.name = sharedPreferences.getString(PREF_DEBUG_CALL_NAME, "Mr. Plow")
|
||||
callSpec.sourceName = sharedPreferences.getString(PREF_DEBUG_CALL_SOURCE_NAME, null)
|
||||
callSpec.sourceAppId = sharedPreferences.getString(PREF_DEBUG_CALL_SOURCE_APP_ID, null)
|
||||
callSpec.key = sharedPreferences.getString(PREF_DEBUG_CALL_KEY, null)
|
||||
callSpec.channelId = sharedPreferences.getString(PREF_DEBUG_CALL_CHANNEL_ID, null)
|
||||
callSpec.category = sharedPreferences.getString(PREF_DEBUG_CALL_CATEGORY, null)
|
||||
callSpec.isVoip = sharedPreferences.getBoolean(PREF_DEBUG_CALL_IS_VOIP, false)
|
||||
callSpec.dndSuppressed = if (sharedPreferences.getBoolean(PREF_DEBUG_CALL_DND_SUPPRESSED_BOOL, false)) 1 else 0
|
||||
|
||||
runOnDebugDevices("Send CallSpec") {
|
||||
GBApplication.deviceService(it).onSetCallState(callSpec)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPreferences() {
|
||||
preferenceScreen.removeAll()
|
||||
|
||||
preferenceManager.sharedPreferences!!.edit(true) {
|
||||
remove(PREF_DEBUG_CALL_COMMAND)
|
||||
remove(PREF_DEBUG_CALL_NUMBER)
|
||||
remove(PREF_DEBUG_CALL_NAME)
|
||||
remove(PREF_DEBUG_CALL_SOURCE_NAME)
|
||||
remove(PREF_DEBUG_CALL_SOURCE_APP_ID)
|
||||
remove(PREF_DEBUG_CALL_KEY)
|
||||
remove(PREF_DEBUG_CALL_CHANNEL_ID)
|
||||
remove(PREF_DEBUG_CALL_CATEGORY)
|
||||
remove(PREF_DEBUG_CALL_IS_VOIP)
|
||||
remove(PREF_DEBUG_CALL_DND_SUPPRESSED_BOOL)
|
||||
}
|
||||
|
||||
// Reload the preference screen to reflect the changes
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_CALL_SEND = "pref_debug_call_send"
|
||||
private const val PREF_DEBUG_CALL_RESET = "pref_debug_call_reset"
|
||||
private const val PREF_DEBUG_HEADER_CALLSPEC = "pref_debug_header_callspec"
|
||||
private const val PREF_DEBUG_CALL_COMMAND = "pref_debug_call_command"
|
||||
private const val PREF_DEBUG_CALL_NUMBER = "pref_debug_call_number"
|
||||
private const val PREF_DEBUG_CALL_NAME = "pref_debug_call_name"
|
||||
private const val PREF_DEBUG_CALL_SOURCE_NAME = "pref_debug_call_source_name"
|
||||
private const val PREF_DEBUG_CALL_SOURCE_APP_ID = "pref_debug_call_source_app_id"
|
||||
private const val PREF_DEBUG_CALL_KEY = "pref_debug_call_key"
|
||||
private const val PREF_DEBUG_CALL_CHANNEL_ID = "pref_debug_call_channel_id"
|
||||
private const val PREF_DEBUG_CALL_CATEGORY = "pref_debug_call_category"
|
||||
private const val PREF_DEBUG_CALL_IS_VOIP = "pref_debug_call_is_voip"
|
||||
private const val PREF_DEBUG_CALL_DND_SUPPRESSED_BOOL = "pref_debug_call_dnd_suppressed_bool"
|
||||
}
|
||||
}
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.bluetooth.BluetoothDevice
|
||||
import android.companion.AssociationInfo
|
||||
import android.companion.AssociationRequest
|
||||
import android.companion.BluetoothDeviceFilter
|
||||
import android.companion.CompanionDeviceManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentSender
|
||||
import android.content.IntentSender.SendIntentException
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.preference.PreferenceCategory
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.Locale
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.O)
|
||||
class CompanionDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_companion, rootKey)
|
||||
|
||||
reloadDevices()
|
||||
}
|
||||
|
||||
private fun reloadDevices() {
|
||||
val companionDevicesHeader: PreferenceCategory = findPreference(PREF_HEADER_COMPANION_DEVICES)!!
|
||||
val otherDevicesHeader: PreferenceCategory = findPreference(PREF_HEADER_OTHER_DEVICES)!!
|
||||
removeDynamicPrefs(companionDevicesHeader)
|
||||
removeDynamicPrefs(otherDevicesHeader)
|
||||
|
||||
val manager = GBApplication.getContext().getSystemService(Context.COMPANION_DEVICE_SERVICE)
|
||||
as CompanionDeviceManager
|
||||
|
||||
val associations: Set<String> = manager.associations.map { it.uppercase(Locale.ROOT) }.toSet()
|
||||
|
||||
val companionDevices = associations
|
||||
.map {
|
||||
val deviceByAddress = GBApplication.app().deviceManager.getDeviceByAddress(it)
|
||||
Device(
|
||||
it,
|
||||
deviceByAddress?.aliasOrName ?: getString(R.string.unknown),
|
||||
deviceByAddress?.deviceCoordinator?.defaultIconResource ?: R.drawable.ic_device_unknown
|
||||
)
|
||||
}
|
||||
.sortedBy { it.name }
|
||||
|
||||
val otherDevices = GBApplication.app().deviceManager.devices
|
||||
.filter { !associations.contains(it.address.uppercase(Locale.ROOT)) }
|
||||
.map {
|
||||
Device(
|
||||
it.address,
|
||||
it.aliasOrName,
|
||||
it.deviceCoordinator.defaultIconResource
|
||||
)
|
||||
}
|
||||
|
||||
if (!companionDevices.isEmpty()) {
|
||||
for (device in companionDevices) {
|
||||
addDynamicPref(companionDevicesHeader, device.name, device.address, device.icon) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setCancelable(true)
|
||||
.setIcon(device.icon)
|
||||
.setTitle("Unpair companion device")
|
||||
.setMessage("Unpair ${device.name} (${device.address}) from companion device?")
|
||||
.setNeutralButton(R.string.Cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.ok) { _, _ -> unpairCompanion(device) }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
addDynamicPref(companionDevicesHeader, "", "No companion devices")
|
||||
}
|
||||
|
||||
if (!otherDevices.isEmpty()) {
|
||||
otherDevicesHeader.isVisible = true
|
||||
for (device in otherDevices) {
|
||||
addDynamicPref(otherDevicesHeader, device.name, device.address, device.icon) {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setCancelable(true)
|
||||
.setIcon(device.icon)
|
||||
.setTitle("Pair as companion")
|
||||
.setMessage("Pair ${device.name} (${device.address}) as companion device?")
|
||||
.setNeutralButton(R.string.Cancel) { _, _ -> }
|
||||
.setPositiveButton(R.string.ok) { _, _ -> pairAsCompanion(device) }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
otherDevicesHeader.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
private data class Device(val address: String, val name: String, val icon: Int)
|
||||
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
if (requestCode == SELECT_DEVICE_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
|
||||
val deviceToPair = data?.getParcelableExtra<BluetoothDevice?>(CompanionDeviceManager.EXTRA_DEVICE)
|
||||
|
||||
if (deviceToPair != null) {
|
||||
if (deviceToPair.getBondState() != BluetoothDevice.BOND_BONDED) {
|
||||
GB.toast("Creating bond...", Toast.LENGTH_SHORT, GB.INFO)
|
||||
deviceToPair.createBond()
|
||||
} else {
|
||||
GB.toast("Bonding complete", Toast.LENGTH_LONG, GB.INFO)
|
||||
reloadDevices()
|
||||
}
|
||||
} else {
|
||||
GB.toast("No device to pair", Toast.LENGTH_LONG, GB.ERROR)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun unpairCompanion(device: Device) {
|
||||
BondingUtil.Disassociate(requireContext(), device.address)
|
||||
reloadDevices()
|
||||
}
|
||||
|
||||
private fun pairAsCompanion(device: Device) {
|
||||
val manager = requireActivity().getSystemService(Context.COMPANION_DEVICE_SERVICE) as CompanionDeviceManager
|
||||
|
||||
if (manager.associations.contains(device.address)) {
|
||||
GB.toast(device.name + " already paired as companion", Toast.LENGTH_LONG, GB.INFO)
|
||||
return
|
||||
}
|
||||
|
||||
val deviceFilter = BluetoothDeviceFilter.Builder()
|
||||
.setAddress(device.address)
|
||||
.build()
|
||||
|
||||
val pairingRequest = AssociationRequest.Builder()
|
||||
.addDeviceFilter(deviceFilter)
|
||||
.setSingleDevice(true)
|
||||
.build()
|
||||
|
||||
val callback: CompanionDeviceManager.Callback = object : CompanionDeviceManager.Callback() {
|
||||
override fun onFailure(error: CharSequence?) {
|
||||
GB.toast("Companion pairing failed: $error", Toast.LENGTH_LONG, GB.ERROR)
|
||||
}
|
||||
|
||||
override fun onAssociationPending(chooserLauncher: IntentSender) {
|
||||
GB.toast("Found device", Toast.LENGTH_SHORT, GB.INFO)
|
||||
|
||||
try {
|
||||
ActivityCompat.startIntentSenderForResult(
|
||||
requireActivity(),
|
||||
chooserLauncher,
|
||||
SELECT_DEVICE_REQUEST_CODE,
|
||||
null,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
null
|
||||
)
|
||||
} catch (e: SendIntentException) {
|
||||
LOG.error("Failed to send intent", e)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAssociationCreated(associationInfo: AssociationInfo) {
|
||||
GB.toast("Companion pairing success", Toast.LENGTH_SHORT, GB.INFO)
|
||||
reloadDevices()
|
||||
}
|
||||
}
|
||||
|
||||
manager.associate(pairingRequest, callback, null)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG: Logger = LoggerFactory.getLogger(CompanionDebugFragment::class.java)
|
||||
const val SELECT_DEVICE_REQUEST_CODE: Int = 1424
|
||||
|
||||
private const val PREF_HEADER_COMPANION_DEVICES = "pref_header_companion_devices"
|
||||
private const val PREF_HEADER_OTHER_DEVICES = "pref_header_other_devices"
|
||||
}
|
||||
}
|
||||
+149
@@ -0,0 +1,149 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
|
||||
class DatabaseDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_database, rootKey)
|
||||
|
||||
onClick(PREF_DEBUG_EMPTY_DATABASE) { emptyDatabase() }
|
||||
onClick(PREF_DEBUG_DELETE_OLD_DATABASE) { deleteOldActivityDbFile() }
|
||||
if (!hasOldActivityDatabase()) {
|
||||
findPreference<Preference>(PREF_DEBUG_DELETE_OLD_DATABASE)!!.isVisible = false
|
||||
}
|
||||
|
||||
addTables()
|
||||
}
|
||||
|
||||
private fun addTables() {
|
||||
val tablesHeader: PreferenceCategory = findPreference(PREF_HEADER_DATABASE_TABLES)!!
|
||||
removeDynamicPrefs(tablesHeader)
|
||||
|
||||
val tableNames = mutableListOf<String>()
|
||||
|
||||
try {
|
||||
GBApplication.acquireDB().use { db ->
|
||||
findPreference<Preference>(PREF_DEBUG_DATABASE_VERSION)?.summary = db.database.version.toString()
|
||||
|
||||
val cursor = db.database.rawQuery(
|
||||
"""
|
||||
SELECT name
|
||||
FROM sqlite_master
|
||||
WHERE type='table'
|
||||
AND name NOT LIKE 'android_%'
|
||||
AND name NOT LIKE 'sqlite_%'
|
||||
""".trimIndent(),
|
||||
null
|
||||
)
|
||||
|
||||
cursor.use {
|
||||
while (it.moveToNext()) {
|
||||
val name = it.getString(it.getColumnIndexOrThrow("name"))
|
||||
tableNames.add(name)
|
||||
}
|
||||
}
|
||||
tableNames.sortWith(Comparator { o1, o2 -> o1.compareTo(o2, true) })
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GB.log("Error accessing database", GB.ERROR, e)
|
||||
}
|
||||
|
||||
for (tableName in tableNames) {
|
||||
addDynamicPref(tablesHeader, tableName) {
|
||||
goTo(
|
||||
DatabaseTableDebugFragment().apply {
|
||||
arguments = Bundle().apply { putString("tableName", tableName) }
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun hasOldActivityDatabase(): Boolean {
|
||||
return DBHelper(requireContext()).existsDB("ActivityDatabase")
|
||||
}
|
||||
|
||||
private fun emptyDatabase() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setCancelable(true)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setTitle(R.string.dbmanagementactivity_delete_activity_data_title)
|
||||
.setMessage(R.string.dbmanagementactivity_really_delete_entire_db)
|
||||
.setPositiveButton(R.string.Delete) { _, _ ->
|
||||
if (GBApplication.deleteActivityDatabase(requireContext())) {
|
||||
GB.toast(
|
||||
requireContext(),
|
||||
getString(R.string.dbmanagementactivity_database_successfully_deleted),
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.INFO
|
||||
)
|
||||
GBApplication.restart()
|
||||
} else {
|
||||
GB.toast(
|
||||
requireContext(),
|
||||
getString(R.string.dbmanagementactivity_db_deletion_failed),
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.INFO
|
||||
)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.Cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteOldActivityDbFile() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.dbmanagementactivity_delete_old_activity_db)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setMessage(R.string.dbmanagementactivity_delete_old_activitydb_confirmation)
|
||||
.setPositiveButton(R.string.Delete) { _, _ ->
|
||||
if (GBApplication.deleteOldActivityDatabase(requireContext())) {
|
||||
GB.toast(
|
||||
requireContext(),
|
||||
getString(R.string.dbmanagementactivity_old_activity_db_successfully_deleted),
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.INFO
|
||||
)
|
||||
} else {
|
||||
GB.toast(
|
||||
requireContext(),
|
||||
getString(R.string.dbmanagementactivity_old_activity_db_deletion_failed),
|
||||
Toast.LENGTH_SHORT,
|
||||
GB.INFO
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasOldActivityDatabase()) {
|
||||
findPreference<Preference>(PREF_DEBUG_DELETE_OLD_DATABASE)!!.isVisible = false
|
||||
}
|
||||
}.setNegativeButton(R.string.Cancel) { _, _ -> }.show()
|
||||
}
|
||||
|
||||
// TODO ability to run raw sql from the UI
|
||||
private fun runSql() {
|
||||
try {
|
||||
GBApplication.acquireDB().use { db ->
|
||||
db.database.execSQL("DROP TABLE IF EXISTS TABLE_NAME_TO_DROP_HERE")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
GB.log("Error accessing database", GB.ERROR, e)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_DATABASE_VERSION = "pref_debug_database_version"
|
||||
private const val DANGEROUS_ACTIONS = "dangerous_actions"
|
||||
private const val PREF_DEBUG_DELETE_OLD_DATABASE = "pref_debug_delete_old_database"
|
||||
private const val PREF_DEBUG_EMPTY_DATABASE = "pref_debug_empty_database"
|
||||
private const val PREF_HEADER_DATABASE_TABLES = "pref_header_database_tables"
|
||||
}
|
||||
}
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.widget.TextView
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceViewHolder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import kotlin.use
|
||||
|
||||
class DatabaseTableDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_empty, rootKey)
|
||||
|
||||
val tableName = arguments?.getString("tableName")!!
|
||||
|
||||
preferenceScreen?.title = tableName
|
||||
|
||||
val ddl = getTableDdl(tableName)
|
||||
|
||||
val pref = object : Preference(requireContext()) {
|
||||
override fun onBindViewHolder(holder: PreferenceViewHolder) {
|
||||
super.onBindViewHolder(holder)
|
||||
val summary = holder.findViewById(android.R.id.summary) as? TextView
|
||||
summary?.let {
|
||||
// HACK: sql can be very long
|
||||
summary.isSingleLine = false
|
||||
summary.maxLines = ddl.lines().size + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
pref.key = "database_table_sql"
|
||||
pref.title = "SQL"
|
||||
pref.summary = ddl
|
||||
pref.isPersistent = false
|
||||
pref.isIconSpaceReserved = false
|
||||
pref.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText(tableName, ddl)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
true
|
||||
}
|
||||
preferenceScreen?.addPreference(pref)
|
||||
}
|
||||
|
||||
private fun getTableDdl(tableName: String): String {
|
||||
try {
|
||||
GBApplication.acquireDB().use { db ->
|
||||
val cursor = db.database.rawQuery(
|
||||
"""
|
||||
SELECT sql
|
||||
FROM sqlite_master
|
||||
WHERE name='${tableName}' OR tbl_name='${tableName}';
|
||||
""".trimIndent(),
|
||||
null
|
||||
)
|
||||
|
||||
val sqlQueries = mutableListOf<String>()
|
||||
cursor.use {
|
||||
while (it.moveToNext()) {
|
||||
if (it.getColumnIndex("sql") >= 0) {
|
||||
sqlQueries.add(formatSql(it.getString(it.getColumnIndexOrThrow("sql")).trim()) + ";")
|
||||
}
|
||||
}
|
||||
}
|
||||
return sqlQueries.joinToString("\n\n")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
return e.message ?: "Failed to get DDL for table $tableName"
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatSql(sql: String): String {
|
||||
val indentStep = " "
|
||||
val sb = StringBuilder()
|
||||
var indentLevel = 0
|
||||
var i = 0
|
||||
|
||||
while (i < sql.length) {
|
||||
when (sql[i]) {
|
||||
'(' -> {
|
||||
indentLevel++
|
||||
sb.append("(\n")
|
||||
sb.append(indentStep.repeat(indentLevel))
|
||||
}
|
||||
|
||||
')' -> {
|
||||
indentLevel--
|
||||
sb.append("\n")
|
||||
sb.append(indentStep.repeat(indentLevel))
|
||||
sb.append(")")
|
||||
}
|
||||
|
||||
',' -> {
|
||||
sb.append(",\n")
|
||||
sb.append(indentStep.repeat(indentLevel))
|
||||
}
|
||||
|
||||
else -> sb.append(sql[i])
|
||||
}
|
||||
i++
|
||||
}
|
||||
|
||||
return sb.toString().trim()
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractSettingsActivityV2
|
||||
|
||||
class DebugActivityV2 : AbstractSettingsActivityV2(), PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
|
||||
override fun newFragment(): PreferenceFragmentCompat? {
|
||||
return MainDebugFragment()
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import androidx.preference.Preference
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.gps.GBLocationService
|
||||
import nodomain.freeyourgadget.gadgetbridge.externalevents.opentracks.OpenTracksController
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
|
||||
class LocationDebugFragment : AbstractDebugFragment() {
|
||||
private val handler = Handler()
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_location, rootKey)
|
||||
|
||||
onClick(PREF_DEBUG_OPENTRACKS_START) { OpenTracksController.startRecording(requireContext()) }
|
||||
onClick(PREF_DEBUG_OPENTRACKS_STOP) { OpenTracksController.stopRecording(requireContext()) }
|
||||
onClick(PREF_DEBUG_GPS_LISTENER_STOP) { GBLocationService.stop(requireContext(), null) }
|
||||
|
||||
refreshOpenTracksStatus()
|
||||
scheduleRefresh()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
refreshOpenTracksStatus()
|
||||
scheduleRefresh()
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
|
||||
private fun scheduleRefresh() {
|
||||
handler.postDelayed({ refreshOpenTracksStatus(); scheduleRefresh() }, 1000)
|
||||
}
|
||||
|
||||
private fun refreshOpenTracksStatus() {
|
||||
val openTracksObserver = GBApplication.app().openTracksObserver
|
||||
LOG.debug("Refreshing OpenTracks status - {}", openTracksObserver != null)
|
||||
|
||||
val pref = findPreference<Preference>(PREF_DEBUG_OPENTRACKS_STATUS)
|
||||
if (openTracksObserver == null) {
|
||||
pref?.summary = "Not running"
|
||||
return
|
||||
} else {
|
||||
val timeSecs = openTracksObserver.getTimeMillisChange() / 1000
|
||||
val distanceCM = openTracksObserver.getDistanceMeterChange() * 100
|
||||
pref?.summary = "TimeSec: $timeSecs, distanceCM $distanceCM"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val LOG: Logger = LoggerFactory.getLogger(LocationDebugFragment::class.java)
|
||||
|
||||
private const val PREF_HEADER_LOCATION_OPENTRACKS = "pref_header_location_opentracks"
|
||||
private const val PREF_DEBUG_OPENTRACKS_STATUS = "pref_debug_opentracks_status"
|
||||
private const val PREF_DEBUG_OPENTRACKS_START = "pref_debug_opentracks_start"
|
||||
private const val PREF_DEBUG_OPENTRACKS_STOP = "pref_debug_opentracks_stop"
|
||||
private const val PREF_HEADER_LOCATION_GPS_LISTENER = "pref_header_location_gps_listener"
|
||||
private const val PREF_DEBUG_GPS_LISTENER_STOP = "pref_debug_gps_listener_stop"
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.CameraActivity
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.PermissionsActivity
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.welcome.WelcomeActivity
|
||||
import nodomain.freeyourgadget.gadgetbridge.deviceevents.GBDeviceEventCameraRemote
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.TestDeviceDialog
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
import java.lang.Boolean
|
||||
import kotlin.Any
|
||||
import kotlin.String
|
||||
import kotlin.let
|
||||
|
||||
class MainDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_main, rootKey)
|
||||
|
||||
onClick(PREF_DEBUG_ADD_TEST_DEVICE) {
|
||||
TestDeviceDialog(requireActivity(), null).show { address, deviceType ->
|
||||
TestDeviceDialog.createTestDevice(requireContext(), deviceType, address, null)
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_SHARE_LOGS) { showLogSharingWarning() }
|
||||
findPreference<SwitchPreferenceCompat>(LOG_TO_FILE)?.let {
|
||||
it.setOnPreferenceChangeListener { _: Preference?, newVal: Any? ->
|
||||
val doEnable = Boolean.TRUE == newVal
|
||||
try {
|
||||
if (doEnable) {
|
||||
FileUtils.getExternalFilesDir() // ensures that it is created
|
||||
}
|
||||
GBApplication.setupLogging(doEnable)
|
||||
} catch (ex: IOException) {
|
||||
GB.toast(
|
||||
requireContext().applicationContext,
|
||||
getString(R.string.error_creating_directory_for_logfiles, ex.localizedMessage),
|
||||
Toast.LENGTH_LONG,
|
||||
GB.ERROR,
|
||||
ex
|
||||
)
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// If we didn't manage to initialize file logging, disable the preference
|
||||
if (!GBApplication.getLogging().isFileLoggerInitialized) {
|
||||
it.isEnabled = false
|
||||
it.setSummary(R.string.pref_write_logfiles_not_available)
|
||||
}
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||
findPreference<Preference>(PREF_DEBUG_COMPANION_DEVICES)?.isVisible = false
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_ACTIVITY_CAMERA) {
|
||||
val intent = Intent(requireContext().applicationContext, CameraActivity::class.java).apply {
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
putExtra(
|
||||
CameraActivity.intentExtraEvent,
|
||||
GBDeviceEventCameraRemote.eventToInt(GBDeviceEventCameraRemote.Event.OPEN_CAMERA)
|
||||
)
|
||||
}
|
||||
requireContext().startActivity(intent)
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_ACTIVITY_WELCOME) {
|
||||
requireContext().startActivity(Intent(requireContext().applicationContext, WelcomeActivity::class.java))
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_ACTIVITY_PERMISSIONS) {
|
||||
requireContext().startActivity(Intent(requireContext().applicationContext, PermissionsActivity::class.java))
|
||||
}
|
||||
}
|
||||
|
||||
private fun showLogSharingWarning() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(R.string.share_log_warning)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> shareLog() }
|
||||
.setNegativeButton(R.string.Cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun shareLog() {
|
||||
val fileName = GBApplication.getLogPath()
|
||||
if (fileName == null || fileName.isEmpty()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Flush the logs, so that we ensure latest lines are also there
|
||||
GBApplication.getLogging().setImmediateFlush(true)
|
||||
LOG.debug("Flushing logs before sharing")
|
||||
GBApplication.getLogging().setImmediateFlush(false)
|
||||
|
||||
val logFile = File(fileName)
|
||||
if (!logFile.exists()) {
|
||||
GB.toast("File does not exist", Toast.LENGTH_LONG, GB.INFO)
|
||||
return
|
||||
}
|
||||
|
||||
val providerUri = FileProvider.getUriForFile(
|
||||
requireContext(),
|
||||
requireContext().applicationContext.packageName + ".screenshot_provider",
|
||||
logFile
|
||||
)
|
||||
|
||||
val emailIntent = Intent(Intent.ACTION_SEND)
|
||||
emailIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
emailIntent.setType("text/plain")
|
||||
emailIntent.putExtra(Intent.EXTRA_SUBJECT, "Gadgetbridge log file")
|
||||
emailIntent.putExtra(Intent.EXTRA_STREAM, providerUri)
|
||||
startActivity(Intent.createChooser(emailIntent, "Share File"))
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG: Logger = LoggerFactory.getLogger(MainDebugFragment::class.java)
|
||||
|
||||
private const val PREF_DEBUG_ADD_TEST_DEVICE = "pref_debug_add_test_device"
|
||||
private const val PREF_HEADER_LOGS = "pref_header_logs"
|
||||
private const val LOG_TO_FILE = "log_to_file"
|
||||
private const val PREF_DEBUG_SHARE_LOGS = "pref_debug_share_logs"
|
||||
private const val PREF_HEADER_DEBUG = "pref_header_debug"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS = "pref_debug_notifications"
|
||||
private const val PREF_DEBUG_CALLS = "pref_debug_calls"
|
||||
private const val PREF_DEBUG_MUSIC = "pref_debug_music"
|
||||
private const val PREF_DEBUG_WEATHER = "pref_debug_weather"
|
||||
private const val PREF_DEBUG_DATABASE = "pref_debug_database"
|
||||
private const val PREF_DEBUG_PREFERENCES = "pref_debug_preferences"
|
||||
private const val PREF_DEBUG_COMPANION_DEVICES = "pref_debug_companion_devices"
|
||||
private const val PREF_DEBUG_WIDGETS = "pref_debug_widgets"
|
||||
private const val PREF_DEBUG_LOCATION = "pref_debug_location"
|
||||
private const val PREF_DEBUG_ACTIVITY_LEGACY = "pref_debug_activity_legacy"
|
||||
private const val PREF_HEADER_ACTIVITIES = "pref_header_activities"
|
||||
private const val PREF_DEBUG_ACTIVITY_CAMERA = "pref_debug_activity_camera"
|
||||
private const val PREF_DEBUG_ACTIVITY_WELCOME = "pref_debug_activity_welcome"
|
||||
private const val PREF_DEBUG_ACTIVITY_PERMISSIONS = "pref_debug_activity_permissions"
|
||||
}
|
||||
}
|
||||
+154
@@ -0,0 +1,154 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.core.content.edit
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicSpec
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.MusicStateSpec
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.MediaManager
|
||||
|
||||
class MusicDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun setupPreferences() {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_music, null)
|
||||
|
||||
onClick(PREF_DEBUG_MUSIC_SEND_MUSICSPEC) { sendMusicSpec() }
|
||||
onClick(PREF_DEBUG_MUSIC_SEND_MUSICSTATESPEC) { sendMusicStateSpec() }
|
||||
onClick(PREF_DEBUG_MUSIC_PULL) { pullMusic() }
|
||||
onClick(PREF_DEBUG_MUSIC_RESET) { resetPreferences() }
|
||||
|
||||
setInputTypeNumber(PREF_DEBUG_MUSIC_DURATION)
|
||||
setInputTypeNumber(PREF_DEBUG_MUSIC_TRACKCOUNT)
|
||||
setInputTypeNumber(PREF_DEBUG_MUSIC_TRACKNR)
|
||||
setInputTypeNumber(PREF_DEBUG_MUSIC_POSITION)
|
||||
setInputTypeNumber(PREF_DEBUG_MUSIC_PLAYRATE)
|
||||
|
||||
setListPreferenceEntries(PREF_DEBUG_MUSIC_STATE, arrayOf(
|
||||
"STATE_UNKNOWN",
|
||||
"STATE_PLAYING",
|
||||
"STATE_PAUSED",
|
||||
"STATE_STOPPED",
|
||||
))
|
||||
}
|
||||
|
||||
private fun sendMusicSpec() {
|
||||
val sharedPreferences = preferenceManager.sharedPreferences!!
|
||||
val musicSpec = MusicSpec()
|
||||
|
||||
musicSpec.artist = sharedPreferences.getString(PREF_DEBUG_MUSIC_ARTIST, "The Artist")
|
||||
musicSpec.album = sharedPreferences.getString(PREF_DEBUG_MUSIC_ALBUM, "The Album")
|
||||
musicSpec.track = sharedPreferences.getString(PREF_DEBUG_MUSIC_TRACK, "The Track Name")
|
||||
musicSpec.duration = sharedPreferences.getString(PREF_DEBUG_MUSIC_DURATION, "120")!!.toInt()
|
||||
musicSpec.trackCount = sharedPreferences.getString(PREF_DEBUG_MUSIC_TRACKCOUNT, "5")!!.toInt()
|
||||
musicSpec.trackNr = sharedPreferences.getString(PREF_DEBUG_MUSIC_TRACKNR, "2")!!.toInt()
|
||||
|
||||
runOnDebugDevices("Send MusicSpec") {
|
||||
GBApplication.deviceService(it).onSetMusicInfo(musicSpec)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMusicStateSpec() {
|
||||
val sharedPreferences = preferenceManager.sharedPreferences!!
|
||||
val stateSpec = MusicStateSpec()
|
||||
|
||||
stateSpec.state = when (sharedPreferences.getString(PREF_DEBUG_MUSIC_STATE, "STATE_PLAYING")) {
|
||||
"STATE_PLAYING" -> 0
|
||||
"STATE_PAUSED" -> 1
|
||||
"STATE_STOPPED" -> 2
|
||||
"STATE_UNKNOWN" -> -1
|
||||
else -> -1
|
||||
}
|
||||
stateSpec.position = sharedPreferences.getString(PREF_DEBUG_MUSIC_POSITION, "30")!!.toInt()
|
||||
stateSpec.playRate = sharedPreferences.getString(PREF_DEBUG_MUSIC_PLAYRATE, "100")!!.toInt()
|
||||
stateSpec.shuffle = if (sharedPreferences.getBoolean(PREF_DEBUG_MUSIC_SHUFFLE_BOOL, false)) 1 else 0
|
||||
stateSpec.repeat = if (sharedPreferences.getBoolean(PREF_DEBUG_MUSIC_REPEAT_BOOL, false)) 1 else 0
|
||||
|
||||
runOnDebugDevices("Send MusicStateSpec") {
|
||||
GBApplication.deviceService(it).onSetMusicState(stateSpec)
|
||||
}
|
||||
}
|
||||
|
||||
private fun pullMusic() {
|
||||
val mediaManager = MediaManager(requireContext())
|
||||
mediaManager.refresh()
|
||||
|
||||
if (mediaManager.bufferMusicSpec == null || mediaManager.bufferMusicStateSpec == null) {
|
||||
GB.toast(requireContext(), "No media playing?", Toast.LENGTH_SHORT, GB.ERROR)
|
||||
}
|
||||
|
||||
preferenceManager.sharedPreferences!!.edit {
|
||||
mediaManager.bufferMusicSpec?.let {
|
||||
putString(PREF_DEBUG_MUSIC_ARTIST, it.artist)
|
||||
putString(PREF_DEBUG_MUSIC_ALBUM, it.album)
|
||||
putString(PREF_DEBUG_MUSIC_TRACK, it.track)
|
||||
putString(PREF_DEBUG_MUSIC_DURATION, it.duration.toString())
|
||||
putString(PREF_DEBUG_MUSIC_TRACKCOUNT, it.trackCount.toString())
|
||||
putString(PREF_DEBUG_MUSIC_TRACKNR, it.trackNr.toString())
|
||||
}
|
||||
|
||||
mediaManager.bufferMusicStateSpec?.let {
|
||||
putString(
|
||||
PREF_DEBUG_MUSIC_STATE, when (it.state) {
|
||||
0.toByte() -> "STATE_PLAYING"
|
||||
1.toByte() -> "STATE_PAUSED"
|
||||
2.toByte() -> "STATE_STOPPED"
|
||||
else -> "STATE_UNKNOWN"
|
||||
}
|
||||
)
|
||||
putString(PREF_DEBUG_MUSIC_POSITION, it.position.toString())
|
||||
putString(PREF_DEBUG_MUSIC_PLAYRATE, it.playRate.toString())
|
||||
putBoolean(PREF_DEBUG_MUSIC_SHUFFLE_BOOL, it.shuffle != 0.toByte())
|
||||
putBoolean(PREF_DEBUG_MUSIC_REPEAT_BOOL, it.repeat != 0.toByte())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPreferences() {
|
||||
preferenceScreen.removeAll()
|
||||
|
||||
preferenceManager.sharedPreferences!!.edit(true) {
|
||||
remove(PREF_HEADER_MUSICSPEC)
|
||||
remove(PREF_DEBUG_MUSIC_ARTIST)
|
||||
remove(PREF_DEBUG_MUSIC_ALBUM)
|
||||
remove(PREF_DEBUG_MUSIC_TRACK)
|
||||
remove(PREF_DEBUG_MUSIC_DURATION)
|
||||
remove(PREF_DEBUG_MUSIC_TRACKCOUNT)
|
||||
remove(PREF_DEBUG_MUSIC_TRACKNR)
|
||||
remove(PREF_HEADER_MUSICSTATESPEC)
|
||||
remove(PREF_DEBUG_MUSIC_STATE)
|
||||
remove(PREF_DEBUG_MUSIC_POSITION)
|
||||
remove(PREF_DEBUG_MUSIC_PLAYRATE)
|
||||
remove(PREF_DEBUG_MUSIC_SHUFFLE_BOOL)
|
||||
remove(PREF_DEBUG_MUSIC_REPEAT_BOOL)
|
||||
}
|
||||
|
||||
// Reload the preference screen to reflect the changes
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_MUSIC_SEND_MUSICSPEC = "pref_debug_music_send_musicspec"
|
||||
private const val PREF_DEBUG_MUSIC_SEND_MUSICSTATESPEC = "pref_debug_music_send_musicstatespec"
|
||||
private const val PREF_DEBUG_MUSIC_PULL = "pref_debug_music_pull"
|
||||
private const val PREF_DEBUG_MUSIC_RESET = "pref_debug_music_reset"
|
||||
private const val PREF_HEADER_MUSICSPEC = "pref_header_musicspec"
|
||||
private const val PREF_DEBUG_MUSIC_ARTIST = "pref_debug_music_artist"
|
||||
private const val PREF_DEBUG_MUSIC_ALBUM = "pref_debug_music_album"
|
||||
private const val PREF_DEBUG_MUSIC_TRACK = "pref_debug_music_track"
|
||||
private const val PREF_DEBUG_MUSIC_DURATION = "pref_debug_music_duration"
|
||||
private const val PREF_DEBUG_MUSIC_TRACKCOUNT = "pref_debug_music_trackCount"
|
||||
private const val PREF_DEBUG_MUSIC_TRACKNR = "pref_debug_music_trackNr"
|
||||
private const val PREF_HEADER_MUSICSTATESPEC = "pref_header_musicstatespec"
|
||||
private const val PREF_DEBUG_MUSIC_STATE = "pref_debug_music_state"
|
||||
private const val PREF_DEBUG_MUSIC_POSITION = "pref_debug_music_position"
|
||||
private const val PREF_DEBUG_MUSIC_PLAYRATE = "pref_debug_music_playRate"
|
||||
private const val PREF_DEBUG_MUSIC_SHUFFLE_BOOL = "pref_debug_music_shuffle_bool"
|
||||
private const val PREF_DEBUG_MUSIC_REPEAT_BOOL = "pref_debug_music_repeat_bool"
|
||||
}
|
||||
}
|
||||
+274
@@ -0,0 +1,274 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.PendingIntent
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.BitmapFactory
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.RemoteInput
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.edit
|
||||
import androidx.preference.ListPreference
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationSpec
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.NotificationType
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.File
|
||||
import java.util.Objects
|
||||
|
||||
class NotificationsDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun setupPreferences() {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_notifications, null)
|
||||
|
||||
onClick(PREF_DEBUG_NOTIFICATIONS_SEND) { sendNotificationSpec() }
|
||||
onClick(PREF_DEBUG_PEBBLEKIT_NOTIFICATION) { testPebbleKitNotification() }
|
||||
onClick(PREF_DEBUG_CREATE_TEST_NOTIFICATION) { createTestNotification() }
|
||||
onClick(PREF_DEBUG_NOTIFICATIONS_RESET) { resetPreferences() }
|
||||
|
||||
val callCommandPref = findPreference<ListPreference>(PREF_DEBUG_NOTIFICATIONS_TYPE)!!
|
||||
callCommandPref.entries = NotificationType.sortedValues().map { it.name }.toList().toTypedArray()
|
||||
callCommandPref.entryValues = callCommandPref.entries
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
val filter = IntentFilter()
|
||||
filter.addAction(ACTION_REPLY)
|
||||
ContextCompat.registerReceiver(requireContext(), mReceiver, filter, ContextCompat.RECEIVER_EXPORTED)
|
||||
}
|
||||
|
||||
override fun onStop() {
|
||||
super.onStop()
|
||||
try {
|
||||
requireContext().unregisterReceiver(mReceiver)
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to unregister receiver", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendNotificationSpec() {
|
||||
val sharedPreferences = preferenceManager.sharedPreferences!!
|
||||
|
||||
val notificationSpec = NotificationSpec()
|
||||
notificationSpec.flags = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_FLAGS, "0")!!.toInt()
|
||||
notificationSpec.key = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_KEY, "0|nodomain.freeyourgadget.gadgetbridge|-493519667|null|10679")
|
||||
notificationSpec.sender = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_SENDER, null)
|
||||
notificationSpec.phoneNumber = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_PHONENUMBER, null)
|
||||
notificationSpec.title = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_TITLE, "Title")
|
||||
notificationSpec.subject = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_SUBJECT, "Subject")
|
||||
notificationSpec.body = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_BODY, "Notification body")
|
||||
notificationSpec.type = NotificationType.valueOf(sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_TYPE, "UNKNOWN")!!)
|
||||
notificationSpec.sourceName = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_SOURCENAME, "Gadgetbridge")
|
||||
if (notificationSpec.type != NotificationType.GENERIC_SMS) {
|
||||
// SMS notifications don't have a source app ID when sent by the SMSReceiver,
|
||||
// so let's not set it here as well for consistency
|
||||
notificationSpec.sourceAppId = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_SOURCEAPPID, BuildConfig.APPLICATION_ID)
|
||||
}
|
||||
notificationSpec.channelId = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_CHANNELID, "the_channel")
|
||||
notificationSpec.category = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_CATEGORY, null)
|
||||
notificationSpec.iconId = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_ICONID, "0")!!.toInt()
|
||||
|
||||
if (sharedPreferences.getBoolean(PREF_DEBUG_NOTIFICATIONS_PICTUREPATH_BOOL, false)) {
|
||||
notificationSpec.picturePath = getTestPicture()?.absolutePath
|
||||
}
|
||||
|
||||
notificationSpec.dndSuppressed = if (sharedPreferences.getBoolean(PREF_DEBUG_NOTIFICATIONS_DNDSUPPRESSED, false)) 1 else 0
|
||||
// TODO notificationSpec.cannedReplies = sharedPreferences.getString(PREF_DEBUG_NOTIFICATIONS_CANNEDREPLIES, null)
|
||||
notificationSpec.attachedActions = ArrayList<NotificationSpec.Action?>()
|
||||
|
||||
// DISMISS action
|
||||
val dismissAction = NotificationSpec.Action()
|
||||
dismissAction.title = getString(R.string.dismiss)
|
||||
dismissAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_DISMISS
|
||||
notificationSpec.attachedActions.add(dismissAction)
|
||||
|
||||
if (sharedPreferences.getBoolean(PREF_DEBUG_NOTIFICATIONS_ATTACHEDACTIONS_REPLY, false)) {
|
||||
// REPLY action
|
||||
if (notificationSpec.type == NotificationType.GENERIC_SMS) {
|
||||
val replyAction = NotificationSpec.Action()
|
||||
replyAction.title = getString(R.string._pebble_watch_reply)
|
||||
replyAction.type = NotificationSpec.Action.TYPE_SYNTECTIC_REPLY_PHONENR
|
||||
notificationSpec.attachedActions.add(replyAction)
|
||||
} else {
|
||||
val replyAction = NotificationSpec.Action()
|
||||
replyAction.title = getString(R.string._pebble_watch_reply)
|
||||
replyAction.type = NotificationSpec.Action.TYPE_WEARABLE_REPLY
|
||||
notificationSpec.attachedActions.add(replyAction)
|
||||
}
|
||||
}
|
||||
|
||||
runOnDebugDevices("Send NotificationSpec") {
|
||||
GBApplication.deviceService(it).onNotification(notificationSpec)
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetPreferences() {
|
||||
preferenceScreen.removeAll()
|
||||
|
||||
preferenceManager.sharedPreferences!!.edit(true) {
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_FLAGS)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_KEY)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_SENDER)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_PHONENUMBER)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_TITLE)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_SUBJECT)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_BODY)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_TYPE)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_SOURCENAME)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_CHANNELID)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_CATEGORY)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_ATTACHEDACTIONS_REPLY)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_SOURCEAPPID)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_ICONID)
|
||||
remove(PREF_DEBUG_NOTIFICATIONS_PICTUREPATH_BOOL)
|
||||
}
|
||||
|
||||
// Reload the preference screen to reflect the changes
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun createTestNotification() {
|
||||
val notificationIntent = Intent(requireContext(), DebugActivityV2::class.java)
|
||||
notificationIntent.setPackage(BuildConfig.APPLICATION_ID)
|
||||
notificationIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
val pendingIntent = PendingIntent.getActivity(requireContext(), 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||
|
||||
val remoteInput = RemoteInput.Builder(EXTRA_REPLY).build()
|
||||
|
||||
val replyIntent = Intent(ACTION_REPLY)
|
||||
replyIntent.setPackage(BuildConfig.APPLICATION_ID)
|
||||
|
||||
val replyPendingIntent = PendingIntent.getBroadcast(requireContext(), 0, replyIntent, PendingIntent.FLAG_MUTABLE)
|
||||
|
||||
val action = NotificationCompat.Action.Builder(android.R.drawable.ic_input_add, "Reply", replyPendingIntent)
|
||||
.addRemoteInput(remoteInput)
|
||||
.build()
|
||||
|
||||
val wearableExtender = NotificationCompat.WearableExtender().addAction(action)
|
||||
|
||||
val pictureUri = getTestPicture()?.let {
|
||||
@SuppressLint("SetWorldReadable") it.setReadable(true, false)
|
||||
FileProvider.getUriForFile(
|
||||
requireContext(),
|
||||
"${BuildConfig.APPLICATION_ID}.screenshot_provider",
|
||||
it
|
||||
)
|
||||
}
|
||||
|
||||
val builder: NotificationCompat.Builder =
|
||||
NotificationCompat.Builder(requireContext(), GB.NOTIFICATION_CHANNEL_ID)
|
||||
.setContentTitle(getString(R.string.test_notification))
|
||||
.setContentText(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
|
||||
.setTicker(getString(R.string.this_is_a_test_notification_from_gadgetbridge))
|
||||
.setSmallIcon(R.drawable.ic_notification)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(pendingIntent)
|
||||
.extend(wearableExtender)
|
||||
|
||||
if (pictureUri != null) {
|
||||
try {
|
||||
val bitmap = BitmapFactory.decodeStream(requireContext().contentResolver.openInputStream(pictureUri))
|
||||
builder.setLargeIcon(bitmap)
|
||||
.setStyle(NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(bitmap)
|
||||
.bigLargeIcon(null as android.graphics.Bitmap?))
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to decode image for notification", e)
|
||||
}
|
||||
}
|
||||
|
||||
GB.notify(System.currentTimeMillis().toInt(), builder.build(), requireContext())
|
||||
}
|
||||
|
||||
private fun getTestPicture(): File? {
|
||||
try {
|
||||
requireContext().assets.open("fossil_hr/default_background.png").use {
|
||||
val tempDir = File(requireContext().cacheDir, "images")
|
||||
tempDir.mkdir()
|
||||
val tempFile = File(tempDir, "test_notification_image.png")
|
||||
tempFile.outputStream().use { output ->
|
||||
it.copyTo(output)
|
||||
}
|
||||
return tempFile
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Failed to copy test picture to cache", e)
|
||||
GB.toast("Test picture failed", Toast.LENGTH_SHORT, GB.ERROR)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun testPebbleKitNotification() {
|
||||
val pebbleKitIntent = Intent("com.getpebble.action.SEND_NOTIFICATION")
|
||||
pebbleKitIntent.putExtra("messageType", "PEBBLE_ALERT")
|
||||
pebbleKitIntent.putExtra(
|
||||
"notificationData",
|
||||
"""
|
||||
[{
|
||||
"title": "PebbleKitTest",
|
||||
"body": "Sent from Gadgetbridge"
|
||||
}]
|
||||
""".trimIndent()
|
||||
)
|
||||
GBApplication.getContext().sendBroadcast(pebbleKitIntent)
|
||||
}
|
||||
|
||||
private val mReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context?, intent: Intent) {
|
||||
when (Objects.requireNonNull(intent.action)) {
|
||||
ACTION_REPLY -> {
|
||||
val remoteInput = RemoteInput.getResultsFromIntent(intent)
|
||||
val reply = remoteInput!!.getCharSequence(EXTRA_REPLY)
|
||||
LOG.info("Got wearable reply: $reply")
|
||||
GB.toast(context, "Reply: $reply", Toast.LENGTH_SHORT, GB.INFO)
|
||||
}
|
||||
|
||||
else -> LOG.warn("Unknown intent action: {}", intent.action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val LOG: Logger = LoggerFactory.getLogger(DebugActivityV2::class.java)
|
||||
|
||||
private const val EXTRA_REPLY = "reply"
|
||||
private const val ACTION_REPLY = "nodomain.freeyourgadget.gadgetbridge.DebugActivity.action.reply"
|
||||
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_SEND = "pref_debug_notifications_send"
|
||||
private const val PREF_DEBUG_PEBBLEKIT_NOTIFICATION = "pref_debug_pebblekit_notification"
|
||||
private const val PREF_DEBUG_CREATE_TEST_NOTIFICATION = "pref_debug_create_test_notification"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_RESET = "pref_debug_notifications_reset"
|
||||
private const val PREF_DEBUG_HEADER_CALLSPEC = "pref_header_callspec"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_FLAGS = "pref_debug_notifications_flags"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_KEY = "pref_debug_notifications_key"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_SENDER = "pref_debug_notifications_sender"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_PHONENUMBER = "pref_debug_notifications_phoneNumber"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_TITLE = "pref_debug_notifications_title"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_SUBJECT = "pref_debug_notifications_subject"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_BODY = "pref_debug_notifications_body"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_TYPE = "pref_debug_notifications_type"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_SOURCENAME = "pref_debug_notifications_sourceName"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_CHANNELID = "pref_debug_notifications_channelId"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_CATEGORY = "pref_debug_notifications_category"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_ATTACHEDACTIONS_REPLY = "pref_debug_notifications_attachedActions_reply"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_SOURCEAPPID = "pref_debug_notifications_sourceAppId"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_ICONID = "pref_debug_notifications_iconId"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_PICTUREPATH_BOOL = "pref_debug_notifications_picturePath_bool"
|
||||
private const val PREF_DEBUG_NOTIFICATIONS_DNDSUPPRESSED = "pref_debug_notifications_dndSuppressed"
|
||||
}
|
||||
}
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.app.DatePickerDialog
|
||||
import android.os.Bundle
|
||||
import android.widget.DatePicker
|
||||
import android.widget.Toast
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RecordedDataTypes
|
||||
import nodomain.freeyourgadget.gadgetbridge.service.serial.GBDeviceProtocol
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import java.util.Calendar
|
||||
import androidx.core.content.edit
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils
|
||||
|
||||
class OtherDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setupPreferences()
|
||||
}
|
||||
|
||||
private fun setupPreferences() {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_other_actions, null)
|
||||
|
||||
onClick(PREF_DEBUG_SET_TIME) {
|
||||
runOnDebugDevices("Set time") {
|
||||
GBApplication.deviceService(it).onSetTime()
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_SET_ACTIVITY_FETCH_TIME) {
|
||||
val currentDate = Calendar.getInstance()
|
||||
runOnDebugDevices {
|
||||
DatePickerDialog(
|
||||
requireActivity(),
|
||||
{ _: DatePicker?, year: Int, monthOfYear: Int, dayOfMonth: Int ->
|
||||
val date = DateTimeUtils.dayStart(year, monthOfYear, dayOfMonth)
|
||||
|
||||
GB.toast("Fetch time: ${date.time}", Toast.LENGTH_LONG, GB.INFO)
|
||||
GBApplication.getDeviceSpecificSharedPrefs(it.address).edit {
|
||||
//FIXME: key reconstruction is BAD
|
||||
|
||||
remove("lastSyncTimeMillis")
|
||||
remove("lastTemperatureTimeMillis")
|
||||
remove("lastStressManualTimeMillis")
|
||||
remove("lastStressAutoTimeMillis")
|
||||
remove("lastDebugTimeMillis")
|
||||
remove("lastStatisticsTimeMillis")
|
||||
remove("lastSportsActivityTimeMillis")
|
||||
remove("lastSpo2sleepTimeMillis")
|
||||
remove("lastSpo2normalTimeMillis")
|
||||
remove("lastSleepSessionTimeMillis")
|
||||
remove("lastPaiTimeMillis")
|
||||
remove("lastHeartRateManualTimeMillis")
|
||||
remove("lastHeartRateRestingTimeMillis")
|
||||
remove("lastHrvTimeMillis")
|
||||
remove("lastHeartRateMaxTimeMillis")
|
||||
remove("lastSleepRespiratoryRateTimeMillis")
|
||||
|
||||
putLong("lastSyncTimeMillis", date.time)
|
||||
putLong("lastTemperatureTimeMillis", date.time)
|
||||
putLong("lastStressManualTimeMillis", date.time)
|
||||
putLong("lastStressAutoTimeMillis", date.time)
|
||||
putLong("lastDebugTimeMillis", date.time)
|
||||
putLong("lastStatisticsTimeMillis", date.time)
|
||||
putLong("lastSportsActivityTimeMillis", date.time)
|
||||
putLong("lastSpo2sleepTimeMillis", date.time)
|
||||
putLong("lastSpo2normalTimeMillis", date.time)
|
||||
putLong("lastSleepSessionTimeMillis", date.time)
|
||||
putLong("lastPaiTimeMillis", date.time)
|
||||
putLong("lastHeartRateManualTimeMillis", date.time)
|
||||
putLong("lastHeartRateRestingTimeMillis", date.time)
|
||||
putLong("lastHrvTimeMillis", date.time)
|
||||
putLong("lastHeartRateMaxTimeMillis", date.time)
|
||||
putLong("lastSleepRespiratoryRateTimeMillis", date.time)
|
||||
}
|
||||
},
|
||||
currentDate.get(Calendar.YEAR),
|
||||
currentDate.get(Calendar.MONTH),
|
||||
currentDate.get(
|
||||
Calendar.DATE
|
||||
)
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_FETCH_DEBUG_LOGS) {
|
||||
runOnDebugDevices("Fetch debug logs", true) {
|
||||
GBApplication.deviceService(it).onFetchRecordedData(RecordedDataTypes.TYPE_DEBUGLOGS)
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_REBOOT) {
|
||||
runOnDebugDevices("Reboot", true) {
|
||||
GBApplication.deviceService(it).onReset(GBDeviceProtocol.RESET_FLAGS_REBOOT)
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_FACTORY_RESET) {
|
||||
MaterialAlertDialogBuilder(requireActivity())
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.debugactivity_really_factoryreset_title)
|
||||
.setMessage(R.string.debugactivity_really_factoryreset)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
runOnDebugDevices(getString(R.string.factory_reset), true) {
|
||||
GBApplication.deviceService(it).onReset(GBDeviceProtocol.RESET_FLAGS_FACTORY_RESET)
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.Cancel) { _, _ -> }
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_SET_TIME = "pref_debug_set_time"
|
||||
private const val ACTIVITY_LIST_DEBUG_EXTRA_TIME_RANGE = "activity_list_debug_extra_time_range"
|
||||
private const val PREF_DEBUG_HEADER_FETCH_RECORDED_DATA = "pref_debug_header_fetch_recorded_data"
|
||||
private const val PREF_DEBUG_SET_ACTIVITY_FETCH_TIME = "pref_debug_set_activity_fetch_time"
|
||||
private const val PREF_DEBUG_FETCH_DEBUG_LOGS = "pref_debug_fetch_debug_logs"
|
||||
private const val PREF_DEBUG_HEADER_RESET = "pref_debug_header_reset"
|
||||
private const val PREF_DEBUG_REBOOT = "pref_debug_reboot"
|
||||
private const val PREF_DEBUG_FACTORY_RESET = "pref_debug_factory_reset"
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.debug.preferences.PreferenceManagerActivity
|
||||
|
||||
class PreferencesDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_preferences, rootKey)
|
||||
|
||||
findPreference<Preference>(PREF_DEBUG_PREFERENCES_VERSION)!!.summary = GBApplication.getPrefs().getString(
|
||||
GBApplication.PREFS_VERSION,
|
||||
getString(R.string.unknown)
|
||||
)
|
||||
|
||||
onClick(PREF_DEBUG_GLOBAL_PREFERENCES) {
|
||||
editPreferences(
|
||||
requireContext().packageName + "_preferences",
|
||||
requireContext().getString(R.string.global_preferences)
|
||||
)
|
||||
}
|
||||
|
||||
val devicePreferences = findPreference<PreferenceCategory>(PREF_HEADER_DEVICE_PREFERENCES)!!
|
||||
removeDynamicPrefs(devicePreferences)
|
||||
|
||||
for (device in GBApplication.app().deviceManager.devices.sortedBy { it.aliasOrName }) {
|
||||
addDynamicPref(
|
||||
devicePreferences,
|
||||
device.aliasOrName,
|
||||
device.address,
|
||||
device.deviceCoordinator.defaultIconResource
|
||||
) {
|
||||
editPreferences("devicesettings_" + device.address.toString().uppercase(), device.aliasOrName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun editPreferences(name: String, title: String) {
|
||||
val intent = Intent(requireContext(), PreferenceManagerActivity::class.java)
|
||||
intent.putExtra(PreferenceManagerActivity.EXTRA_NAME, name)
|
||||
intent.putExtra(PreferenceManagerActivity.EXTRA_TITLE, title)
|
||||
requireContext().startActivity(intent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_PREFERENCES_VERSION = "pref_debug_preferences_version";
|
||||
private const val PREF_DEBUG_GLOBAL_PREFERENCES = "pref_debug_global_preferences";
|
||||
private const val PREF_HEADER_DEVICE_PREFERENCES = "pref_header_device_preferences";
|
||||
}
|
||||
}
|
||||
+168
@@ -0,0 +1,168 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SimpleIconListAdapter
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RunnableListIconItem
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.weather.Weather
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.weather.WeatherCacheManager
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB
|
||||
import java.lang.Boolean
|
||||
import kotlin.Any
|
||||
import kotlin.Int
|
||||
import kotlin.String
|
||||
import kotlin.apply
|
||||
import kotlin.plus
|
||||
|
||||
class WeatherDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_weather, rootKey)
|
||||
|
||||
onClick(PREF_DEBUG_WEATHER_SEND) {
|
||||
if (Weather.getWeatherSpecs().isEmpty()) {
|
||||
GB.toast(requireContext(), "No cached weather to send", Toast.LENGTH_SHORT, GB.ERROR)
|
||||
return@onClick
|
||||
}
|
||||
|
||||
runOnDebugDevices("Send weather to devices") {
|
||||
GBApplication.deviceService(it).onSendWeather()
|
||||
}
|
||||
}
|
||||
|
||||
onClick(PREF_DEBUG_WEATHER_ADD_TEST) {
|
||||
val weatherSpec = WeatherSpec.createTestWeather()
|
||||
if (!Weather.getWeatherSpecs().isEmpty()) {
|
||||
weatherSpec.location += " (${Weather.getWeatherSpecs().size})"
|
||||
}
|
||||
Weather.setWeatherSpec((Weather.getWeatherSpecs() + weatherSpec).toMutableList())
|
||||
reloadCachedWeather()
|
||||
}
|
||||
|
||||
findPreference<Preference>(CACHE_WEATHER)!!.setOnPreferenceChangeListener { _: Preference?, newVal: Any? ->
|
||||
val doEnable = Boolean.TRUE == newVal
|
||||
Weather.initializeCache(WeatherCacheManager(requireContext().cacheDir, doEnable))
|
||||
true
|
||||
}
|
||||
|
||||
reloadCachedWeather()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
reloadCachedWeather()
|
||||
}
|
||||
|
||||
private fun reloadCachedWeather() {
|
||||
val cachedWeatherHeader: PreferenceCategory = findPreference(PREF_HEADER_CACHED_WEATHER)!!
|
||||
|
||||
val weatherSpecs: List<WeatherSpec> = Weather.getWeatherSpecs()
|
||||
|
||||
removeDynamicPrefs(cachedWeatherHeader)
|
||||
|
||||
if (weatherSpecs.isEmpty()) {
|
||||
addDynamicPref(cachedWeatherHeader, "", "No cached weather")
|
||||
return
|
||||
}
|
||||
|
||||
for ((i, weatherSpec) in weatherSpecs.withIndex()) {
|
||||
addDynamicPref(
|
||||
group = cachedWeatherHeader,
|
||||
title = weatherSpec.location ?: "Unknown location",
|
||||
icon = R.drawable.ic_wb_sunny,
|
||||
onClickFunction = { onWeatherClick(weatherSpecs, i) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun <T> List<T>.moveTo(from: Int, to: Int): List<T> {
|
||||
val target = to.coerceIn(indices)
|
||||
if (from == target) return this
|
||||
|
||||
return toMutableList().apply {
|
||||
add(target, removeAt(from))
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWeatherClick(weatherSpecs: List<WeatherSpec>, i: Int) {
|
||||
val weatherSpec = weatherSpecs[i]
|
||||
|
||||
val items: MutableList<RunnableListIconItem?> = ArrayList(4)
|
||||
|
||||
// Show details
|
||||
items.add(
|
||||
RunnableListIconItem(
|
||||
"Show details",
|
||||
R.drawable.ic_wb_sunny
|
||||
) {
|
||||
goTo(
|
||||
WeatherSpecDebugFragment().apply {
|
||||
arguments = Bundle().apply { putParcelable("weatherSpec", weatherSpec) }
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// Move up
|
||||
if (i > 0) {
|
||||
items.add(
|
||||
RunnableListIconItem(
|
||||
requireContext().getString(R.string.widget_move_up),
|
||||
R.drawable.ic_arrow_upward
|
||||
) {
|
||||
Weather.setWeatherSpec(Weather.getWeatherSpecs().moveTo(i, i - 1))
|
||||
reloadCachedWeather()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Move down
|
||||
if (i < weatherSpecs.size - 1) {
|
||||
items.add(
|
||||
RunnableListIconItem(
|
||||
requireContext().getString(R.string.widget_move_down),
|
||||
R.drawable.ic_arrow_downward
|
||||
) {
|
||||
Weather.setWeatherSpec(Weather.getWeatherSpecs().moveTo(i, i + 1))
|
||||
reloadCachedWeather()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Delete
|
||||
items.add(
|
||||
RunnableListIconItem(
|
||||
requireContext().getString(R.string.Delete),
|
||||
R.drawable.ic_delete
|
||||
) {
|
||||
val newList = Weather.getWeatherSpecs().toMutableList()
|
||||
newList.removeAt(i)
|
||||
Weather.setWeatherSpec(newList)
|
||||
reloadCachedWeather()
|
||||
}
|
||||
)
|
||||
|
||||
val adapter = SimpleIconListAdapter(context, items)
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setAdapter(adapter) { _: DialogInterface?, i1: Int -> items[i1]!!.action.run() }
|
||||
.setTitle(weatherSpec.location)
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> }
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_HEADER_WEATHER = "pref_header_weather"
|
||||
private const val PREF_DEBUG_WEATHER_SEND = "pref_debug_weather_send"
|
||||
private const val CACHE_WEATHER = "cache_weather"
|
||||
private const val PREF_DEBUG_WEATHER_ADD_TEST = "pref_debug_weather_add_test"
|
||||
private const val PREF_HEADER_CACHED_WEATHER = "pref_header_cached_weather"
|
||||
}
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceCategory
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.WeatherSpec
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.UUID
|
||||
|
||||
class WeatherSpecDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_empty, rootKey)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
val weatherSpec = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.TIRAMISU) {
|
||||
arguments?.getParcelable("weatherSpec", WeatherSpec::class.java)!!
|
||||
} else {
|
||||
arguments?.getParcelable<WeatherSpec>("weatherSpec")!!
|
||||
}
|
||||
val day = arguments?.getBoolean("days", false)
|
||||
val hour = arguments?.getBoolean("hours", false)
|
||||
|
||||
preferenceScreen?.title = weatherSpec.location
|
||||
|
||||
if (day == true) {
|
||||
showDays(weatherSpec)
|
||||
} else if (hour == true) {
|
||||
showHours(weatherSpec)
|
||||
} else {
|
||||
showMainWeather(weatherSpec)
|
||||
}
|
||||
}
|
||||
|
||||
private fun addPreference(title: String, summary: Any?, onClickFunction: (() -> Unit)? = null) {
|
||||
addDynamicPref(preferenceScreen, title, summary?.toString() ?: "<null>", R.drawable.ic_widgets) {
|
||||
onClickFunction?.invoke()
|
||||
}
|
||||
}
|
||||
|
||||
private fun addCategory(title: String) {
|
||||
val pref = PreferenceCategory(requireContext())
|
||||
pref.key = "${PREF_DYNAMIC_PREFIX}_category_${UUID.randomUUID()})"
|
||||
pref.title = title
|
||||
pref.isPersistent = false
|
||||
pref.isIconSpaceReserved = false
|
||||
preferenceScreen?.addPreference(pref)
|
||||
}
|
||||
|
||||
private fun showMainWeather(weatherSpec: WeatherSpec) {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)
|
||||
|
||||
addPreference("Location", weatherSpec.location)
|
||||
addPreference("Location", weatherSpec.location)
|
||||
addPreference("Timestamp", sdf.format(Date(weatherSpec.timestamp * 1000L)))
|
||||
addPreference("Current Temp", "${weatherSpec.currentTemp} K (${weatherSpec.currentTemp - 273} °C)")
|
||||
addPreference("Max Temp", "${weatherSpec.todayMaxTemp} K (${weatherSpec.todayMaxTemp - 273} °C)")
|
||||
addPreference("Min Temp", "${weatherSpec.todayMinTemp} K (${weatherSpec.todayMinTemp - 273} °C)")
|
||||
addPreference("Condition", weatherSpec.currentCondition)
|
||||
addPreference("Condition Code", weatherSpec.currentConditionCode)
|
||||
addPreference("Humidity", weatherSpec.currentHumidity)
|
||||
addPreference("Wind Speed", "${weatherSpec.windSpeed} kmph")
|
||||
addPreference("Wind Direction", "${weatherSpec.windDirection} deg")
|
||||
addPreference("UV Index", weatherSpec.uvIndex)
|
||||
addPreference("Precip Probability", "${weatherSpec.precipProbability} %")
|
||||
addPreference("Dew Point", "${weatherSpec.dewPoint} K (${weatherSpec.dewPoint - 273} °C)")
|
||||
addPreference("Pressure", "${weatherSpec.pressure} mb")
|
||||
addPreference("Cloud Cover", "${weatherSpec.cloudCover} %")
|
||||
addPreference("Visibility", "${weatherSpec.visibility} m")
|
||||
addPreference("Sun Rise", sdf.format(Date(weatherSpec.sunRise * 1000L)))
|
||||
addPreference("Sun Set", sdf.format(Date(weatherSpec.sunSet * 1000L)))
|
||||
addPreference("Moon Rise", sdf.format(Date(weatherSpec.moonRise * 1000L)))
|
||||
addPreference("Moon Set", sdf.format(Date(weatherSpec.moonSet * 1000L)))
|
||||
addPreference("Moon Phase", "${weatherSpec.moonPhase} deg")
|
||||
addPreference("Latitude", weatherSpec.latitude)
|
||||
addPreference("Longitude", weatherSpec.longitude)
|
||||
addPreference("Feels Like Temp", "${weatherSpec.feelsLikeTemp} K (${weatherSpec.feelsLikeTemp - 273} °C)")
|
||||
addPreference("Is Current Location", weatherSpec.getIsCurrentLocation())
|
||||
|
||||
if (weatherSpec.airQuality != null) {
|
||||
addPreference("Air Quality aqi", weatherSpec.airQuality!!.aqi)
|
||||
addPreference("Air Quality co", weatherSpec.airQuality!!.co)
|
||||
addPreference("Air Quality no2", weatherSpec.airQuality!!.no2)
|
||||
addPreference("Air Quality o3", weatherSpec.airQuality!!.o3)
|
||||
addPreference("Air Quality pm10", weatherSpec.airQuality!!.pm10)
|
||||
addPreference("Air Quality pm25", weatherSpec.airQuality!!.pm25)
|
||||
addPreference("Air Quality so2", weatherSpec.airQuality!!.so2)
|
||||
addPreference("Air Quality coAqi", weatherSpec.airQuality!!.coAqi)
|
||||
addPreference("Air Quality no2Aqi", weatherSpec.airQuality!!.no2Aqi)
|
||||
addPreference("Air Quality o3Aqi", weatherSpec.airQuality!!.o3Aqi)
|
||||
addPreference("Air Quality pm10Aqi", weatherSpec.airQuality!!.pm10Aqi)
|
||||
addPreference("Air Quality pm25Aqi", weatherSpec.airQuality!!.pm25Aqi)
|
||||
addPreference("Air Quality so2Aqi", weatherSpec.airQuality!!.so2Aqi)
|
||||
} else {
|
||||
addPreference("Air Quality", null)
|
||||
}
|
||||
|
||||
addPreference("Daily Forecasts", "${weatherSpec.forecasts.size} entries") {
|
||||
if (weatherSpec.forecasts.isNotEmpty()) {
|
||||
goTo(
|
||||
WeatherSpecDebugFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable("weatherSpec", weatherSpec)
|
||||
putBoolean("days", true)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
addPreference("Hourly Forecasts", "${weatherSpec.hourly.size} entries") {
|
||||
if (weatherSpec.hourly.isNotEmpty()) {
|
||||
goTo(
|
||||
WeatherSpecDebugFragment().apply {
|
||||
arguments = Bundle().apply {
|
||||
putParcelable("weatherSpec", weatherSpec)
|
||||
putBoolean("hours", true)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showDays(weatherSpec: WeatherSpec) {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)
|
||||
|
||||
for ((i, daily) in weatherSpec.forecasts.withIndex()) {
|
||||
addCategory("Day $i")
|
||||
addPreference("Max Temp", "${daily.maxTemp} K (${daily.maxTemp - 273} °C)")
|
||||
addPreference("Min Temp", "${daily.minTemp} K (${daily.minTemp - 273} °C)")
|
||||
addPreference("Condition Code", daily.conditionCode)
|
||||
addPreference("Humidity", daily.humidity)
|
||||
addPreference("Wind Speed", "${daily.windSpeed} kmph")
|
||||
addPreference("Wind Direction", "${daily.windDirection} deg")
|
||||
addPreference("UV Index", daily.uvIndex)
|
||||
addPreference("Precip Probability", "${daily.precipProbability} %")
|
||||
addPreference("Sun Rise", sdf.format(Date(daily.sunRise * 1000L)))
|
||||
addPreference("Sun Set", sdf.format(Date(daily.sunSet * 1000L)))
|
||||
addPreference("Moon Rise", sdf.format(Date(daily.moonRise * 1000L)))
|
||||
addPreference("Moon Set", sdf.format(Date(daily.moonSet * 1000L)))
|
||||
addPreference("Moon Phase", "${daily.moonPhase} deg")
|
||||
|
||||
if (daily.airQuality != null) {
|
||||
addPreference("Air Quality aqi", daily.airQuality!!.aqi)
|
||||
addPreference("Air Quality co", daily.airQuality!!.co)
|
||||
addPreference("Air Quality no2", daily.airQuality!!.no2)
|
||||
addPreference("Air Quality o3", daily.airQuality!!.o3)
|
||||
addPreference("Air Quality pm10", daily.airQuality!!.pm10)
|
||||
addPreference("Air Quality pm25", daily.airQuality!!.pm25)
|
||||
addPreference("Air Quality so2", daily.airQuality!!.so2)
|
||||
addPreference("Air Quality coAqi", daily.airQuality!!.coAqi)
|
||||
addPreference("Air Quality no2Aqi", daily.airQuality!!.no2Aqi)
|
||||
addPreference("Air Quality o3Aqi", daily.airQuality!!.o3Aqi)
|
||||
addPreference("Air Quality pm10Aqi", daily.airQuality!!.pm10Aqi)
|
||||
addPreference("Air Quality pm25Aqi", daily.airQuality!!.pm25Aqi)
|
||||
addPreference("Air Quality so2Aqi", daily.airQuality!!.so2Aqi)
|
||||
} else {
|
||||
addPreference("Air Quality", null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showHours(weatherSpec: WeatherSpec) {
|
||||
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.ROOT)
|
||||
for ((i, hourly) in weatherSpec.hourly.withIndex()) {
|
||||
addCategory("Hour $i - ${sdf.format(Date(hourly.timestamp * 1000L))}")
|
||||
addPreference("Max Temp", "${hourly.temp} K (${hourly.temp - 273} °C)")
|
||||
addPreference("Condition Code", hourly.conditionCode)
|
||||
addPreference("Humidity", hourly.humidity)
|
||||
addPreference("Wind Speed", "${hourly.windSpeed} kmph")
|
||||
addPreference("Wind Direction", "${hourly.windDirection} deg")
|
||||
addPreference("UV Index", hourly.uvIndex)
|
||||
addPreference("Precip Probability", "${hourly.precipProbability} %")
|
||||
}
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug
|
||||
|
||||
import android.appwidget.AppWidgetHost
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.preference.PreferenceCategory
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.Widget
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SimpleIconListAdapter
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.RunnableListIconItem
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.WidgetPreferenceStorage
|
||||
|
||||
class WidgetsDebugFragment : AbstractDebugFragment() {
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.debug_preferences_widgets, rootKey)
|
||||
|
||||
onClick(PREF_DEBUG_WIDGETS_PREFS_SHOW) { showAppWidgetsPrefs() }
|
||||
onClick(PREF_DEBUG_WIDGETS_PREFS_DELETE) { deleteWidgetsPrefs() }
|
||||
|
||||
reloadWidgets()
|
||||
}
|
||||
|
||||
private fun reloadWidgets() {
|
||||
val widgetsHeader: PreferenceCategory = findPreference(PREF_HEADER_WIDGETS)!!
|
||||
removeDynamicPrefs(widgetsHeader)
|
||||
|
||||
// https://stackoverflow.com/questions/17387191/check-if-a-widget-is-exists-on-homescreen-using-appwidgetid
|
||||
val appWidgetManager = AppWidgetManager.getInstance(requireContext())
|
||||
val appWidgetHost = AppWidgetHost(requireContext(), 1) // for removing phantoms
|
||||
val appWidgetIDs = appWidgetManager.getAppWidgetIds(ComponentName(requireContext(), Widget::class.java))
|
||||
if (appWidgetIDs.isEmpty()) {
|
||||
addDynamicPref(widgetsHeader, "", "No widgets found")
|
||||
return
|
||||
}
|
||||
|
||||
for ((i, appWidgetID) in appWidgetIDs.withIndex()) {
|
||||
addDynamicPref(widgetsHeader, "Widget $i", "id = $appWidgetID") {
|
||||
val items: MutableList<RunnableListIconItem?> = ArrayList(4)
|
||||
|
||||
items.add(
|
||||
RunnableListIconItem(getString(R.string.Delete), R.drawable.ic_delete_forever) {
|
||||
appWidgetHost.deleteAppWidgetId(appWidgetID)
|
||||
reloadWidgets()
|
||||
}
|
||||
)
|
||||
|
||||
val adapter = SimpleIconListAdapter(context, items)
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setAdapter(adapter) { _: DialogInterface?, i1: Int -> items[i1]!!.action.run() }
|
||||
.setTitle("Widget $i / id=$appWidgetID")
|
||||
.setNegativeButton(android.R.string.cancel) { _: DialogInterface?, _: Int -> }
|
||||
.create()
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteWidgetsPrefs() {
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle("Delete widgets preferences")
|
||||
.setMessage(R.string.are_you_sure)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
val widgetPreferenceStorage = WidgetPreferenceStorage()
|
||||
widgetPreferenceStorage.deleteWidgetsPrefs(requireContext())
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun showAppWidgetsPrefs() {
|
||||
val widgetPreferenceStorage = WidgetPreferenceStorage()
|
||||
val appWidgetsPrefs = widgetPreferenceStorage.getAppWidgetsPrefs(requireContext())
|
||||
|
||||
MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle("Saved widget preferences")
|
||||
.setMessage(appWidgetsPrefs)
|
||||
.setNeutralButton(android.R.string.copy) { _, _ ->
|
||||
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||
val clip = ClipData.newPlainText("Saved widget preferences", appWidgetsPrefs)
|
||||
clipboard.setPrimaryClip(clip)
|
||||
}
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PREF_DEBUG_WIDGETS_PREFS_SHOW = "pref_debug_widgets_prefs_show"
|
||||
private const val PREF_DEBUG_WIDGETS_PREFS_DELETE = "pref_debug_widgets_prefs_delete"
|
||||
private const val PREF_HEADER_WIDGETS = "pref_header_widgets"
|
||||
}
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.debug.preferences;
|
||||
|
||||
public record DebugPreference(String key, String value) {
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
/* 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.activities.debug.preferences;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.view.MenuProvider;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.AbstractGBActivity;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.backup.JsonBackupPreferences;
|
||||
|
||||
public class PreferenceManagerActivity extends AbstractGBActivity implements MenuProvider {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PreferenceManagerActivity.class);
|
||||
|
||||
public static final String EXTRA_NAME = "name";
|
||||
public static final String EXTRA_TITLE = "title";
|
||||
|
||||
private SearchView searchView;
|
||||
private String sharedPreferencesName;
|
||||
private final List<DebugPreference> allPrefs = new ArrayList<>();
|
||||
private PreferenceManagerAdapter listAdapter;
|
||||
|
||||
private final ActivityResultLauncher<String> exportFileChooser = registerForActivityResult(
|
||||
new ActivityResultContracts.CreateDocument("application/json"),
|
||||
uri -> {
|
||||
LOG.info("Got target export file: {}", uri);
|
||||
if (uri != null) {
|
||||
exportPreferences(uri);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
private final ActivityResultLauncher<String[]> importFileChooser = registerForActivityResult(
|
||||
new ActivityResultContracts.OpenDocument(),
|
||||
uri -> {
|
||||
LOG.info("Got import file: {}", uri);
|
||||
if (uri != null) {
|
||||
importPreferences(uri);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@Override
|
||||
protected void onCreate(final Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_debug_preferences);
|
||||
addMenuProvider(this);
|
||||
|
||||
final RecyclerView preferenceListView = findViewById(R.id.preferenceListView);
|
||||
preferenceListView.setLayoutManager(new LinearLayoutManager(this));
|
||||
|
||||
sharedPreferencesName = getIntent().getStringExtra(EXTRA_NAME);
|
||||
if (sharedPreferencesName == null) {
|
||||
GB.toast("No preferences name!", Toast.LENGTH_LONG, GB.ERROR);
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
final ActionBar actionBar = getSupportActionBar();
|
||||
final String title = getIntent().getStringExtra(EXTRA_TITLE);
|
||||
if (actionBar != null) {
|
||||
actionBar.setTitle(title != null ? title : sharedPreferencesName);
|
||||
}
|
||||
|
||||
listAdapter = new PreferenceManagerAdapter(this, allPrefs);
|
||||
preferenceListView.setAdapter(listAdapter);
|
||||
|
||||
searchView = findViewById(R.id.preferenceListSearchView);
|
||||
searchView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setVisibility(View.GONE);
|
||||
searchView.setIconified(false);
|
||||
searchView.setQuery("", false);
|
||||
searchView.clearFocus();
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(final String query) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(final String newText) {
|
||||
listAdapter.getFilter().filter(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
reloadPreferences();
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged") // the entire list will be reloaded
|
||||
private void reloadPreferences() {
|
||||
allPrefs.clear();
|
||||
final SharedPreferences sharedPreferences = getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
|
||||
for (Map.Entry<String, ?> e : sharedPreferences.getAll().entrySet()) {
|
||||
allPrefs.add(new DebugPreference(
|
||||
e.getKey(),
|
||||
e.getValue().toString()
|
||||
));
|
||||
}
|
||||
LOG.debug("Got {} preferences", allPrefs.size());
|
||||
allPrefs.sort((a, b) -> a.key().compareToIgnoreCase(b.key()));
|
||||
listAdapter.notifyDataSetChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateMenu(@NonNull final Menu menu, @NonNull final MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_debug_preferences, menu);
|
||||
if (!sharedPreferencesName.startsWith("devicesettings_")) {
|
||||
menu.findItem(R.id.debug_preferences_reset).setVisible(false);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("ApplySharedPref")
|
||||
@Override
|
||||
public boolean onMenuItemSelected(@NonNull final MenuItem menuItem) {
|
||||
final SharedPreferences sharedPreferences = getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
|
||||
final int itemId = menuItem.getItemId();
|
||||
if (itemId == R.id.debug_preferences_search) {
|
||||
searchView.setVisibility(View.VISIBLE);
|
||||
searchView.requestFocus();
|
||||
searchView.setIconified(true);
|
||||
searchView.setIconified(false);
|
||||
return true;
|
||||
} else if (itemId == R.id.debug_preferences_export) {
|
||||
final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault());
|
||||
final String defaultFilename = String.format(Locale.ROOT, "%s_%s.json", sharedPreferencesName, sdf.format(new Date()))
|
||||
.replace(":", "_")
|
||||
.toLowerCase(Locale.ROOT);
|
||||
exportFileChooser.launch(defaultFilename);
|
||||
return true;
|
||||
} else if (itemId == R.id.debug_preferences_import) {
|
||||
importFileChooser.launch(new String[]{"application/json"});
|
||||
return true;
|
||||
} else if (itemId == R.id.debug_preferences_reset) {
|
||||
if (!sharedPreferencesName.startsWith("devicesettings_")) {
|
||||
LOG.warn("Preventing non device settings clear");
|
||||
return true;
|
||||
}
|
||||
new MaterialAlertDialogBuilder(this)
|
||||
.setCancelable(true)
|
||||
.setIcon(R.drawable.ic_warning)
|
||||
.setTitle(R.string.debugactivity_confirm_remove_device_preferences_title)
|
||||
.setMessage(R.string.debugactivity_confirm_remove_device_preferences)
|
||||
.setNegativeButton(R.string.Cancel, (dialog, which) -> {
|
||||
dialog.dismiss();
|
||||
})
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
sharedPreferences.edit().clear().commit();
|
||||
reloadPreferences();
|
||||
}).show();
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void exportPreferences(final Uri uri) {
|
||||
try {
|
||||
final SharedPreferences sharedPreferences = getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
|
||||
final JsonBackupPreferences jsonBackupPreferences = JsonBackupPreferences.exportFrom(sharedPreferences);
|
||||
final String preferencesJson = jsonBackupPreferences.toJson();
|
||||
|
||||
try (OutputStream outputStream = getContentResolver().openOutputStream(uri)) {
|
||||
if (outputStream == null) {
|
||||
GB.toast(this, "Failed to open output file", Toast.LENGTH_LONG, GB.ERROR);
|
||||
return;
|
||||
}
|
||||
outputStream.write(preferencesJson.getBytes(StandardCharsets.UTF_8));
|
||||
GB.toast(this, getString(R.string.export_success), Toast.LENGTH_SHORT, GB.INFO);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
LOG.error("Error exporting preferences", e);
|
||||
GB.toast(this, "Error exporting preferences: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
private void importPreferences(final Uri uri) {
|
||||
try {
|
||||
final JsonBackupPreferences jsonBackupPreferences;
|
||||
try (InputStream is = getContentResolver().openInputStream(uri)) {
|
||||
jsonBackupPreferences = JsonBackupPreferences.fromJson(is);
|
||||
}
|
||||
|
||||
final SharedPreferences sharedPreferences = getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE);
|
||||
jsonBackupPreferences.importInto(sharedPreferences);
|
||||
|
||||
reloadPreferences();
|
||||
GB.toast(this, getString(R.string.import_success), Toast.LENGTH_SHORT, GB.INFO);
|
||||
} catch (IOException | IllegalArgumentException e) {
|
||||
LOG.error("Error importing preferences", e);
|
||||
GB.toast(this, "Error importing preferences: " + e.getMessage(), Toast.LENGTH_LONG, GB.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||
if (item.getItemId() == android.R.id.home) {
|
||||
onBackPressed();
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/* Copyright (C) 2023-2024 akasaka / Genjitsu Labs
|
||||
|
||||
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.activities.debug.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Filter;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class PreferenceManagerAdapter extends RecyclerView.Adapter<PreferenceManagerAdapter.DebugPreferenceViewHolder> {
|
||||
protected static final Logger LOG = LoggerFactory.getLogger(PreferenceManagerAdapter.class);
|
||||
|
||||
private final List<DebugPreference> preferenceList;
|
||||
private final Context mContext;
|
||||
|
||||
private final PreferenceFilter preferenceFilter;
|
||||
|
||||
public PreferenceManagerAdapter(final Context context, final List<DebugPreference> preferenceList) {
|
||||
mContext = context;
|
||||
|
||||
this.preferenceList = preferenceList;
|
||||
this.preferenceFilter = new PreferenceFilter(this, preferenceList);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public DebugPreferenceViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
|
||||
final View view = LayoutInflater.from(mContext).inflate(R.layout.item_debug_preference, parent, false);
|
||||
return new DebugPreferenceViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final DebugPreferenceViewHolder holder, final int position) {
|
||||
final DebugPreference preference = preferenceList.get(position);
|
||||
|
||||
//holder.icon.setVisibility(View.GONE);
|
||||
holder.name.setText(preference.key());
|
||||
holder.description.setText(preference.value());
|
||||
holder.description.setMaxLines(10);
|
||||
holder.menu.setVisibility(View.INVISIBLE);
|
||||
//holder.menu.setOnClickListener(view -> {
|
||||
// final PopupMenu menu = new PopupMenu(mContext, holder.menu);
|
||||
// menu.inflate(R.menu.file_manager_file);
|
||||
// menu.getMenu().findItem(R.id.file_manager_preference_menu_view).setVisible(file.getPath().toLowerCase(Locale.ROOT).endsWith(".fit"));
|
||||
// menu.setOnMenuItemClickListener(item -> {
|
||||
// final int itemId = item.getItemId();
|
||||
// if (itemId == R.id.file_manager_preference_menu_share) {
|
||||
// try {
|
||||
// AndroidUtils.shareFile(mContext, file, "*/*");
|
||||
// } catch (final IOException e) {
|
||||
// GB.toast("Failed to share file", Toast.LENGTH_LONG, GB.ERROR, e);
|
||||
// }
|
||||
// return true;
|
||||
// }
|
||||
// if (itemId == R.id.file_manager_preference_menu_view) {
|
||||
// final Intent inspectFileIntent = new Intent(mContext, FitViewerActivity.class);
|
||||
// inspectFileIntent.putExtra(FitViewerActivity.EXTRA_PATH, file.getPath());
|
||||
// mContext.startActivity(inspectFileIntent);
|
||||
// return true;
|
||||
// }
|
||||
// return false;
|
||||
// });
|
||||
// menu.show();
|
||||
//});
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return preferenceList.size();
|
||||
}
|
||||
|
||||
public Filter getFilter() {
|
||||
return preferenceFilter;
|
||||
}
|
||||
|
||||
public static class DebugPreferenceViewHolder extends RecyclerView.ViewHolder {
|
||||
final TextView name;
|
||||
final TextView description;
|
||||
final ImageView menu;
|
||||
|
||||
DebugPreferenceViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
name = itemView.findViewById(R.id.preference_key);
|
||||
description = itemView.findViewById(R.id.preference_description);
|
||||
menu = itemView.findViewById(R.id.preference_menu);
|
||||
}
|
||||
}
|
||||
|
||||
private static class PreferenceFilter extends Filter {
|
||||
private final PreferenceManagerAdapter adapter;
|
||||
private final List<DebugPreference> originalList;
|
||||
private final List<DebugPreference> filteredList;
|
||||
|
||||
private PreferenceFilter(final PreferenceManagerAdapter adapter, final List<DebugPreference> originalList) {
|
||||
super();
|
||||
this.originalList = new ArrayList<>(originalList);
|
||||
this.filteredList = new ArrayList<>();
|
||||
this.adapter = adapter;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FilterResults performFiltering(final CharSequence filter) {
|
||||
if (originalList.isEmpty()) {
|
||||
originalList.addAll(adapter.preferenceList);
|
||||
}
|
||||
|
||||
filteredList.clear();
|
||||
final FilterResults results = new FilterResults();
|
||||
|
||||
if (filter == null || filter.length() == 0)
|
||||
filteredList.addAll(originalList);
|
||||
else {
|
||||
final String filterPattern = filter.toString().toLowerCase().trim();
|
||||
|
||||
for (DebugPreference p : originalList) {
|
||||
if (p.key().toLowerCase().contains(filterPattern) || p.value().toLowerCase().contains(filterPattern)) {
|
||||
filteredList.add(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results.values = filteredList;
|
||||
results.count = filteredList.size();
|
||||
return results;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void publishResults(final CharSequence constraint, final FilterResults filterResults) {
|
||||
adapter.preferenceList.clear();
|
||||
adapter.preferenceList.addAll((List<DebugPreference>) filterResults.values);
|
||||
adapter.notifyDataSetChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
+8
-59
@@ -44,7 +44,6 @@ import android.os.ParcelUuid;
|
||||
import android.os.Parcelable;
|
||||
import android.provider.Settings;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
@@ -52,10 +51,8 @@ import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListView;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.result.ActivityResultLauncher;
|
||||
@@ -76,7 +73,6 @@ import org.slf4j.LoggerFactory;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -84,12 +80,9 @@ 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;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SimpleIconListAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconAdapter;
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconItem;
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceCoordinator;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice;
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDeviceCandidate;
|
||||
@@ -102,6 +95,7 @@ import nodomain.freeyourgadget.gadgetbridge.util.BondingUtil;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.DeviceHelper;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GB;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.TestDeviceDialog;
|
||||
|
||||
|
||||
public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterView.OnItemClickListener,
|
||||
@@ -131,8 +125,6 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
|
||||
private ActivityResultLauncher<Intent> authKeyLauncher;
|
||||
|
||||
private long selectedUnsupportedDeviceKey = DebugActivity.SELECT_DEVICE;
|
||||
|
||||
private final Runnable stopRunnable = () -> {
|
||||
stopDiscovery();
|
||||
LOG.info("Discovery stopped by thread timeout.");
|
||||
@@ -783,56 +775,13 @@ public class DiscoveryActivityV2 extends AbstractGBActivity implements AdapterVi
|
||||
private void showUnsupportedDeviceDialog(final GBDeviceCandidate deviceCandidate) {
|
||||
LOG.info("Unsupported device candidate selected: {}", deviceCandidate);
|
||||
|
||||
final Map<String, Pair<Long, Integer>> allDevices = DebugActivity.getAllSupportedDevices(getApplicationContext());
|
||||
|
||||
final LinearLayout linearLayout = new LinearLayout(DiscoveryActivityV2.this);
|
||||
linearLayout.setOrientation(LinearLayout.VERTICAL);
|
||||
|
||||
final ArrayList<SpinnerWithIconItem> deviceListArray = new ArrayList<>();
|
||||
for (Map.Entry<String, Pair<Long, Integer>> item : allDevices.entrySet()) {
|
||||
deviceListArray.add(new SpinnerWithIconItem(item.getKey(), item.getValue().first, item.getValue().second));
|
||||
}
|
||||
final SpinnerWithIconAdapter deviceListAdapter = new SpinnerWithIconAdapter(
|
||||
DiscoveryActivityV2.this,
|
||||
R.layout.spinner_with_image_layout,
|
||||
R.id.spinner_item_text,
|
||||
deviceListArray
|
||||
);
|
||||
|
||||
final Spinner deviceListSpinner = new Spinner(DiscoveryActivityV2.this);
|
||||
deviceListSpinner.setAdapter(deviceListAdapter);
|
||||
deviceListSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||
@Override
|
||||
public void onItemSelected(final AdapterView<?> parent, final View view, final int pos, final long id) {
|
||||
final SpinnerWithIconItem selectedItem = (SpinnerWithIconItem) parent.getItemAtPosition(pos);
|
||||
selectedUnsupportedDeviceKey = selectedItem.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNothingSelected(final AdapterView<?> arg0) {
|
||||
}
|
||||
});
|
||||
linearLayout.addView(deviceListSpinner);
|
||||
|
||||
final LinearLayout macLayout = new LinearLayout(DiscoveryActivityV2.this);
|
||||
macLayout.setOrientation(LinearLayout.HORIZONTAL);
|
||||
macLayout.setPadding(20, 0, 20, 0);
|
||||
linearLayout.addView(macLayout);
|
||||
|
||||
new MaterialAlertDialogBuilder(DiscoveryActivityV2.this)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.add_test_device)
|
||||
.setView(linearLayout)
|
||||
.setPositiveButton(R.string.ok, (dialog, which) -> {
|
||||
if (selectedUnsupportedDeviceKey != DebugActivity.SELECT_DEVICE) {
|
||||
final DeviceType deviceType = DeviceType.values()[(int) selectedUnsupportedDeviceKey];
|
||||
DeviceHelper.getInstance().setForcedDeviceType(deviceCandidate.getMacAddress().toLowerCase(), deviceType);
|
||||
preparePair(deviceCandidate);
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.Cancel, (dialog, which) -> {
|
||||
})
|
||||
.show();
|
||||
new TestDeviceDialog(this, deviceCandidate.getMacAddress())
|
||||
.show((macAddress, deviceType) -> {
|
||||
LOG.debug("Force-pairing {} as {}", deviceCandidate, deviceType);
|
||||
DeviceHelper.getInstance().setForcedDeviceType(deviceCandidate.getMacAddress().toLowerCase(), deviceType);
|
||||
preparePair(deviceCandidate);
|
||||
return kotlin.Unit.INSTANCE;
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+2
@@ -17,6 +17,7 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.activities.files;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.text.InputType;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -87,6 +88,7 @@ public class FileManagerActivity extends AbstractGBActivity implements MenuProvi
|
||||
fileListView.setAdapter(appListAdapter);
|
||||
|
||||
searchView = findViewById(R.id.fileListSearchView);
|
||||
searchView.setInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
searchView.setIconifiedByDefault(false);
|
||||
searchView.setVisibility(View.GONE);
|
||||
searchView.setIconified(false);
|
||||
|
||||
@@ -99,6 +99,7 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
import nodomain.freeyourgadget.gadgetbridge.activities.workouts.WorkoutListActivity;
|
||||
@@ -916,6 +917,9 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
|
||||
final boolean detailsShown = expandedDeviceAddress.equals(device.getAddress());
|
||||
boolean showInfoIcon = device.hasDeviceInfos() && !device.isBusy();
|
||||
|
||||
if (BuildConfig.DEBUG) {
|
||||
menu.getMenu().findItem(R.id.controlcenter_device_submenu_test_new_function).setVisible(deviceConnected);
|
||||
}
|
||||
menu.getMenu().findItem(R.id.controlcenter_device_submenu_connect).setVisible(!deviceConnected);
|
||||
menu.getMenu().findItem(R.id.controlcenter_device_submenu_disconnect).setVisible(deviceConnected);
|
||||
menu.getMenu().findItem(R.id.controlcenter_device_submenu_show_details).setEnabled(showInfoIcon);
|
||||
@@ -939,6 +943,12 @@ public class GBDeviceAdapterv2 extends ListAdapter<GBDevice, GBDeviceAdapterv2.V
|
||||
}
|
||||
removeFromLastDeviceAddressesPref(device);
|
||||
return true;
|
||||
} else if (itemId == R.id.controlcenter_device_submenu_test_new_function) {
|
||||
if (device.isInitialized()) {
|
||||
GBApplication.deviceService(device).onTestNewFunction();
|
||||
showTransientSnackbar(R.string.controlcenter_test_new_function);
|
||||
}
|
||||
return true;
|
||||
} else if (itemId == R.id.controlcenter_device_submenu_set_alias) {
|
||||
showSetAliasDialog(device);
|
||||
return true;
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ public class SpinnerWithIconItem {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return text + " " + id + " " + imageId;
|
||||
return text;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+1
-2
@@ -444,9 +444,8 @@ public class NotificationListener extends NotificationListenerService {
|
||||
}
|
||||
}
|
||||
|
||||
NotificationSpec notificationSpec = new NotificationSpec();
|
||||
NotificationSpec notificationSpec = new NotificationSpec(-1, notification.when);
|
||||
notificationSpec.key = sbn.getKey();
|
||||
notificationSpec.when = notification.when;
|
||||
|
||||
// determinate Source App Name ("Label")
|
||||
String name = NotificationUtils.getApplicationLabel(this, source);
|
||||
|
||||
@@ -17,7 +17,12 @@
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
||||
package nodomain.freeyourgadget.gadgetbridge.model;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import nodomain.freeyourgadget.gadgetbridge.util.GBToStringBuilder;
|
||||
|
||||
public class CallSpec {
|
||||
// TODO: Migrate all usages to the enum..
|
||||
public static final int CALL_UNDEFINED = 0;
|
||||
public static final int CALL_ACCEPT = 1;
|
||||
public static final int CALL_INCOMING = 2;
|
||||
@@ -44,4 +49,35 @@ public class CallSpec {
|
||||
|
||||
public int command;
|
||||
public int dndSuppressed;
|
||||
|
||||
public enum Command {
|
||||
UNDEFINED,
|
||||
ACCEPT,
|
||||
INCOMING,
|
||||
OUTGOING,
|
||||
REJECT,
|
||||
START,
|
||||
END,
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String toString() {
|
||||
final GBToStringBuilder tsb = new GBToStringBuilder(this);
|
||||
tsb.append("command", Command.values()[command]);
|
||||
tsb.append("number", number);
|
||||
tsb.append("name", name);
|
||||
tsb.append("sourceName", sourceName);
|
||||
tsb.append("sourceAppId", sourceAppId);
|
||||
tsb.append("key", key);
|
||||
tsb.append("channelId", channelId);
|
||||
tsb.append("category", category);
|
||||
if (isVoip) {
|
||||
tsb.append("isVoip", isVoip);
|
||||
}
|
||||
if (dndSuppressed != 0) {
|
||||
tsb.append("dndSuppressed", dndSuppressed);
|
||||
}
|
||||
return tsb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,9 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
public class NotificationSpec {
|
||||
public int flags;
|
||||
private static final AtomicInteger c = new AtomicInteger((int) (System.currentTimeMillis()/1000));
|
||||
private int id;
|
||||
private final int id;
|
||||
public String key;
|
||||
public long when;
|
||||
public final long when;
|
||||
public String sender;
|
||||
public String phoneNumber;
|
||||
public String title;
|
||||
@@ -59,11 +59,15 @@ public class NotificationSpec {
|
||||
}
|
||||
|
||||
public NotificationSpec(int id) {
|
||||
this(id, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public NotificationSpec(int id, long when) {
|
||||
if (id != -1)
|
||||
this.id = id;
|
||||
else
|
||||
this.id = c.incrementAndGet();
|
||||
this.when = System.currentTimeMillis();
|
||||
this.when = when;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
|
||||
@@ -57,10 +57,10 @@ class WeatherSpec() : Parcelable {
|
||||
|
||||
// Forecasts from the next day onward, in chronological order, one entry per day.
|
||||
// It should not include the current or previous days
|
||||
var forecasts: ArrayList<Daily?> = ArrayList()
|
||||
var forecasts: ArrayList<Daily> = ArrayList()
|
||||
|
||||
// Hourly forecasts
|
||||
var hourly: ArrayList<Hourly?> = ArrayList()
|
||||
var hourly: ArrayList<Hourly> = ArrayList()
|
||||
|
||||
constructor(parcel: Parcel) : this() {
|
||||
val version = parcel.readInt()
|
||||
@@ -643,5 +643,58 @@ class WeatherSpec() : Parcelable {
|
||||
}
|
||||
return level
|
||||
}
|
||||
|
||||
fun createTestWeather(): WeatherSpec {
|
||||
val weather = WeatherSpec()
|
||||
|
||||
weather.location = "Green Hill"
|
||||
weather.timestamp = 1764364324
|
||||
weather.currentTemp = 15 + 273
|
||||
weather.todayMinTemp = 10 + 273
|
||||
weather.todayMaxTemp = 25 + 273
|
||||
weather.currentConditionCode = 601 // snow
|
||||
weather.currentCondition = "Snowy"
|
||||
weather.windDirection = 12
|
||||
weather.precipProbability = 99
|
||||
weather.windSpeed = 10f
|
||||
weather.feelsLikeTemp = 13 + 273
|
||||
weather.currentHumidity = 70
|
||||
weather.latitude = 38.250137f
|
||||
weather.longitude = -122.410805f
|
||||
weather.dewPoint = 10 + 273
|
||||
val airQuality = AirQuality()
|
||||
airQuality.aqi = 50
|
||||
weather.airQuality = airQuality
|
||||
weather.currentHumidity = 30
|
||||
|
||||
weather.hourly = ArrayList()
|
||||
for (i in 0..23) {
|
||||
val gbForecast = Hourly()
|
||||
gbForecast.temp = 10 + i + 273
|
||||
gbForecast.conditionCode = 800 // clear
|
||||
gbForecast.precipProbability = 50 + i
|
||||
gbForecast.windDirection = 30 + i
|
||||
gbForecast.windSpeed = 20f + i
|
||||
gbForecast.humidity = 10 + i
|
||||
gbForecast.uvIndex = 2f + i
|
||||
|
||||
weather.hourly.add(gbForecast)
|
||||
}
|
||||
|
||||
weather.forecasts = ArrayList()
|
||||
for (i in 0..4) {
|
||||
val gbForecast = Daily()
|
||||
gbForecast.minTemp = 10 + i + 273
|
||||
gbForecast.maxTemp = 25 + i + 273
|
||||
gbForecast.conditionCode = 800 // clear
|
||||
gbForecast.precipProbability = 50 + i
|
||||
val airQualityDaily = AirQuality()
|
||||
airQualityDaily.aqi = 120 + i
|
||||
gbForecast.airQuality = airQualityDaily
|
||||
weather.forecasts.add(gbForecast)
|
||||
}
|
||||
|
||||
return weather
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -785,12 +785,12 @@ public class DeviceCommunicationService extends Service implements SharedPrefere
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
LOG.debug("Service startcommand: " + action);
|
||||
|
||||
// when we get past this, we should have valid mDeviceSupport and mGBDevice instances
|
||||
|
||||
GBDevice targetDevice = intent.getParcelableExtra(GBDevice.EXTRA_DEVICE);
|
||||
|
||||
LOG.debug("Service startcommand: {}{}", action, targetDevice != null ? " (" + targetDevice.getAddress() + ")" : "");
|
||||
|
||||
Prefs prefs = getPrefs();
|
||||
switch (action) {
|
||||
case ACTION_CONNECT:
|
||||
|
||||
@@ -20,6 +20,8 @@ package nodomain.freeyourgadget.gadgetbridge.util;
|
||||
import android.content.Context;
|
||||
import android.text.format.DateUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.github.pfichtner.durationformatter.DurationFormatter;
|
||||
|
||||
import java.text.FieldPosition;
|
||||
@@ -39,8 +41,8 @@ import nodomain.freeyourgadget.gadgetbridge.GBApplication;
|
||||
import nodomain.freeyourgadget.gadgetbridge.R;
|
||||
|
||||
public class DateTimeUtils {
|
||||
private static SimpleDateFormat DAY_STORAGE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
private static SimpleDateFormat HOURS_MINUTES_FORMAT = new SimpleDateFormat("HH:mm", Locale.US);
|
||||
private static final SimpleDateFormat DAY_STORAGE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||
private static final SimpleDateFormat HOURS_MINUTES_FORMAT = new SimpleDateFormat("HH:mm", Locale.US);
|
||||
public static SimpleDateFormat ISO_8601_FORMAT = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US){
|
||||
//see https://github.com/Freeyourgadget/Gadgetbridge/issues/1076#issuecomment-383834116 and https://stackoverflow.com/a/30221245
|
||||
|
||||
@@ -53,6 +55,7 @@ public class DateTimeUtils {
|
||||
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition pos) {
|
||||
StringBuffer rfcFormat = super.format(date, toAppendTo, pos);
|
||||
@@ -118,8 +121,7 @@ public class DateTimeUtils {
|
||||
Calendar cal = GregorianCalendar.getInstance();
|
||||
cal.setTime(date);
|
||||
cal.add(GregorianCalendar.DAY_OF_YEAR, offset);
|
||||
Date newDate = cal.getTime();
|
||||
return newDate;
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static Date dayStart(final Date date) {
|
||||
@@ -132,6 +134,16 @@ public class DateTimeUtils {
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public static Date dayStart(final int year, final int monthOfYear, final int dayOfMonth) {
|
||||
final Calendar calendar = Calendar.getInstance();
|
||||
calendar.set(year, monthOfYear, dayOfMonth);
|
||||
calendar.set(Calendar.HOUR_OF_DAY, 0);
|
||||
calendar.set(Calendar.MINUTE, 0);
|
||||
calendar.set(Calendar.SECOND, 0);
|
||||
calendar.set(Calendar.MILLISECOND, 0);
|
||||
return calendar.getTime();
|
||||
}
|
||||
|
||||
public static Calendar dayStart(final Calendar calendar) {
|
||||
final Calendar ret = (Calendar) calendar.clone();
|
||||
ret.set(Calendar.HOUR_OF_DAY, 0);
|
||||
@@ -189,10 +201,6 @@ public class DateTimeUtils {
|
||||
return cal.getTime();
|
||||
}
|
||||
|
||||
public static String dayToString(Date date) {
|
||||
return DAY_STORAGE_FORMAT.format(date);
|
||||
}
|
||||
|
||||
public static Date dayFromString(String day) throws ParseException {
|
||||
return DAY_STORAGE_FORMAT.parse(day);
|
||||
}
|
||||
@@ -225,9 +233,6 @@ public class DateTimeUtils {
|
||||
/**
|
||||
* Calculates new timestamp with a month offset (positive to add or negative to remove)
|
||||
* from a given time
|
||||
*
|
||||
* @param time
|
||||
* @param month
|
||||
*/
|
||||
public static int shiftMonths(int time, int month) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
@@ -239,9 +244,6 @@ public class DateTimeUtils {
|
||||
/**
|
||||
* Calculates new timestamp with a day offset (positive to add or negative to remove)
|
||||
* from a given time
|
||||
*
|
||||
* @param time
|
||||
* @param days
|
||||
*/
|
||||
public static int shiftDays(int time, int days) {
|
||||
Calendar day = Calendar.getInstance();
|
||||
@@ -250,16 +252,6 @@ public class DateTimeUtils {
|
||||
return (int) (day.getTimeInMillis() / 1000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates difference in days between two timestamps
|
||||
*
|
||||
* @param time1
|
||||
* @param time2
|
||||
*/
|
||||
public static int getDaysBetweenTimes(int time1, int time2) {
|
||||
return (int) TimeUnit.MILLISECONDS.toDays((time2 - time1) * 1000L);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether two Calendar instances are on the same day
|
||||
*
|
||||
@@ -322,10 +314,11 @@ public class DateTimeUtils {
|
||||
}
|
||||
|
||||
public static String formatDaysUntil(int days, int endTs) {
|
||||
Date to = new Date((long) endTs * 1000);
|
||||
Date from = org.apache.commons.lang3.time.DateUtils.addDays(to, - (days - 1));
|
||||
String toFormattedDate = new SimpleDateFormat("E, MMM dd").format(to);
|
||||
String fromFormattedDate = new SimpleDateFormat("E, MMM dd").format(from);
|
||||
final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("E, MMM dd", Locale.getDefault());
|
||||
final Date to = new Date((long) endTs * 1000);
|
||||
final Date from = org.apache.commons.lang3.time.DateUtils.addDays(to, - (days - 1));
|
||||
final String toFormattedDate = simpleDateFormat.format(to);
|
||||
final String fromFormattedDate = simpleDateFormat.format(from);
|
||||
return fromFormattedDate + " - " + toFormattedDate;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,10 +49,12 @@ public class MediaManager {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MusicSpec getBufferMusicSpec() {
|
||||
return bufferMusicSpec;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public MusicStateSpec getBufferMusicStateSpec() {
|
||||
return bufferMusicStateSpec;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
package nodomain.freeyourgadget.gadgetbridge.util
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.text.Editable
|
||||
import android.text.InputFilter
|
||||
import android.text.InputType
|
||||
import android.text.Spanned
|
||||
import android.text.TextWatcher
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import nodomain.freeyourgadget.gadgetbridge.BuildConfig
|
||||
import nodomain.freeyourgadget.gadgetbridge.GBApplication
|
||||
import nodomain.freeyourgadget.gadgetbridge.R
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconAdapter
|
||||
import nodomain.freeyourgadget.gadgetbridge.adapter.SpinnerWithIconItem
|
||||
import nodomain.freeyourgadget.gadgetbridge.database.DBHelper
|
||||
import nodomain.freeyourgadget.gadgetbridge.databinding.DialogTestDeviceBinding
|
||||
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager
|
||||
import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice
|
||||
import nodomain.freeyourgadget.gadgetbridge.model.DeviceType
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.util.Random
|
||||
import java.util.TreeMap
|
||||
|
||||
class TestDeviceDialog(
|
||||
private val activity: Activity,
|
||||
private val macAddress: String?
|
||||
) {
|
||||
var selectedTestDeviceMAC = macAddress ?: randomMac()
|
||||
var selectedTestDeviceKey = -1L
|
||||
private lateinit var updateOkButtonState: (() -> Unit)
|
||||
|
||||
fun show(onSelection: (String, DeviceType) -> Unit) {
|
||||
val binding = DialogTestDeviceBinding.inflate(activity.layoutInflater)
|
||||
|
||||
val allDevicesByName: MutableMap<String, DeviceTypeWithIcon> = getAllSupportedDevices(activity)
|
||||
|
||||
// Get unique manufacturers
|
||||
val manufacturerMap = mutableMapOf<String, MutableList<Map.Entry<String, DeviceTypeWithIcon>>>()
|
||||
val manufacturers = mutableSetOf<String>()
|
||||
for (entry in allDevicesByName.entries) {
|
||||
val manufacturer = entry.value.deviceType.getDeviceCoordinator().getManufacturer()
|
||||
manufacturers.add(manufacturer)
|
||||
manufacturerMap.getOrPut(manufacturer) { mutableListOf() }.add(entry)
|
||||
}
|
||||
|
||||
val manufacturerPriority = listOf(
|
||||
"Gadgetbridge",
|
||||
activity.getString(R.string.pref_header_generic),
|
||||
activity.getString(R.string.unknown)
|
||||
)
|
||||
val sortedManufacturers = manufacturers.toList().sortedWith(
|
||||
compareBy<String> { item ->
|
||||
val index = manufacturerPriority.indexOfFirst { it.equals(item, ignoreCase = true) }
|
||||
if (index >= 0) index else Int.MAX_VALUE
|
||||
}.thenBy(String.CASE_INSENSITIVE_ORDER) { it }
|
||||
)
|
||||
val manufacturerListArray = ArrayList<String>()
|
||||
manufacturerListArray.add(activity.getString(R.string.all_manufacturers))
|
||||
manufacturerListArray.addAll(sortedManufacturers)
|
||||
|
||||
// Create full device list
|
||||
val allDeviceListArray = allDevicesByName.entries
|
||||
.map { SpinnerWithIconItem(it.key, it.value.deviceType.ordinal.toLong(), it.value.icon) }
|
||||
.toCollection(ArrayList())
|
||||
|
||||
val currentDeviceList = ArrayList(allDeviceListArray)
|
||||
|
||||
val deviceListAdapter = SpinnerWithIconAdapter(
|
||||
activity,
|
||||
R.layout.spinner_with_image_layout,
|
||||
R.id.spinner_item_text,
|
||||
currentDeviceList
|
||||
)
|
||||
binding.deviceTypeDropdown.setAdapter(deviceListAdapter)
|
||||
binding.deviceTypeDropdown.onItemClickListener = AdapterView.OnItemClickListener { parent, _, position, _ ->
|
||||
val selectedItem = parent.getItemAtPosition(position) as SpinnerWithIconItem
|
||||
selectedTestDeviceKey = selectedItem.getId()
|
||||
updateOkButtonState.invoke()
|
||||
}
|
||||
|
||||
// Set up manufacturer dropdown
|
||||
binding.deviceManufacturerDropdown.setAdapter(
|
||||
ArrayAdapter(
|
||||
activity,
|
||||
android.R.layout.simple_dropdown_item_1line,
|
||||
manufacturerListArray
|
||||
)
|
||||
)
|
||||
binding.deviceManufacturerDropdown.setText(activity.getString(R.string.all_manufacturers), false)
|
||||
binding.deviceManufacturerDropdown.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { parent, _, position, _ ->
|
||||
val selectedManufacturer = parent.getItemAtPosition(position) as String
|
||||
|
||||
// Filter device list based on selected manufacturer
|
||||
currentDeviceList.clear()
|
||||
if (position == 0) {
|
||||
// Show all devices
|
||||
currentDeviceList.addAll(allDeviceListArray)
|
||||
} else {
|
||||
// Show only devices from selected manufacturer
|
||||
val filteredDevices = manufacturerMap[selectedManufacturer]?.map {
|
||||
SpinnerWithIconItem(
|
||||
it.key.replace("^${selectedManufacturer} ".toRegex(), "")
|
||||
.replace(" \\(${selectedManufacturer}\\)$".toRegex(), ""),
|
||||
it.value.deviceType.ordinal.toLong(),
|
||||
it.value.icon
|
||||
)
|
||||
} ?: emptyList()
|
||||
currentDeviceList.addAll(filteredDevices)
|
||||
}
|
||||
deviceListAdapter.notifyDataSetChanged()
|
||||
|
||||
// Reset device selection
|
||||
binding.deviceTypeDropdown.setText("", false)
|
||||
selectedTestDeviceKey = -1L
|
||||
updateOkButtonState.invoke()
|
||||
}
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(activity)
|
||||
.setCancelable(true)
|
||||
.setTitle(R.string.add_test_device)
|
||||
.setView(binding.root)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
onSelection.invoke(
|
||||
binding.macEditText.text.toString(),
|
||||
DeviceType.entries[selectedTestDeviceKey.toInt()]
|
||||
)
|
||||
}
|
||||
.setNegativeButton(R.string.Cancel) { _, _ -> }
|
||||
.create()
|
||||
|
||||
// Validate and update OK button state
|
||||
updateOkButtonState = {
|
||||
val positiveButton = dialog.getButton(androidx.appcompat.app.AlertDialog.BUTTON_POSITIVE)
|
||||
val isMacValid = isValidMacAddress(binding.macEditText.text.toString())
|
||||
val isDeviceSelected = selectedTestDeviceKey != -1L
|
||||
positiveButton?.isEnabled = isMacValid && isDeviceSelected
|
||||
}
|
||||
|
||||
dialog.setOnShowListener {
|
||||
updateOkButtonState.invoke()
|
||||
}
|
||||
|
||||
setupMacAddressInput(binding.macEditText)
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
private fun getAllSupportedDevices(context: Context): MutableMap<String, DeviceTypeWithIcon> {
|
||||
var newMap = LinkedHashMap<String, DeviceTypeWithIcon>(1)
|
||||
for (deviceType in DeviceType.entries) {
|
||||
val coordinator = deviceType.getDeviceCoordinator()
|
||||
val icon = coordinator.getDefaultIconResource()
|
||||
var name = context.getString(coordinator.getDeviceNameResource())
|
||||
val manufacturer = when (coordinator.getManufacturer()) {
|
||||
"Unknown" -> context.getString(R.string.unknown)
|
||||
"Generic" -> context.getString(R.string.pref_header_generic)
|
||||
else -> coordinator.getManufacturer()
|
||||
}
|
||||
if (!name.startsWith(manufacturer)) {
|
||||
name += " (${manufacturer})"
|
||||
}
|
||||
newMap[name] = DeviceTypeWithIcon(deviceType, icon)
|
||||
}
|
||||
|
||||
val sortedMap = TreeMap<String, DeviceTypeWithIcon>(String.CASE_INSENSITIVE_ORDER)
|
||||
sortedMap.putAll(newMap)
|
||||
newMap = LinkedHashMap(sortedMap.size + 3)
|
||||
|
||||
// Ensure some devices are first
|
||||
//newMap[context.getString(R.string.devicetype_scannable)] =
|
||||
// DeviceTypeWithIcon(DeviceType.SCANNABLE, R.drawable.ic_device_scannable)
|
||||
//newMap[context.getString(R.string.devicetype_ble_gatt_client)] =
|
||||
// DeviceTypeWithIcon(DeviceType.BLE_GATT_CLIENT, R.drawable.ic_device_scannable)
|
||||
|
||||
newMap.putAll(sortedMap)
|
||||
|
||||
return newMap
|
||||
}
|
||||
|
||||
private fun setupMacAddressInput(editText: EditText) {
|
||||
editText.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
|
||||
|
||||
if (macAddress != null) {
|
||||
editText.isEnabled = false
|
||||
}
|
||||
|
||||
// help the user to input a properly formated MAC - see BluetoothAdapter.checkBluetoothAddress
|
||||
// for Bangle.js builds: also support pebble emulator addresses
|
||||
editText.filters = arrayOf(object : InputFilter {
|
||||
@Suppress("KotlinConstantConditions")
|
||||
override fun filter(
|
||||
source: CharSequence,
|
||||
start: Int, end: Int,
|
||||
dest: Spanned,
|
||||
dstart: Int, dend: Int
|
||||
): CharSequence {
|
||||
val builder = StringBuilder()
|
||||
for (i in start..<end) {
|
||||
val c = source[i]
|
||||
if ((c in '0'..'9') || (c in 'A'..'F') || c == ':') {
|
||||
builder.append(c)
|
||||
} else if (c in 'a'..'f') {
|
||||
builder.append((c.code - 'a'.code + 'A'.code).toChar())
|
||||
} else if (BuildConfig.INTERNET_ACCESS) {
|
||||
builder.append(c)
|
||||
} else if (c == '-') {
|
||||
builder.append(':')
|
||||
}
|
||||
}
|
||||
return builder
|
||||
}
|
||||
})
|
||||
|
||||
editText.setText(selectedTestDeviceMAC)
|
||||
editText.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {
|
||||
}
|
||||
|
||||
override fun onTextChanged(charSequence: CharSequence?, i: Int, i1: Int, i2: Int) {
|
||||
}
|
||||
|
||||
override fun afterTextChanged(editable: Editable) {
|
||||
selectedTestDeviceMAC = editable.toString()
|
||||
updateOkButtonState.invoke()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private data class DeviceTypeWithIcon(val deviceType: DeviceType, val icon: Int)
|
||||
|
||||
companion object {
|
||||
private val LOG: Logger = LoggerFactory.getLogger(TestDeviceDialog::class.java)
|
||||
|
||||
private fun isValidMacAddress(mac: String): Boolean {
|
||||
if (BuildConfig.INTERNET_ACCESS) {
|
||||
// For builds with internet access (Bangle.js), allow more flexible formats
|
||||
return mac.isNotEmpty()
|
||||
}
|
||||
// Standard MAC address validation: XX:XX:XX:XX:XX:XX
|
||||
val macRegex = "^([0-9A-F]{2}:){5}[0-9A-F]{2}$".toRegex()
|
||||
return macRegex.matches(mac)
|
||||
}
|
||||
|
||||
private fun randomMac(): String {
|
||||
val random = Random()
|
||||
return String.format(
|
||||
"%02X:%02X:%02X:%02X:%02X:%02X",
|
||||
random.nextInt(0xff),
|
||||
random.nextInt(0xff),
|
||||
random.nextInt(0xff),
|
||||
random.nextInt(0xff),
|
||||
random.nextInt(0xff),
|
||||
random.nextInt(0xff)
|
||||
)
|
||||
}
|
||||
|
||||
fun createTestDevice(
|
||||
context: Context,
|
||||
deviceType: DeviceType,
|
||||
deviceMac: String,
|
||||
inputDeviceName: String?
|
||||
) {
|
||||
val deviceNameResource = deviceType.getDeviceCoordinator().getDeviceNameResource()
|
||||
val deviceName: String =
|
||||
inputDeviceName ?: if (deviceNameResource == 0) deviceType.name else context.getString(
|
||||
deviceNameResource
|
||||
)
|
||||
|
||||
try {
|
||||
GBApplication.acquireDB().use { db ->
|
||||
val daoSession = db.getDaoSession()
|
||||
val gbDevice = GBDevice(deviceMac, deviceName, "", null, deviceType)
|
||||
gbDevice.firmwareVersion = "N/A"
|
||||
gbDevice.firmwareVersion2 = "N/A"
|
||||
|
||||
//this causes the attributes (fw version) to be stored as well. Not much useful, but still...
|
||||
gbDevice.setState(GBDevice.State.INITIALIZED)
|
||||
|
||||
DBHelper.getDevice(gbDevice, daoSession) //the addition happens here
|
||||
|
||||
val refreshIntent = Intent(DeviceManager.ACTION_REFRESH_DEVICELIST)
|
||||
LocalBroadcastManager.getInstance(context).sendBroadcast(refreshIntent)
|
||||
GB.toast(context, "Added test device: $deviceName", Toast.LENGTH_SHORT, GB.INFO)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LOG.error("Error accessing database", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+7
-7
@@ -142,17 +142,17 @@ public class WidgetPreferenceStorage {
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
public void showAppWidgetsPrefs(Context context) {
|
||||
public String getAppWidgetsPrefs(Context context) {
|
||||
SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(context);
|
||||
String savedWidgetsPreferencesData = sharedPrefs.getString(PREFS_WIDGET_SETTINGS, "");
|
||||
JSONArray savedWidgetsPreferencesDataArray = null;
|
||||
if (savedWidgetsPreferencesData.isBlank()) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
savedWidgetsPreferencesDataArray = new JSONArray(savedWidgetsPreferencesData);
|
||||
} catch (
|
||||
JSONException e) {
|
||||
LOG.error(e.getMessage());
|
||||
return new JSONArray(savedWidgetsPreferencesData).toString();
|
||||
} catch (JSONException e) {
|
||||
return e.getMessage();
|
||||
}
|
||||
GB.toast("Saved app widget preferences: " + savedWidgetsPreferencesDataArray, Toast.LENGTH_SHORT, GB.INFO);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<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="M200,840q-51,0 -72.5,-45.5T138,710l222,-270v-240h-40q-17,0 -28.5,-11.5T280,160q0,-17 11.5,-28.5T320,120h320q17,0 28.5,11.5T680,160q0,17 -11.5,28.5T640,200h-40v240l222,270q32,39 10.5,84.5T760,840L200,840ZM200,760h560L520,468v-268h-80v268L200,760ZM480,480Z" />
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<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="M120,800v-640l760,320 -760,320ZM200,680 L674,480 200,280v140l240,60 -240,60v140ZM200,680v-400,400Z" />
|
||||
</vector>
|
||||
@@ -125,74 +125,6 @@
|
||||
android:layout_weight="1"
|
||||
android:text="@string/activity_DB_ShowContentButton" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cleanExportDirectory_label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/activity_db_management_clean_export_directory_label"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/cleanExportDirectory_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/activity_db_management_clean_export_directory_text" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/activity_data_management_path2"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textStyle="bold" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/cleanExportDirectoryButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/activity_db_management_clean_export_directory_label" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/mergeOldActivityDataTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/activity_db_management_merge_old_title"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
|
||||
<Button
|
||||
android:id="@+id/deleteOldActivityDB"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/activity_DB_delete_legacy_button" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyActivityDataTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/activity_db_management_empty_DB"
|
||||
android:textAppearance="?android:attr/textAppearanceMedium"
|
||||
android:textColor="@color/accent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/emptyDBText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/activity_db_management_empty_db_warning"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/emptyDBButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/activity_DB_empty_button" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -1,332 +0,0 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:grid="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.ControlCenterv2">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/scrollView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentTop="true">
|
||||
|
||||
<androidx.gridlayout.widget.GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||
android:paddingTop="@dimen/activity_vertical_margin"
|
||||
grid:alignmentMode="alignMargins"
|
||||
grid:columnCount="2">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Message / Caller"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/editContent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:ems="10"
|
||||
android:inputType="textMultiLine"
|
||||
android:text="Test" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/sendTypeSpinner"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
grid:layout_columnSpan="2"
|
||||
android:text="send as SMS" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sendButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Send" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/incomingCallButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="incoming call" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/outgoingCallButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="outgoing call" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startCallButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="start call" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/endCallButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="end call" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setTimeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="set time" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setMusicInfoButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="set music info" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/HeartRateButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Heart Rate Test" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/rebootButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="reboot" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/SetFetchTimeButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Set Activity Fetch Time" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/setWeatherButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Set Weather" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/showCachedWeatherButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Show Cached Weather" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/factoryResetButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="factory reset" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/testNotificationButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="create test notification" />
|
||||
<Button
|
||||
android:id="@+id/testPebbleKitNotificationButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="create PebbleKit test notification" />
|
||||
<Button
|
||||
android:id="@+id/fetchDebugLogsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Fetch device Debug Logs" />
|
||||
<Button
|
||||
android:id="@+id/testNewFunctionality"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Test New Functionality" />
|
||||
<Button
|
||||
android:id="@+id/shareLog"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="@string/share_log" />
|
||||
<Button
|
||||
android:id="@+id/showWidgetsButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Show all registered app widgets IDs" />
|
||||
<Button
|
||||
android:id="@+id/deleteWidgets"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Delete all registered app widgets IDs" />
|
||||
<Button
|
||||
android:id="@+id/showWidgetsPrefs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Show app Widgets Preferences" />
|
||||
<Button
|
||||
android:id="@+id/deleteWidgetsPrefs"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Delete app Widgets Preferences" />
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/activity_list_debug_extra_time_range"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="4dp"
|
||||
android:layout_marginTop="7dp"
|
||||
android:padding="4dp"
|
||||
android:paddingLeft="10dp"
|
||||
android:text="Activity summary extra date range (takes long to generate when used)"
|
||||
android:textStyle="bold"
|
||||
grid:layout_columnSpan="2" />
|
||||
<Button
|
||||
android:id="@+id/addDeviceButtonDebug"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Add test device manually" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/removeDevicePreferences"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Remove device preferences"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/runDebugFunction"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Run Debug Function"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startFitnessAppTracking"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_device_action_fitness_app_control_start"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/stopFitnessAppTracking"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_device_action_fitness_app_control_stop"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/showStatusFitnessAppTracking"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Show Fit.App.Track. Status"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/stopPhoneGpsLocationListener"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/pref_device_action_phone_gps_location_listener_stop"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/showCompanionDevices"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="@string/debug_companion_show_associated" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/pairAsCompanion"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="@string/debug_companion_pair_current" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/cameraOpen"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
grid:layout_columnSpan="2"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="@string/open_camera" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startWelcomeActivity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Welcome Activity" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/startPermissionsActivity"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="2dp"
|
||||
grid:layout_gravity="fill_horizontal"
|
||||
android:text="Permissions Screen" />
|
||||
</androidx.gridlayout.widget.GridLayout>
|
||||
|
||||
</ScrollView>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,28 @@
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.debug.preferences.PreferenceManagerActivity">
|
||||
|
||||
<androidx.appcompat.widget.SearchView
|
||||
android:id="@+id/preferenceListSearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:inputType="textNoSuggestions"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/preferenceListView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/preferenceListSearchView"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:divider="@null"
|
||||
android:paddingStart="8dp"
|
||||
android:paddingEnd="8dp"
|
||||
android:scrollbarSize="5dp"
|
||||
android:scrollbars="vertical" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,59 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="12dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/device_manufacturer_input_layout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/device_manufacturer"
|
||||
android:labelFor="@+id/device_manufacturer_dropdown">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/device_manufacturer_dropdown"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:maxLines="1"
|
||||
app:simpleItems="@array/empty_array" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/device_type_input_layout"
|
||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:hint="@string/widget_settings_select_device_title"
|
||||
android:labelFor="@+id/device_type_dropdown">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/device_type_dropdown"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="none"
|
||||
android:maxLines="1"
|
||||
app:simpleItems="@array/empty_array" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/mac_input_layout"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/mac_edit_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/device_mac_address"
|
||||
android:inputType="textNoSuggestions" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</LinearLayout>
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:minHeight="50dp">
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
@@ -20,12 +20,12 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:text="Placeholder: Permission name" />
|
||||
tools:text="Placeholder: Permission name" />
|
||||
<TextView
|
||||
android:id="@+id/permission_summary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Placeholder: Permission summary" />
|
||||
tools:text="Placeholder: Permission summary" />
|
||||
</LinearLayout>
|
||||
<Button
|
||||
android:id="@+id/permission_request"
|
||||
@@ -41,7 +41,7 @@
|
||||
android:layout_marginEnd="20dp"
|
||||
android:src="@drawable/cpv_preset_checked"
|
||||
android:visibility="gone"
|
||||
app:tint="@android:color/holo_green_dark"
|
||||
app:tint="?attr/checkmarkColor"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout 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="wrap_content"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:minHeight="60dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toStartOf="@+id/preference_menu"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_key"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scrollHorizontally="false"
|
||||
android:text="-"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Subhead"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/preference_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:text=""
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Small" />
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/preference_menu"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
app:srcCompat="@drawable/ic_more_vert" />
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -6,6 +6,10 @@
|
||||
<item
|
||||
android:id="@+id/controlcenter_device_submenu_disconnect"
|
||||
android:title="@string/controlcenter_disconnect" />
|
||||
<item
|
||||
android:id="@+id/controlcenter_device_submenu_test_new_function"
|
||||
android:title="@string/controlcenter_test_new_function"
|
||||
android:visible="false" />
|
||||
<item
|
||||
android:id="@+id/controlcenter_device_submenu_set_parent_folder"
|
||||
android:title="@string/controlcenter_set_parent_folder" />
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
<menu 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"
|
||||
tools:context="nodomain.freeyourgadget.gadgetbridge.activities.debug.preferences.PreferenceManagerActivity">
|
||||
<item
|
||||
android:id="@+id/debug_preferences_search"
|
||||
android:icon="@drawable/ic_search"
|
||||
android:title="@string/search"
|
||||
app:iconTint="?attr/actionmenu_icon_color"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/debug_preferences_export"
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:title="@string/file_export"
|
||||
app:iconTint="?attr/actionmenu_icon_color"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/debug_preferences_import"
|
||||
android:icon="@drawable/ic_download"
|
||||
android:title="@string/file_import"
|
||||
app:iconTint="?attr/actionmenu_icon_color"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/debug_preferences_reset"
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:title="@string/reset"
|
||||
app:iconTint="?attr/actionmenu_icon_color"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
@@ -19,6 +19,7 @@
|
||||
<attr name="training_acute_load" format="color" />
|
||||
<attr name="alternate_row_background" format="color" />
|
||||
|
||||
<attr name="checkmarkColor" format="color" />
|
||||
<attr name="deviceIconPrimary" format="color" />
|
||||
<attr name="deviceIconOnPrimary" format="color" />
|
||||
<attr name="deviceIconLight" format="color" />
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
<string name="debugactivity_really_factoryreset_title">Really factory reset?</string>
|
||||
<string name="debugactivity_really_factoryreset">Doing a factory reset will delete all data from the connected device (if supported). Xiaomi/Huami devices also change Bluetooth MAC address, so they appear as a new devices to Gadgetbridge.</string>
|
||||
<string name="debugactivity_confirm_remove_device_preferences_title">Remove device preferences?</string>
|
||||
<string name="debugactivity_confirm_remove_device_preferences">This will reset the device preferences for all connected devices. Are you sure?</string>
|
||||
<string name="debugactivity_confirm_remove_device_preferences">This will reset the device preferences for this device. Are you sure?</string>
|
||||
<!-- Strings related to AppManager -->
|
||||
<string name="title_activity_appmanager">App Manager</string>
|
||||
<string name="appmanager_cached_watchapps_watchfaces">Apps in cache</string>
|
||||
@@ -1345,7 +1345,6 @@
|
||||
<string name="action_db_management">Data management</string>
|
||||
<string name="title_activity_db_management">Data management</string>
|
||||
<string name="activity_db_management_import_export_explanation">The export/import operations use the following path (see below) to a directory on your device. This directory is accessible to other Android apps and your computer. Do note, that this directory and all containing files are deleted if you uninstall Gadgetbridge. The data includes:\n Export_preference - global settings\n Export_preference_MAC - device specific settings\n Gadgetbridge - device and activity database\n Gadgetbridge_date - database exported on a date\n *.gpx - GPS recordings\n *.log - log files\nExpect to find your exported files (or place the files you want to import) there:</string>
|
||||
<string name="activity_db_management_merge_old_title">Legacy database delete</string>
|
||||
<string name="dbmanagementactivvity_cannot_access_export_path">Cannot access export path. Please contact the developers.</string>
|
||||
<string name="dbmanagementactivity_exported_to">Exported to: %1$s</string>
|
||||
<string name="dbmanagementactivity_error_exporting_db">Error exporting DB: %1$s</string>
|
||||
@@ -1392,10 +1391,6 @@
|
||||
<string name="set">Set</string>
|
||||
<string name="activity_data_management_directory_content_title">Export/Import directory content</string>
|
||||
<string name="activity_DB_ShowContentButton">Show Export/Import directory content</string>
|
||||
<string name="activity_db_management_clean_export_directory_label">Delete files in Export/Import directory</string>
|
||||
<string name="activity_DB_clean_export_directory_warning_title">Delete files in the Export/Import directory?</string>
|
||||
<string name="activity_DB_clean_export_directory_warning_message">Really delete files in the Export/Import directory?</string>
|
||||
<string name="activity_db_management_clean_export_directory_text">Exported files in the Export/Import directory are accessible by any app on your device. You might like to remove these files after synchronisation or backup. Make sure to have a backup before deleting them. GPX files, sub-directories and auto-exported database file (if exist) will not be deleted. The path to the Export/Import directory is:</string>
|
||||
<string name="dbmanagementactivity_export_finished">Deletion finished</string>
|
||||
<!-- Strings related to Vibration Activity -->
|
||||
<string name="title_activity_vibration">Vibration</string>
|
||||
@@ -1943,7 +1938,7 @@
|
||||
<string name="devicetype_garmin_quatix_8" translatable="false">Garmin Quatix 8</string>
|
||||
<string name="devicetype_garmin_descent_mk3" translatable="false">Garmin Descent Mk3</string>
|
||||
<string name="devicetype_garmin_descent_g2" translatable="false">Garmin Descent G2</string>
|
||||
<string name="devicetype_garmin_inReach_mini_2" translatable="false">inReach Mini 2</string>
|
||||
<string name="devicetype_garmin_inReach_mini_2" translatable="false">Garmin inReach Mini 2</string>
|
||||
<string name="devicetype_garmin_instinct" translatable="false">Garmin Instinct</string>
|
||||
<string name="devicetype_garmin_instinct_solar" translatable="false">Garmin Instinct Solar</string>
|
||||
<string name="devicetype_garmin_instinct_2" translatable="false">Garmin Instinct 2</string>
|
||||
@@ -2033,7 +2028,7 @@
|
||||
<string name="devicetype_polarh9" translatable="false">Polar H9</string>
|
||||
<string name="devicetype_polarh10" translatable="false">Polar H10</string>
|
||||
<string name="devicetype_sonyswr12" translatable="false">Sony SWR12</string>
|
||||
<string name="devicetype_waspos" translatable="false">Wasp-os</string>
|
||||
<string name="devicetype_waspos" translatable="false">Wasp-OS</string>
|
||||
<string name="devicetype_smaq2oss" translatable="false">SMA-Q2 OSS</string>
|
||||
<string name="devicetype_fitpro" translatable="false">FitPro</string>
|
||||
<string name="devicetype_colacao21" translatable="false">ColaCao 2021</string>
|
||||
@@ -2325,7 +2320,6 @@
|
||||
<string name="preferences_rtl_settings">Right To Left Support</string>
|
||||
<string name="share_log">Share log</string>
|
||||
<string name="share_log_warning">Please keep in mind Gadgetbridge logs files that may contain lots of personal info, including but not limited to health data, unique identifiers (such as a device\'s MAC address), music preferences, etc. Consider editing the file and removing this info before sending the file to a public issue report.</string>
|
||||
<string name="share_log_not_enabled_message">You must first enable logging in the Settings - Write log files</string>
|
||||
<string name="warning">Warning!</string>
|
||||
<string name="note">Note</string>
|
||||
<string name="no_data">No data</string>
|
||||
@@ -3490,6 +3484,7 @@
|
||||
|
||||
<string name="info_no_devices_connected">No devices connected</string>
|
||||
<string name="info_connected_count">%d devices connected</string>
|
||||
<string name="controlcenter_test_new_function">Test new function</string>
|
||||
<string name="controlcenter_set_parent_folder">Set parent folder</string>
|
||||
<string name="controlcenter_set_preferences">Set preferences</string>
|
||||
<string name="controlcenter_toggle_details">Toggle details</string>
|
||||
@@ -3529,7 +3524,7 @@
|
||||
<string name="weekly_total">Weekly total</string>
|
||||
<string name="prefs_hourly_chime">Hourly chime</string>
|
||||
<string name="prefs_hourly_chime_summary">The watch will beep once an hour</string>
|
||||
<string name="devicetype_flipper_zero" translatable="false">Flipper zero</string>
|
||||
<string name="devicetype_flipper_zero" translatable="false">Flipper Zero</string>
|
||||
<string name="activity_prefs_allow_bluetooth_intent_api">Bluetooth Intent API</string>
|
||||
<string name="activity_prefs_summary_allow_bluetooth_intent_api">Allow controlling Bluetooth connection via Intent API</string>
|
||||
<string name="intent_api_allow_activity_sync_title">Allow activity sync trigger</string>
|
||||
@@ -4488,4 +4483,18 @@
|
||||
<string name="solar_panel2_peak_w">Solar panel 2 peak W</string>
|
||||
<string name="solar_panel3_peak_w">Solar panel 3 peak W</string>
|
||||
<string name="solar_panel4_peak_w">Solar panel 4 peak W</string>
|
||||
<string name="all_manufacturers">All manufacturers</string>
|
||||
<string name="device_manufacturer">Manufacturer</string>
|
||||
<string name="debug_selected_device">Debug device</string>
|
||||
<string name="device_mac_address">MAC address</string>
|
||||
<string name="database">Database</string>
|
||||
<string name="file_export">Export</string>
|
||||
<string name="file_import">Import</string>
|
||||
<string name="global_preferences">Global preferences</string>
|
||||
<string name="export_success">Export successful</string>
|
||||
<string name="import_success">Import successful</string>
|
||||
<string name="reset_fields">Reset fields</string>
|
||||
<string name="are_you_sure">Are you sure?</string>
|
||||
<string name="dangerous_actions">Dangerous actions</string>
|
||||
<string name="debug_other_actions">Other actions</string>
|
||||
</resources>
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<item name="alternate_row_background">@color/alternate_row_background_light</item>
|
||||
<item name="row_separator">@color/row_separator_light</item>
|
||||
|
||||
<item name="checkmarkColor">#669900</item>
|
||||
<item name="deviceIconPrimary">#2196f3</item>
|
||||
<item name="deviceIconOnPrimary">#ffffff</item>
|
||||
<item name="deviceIconLight">#1f7fdb</item>
|
||||
@@ -81,6 +82,7 @@
|
||||
<item name="alternate_row_background">@color/alternate_row_background_dark</item>
|
||||
<item name="row_separator">@color/row_separator_dark</item>
|
||||
|
||||
<item name="checkmarkColor">#669900</item>
|
||||
<item name="deviceIconPrimary">#2196f3</item>
|
||||
<item name="deviceIconOnPrimary">#ffffff</item>
|
||||
<item name="deviceIconLight">#1f7fdb</item>
|
||||
@@ -115,6 +117,7 @@
|
||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
||||
<!-- FIXME: SDK 21 to 27 has light navbar on light background -->
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="27">true</item>
|
||||
<item name="checkmarkColor">?attr/colorPrimary</item>
|
||||
<item name="deviceIconPrimary">?attr/colorPrimary</item>
|
||||
<item name="deviceIconOnPrimary">?attr/colorOnPrimary</item>
|
||||
<item name="deviceIconLight">?attr/colorPrimaryVariant</item>
|
||||
@@ -137,6 +140,7 @@
|
||||
<item name="preferenceTheme">@style/AppPreferenceThemeOverlay</item>
|
||||
<item name="android:navigationBarColor">?attr/colorSurface</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="27">false</item>
|
||||
<item name="checkmarkColor">?attr/colorPrimary</item>
|
||||
<item name="deviceIconPrimary">?attr/colorPrimary</item>
|
||||
<item name="deviceIconOnPrimary">?attr/colorOnPrimary</item>
|
||||
<item name="deviceIconLight">?attr/colorPrimaryVariant</item>
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
<?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"
|
||||
android:title="@string/phone_calls">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="pref_debug_call_send"
|
||||
android:persistent="false"
|
||||
android:title="Send CallSpec" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:key="pref_debug_call_reset"
|
||||
android:persistent="false"
|
||||
android:title="@string/reset_fields" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_debug_header_callspec"
|
||||
android:title="CallSpec"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="INCOMING"
|
||||
android:entries="@array/empty_array"
|
||||
android:entryValues="@array/empty_array"
|
||||
android:key="pref_debug_call_command"
|
||||
android:title="command"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="6365553226"
|
||||
android:key="pref_debug_call_number"
|
||||
android:title="number"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="Mr. Plow"
|
||||
android:key="pref_debug_call_name"
|
||||
android:title="name"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_call_source_name"
|
||||
android:title="sourceName"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_call_source_app_id"
|
||||
android:title="sourceAppId"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_call_key"
|
||||
android:title="key"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_call_channel_id"
|
||||
android:title="channelId"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_call_category"
|
||||
android:title="category"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_call_is_voip"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="isVoip"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_call_dnd_suppressed_bool"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="dndSuppressed"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/companion_pairing_request_title">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_companion_devices"
|
||||
android:title="@string/companion_pairing_request_title">
|
||||
|
||||
<!-- to be filled up dynamically -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_other_devices"
|
||||
android:title="@string/Other">
|
||||
|
||||
<!-- to be filled up dynamically -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,45 @@
|
||||
<?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"
|
||||
android:title="@string/database">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_database"
|
||||
android:key="pref_debug_database_version"
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
android:title="Database version" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_warning_gray"
|
||||
android:key="dangerous_actions"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:persistent="false"
|
||||
android:title="@string/dangerous_actions" />
|
||||
|
||||
<Preference
|
||||
android:dependency="dangerous_actions"
|
||||
android:icon="@drawable/ic_delete_forever"
|
||||
android:key="pref_debug_delete_old_database"
|
||||
android:persistent="false"
|
||||
android:title="@string/activity_DB_delete_legacy_button" />
|
||||
|
||||
<Preference
|
||||
android:dependency="dangerous_actions"
|
||||
android:icon="@drawable/ic_delete_forever"
|
||||
android:key="pref_debug_empty_database"
|
||||
android:persistent="false"
|
||||
android:summary="@string/activity_db_management_empty_db_warning"
|
||||
android:title="@string/activity_db_management_empty_DB" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_database_tables"
|
||||
android:title="Tables"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<!-- to be dynamically filled -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- details will be added programmatically -->
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/pref_header_location">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_location_opentracks"
|
||||
android:title="OpenTracks">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_gps_location"
|
||||
android:key="pref_debug_opentracks_status"
|
||||
android:persistent="false"
|
||||
android:title="@string/status" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_play"
|
||||
android:key="pref_debug_opentracks_start"
|
||||
android:persistent="false"
|
||||
android:title="@string/start" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_stop"
|
||||
android:key="pref_debug_opentracks_stop"
|
||||
android:persistent="false"
|
||||
android:title="@string/stop" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_location_gps_listener"
|
||||
android:title="GPS Listener">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_stop"
|
||||
android:key="pref_debug_gps_listener_stop"
|
||||
android:persistent="false"
|
||||
android:title="@string/stop" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,131 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_add"
|
||||
android:key="pref_debug_add_test_device"
|
||||
android:persistent="false"
|
||||
android:title="@string/add_test_device" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_logs"
|
||||
android:title="@string/pref_app_logs_title">
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_settings_applications"
|
||||
android:key="log_to_file"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="@string/pref_write_logfiles" />
|
||||
|
||||
<Preference
|
||||
android:dependency="log_to_file"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:key="pref_debug_share_logs"
|
||||
android:persistent="false"
|
||||
android:title="@string/share_log" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_debug"
|
||||
android:title="@string/action_debug">
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.NotificationsDebugFragment"
|
||||
android:icon="@drawable/ic_notifications"
|
||||
android:key="pref_debug_notifications"
|
||||
android:persistent="false"
|
||||
android:title="@string/menuitem_notifications" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.CallsDebugFragment"
|
||||
android:icon="@drawable/ic_phone"
|
||||
android:key="pref_debug_calls"
|
||||
android:persistent="false"
|
||||
android:title="@string/phone_calls" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.MusicDebugFragment"
|
||||
android:icon="@drawable/ic_music_note"
|
||||
android:key="pref_debug_music"
|
||||
android:persistent="false"
|
||||
android:title="@string/menuitem_music" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.WeatherDebugFragment"
|
||||
android:icon="@drawable/ic_wb_sunny"
|
||||
android:key="pref_debug_weather"
|
||||
android:persistent="false"
|
||||
android:title="@string/menuitem_weather" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.DatabaseDebugFragment"
|
||||
android:icon="@drawable/ic_database"
|
||||
android:key="pref_debug_database"
|
||||
android:persistent="false"
|
||||
android:title="@string/database" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.PreferencesDebugFragment"
|
||||
android:icon="@drawable/ic_settings_applications"
|
||||
android:key="pref_debug_preferences"
|
||||
android:persistent="false"
|
||||
android:title="@string/menuitem_settings" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.CompanionDebugFragment"
|
||||
android:icon="@drawable/ic_devices_other"
|
||||
android:key="pref_debug_companion_devices"
|
||||
android:persistent="false"
|
||||
android:title="@string/companion_pairing_request_title"
|
||||
tools:ignore="NewApi" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.WidgetsDebugFragment"
|
||||
android:icon="@drawable/ic_widgets"
|
||||
android:key="pref_debug_widgets"
|
||||
android:persistent="false"
|
||||
android:title="@string/menuitem_widgets" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.LocationDebugFragment"
|
||||
android:icon="@drawable/ic_gps_location"
|
||||
android:key="pref_debug_location"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_header_location" />
|
||||
|
||||
<Preference
|
||||
android:fragment="nodomain.freeyourgadget.gadgetbridge.activities.debug.OtherDebugFragment"
|
||||
android:icon="@drawable/ic_developer_mode"
|
||||
android:key="pref_debug_other"
|
||||
android:persistent="false"
|
||||
android:title="@string/debug_other_actions" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_activities"
|
||||
android:title="Activities">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_camera_remote"
|
||||
android:key="pref_debug_activity_camera"
|
||||
android:persistent="false"
|
||||
android:title="@string/pref_camera_remote_title" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_open_in_browser"
|
||||
android:key="pref_debug_activity_welcome"
|
||||
android:persistent="false"
|
||||
android:title="@string/first_start_welcome_title" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_shield"
|
||||
android:key="pref_debug_activity_permissions"
|
||||
android:persistent="false"
|
||||
android:title="@string/first_start_permissions_title" />
|
||||
|
||||
</PreferenceCategory>
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,126 @@
|
||||
<?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"
|
||||
android:title="@string/menuitem_music">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="pref_debug_music_send_musicspec"
|
||||
android:persistent="false"
|
||||
android:title="Send MusicSpec" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="pref_debug_music_send_musicstatespec"
|
||||
android:persistent="false"
|
||||
android:title="Send MusicStateSpec" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_download"
|
||||
android:key="pref_debug_music_pull"
|
||||
android:persistent="false"
|
||||
android:title="Pull fields from phone" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:key="pref_debug_music_reset"
|
||||
android:persistent="false"
|
||||
android:title="@string/reset_fields" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_musicspec"
|
||||
android:title="MusicSpec"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="The Artist"
|
||||
android:key="pref_debug_music_artist"
|
||||
android:title="artist"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="The Album"
|
||||
android:key="pref_debug_music_album"
|
||||
android:title="album"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="The Track Name"
|
||||
android:key="pref_debug_music_track"
|
||||
android:title="track"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="120"
|
||||
android:inputType="number"
|
||||
android:key="pref_debug_music_duration"
|
||||
android:title="duration"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="5"
|
||||
android:inputType="number"
|
||||
android:key="pref_debug_music_trackCount"
|
||||
android:title="trackCount"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="2"
|
||||
android:inputType="number"
|
||||
android:key="pref_debug_music_trackNr"
|
||||
android:title="trackNr"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_musicstatespec"
|
||||
android:title="MusicStateSpec"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="STATE_PLAYING"
|
||||
android:entries="@array/empty_array"
|
||||
android:entryValues="@array/empty_array"
|
||||
android:key="pref_debug_music_state"
|
||||
android:title="state"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="30"
|
||||
android:inputType="number"
|
||||
android:key="pref_debug_music_position"
|
||||
android:title="position"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="100"
|
||||
android:inputType="number"
|
||||
android:key="pref_debug_music_playRate"
|
||||
android:title="playRate"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_music_shuffle_bool"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="shuffle"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_music_repeat_bool"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="repeat"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,146 @@
|
||||
<?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">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="pref_debug_notifications_send"
|
||||
android:persistent="false"
|
||||
android:title="Send NotificationSpec" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_arrow_upload_progress"
|
||||
android:key="pref_debug_pebblekit_notification"
|
||||
android:persistent="false"
|
||||
android:title="Send PebbleKit notification" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_notifications"
|
||||
android:key="pref_debug_create_test_notification"
|
||||
android:persistent="false"
|
||||
android:title="Create test notification" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:key="pref_debug_notifications_reset"
|
||||
android:persistent="false"
|
||||
android:title="@string/reset_fields" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_debug_header_notificationspec"
|
||||
android:title="NotificationSpec"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<ListPreference
|
||||
android:defaultValue="UNKNOWN"
|
||||
android:entries="@array/empty_array"
|
||||
android:entryValues="@array/empty_array"
|
||||
android:key="pref_debug_notifications_type"
|
||||
android:title="type"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_notifications_sender"
|
||||
android:title="sender"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="Title"
|
||||
android:key="pref_debug_notifications_title"
|
||||
android:title="title"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="Subject"
|
||||
android:key="pref_debug_notifications_subject"
|
||||
android:title="subject"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="Notification body"
|
||||
android:key="pref_debug_notifications_body"
|
||||
android:title="body"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_notifications_attachedActions_reply"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="attachedActions - REPLY"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_notifications_picturePath_bool"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="picturePath - Include picture"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_notifications_phoneNumber"
|
||||
android:title="phoneNumber"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="Gadgetbridge"
|
||||
android:key="pref_debug_notifications_sourceName"
|
||||
android:title="sourceName"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="@string/applicationId"
|
||||
android:key="pref_debug_notifications_sourceAppId"
|
||||
android:title="sourceAppId"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="0"
|
||||
android:key="pref_debug_notifications_flags"
|
||||
android:title="flags"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="0|nodomain.freeyourgadget.gadgetbridge|-493519667|null|10679"
|
||||
android:key="pref_debug_notifications_key"
|
||||
android:title="key"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="the_channel"
|
||||
android:key="pref_debug_notifications_channelId"
|
||||
android:title="channelId"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:key="pref_debug_notifications_category"
|
||||
android:title="category"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<EditTextPreference
|
||||
android:defaultValue="0"
|
||||
android:key="pref_debug_notifications_iconId"
|
||||
android:title="iconId"
|
||||
app:iconSpaceReserved="false"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="pref_debug_notifications_dndSuppressed"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:title="dndSuppressed"
|
||||
app:iconSpaceReserved="false" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,66 @@
|
||||
<?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"
|
||||
android:title="@string/debug_other_actions">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_access_time"
|
||||
android:key="pref_debug_set_time"
|
||||
android:persistent="false"
|
||||
android:title="Set time" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_show_chart"
|
||||
android:key="activity_list_debug_extra_time_range"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="Takes long to generate when used"
|
||||
android:title="Activity summary extra date range" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_debug_header_fetch_recorded_data"
|
||||
android:title="Data fetch"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_timer"
|
||||
android:key="pref_debug_set_activity_fetch_time"
|
||||
android:persistent="false"
|
||||
android:title="Set activity fetch time" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_download"
|
||||
android:key="pref_debug_fetch_debug_logs"
|
||||
android:persistent="false"
|
||||
android:title="Fetch debug logs" />
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_debug_header_reset"
|
||||
android:title="@string/reset"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_refresh"
|
||||
android:key="pref_debug_reboot"
|
||||
android:persistent="false"
|
||||
android:title="Reboot" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:icon="@drawable/ic_warning_gray"
|
||||
android:key="dangerous_actions"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:persistent="false"
|
||||
android:title="@string/dangerous_actions" />
|
||||
|
||||
<Preference
|
||||
android:dependency="dangerous_actions"
|
||||
android:icon="@drawable/ic_delete"
|
||||
android:key="pref_debug_factory_reset"
|
||||
android:persistent="false"
|
||||
android:title="@string/factory_reset" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</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"
|
||||
android:title="@string/menuitem_settings">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_settings_applications"
|
||||
android:key="pref_debug_preferences_version"
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
android:title="Preferences version" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:key="pref_debug_global_preferences"
|
||||
android:persistent="false"
|
||||
android:title="@string/global_preferences" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_device_preferences"
|
||||
android:title="@string/bottom_nav_devices"
|
||||
app:iconSpaceReserved="false">
|
||||
|
||||
<!-- to be dynamically filled -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/menuitem_weather">
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_weather"
|
||||
android:title="@string/menuitem_weather">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_file_upload"
|
||||
android:key="pref_debug_weather_send"
|
||||
android:persistent="false"
|
||||
android:title="Send weather to devices" />
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:icon="@drawable/ic_database"
|
||||
android:key="cache_weather"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="@string/pref_cache_weather_summary"
|
||||
android:title="@string/pref_cache_weather" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_add_gray"
|
||||
android:key="pref_debug_weather_add_test"
|
||||
android:persistent="false"
|
||||
android:title="Add test weather to cache" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_cached_weather"
|
||||
android:title="Cache">
|
||||
|
||||
<!-- to be filled up dynamically -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.preference.PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:title="@string/menuitem_widgets">
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_settings"
|
||||
android:key="pref_debug_widgets_prefs_show"
|
||||
android:persistent="false"
|
||||
android:title="Show widgets preferences" />
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/ic_delete_forever"
|
||||
android:key="pref_debug_widgets_prefs_delete"
|
||||
android:persistent="false"
|
||||
android:title="Delete widgets preferences" />
|
||||
|
||||
<PreferenceCategory
|
||||
android:key="pref_header_widgets"
|
||||
android:title="Widgets">
|
||||
|
||||
<!-- to be filled up dynamically -->
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</androidx.preference.PreferenceScreen>
|
||||
@@ -370,13 +370,6 @@
|
||||
android:summary="@string/pref_crash_notification_summary"
|
||||
android:title="@string/pref_crash_notification_title"
|
||||
app:iconSpaceReserved="false" />
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
android:key="cache_weather"
|
||||
android:layout="@layout/preference_checkbox"
|
||||
android:summary="@string/pref_cache_weather_summary"
|
||||
android:title="@string/pref_cache_weather"
|
||||
app:iconSpaceReserved="false" />
|
||||
|
||||
<PreferenceScreen
|
||||
android:key="pref_screen_intent_api"
|
||||
|
||||
@@ -6,4 +6,5 @@
|
||||
<cache-path name="raw" path="raw/" />
|
||||
<cache-path name="gpx" path="gpx/" />
|
||||
<cache-path name="audio" path="audio/" />
|
||||
<cache-path name="images" path="images/" />
|
||||
</paths>
|
||||
|
||||
Reference in New Issue
Block a user