mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-05-15 06:22:49 +00:00
Store and display backup error messages more clearly
This commit is contained in:
parent
4427498d5e
commit
8ae8130b71
10 changed files with 242 additions and 34 deletions
|
@ -29,7 +29,7 @@ public class AegisBackupAgent extends BackupAgent {
|
|||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// cannot use injection with Dagger Hilt here, because the app is launched in a restricted mode on restore
|
||||
// Cannot use injection with Dagger Hilt here, because the app is launched in a restricted mode on restore
|
||||
_prefs = new Preferences(this);
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ public class AegisBackupAgent extends BackupAgent {
|
|||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? data.getQuota() : -1));
|
||||
|
||||
boolean isD2D = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
|
||||
&& (data.getTransportFlags() & FLAG_DEVICE_TO_DEVICE_TRANSFER) == FLAG_DEVICE_TO_DEVICE_TRANSFER;
|
||||
&& (data.getTransportFlags() & FLAG_DEVICE_TO_DEVICE_TRANSFER) == FLAG_DEVICE_TO_DEVICE_TRANSFER;
|
||||
|
||||
if (isD2D) {
|
||||
Log.i(TAG, "onFullBackup(): allowing D2D transfer");
|
||||
|
@ -49,31 +49,39 @@ public class AegisBackupAgent extends BackupAgent {
|
|||
return;
|
||||
}
|
||||
|
||||
// first copy the vault to the files/backup directory
|
||||
// We perform a catch of any Exception here to make sure we also
|
||||
// report any runtime exceptions, in addition to the expected IOExceptions.
|
||||
try {
|
||||
fullBackup(data);
|
||||
_prefs.setAndroidBackupResult(new Preferences.BackupResult(null));
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||
_prefs.setAndroidBackupResult(new Preferences.BackupResult(e));
|
||||
throw e;
|
||||
}
|
||||
|
||||
Log.i(TAG, "onFullBackup() finished");
|
||||
}
|
||||
|
||||
private void fullBackup(FullBackupDataOutput data) throws IOException {
|
||||
// First copy the vault to the files/backup directory
|
||||
createBackupDir();
|
||||
File vaultBackupFile = getVaultBackupFile();
|
||||
try (OutputStream outputStream = new FileOutputStream(vaultBackupFile)) {
|
||||
createBackupDir();
|
||||
|
||||
VaultFile vaultFile = VaultRepository.readVaultFile(this);
|
||||
byte[] bytes = vaultFile.exportable().toBytes();
|
||||
outputStream.write(bytes);
|
||||
} catch (VaultRepositoryException | IOException e) {
|
||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||
deleteBackupDir();
|
||||
throw new IOException(e);
|
||||
}
|
||||
|
||||
// then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
||||
// Then call the original implementation so that fullBackupContent specified in AndroidManifest is read
|
||||
try {
|
||||
super.onFullBackup(data);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, String.format("onFullBackup() failed: %s", e));
|
||||
throw e;
|
||||
} finally {
|
||||
deleteBackupDir();
|
||||
}
|
||||
|
||||
Log.i(TAG, "onFullBackup() finished");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -114,14 +122,16 @@ public class AegisBackupAgent extends BackupAgent {
|
|||
|
||||
private void createBackupDir() throws IOException {
|
||||
File dir = getVaultBackupFile().getParentFile();
|
||||
if (!dir.exists() && !dir.mkdir()) {
|
||||
throw new IOException(String.format("Unable to create backup directory: %s", dir.toString()));
|
||||
if (dir == null || (!dir.exists() && !dir.mkdir())) {
|
||||
throw new IOException(String.format("Unable to create backup directory: %s", dir));
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteBackupDir() {
|
||||
File dir = getVaultBackupFile().getParentFile();
|
||||
IOUtils.clearDirectory(dir, true);
|
||||
if (dir != null) {
|
||||
IOUtils.clearDirectory(dir, true);
|
||||
}
|
||||
}
|
||||
|
||||
private File getVaultBackupFile() {
|
||||
|
|
|
@ -7,10 +7,15 @@ import android.net.Uri;
|
|||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.beemdevelopment.aegis.util.JsonUtils;
|
||||
|
||||
import org.json.JSONArray;
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.text.DateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
|
@ -259,6 +264,7 @@ public class Preferences {
|
|||
|
||||
public void setIsAndroidBackupsEnabled(boolean enabled) {
|
||||
_prefs.edit().putBoolean("pref_android_backups", enabled).apply();
|
||||
setAndroidBackupResult(null);
|
||||
}
|
||||
|
||||
public boolean isBackupsEnabled() {
|
||||
|
@ -267,6 +273,7 @@ public class Preferences {
|
|||
|
||||
public void setIsBackupsEnabled(boolean enabled) {
|
||||
_prefs.edit().putBoolean("pref_backups", enabled).apply();
|
||||
setBuiltInBackupResult(null);
|
||||
}
|
||||
|
||||
public Uri getBackupsLocation() {
|
||||
|
@ -298,12 +305,64 @@ public class Preferences {
|
|||
_prefs.edit().putInt("pref_backups_versions", versions).apply();
|
||||
}
|
||||
|
||||
public void setBackupsError(Exception e) {
|
||||
_prefs.edit().putString("pref_backups_error", e == null ? null : e.toString()).apply();
|
||||
public void setAndroidBackupResult(@Nullable BackupResult res) {
|
||||
setBackupResult(false, res);
|
||||
}
|
||||
|
||||
public String getBackupsError() {
|
||||
return _prefs.getString("pref_backups_error", null);
|
||||
public void setBuiltInBackupResult(@Nullable BackupResult res) {
|
||||
setBackupResult(true, res);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BackupResult getAndroidBackupResult() {
|
||||
return getBackupResult(false);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public BackupResult getBuiltInBackupResult() {
|
||||
return getBackupResult(true);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Preferences.BackupResult getErroredBackupResult() {
|
||||
Preferences.BackupResult res = getBuiltInBackupResult();
|
||||
if (res != null && !res.isSuccessful()) {
|
||||
return res;
|
||||
}
|
||||
res = getAndroidBackupResult();
|
||||
if (res != null && !res.isSuccessful()) {
|
||||
return res;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void setBackupResult(boolean isBuiltInBackup, @Nullable BackupResult res) {
|
||||
String json = null;
|
||||
if (res != null) {
|
||||
res.setIsBuiltIn(isBuiltInBackup);
|
||||
json = res.toJson();
|
||||
}
|
||||
_prefs.edit().putString(getBackupResultKey(isBuiltInBackup), json).apply();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private BackupResult getBackupResult(boolean isBuiltInBackup) {
|
||||
String json = _prefs.getString(getBackupResultKey(isBuiltInBackup), null);
|
||||
if (json == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
BackupResult res = BackupResult.fromJson(json);
|
||||
res.setIsBuiltIn(isBuiltInBackup);
|
||||
return res;
|
||||
} catch (JSONException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static String getBackupResultKey(boolean isBuiltInBackup) {
|
||||
return isBuiltInBackup ? "pref_backups_result_builtin": "pref_backups_result_android";
|
||||
}
|
||||
|
||||
public void setIsBackupReminderNeeded(boolean needed) {
|
||||
|
@ -382,4 +441,63 @@ public class Preferences {
|
|||
}
|
||||
}
|
||||
|
||||
public static class BackupResult {
|
||||
private final Date _time;
|
||||
private boolean _isBuiltIn;
|
||||
private final String _error;
|
||||
|
||||
public BackupResult(@Nullable Exception e) {
|
||||
this(new Date(), e == null ? null : e.toString());
|
||||
}
|
||||
|
||||
private BackupResult(Date time, @Nullable String error) {
|
||||
_time = time;
|
||||
_error = error;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getError() {
|
||||
return _error;
|
||||
}
|
||||
|
||||
public boolean isSuccessful() {
|
||||
return _error == null;
|
||||
}
|
||||
|
||||
public Date getTime() {
|
||||
return _time;
|
||||
}
|
||||
|
||||
public String getHumanReadableTime() {
|
||||
return DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(_time);
|
||||
}
|
||||
|
||||
public boolean isBuiltIn() {
|
||||
return _isBuiltIn;
|
||||
}
|
||||
|
||||
private void setIsBuiltIn(boolean isBuiltIn) {
|
||||
_isBuiltIn = isBuiltIn;
|
||||
}
|
||||
|
||||
public String toJson() {
|
||||
JSONObject obj = new JSONObject();
|
||||
|
||||
try {
|
||||
obj.put("time", _time.getTime());
|
||||
obj.put("error", _error == null ? JSONObject.NULL : _error);
|
||||
} catch (JSONException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
return obj.toString();
|
||||
}
|
||||
|
||||
public static BackupResult fromJson(String json) throws JSONException {
|
||||
JSONObject obj = new JSONObject(json);
|
||||
long time = obj.getLong("time");
|
||||
String error = JsonUtils.optString(obj, "error");
|
||||
return new BackupResult(new Date(time), error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
|||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfo;
|
||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||
import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment;
|
||||
|
@ -781,15 +780,13 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
|||
}
|
||||
|
||||
private void updateErrorBar() {
|
||||
String backupError = null;
|
||||
if (_prefs.isBackupsEnabled()) {
|
||||
backupError = _prefs.getBackupsError();
|
||||
}
|
||||
|
||||
if (backupError != null) {
|
||||
Preferences.BackupResult backupRes = _prefs.getErroredBackupResult();
|
||||
if (backupRes != null) {
|
||||
_textErrorBar.setText(R.string.backup_error_bar_message);
|
||||
_btnErrorBar.setOnClickListener(view -> {
|
||||
startPreferencesActivity(BackupsPreferencesFragment.class, "pref_backups");
|
||||
Dialogs.showBackupErrorDialog(this, backupRes, (dialog, which) -> {
|
||||
startPreferencesActivity(BackupsPreferencesFragment.class, "pref_backups");
|
||||
});
|
||||
});
|
||||
_btnErrorBar.setVisibility(View.VISIBLE);
|
||||
} else if (_prefs.isBackupsReminderNeeded()) {
|
||||
|
|
|
@ -36,6 +36,7 @@ import com.beemdevelopment.aegis.R;
|
|||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||
import com.beemdevelopment.aegis.helpers.PasswordStrengthHelper;
|
||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||
import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFragment;
|
||||
import com.beemdevelopment.aegis.ui.tasks.KeyDerivationTask;
|
||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||
import com.beemdevelopment.aegis.vault.slots.PasswordSlot;
|
||||
|
@ -403,6 +404,12 @@ public class Dialogs {
|
|||
Dialogs.showSecureDialog(dialog);
|
||||
}
|
||||
|
||||
public static void showBackupErrorDialog(Context context, Preferences.BackupResult backupRes, DialogInterface.OnClickListener listener) {
|
||||
String system = context.getString(backupRes.isBuiltIn() ? R.string.backup_system_builtin : R.string.backup_system_android);
|
||||
String message = context.getString(R.string.backup_error_dialog_details, system, backupRes.getHumanReadableTime());
|
||||
Dialogs.showErrorDialog(context, message, backupRes.getError(), listener);
|
||||
}
|
||||
|
||||
public static void showMultiMessageDialog(
|
||||
Context context, @StringRes int title, String message, List<CharSequence> messages, DialogInterface.OnClickListener listener) {
|
||||
Dialogs.showSecureDialog(new AlertDialog.Builder(context)
|
||||
|
|
|
@ -2,10 +2,16 @@ package com.beemdevelopment.aegis.ui.fragments.preferences;
|
|||
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.SwitchPreferenceCompat;
|
||||
|
||||
|
@ -21,6 +27,9 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
private Preference _backupsTriggerPreference;
|
||||
private Preference _backupsVersionsPreference;
|
||||
|
||||
private Preference _builtinBackupStatusPreference;
|
||||
private Preference _androidBackupStatusPreference;
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
|
@ -32,6 +41,23 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
super.onCreatePreferences(savedInstanceState, rootKey);
|
||||
addPreferencesFromResource(R.xml.preferences_backups);
|
||||
|
||||
_builtinBackupStatusPreference = requirePreference("pref_status_backup_builtin");
|
||||
_builtinBackupStatusPreference.setOnPreferenceClickListener(preference -> {
|
||||
Preferences.BackupResult backupRes = _prefs.getBuiltInBackupResult();
|
||||
if (backupRes != null && !backupRes.isSuccessful()) {
|
||||
Dialogs.showBackupErrorDialog(requireContext(), backupRes, null);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
_androidBackupStatusPreference = requirePreference("pref_status_backup_android");
|
||||
_androidBackupStatusPreference.setOnPreferenceClickListener(preference -> {
|
||||
Preferences.BackupResult backupRes = _prefs.getAndroidBackupResult();
|
||||
if (backupRes != null && !backupRes.isSuccessful()) {
|
||||
Dialogs.showBackupErrorDialog(requireContext(), backupRes, null);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
_backupsPreference = requirePreference("pref_backups");
|
||||
_backupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
if ((boolean) newValue) {
|
||||
|
@ -48,7 +74,9 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
_androidBackupsPreference.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
_prefs.setIsAndroidBackupsEnabled((boolean) newValue);
|
||||
updateBackupPreference();
|
||||
_vaultManager.scheduleAndroidBackup();
|
||||
if ((boolean) newValue) {
|
||||
_vaultManager.scheduleAndroidBackup();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
@ -66,6 +94,7 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
_backupsTriggerPreference.setOnPreferenceClickListener(preference -> {
|
||||
if (_prefs.isBackupsEnabled()) {
|
||||
scheduleBackup();
|
||||
_builtinBackupStatusPreference.setVisible(false);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
@ -100,7 +129,6 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
|
||||
_prefs.setBackupsLocation(uri);
|
||||
_prefs.setIsBackupsEnabled(true);
|
||||
_prefs.setBackupsError(null);
|
||||
_backupsLocationPreference.setSummary(String.format("%s: %s", getString(R.string.pref_backups_location_summary), Uri.decode(uri.toString())));
|
||||
updateBackupPreference();
|
||||
scheduleBackup();
|
||||
|
@ -117,6 +145,38 @@ public class BackupsPreferencesFragment extends PreferencesFragment {
|
|||
_backupsLocationPreference.setVisible(backupEnabled);
|
||||
_backupsTriggerPreference.setVisible(backupEnabled);
|
||||
_backupsVersionsPreference.setVisible(backupEnabled);
|
||||
if (backupEnabled) {
|
||||
Preferences.BackupResult backupRes = _prefs.getBuiltInBackupResult();
|
||||
_builtinBackupStatusPreference.setSummary(getBackupStatusMessage(backupRes));
|
||||
_builtinBackupStatusPreference.setSelectable(backupRes != null && !backupRes.isSuccessful());
|
||||
}
|
||||
if (androidBackupEnabled) {
|
||||
Preferences.BackupResult backupRes = _prefs.getAndroidBackupResult();
|
||||
_androidBackupStatusPreference.setSummary(getBackupStatusMessage(backupRes));
|
||||
_androidBackupStatusPreference.setSelectable(backupRes != null && !backupRes.isSuccessful());
|
||||
}
|
||||
_builtinBackupStatusPreference.setVisible(backupEnabled);
|
||||
_androidBackupStatusPreference.setVisible(androidBackupEnabled);
|
||||
}
|
||||
|
||||
private CharSequence getBackupStatusMessage(@Nullable Preferences.BackupResult res) {
|
||||
String message;
|
||||
int color = R.color.warning_color;
|
||||
if (res == null) {
|
||||
message = getString(R.string.backup_status_none);
|
||||
} else if (res.isSuccessful()) {
|
||||
color = R.color.success_color;
|
||||
message = getString(R.string.backup_status_success, res.getHumanReadableTime());
|
||||
} else {
|
||||
message = getString(R.string.backup_status_failed, res.getHumanReadableTime());
|
||||
}
|
||||
|
||||
Spannable spannable = new SpannableString(message);
|
||||
spannable.setSpan(new ForegroundColorSpan(getResources().getColor(color)), 0, message.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
if (color == R.color.warning_color) {
|
||||
spannable.setSpan(new StyleSpan(Typeface.BOLD), 0, message.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
private void selectBackupsLocation() {
|
||||
|
|
|
@ -52,10 +52,10 @@ public class VaultBackupManager {
|
|||
_executor.execute(() -> {
|
||||
try {
|
||||
createBackup(tempFile, dirUri, versionsToKeep);
|
||||
_prefs.setBackupsError(null);
|
||||
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(null));
|
||||
} catch (VaultRepositoryException e) {
|
||||
e.printStackTrace();
|
||||
_prefs.setBackupsError(e);
|
||||
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(e));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ public class VaultFile {
|
|||
return this;
|
||||
}
|
||||
|
||||
return new VaultFile(getContent(), new VaultFile.Header(
|
||||
return new VaultFile(_content, new VaultFile.Header(
|
||||
getHeader().getSlots().exportable(),
|
||||
getHeader().getParams()
|
||||
));
|
||||
|
|
|
@ -196,9 +196,8 @@ public class VaultManager {
|
|||
backedUp = true;
|
||||
try {
|
||||
scheduleBackup();
|
||||
_prefs.setBackupsError(null);
|
||||
} catch (VaultRepositoryException e) {
|
||||
_prefs.setBackupsError(e);
|
||||
_prefs.setBuiltInBackupResult(new Preferences.BackupResult(e));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -205,6 +205,9 @@
|
|||
<string name="disable_encryption_error">An error occurred while disabling encryption</string>
|
||||
<string name="backup_successful">The backup was scheduled successfully</string>
|
||||
<string name="backup_error">An error occurred while trying to create a backup</string>
|
||||
<string name="backup_status_success">Most recent backup successful: %s</string>
|
||||
<string name="backup_status_failed">Most recent backup failed: %s</string>
|
||||
<string name="backup_status_none">No backups have been made yet</string>
|
||||
<string name="documentsui_error">DocumentsUI appears to be missing from your device. This is an important system component necessary for the selection and creation of documents. If you used a tool to "debloat" your device, you may have accidentally deleted it and will have to reinstall it.</string>
|
||||
<string name="icon_pack_import_error">An error occurred while trying to import an icon pack</string>
|
||||
<string name="icon_pack_import_exists_error">The icon pack you\'re trying to import already exists. Do you want to overwrite it?</string>
|
||||
|
@ -351,6 +354,9 @@
|
|||
</plurals>
|
||||
<string name="google_qr_export_unexpected">Expected QR code #%d, but scanned #%d instead</string>
|
||||
<string name="backup_error_bar_message"><b>Vault backup failed recently</b></string>
|
||||
<string name="backup_error_dialog_details">A recent vault backup attempt using %s failed because an error occurred. The backup was attempted at: %s. Please check your backup settings to make sure backups can complete successfully.</string>
|
||||
<string name="backup_system_builtin">Aegis\' built-in automatic backups</string>
|
||||
<string name="backup_system_android">Android\'s cloud backup system</string>
|
||||
<string name="backup_reminder_bar_message"><b>Recent vault changes are not backed up</b></string>
|
||||
<string name="backup_plaintext_export_warning"><b>The vault was recently exported in plain text</b></string>
|
||||
<string name="pref_show_plaintext_warning_hint">Don\'t show this warning again</string>
|
||||
|
|
|
@ -25,6 +25,11 @@
|
|||
android:key="pref_backups_versions"
|
||||
android:title="@string/pref_backups_versions_title"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<Preference
|
||||
android:key="pref_status_backup_builtin"
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
app:iconSpaceReserved="false"/>
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory
|
||||
android:title="@string/pref_cat_backups_android"
|
||||
|
@ -36,6 +41,12 @@
|
|||
android:summary="@string/pref_android_backups_summary"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<Preference
|
||||
android:key="pref_status_backup_android"
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
app:iconSpaceReserved="false"/>
|
||||
<Preference
|
||||
android:persistent="false"
|
||||
android:selectable="false"
|
||||
android:summary="@string/pref_android_backups_hint"
|
||||
app:iconSpaceReserved="false"/>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue