1
0
mirror of https://codeberg.org/Freeyourgadget/Gadgetbridge synced 2024-11-03 17:02:13 +01:00
Gadgetbridge/app/src/main/java/nodomain/freeyourgadget/gadgetbridge/activities/SettingsActivity.java

567 lines
26 KiB
Java
Raw Normal View History

2020-01-09 10:44:32 +01:00
/* Copyright (C) 2015-2020 0nse, Andreas Shimokawa, Carsten Pfeiffer,
2019-12-06 22:49:44 +01:00
Daniel Dakhno, Daniele Gobbetti, Felix Konstantin Maurer, José Rebelo,
Martin, Normano64, Pavel Elagin, Sebastian Kranz, vanous
2017-03-10 14:53:19 +01:00
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 <http://www.gnu.org/licenses/>. */
package nodomain.freeyourgadget.gadgetbridge.activities;
2015-01-07 14:00:18 +01:00
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
2018-01-04 15:13:06 +01:00
import android.database.Cursor;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
2018-01-04 15:13:06 +01:00
import android.net.Uri;
2015-01-07 14:00:18 +01:00
import android.os.Bundle;
import android.preference.EditTextPreference;
import android.preference.ListPreference;
2015-01-07 14:00:18 +01:00
import android.preference.Preference;
2018-01-04 15:13:06 +01:00
import android.preference.PreferenceManager;
import android.provider.DocumentsContract;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.Spinner;
import android.widget.Toast;
2015-01-07 14:00:18 +01:00
import androidx.core.app.ActivityCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import nodomain.freeyourgadget.gadgetbridge.BuildConfig;
import nodomain.freeyourgadget.gadgetbridge.GBApplication;
import nodomain.freeyourgadget.gadgetbridge.R;
import nodomain.freeyourgadget.gadgetbridge.activities.charts.ChartsPreferencesActivity;
2018-01-04 15:13:06 +01:00
import nodomain.freeyourgadget.gadgetbridge.database.PeriodicExporter;
import nodomain.freeyourgadget.gadgetbridge.devices.DeviceManager;
import nodomain.freeyourgadget.gadgetbridge.devices.miband.MiBandPreferencesActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.qhybrid.ConfigActivity;
import nodomain.freeyourgadget.gadgetbridge.devices.zetime.ZeTimePreferenceActivity;
2022-08-18 23:03:28 +02:00
import nodomain.freeyourgadget.gadgetbridge.model.Weather;
import nodomain.freeyourgadget.gadgetbridge.util.AndroidUtils;
import nodomain.freeyourgadget.gadgetbridge.util.FileUtils;
import nodomain.freeyourgadget.gadgetbridge.util.GB;
2018-01-07 12:50:59 +01:00
import nodomain.freeyourgadget.gadgetbridge.util.GBPrefs;
import nodomain.freeyourgadget.gadgetbridge.util.Prefs;
public class SettingsActivity extends AbstractSettingsActivity {
private static final Logger LOG = LoggerFactory.getLogger(SettingsActivity.class);
public static final String PREF_MEASUREMENT_SYSTEM = "measurement_system";
2018-01-04 15:13:06 +01:00
private static final int FILE_REQUEST_CODE = 4711;
private EditText fitnessAppEditText = null;
private int fitnessAppSelectionListSpinnerFirstRun = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
}
2015-01-07 14:00:18 +01:00
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
2018-08-17 20:06:02 +02:00
Prefs prefs = GBApplication.getPrefs();
Preference pref = findPreference("pref_category_activity_personal");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, AboutUserPreferencesActivity.class);
startActivity(enableIntent);
return true;
}
});
pref = findPreference("pref_charts");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, ChartsPreferencesActivity.class);
startActivity(enableIntent);
return true;
}
});
pref = findPreference("pref_key_miband");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, MiBandPreferencesActivity.class);
startActivity(enableIntent);
return true;
}
});
pref = findPreference("pref_key_qhybrid");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
startActivity(new Intent(SettingsActivity.this, ConfigActivity.class));
return true;
}
});
pref = findPreference("pref_key_zetime");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, ZeTimePreferenceActivity.class);
startActivity(enableIntent);
return true;
}
});
pref = findPreference("pebble_emu_addr");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
}
});
pref = findPreference("pebble_emu_port");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
Intent refreshIntent = new Intent(DeviceManager.ACTION_REFRESH_DEVICELIST);
LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(refreshIntent);
preference.setSummary(newVal.toString());
return true;
}
});
pref = findPreference("log_to_file");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
boolean doEnable = Boolean.TRUE.equals(newVal);
try {
if (doEnable) {
FileUtils.getExternalFilesDir(); // ensures that it is created
}
GBApplication.setupLogging(doEnable);
} catch (IOException ex) {
GB.toast(getApplicationContext(),
getString(R.string.error_creating_directory_for_logfiles, ex.getLocalizedMessage()),
Toast.LENGTH_LONG,
GB.ERROR,
ex);
}
return true;
}
});
2022-08-18 23:03:28 +02:00
pref = findPreference("cache_weather");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
boolean doEnable = Boolean.TRUE.equals(newVal);
Weather.getInstance().setCacheFile(getCacheDir(), doEnable);
return true;
}
});
// If we didn't manage to initialize file logging, disable the preference
2022-08-07 21:51:48 +02:00
if (!GBApplication.getLogging().isFileLoggerInitialized()) {
pref.setEnabled(false);
pref.setSummary(R.string.pref_write_logfiles_not_available);
}
2021-07-06 08:31:22 +02:00
pref = findPreference("language");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
String newLang = newVal.toString();
try {
GBApplication.setLanguage(newLang);
recreate();
} catch (Exception ex) {
GB.toast(getApplicationContext(),
"Error setting language: " + ex.getLocalizedMessage(),
Toast.LENGTH_LONG,
GB.ERROR,
ex);
}
return true;
}
});
final Preference unit = findPreference(PREF_MEASUREMENT_SYSTEM);
unit.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
invokeLater(new Runnable() {
@Override
public void run() {
GBApplication.deviceService().onSendConfiguration(PREF_MEASUREMENT_SYSTEM);
}
});
preference.setSummary(newVal.toString());
return true;
}
});
pref = findPreference("location_aquire");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
if (ActivityCompat.checkSelfPermission(getApplicationContext(), Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(SettingsActivity.this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, 0);
}
LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
Criteria criteria = new Criteria();
String provider = locationManager.getBestProvider(criteria, false);
if (provider != null) {
Location location = locationManager.getLastKnownLocation(provider);
if (location != null) {
setLocationPreferences(location);
} else {
locationManager.requestSingleUpdate(provider, new LocationListener() {
@Override
public void onLocationChanged(Location location) {
setLocationPreferences(location);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
LOG.info("provider status changed to " + status + " (" + provider + ")");
}
@Override
public void onProviderEnabled(String provider) {
LOG.info("provider enabled (" + provider + ")");
}
@Override
public void onProviderDisabled(String provider) {
LOG.info("provider disabled (" + provider + ")");
GB.toast(SettingsActivity.this, getString(R.string.toast_enable_networklocationprovider), 3000, 0);
}
}, null);
}
} else {
LOG.warn("No location provider found, did you deny location permission?");
}
return true;
}
});
pref = findPreference("weather_city");
2018-01-04 15:13:06 +01:00
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
// reset city id and force a new lookup
GBApplication.getPrefs().getPreferences().edit().putString("weather_cityid", null).apply();
preference.setSummary(newVal.toString());
Intent intent = new Intent("GB_UPDATE_WEATHER");
intent.setPackage(BuildConfig.APPLICATION_ID);
sendBroadcast(intent);
return true;
}
2018-01-04 15:13:06 +01:00
});
2021-09-03 18:01:47 +02:00
2018-01-08 11:27:28 +01:00
pref = findPreference(GBPrefs.AUTO_EXPORT_LOCATION);
2018-01-04 15:13:06 +01:00
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
i.setType("application/x-sqlite3");
i.addCategory(Intent.CATEGORY_OPENABLE);
i.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
2018-01-08 11:27:28 +01:00
String title = getApplicationContext().getString(R.string.choose_auto_export_location);
startActivityForResult(Intent.createChooser(i, title), FILE_REQUEST_CODE);
2018-01-04 15:13:06 +01:00
return true;
}
});
2018-01-08 11:27:28 +01:00
pref.setSummary(getAutoExportLocationSummary());
2018-01-04 15:13:06 +01:00
2018-01-08 11:27:28 +01:00
pref = findPreference(GBPrefs.AUTO_EXPORT_INTERVAL);
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
2018-01-08 11:27:28 +01:00
public boolean onPreferenceChange(Preference preference, Object autoExportInterval) {
String summary = String.format(
getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
2018-01-24 15:02:25 +01:00
Integer.valueOf((String) autoExportInterval));
2018-01-08 11:27:28 +01:00
preference.setSummary(summary);
boolean auto_export_enabled = GBApplication.getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
PeriodicExporter.scheduleAlarm(getApplicationContext(), Integer.valueOf((String) autoExportInterval), auto_export_enabled);
return true;
}
2018-01-04 15:13:06 +01:00
});
2018-01-08 11:27:28 +01:00
int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
String summary = String.format(
getApplicationContext().getString(R.string.pref_summary_auto_export_interval),
(int) autoExportInterval);
pref.setSummary(summary);
2018-01-08 11:27:28 +01:00
findPreference(GBPrefs.AUTO_EXPORT_ENABLED).setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
2018-01-04 15:13:06 +01:00
@Override
public boolean onPreferenceChange(Preference preference, Object autoExportEnabled) {
2018-01-08 11:27:28 +01:00
int autoExportInterval = GBApplication.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
PeriodicExporter.scheduleAlarm(getApplicationContext(), autoExportInterval, (boolean) autoExportEnabled);
2018-01-04 15:13:06 +01:00
return true;
}
});
pref = findPreference("auto_fetch_interval_limit");
pref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object autoFetchInterval) {
String summary = String.format(
getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
Integer.valueOf((String) autoFetchInterval));
preference.setSummary(summary);
return true;
}
});
int autoFetchInterval = GBApplication.getPrefs().getInt("auto_fetch_interval_limit", 0);
summary = String.format(
getApplicationContext().getString(R.string.pref_auto_fetch_limit_fetches_summary),
2018-08-08 15:39:23 +02:00
autoFetchInterval);
pref.setSummary(summary);
// Get all receivers of Media Buttons
Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
PackageManager pm = getPackageManager();
List<ResolveInfo> mediaReceivers = pm.queryBroadcastReceivers(mediaButtonIntent,
PackageManager.GET_INTENT_FILTERS | PackageManager.GET_RESOLVED_FILTER);
CharSequence[] newEntries = new CharSequence[mediaReceivers.size() + 1];
CharSequence[] newValues = new CharSequence[mediaReceivers.size() + 1];
newEntries[0] = getString(R.string.pref_default);
newValues[0] = "default";
int i = 1;
Set<String> existingNames = new HashSet<>();
for (ResolveInfo resolveInfo : mediaReceivers) {
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.packageName + ")";
if (existingNames.contains(newEntries[i].toString().trim())) {
newEntries[i] = resolveInfo.activityInfo.loadLabel(pm) + " (" + resolveInfo.activityInfo.name + ")";
} else {
existingNames.add(newEntries[i].toString().trim());
}
newValues[i] = resolveInfo.activityInfo.packageName;
i++;
}
final ListPreference audioPlayer = (ListPreference) findPreference("audio_player");
audioPlayer.setEntries(newEntries);
audioPlayer.setEntryValues(newValues);
audioPlayer.setDefaultValue(newValues[0]);
2021-07-06 08:31:22 +02:00
final Preference theme = (ListPreference) findPreference("pref_key_theme");
final Preference amoled_black = findPreference("pref_key_theme_amoled_black");
String selectedTheme = prefs.getString("pref_key_theme", SettingsActivity.this.getString(R.string.pref_theme_value_system));
if (selectedTheme.equals("light"))
amoled_black.setEnabled(false);
else
amoled_black.setEnabled(true);
theme.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newVal) {
final String val = newVal.toString();
if (val.equals("light"))
amoled_black.setEnabled(false);
else
amoled_black.setEnabled(true);
return true;
}
});
pref = findPreference("pref_discovery_pairing");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
Intent enableIntent = new Intent(SettingsActivity.this, DiscoveryPairingPreferenceActivity.class);
startActivity(enableIntent);
return true;
}
});
//fitness app (OpenTracks) package name selection for OpenTracks observer
pref = findPreference("pref_key_opentracks_packagename");
pref.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
public boolean onPreferenceClick(Preference preference) {
final LinearLayout outerLayout = new LinearLayout(SettingsActivity.this);
outerLayout.setOrientation(LinearLayout.VERTICAL);
final LinearLayout innerLayout = new LinearLayout(SettingsActivity.this);
innerLayout.setOrientation(LinearLayout.HORIZONTAL);
innerLayout.setPadding(20, 0, 20, 0);
final Spinner selectionListSpinner = new Spinner(SettingsActivity.this);
String[] appListArray = getResources().getStringArray(R.array.fitness_tracking_apps_package_names);
ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<String>(SettingsActivity.this,
android.R.layout.simple_spinner_dropdown_item, appListArray);
selectionListSpinner.setAdapter(spinnerArrayAdapter);
fitnessAppSelectionListSpinnerFirstRun = 0;
addListenerOnSpinnerDeviceSelection(selectionListSpinner);
Prefs prefs = GBApplication.getPrefs();
String packageName = prefs.getString("opentracks_packagename", "de.dennisguse.opentracks");
fitnessAppEditText = new EditText(SettingsActivity.this);
fitnessAppEditText.setText(packageName);
innerLayout.addView(fitnessAppEditText);
outerLayout.addView(selectionListSpinner);
outerLayout.addView(innerLayout);
new AlertDialog.Builder(SettingsActivity.this)
.setCancelable(true)
.setTitle(R.string.pref_title_opentracks_packagename)
.setView(outerLayout)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
SharedPreferences.Editor editor = GBApplication.getPrefs().getPreferences().edit();
editor.putString("opentracks_packagename", fitnessAppEditText.getText().toString());
editor.apply();
editor.commit();
}
})
.setNegativeButton(R.string.Cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
})
.show();
return false;
}
});
}
private void addListenerOnSpinnerDeviceSelection(Spinner spinner) {
spinner.setOnItemSelectedListener(new SettingsActivity.CustomOnDeviceSelectedListener());
}
public class CustomOnDeviceSelectedListener implements AdapterView.OnItemSelectedListener {
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
if (++fitnessAppSelectionListSpinnerFirstRun > 1) { //this prevents the setText to be set when spinner just is being initialized
fitnessAppEditText.setText(parent.getItemAtPosition(pos).toString());
}
}
@Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
}
2018-01-04 15:13:06 +01:00
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (requestCode == FILE_REQUEST_CODE && intent != null) {
Uri uri = intent.getData();
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
2018-01-04 15:13:06 +01:00
PreferenceManager
2018-01-08 11:27:28 +01:00
.getDefaultSharedPreferences(this)
.edit()
.putString(GBPrefs.AUTO_EXPORT_LOCATION, uri.toString())
.apply();
String summary = getAutoExportLocationSummary();
findPreference(GBPrefs.AUTO_EXPORT_LOCATION).setSummary(summary);
2018-01-04 15:13:06 +01:00
boolean autoExportEnabled = GBApplication
2018-01-08 11:27:28 +01:00
.getPrefs().getBoolean(GBPrefs.AUTO_EXPORT_ENABLED, false);
2018-01-04 15:13:06 +01:00
int autoExportPeriod = GBApplication
2018-01-08 11:27:28 +01:00
.getPrefs().getInt(GBPrefs.AUTO_EXPORT_INTERVAL, 0);
PeriodicExporter.scheduleAlarm(getApplicationContext(), autoExportPeriod, autoExportEnabled);
2018-01-04 15:13:06 +01:00
}
}
2018-01-08 11:27:28 +01:00
/*
Either returns the file path of the selected document, or the display name, or an empty string
2018-01-08 11:27:28 +01:00
*/
public String getAutoExportLocationSummary() {
2018-01-08 11:27:28 +01:00
String autoExportLocation = GBApplication.getPrefs().getString(GBPrefs.AUTO_EXPORT_LOCATION, null);
if (autoExportLocation == null) {
return "";
}
Uri uri = Uri.parse(autoExportLocation);
try {
return AndroidUtils.getFilePath(getApplicationContext(), uri);
} catch (IllegalArgumentException e) {
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 fdfsdfds) {
LOG.warn("fuck");
2018-01-08 11:27:28 +01:00
}
}
return "";
}
/*
* delayed execution so that the preferences are applied first
*/
private void invokeLater(Runnable runnable) {
getListView().post(runnable);
}
@Override
protected String[] getPreferenceKeysWithSummary() {
return new String[]{
"pebble_emu_addr",
"pebble_emu_port",
"pebble_reconnect_attempts",
"location_latitude",
"location_longitude",
"weather_city",
};
2015-01-07 14:00:18 +01:00
}
private void setLocationPreferences(Location location) {
String latitude = String.format(Locale.US, "%.6g", location.getLatitude());
String longitude = String.format(Locale.US, "%.6g", location.getLongitude());
LOG.info("got location. Lat: " + latitude + " Lng: " + longitude);
GB.toast(SettingsActivity.this, getString(R.string.toast_aqurired_networklocation), 2000, 0);
EditTextPreference pref_latitude = (EditTextPreference) findPreference("location_latitude");
EditTextPreference pref_longitude = (EditTextPreference) findPreference("location_longitude");
pref_latitude.setText(latitude);
pref_longitude.setText(longitude);
pref_latitude.setSummary(latitude);
pref_longitude.setSummary(longitude);
}
2015-01-07 14:00:18 +01:00
}