package nodomain.freeyourgadget.gadgetbridge.devices.garmin; import static nodomain.freeyourgadget.gadgetbridge.util.GB.toast; import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Parcel; import android.widget.Toast; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.documentfile.provider.DocumentFile; import androidx.preference.Preference; import androidx.preference.PreferenceCategory; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import nodomain.freeyourgadget.gadgetbridge.R; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSettingsPreferenceConst; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsCustomizer; import nodomain.freeyourgadget.gadgetbridge.activities.devicesettings.DeviceSpecificSettingsHandler; import nodomain.freeyourgadget.gadgetbridge.impl.GBDevice; import nodomain.freeyourgadget.gadgetbridge.service.devices.garmin.agps.GarminAgpsStatus; import nodomain.freeyourgadget.gadgetbridge.util.DateTimeUtils; import nodomain.freeyourgadget.gadgetbridge.util.GB; import nodomain.freeyourgadget.gadgetbridge.util.Prefs; public class GarminSettingsCustomizer implements DeviceSpecificSettingsCustomizer { private static final Logger LOG = LoggerFactory.getLogger(GarminSettingsCustomizer.class); private final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); @Override public void onPreferenceChange(final Preference preference, final DeviceSpecificSettingsHandler handler) { } @Override public void customizeSettings(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String rootKey) { final Preference realtimeSettings = handler.findPreference(GarminPreferences.PREF_GARMIN_REALTIME_SETTINGS); if (realtimeSettings != null) { realtimeSettings.setOnPreferenceClickListener(preference -> { final Intent intent = new Intent(handler.getContext(), GarminRealtimeSettingsActivity.class); intent.putExtra(GBDevice.EXTRA_DEVICE, handler.getDevice()); handler.getContext().startActivity(intent); return true; }); } final PreferenceCategory prefAgpsHeader = handler.findPreference(DeviceSettingsPreferenceConst.PREF_HEADER_AGPS); if (prefAgpsHeader != null) { final List urls = prefs.getList(GarminPreferences.PREF_AGPS_KNOWN_URLS, Collections.emptyList(), "\n"); if (urls.isEmpty()) { return; } final String currentFolder = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, ""); final Preference prefFolder = handler.findPreference(GarminPreferences.PREF_GARMIN_AGPS_FOLDER); final ActivityResultLauncher agpsFolderChooser = handler.registerForActivityResult( new ActivityResultContracts.OpenDocumentTree(), localUri -> { LOG.info("Garmin agps folder: {}", localUri); if (localUri != null) { handler.getContext().getContentResolver().takePersistableUriPermission(localUri, Intent.FLAG_GRANT_READ_URI_PERMISSION); prefs.getPreferences().edit() .putString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, localUri.toString()) .apply(); prefFolder.setSummary(localUri.toString()); for (final String url : urls) { updateAgpsStatus(handler, prefs, url); } } } ); prefFolder.setOnPreferenceClickListener(preference -> { agpsFolderChooser.launch(null); return true; }); prefFolder.setSummary(currentFolder); prefAgpsHeader.addPreference(prefFolder); int i = 0; for (final String url : urls) { i++; final Preference prefHeader = new PreferenceCategory(handler.getContext()); prefHeader.setKey("pref_agps_url_header_" + i); prefHeader.setIconSpaceReserved(false); prefHeader.setTitle(handler.getContext().getString(R.string.garmin_agps_url_i, i)); prefAgpsHeader.addPreference(prefHeader); final Preference prefUrl = new Preference(handler.getContext()); prefUrl.setOnPreferenceClickListener(preference -> { final ClipboardManager clipboard = (ClipboardManager) handler.getContext().getSystemService(Context.CLIPBOARD_SERVICE); final ClipData clip = ClipData.newPlainText(handler.getContext().getString(R.string.url), url); clipboard.setPrimaryClip(clip); toast(handler.getContext(), handler.getContext().getString(R.string.copied_to_clipboard), Toast.LENGTH_SHORT, GB.INFO); return true; }); prefUrl.setKey("pref_garmin_agps_url_" + i); prefUrl.setIcon(R.drawable.ic_link); prefUrl.setTitle(R.string.url); prefUrl.setSummary(url); prefAgpsHeader.addPreference(prefUrl); final Preference prefLocalFile = new Preference(handler.getContext()); prefLocalFile.setOnPreferenceClickListener(preference -> { selectAgpsFile(handler, prefs, url, prefLocalFile); return true; }); prefLocalFile.setKey(GarminPreferences.agpsFilename(url)); prefLocalFile.setIcon(R.drawable.ic_file_open); prefLocalFile.setTitle(R.string.garmin_agps_local_file); prefLocalFile.setSummary(prefs.getString(GarminPreferences.agpsFilename(url), "")); prefAgpsHeader.addPreference(prefLocalFile); final Preference prefStatus = new Preference(handler.getContext()); prefStatus.setKey(GarminPreferences.agpsStatus(url)); prefStatus.setIcon(R.drawable.ic_health); prefStatus.setTitle(R.string.status); prefAgpsHeader.addPreference(prefStatus); updateAgpsStatus(handler, prefs, url); final Preference prefUpdateTime = new Preference(handler.getContext()); prefUpdateTime.setKey(GarminPreferences.agpsUpdateTime(url)); prefUpdateTime.setIcon(R.drawable.ic_calendar_today); prefUpdateTime.setTitle(R.string.pref_agps_update_time); final long ts = prefs.getLong(GarminPreferences.agpsUpdateTime(url), 0L); if (ts > 0) { prefUpdateTime.setSummary(String.format("%s (%s)", SDF.format(new Date(ts)), DateTimeUtils.formatDurationHoursMinutes(System.currentTimeMillis() - ts, TimeUnit.MILLISECONDS) )); } else { prefUpdateTime.setSummary(handler.getContext().getString(R.string.unknown)); } prefAgpsHeader.addPreference(prefUpdateTime); } } } private void selectAgpsFile(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String url, final Preference prefLocalFile) { final String currentFolder = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, ""); final String folderUri = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, ""); if (folderUri.isEmpty()) { GB.toast(handler.getContext().getString(R.string.no_folder_selected), Toast.LENGTH_SHORT, GB.INFO); return; } final DocumentFile folder = DocumentFile.fromTreeUri(handler.getContext(), Uri.parse(currentFolder)); if (folder == null || folder.listFiles().length == 0) { GB.toast(handler.getContext().getString(R.string.folder_is_empty), Toast.LENGTH_SHORT, GB.INFO); return; } final DocumentFile[] documentFiles = folder.listFiles(); final String[] files = new String[documentFiles.length + 1]; files[0] = handler.getContext().getString(R.string.none); final String selectedFile = prefs.getString(GarminPreferences.agpsFilename(url), ""); int checkedItem = 0; for (int j = 0; j < documentFiles.length; j++) { files[j + 1] = documentFiles[j].getName(); if (selectedFile.equals(files[j + 1])) { checkedItem = j + 1; } } final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(handler.getContext()); builder.setTitle(R.string.garmin_agps_local_file); final AtomicInteger selectedIdx = new AtomicInteger(0); builder.setSingleChoiceItems(files, checkedItem, (dialog, which) -> { selectedIdx.set(which); }); builder.setPositiveButton(android.R.string.ok, (dialog, which) -> { final String selectedFilename = selectedIdx.get() > 0 ? files[selectedIdx.get()] : null; prefs.getPreferences().edit() .putString(GarminPreferences.agpsFilename(url), selectedFilename) .apply(); prefLocalFile.setSummary(selectedFilename); updateAgpsStatus(handler, prefs, url); }); builder.setNegativeButton(android.R.string.cancel, null); builder.create().show(); } private void updateAgpsStatus(final DeviceSpecificSettingsHandler handler, final Prefs prefs, final String url) { final Preference prefStatus = handler.findPreference(GarminPreferences.agpsStatus(url)); final String filename = prefs.getString(GarminPreferences.agpsFilename(url), ""); if (filename.isEmpty()) { prefStatus.setSummary(""); return; } final String folderUri = prefs.getString(GarminPreferences.PREF_GARMIN_AGPS_FOLDER, ""); if (folderUri.isEmpty()) { prefStatus.setSummary(""); return; } final DocumentFile folder = DocumentFile.fromTreeUri(handler.getContext(), Uri.parse(folderUri)); if (folder == null) { prefStatus.setSummary(""); return; } final GarminAgpsStatus agpsStatus; final DocumentFile localFile = folder.findFile(filename); if (localFile != null && localFile.isFile() && localFile.canRead()) { if (localFile.lastModified() < prefs.getLong(GarminPreferences.agpsUpdateTime(url), 0L)) { agpsStatus = GarminAgpsStatus.CURRENT; } else { agpsStatus = GarminAgpsStatus.PENDING; } } else { agpsStatus = GarminAgpsStatus.MISSING; } prefStatus.setSummary(handler.getContext().getString(agpsStatus.getText())); } @Override public Set getPreferenceKeysWithSummary() { return Collections.emptySet(); } public static final Creator CREATOR = new Creator() { @Override public GarminSettingsCustomizer createFromParcel(final Parcel in) { return new GarminSettingsCustomizer(); } @Override public GarminSettingsCustomizer[] newArray(final int size) { return new GarminSettingsCustomizer[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(@NonNull Parcel dest, int flags) { } }