mirror of
https://github.com/beemdevelopment/Aegis.git
synced 2025-04-22 23:09:13 +00:00
Merge pull request #1591 from alexbakker/resize-icons
Store non-SVG icons at a maximum of 512x512 and migrate existing icons
This commit is contained in:
commit
ec92fb2b31
11 changed files with 224 additions and 22 deletions
|
@ -1,6 +1,13 @@
|
||||||
package com.beemdevelopment.aegis.helpers;
|
package com.beemdevelopment.aegis.helpers;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.icons.IconType;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
public class BitmapHelper {
|
public class BitmapHelper {
|
||||||
private BitmapHelper() {
|
private BitmapHelper() {
|
||||||
|
@ -28,4 +35,29 @@ public class BitmapHelper {
|
||||||
|
|
||||||
return Bitmap.createScaledBitmap(bitmap, width, height, true);
|
return Bitmap.createScaledBitmap(bitmap, width, height, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isVaultEntryIconOptimized(VaultEntryIcon icon) {
|
||||||
|
BitmapFactory.Options opts = new BitmapFactory.Options();
|
||||||
|
opts.inJustDecodeBounds = true;
|
||||||
|
BitmapFactory.decodeByteArray(icon.getBytes(), 0, icon.getBytes().length, opts);
|
||||||
|
return opts.outWidth <= VaultEntryIcon.MAX_DIMENS && opts.outHeight <= VaultEntryIcon.MAX_DIMENS;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static VaultEntryIcon toVaultEntryIcon(Bitmap bitmap, IconType iconType) {
|
||||||
|
if (bitmap.getWidth() > VaultEntryIcon.MAX_DIMENS
|
||||||
|
|| bitmap.getHeight() > VaultEntryIcon.MAX_DIMENS) {
|
||||||
|
bitmap = resize(bitmap, VaultEntryIcon.MAX_DIMENS, VaultEntryIcon.MAX_DIMENS);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||||
|
if (Objects.equals(iconType, IconType.PNG)) {
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
||||||
|
} else {
|
||||||
|
iconType = IconType.JPEG;
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 90, stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] data = stream.toByteArray();
|
||||||
|
return new VaultEntryIcon(data, iconType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,12 +35,14 @@ import com.beemdevelopment.aegis.encoding.Base32;
|
||||||
import com.beemdevelopment.aegis.encoding.EncodingException;
|
import com.beemdevelopment.aegis.encoding.EncodingException;
|
||||||
import com.beemdevelopment.aegis.encoding.Hex;
|
import com.beemdevelopment.aegis.encoding.Hex;
|
||||||
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
|
import com.beemdevelopment.aegis.helpers.AnimationsHelper;
|
||||||
|
import com.beemdevelopment.aegis.helpers.BitmapHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
import com.beemdevelopment.aegis.helpers.EditTextHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.SafHelper;
|
import com.beemdevelopment.aegis.helpers.SafHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener;
|
import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener;
|
||||||
import com.beemdevelopment.aegis.helpers.SimpleTextWatcher;
|
import com.beemdevelopment.aegis.helpers.SimpleTextWatcher;
|
||||||
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
import com.beemdevelopment.aegis.helpers.TextDrawableHelper;
|
||||||
|
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
||||||
import com.beemdevelopment.aegis.icons.IconPack;
|
import com.beemdevelopment.aegis.icons.IconPack;
|
||||||
import com.beemdevelopment.aegis.icons.IconType;
|
import com.beemdevelopment.aegis.icons.IconType;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
|
@ -59,7 +61,6 @@ import com.beemdevelopment.aegis.ui.tasks.ImportFileTask;
|
||||||
import com.beemdevelopment.aegis.ui.views.IconAdapter;
|
import com.beemdevelopment.aegis.ui.views.IconAdapter;
|
||||||
import com.beemdevelopment.aegis.util.Cloner;
|
import com.beemdevelopment.aegis.util.Cloner;
|
||||||
import com.beemdevelopment.aegis.util.IOUtils;
|
import com.beemdevelopment.aegis.util.IOUtils;
|
||||||
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||||
import com.beemdevelopment.aegis.vault.VaultGroup;
|
import com.beemdevelopment.aegis.vault.VaultGroup;
|
||||||
|
@ -76,7 +77,6 @@ import com.google.android.material.imageview.ShapeableImageView;
|
||||||
import com.google.android.material.textfield.TextInputEditText;
|
import com.google.android.material.textfield.TextInputEditText;
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -103,6 +103,7 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
// keep track of icon changes separately as the generated jpeg's are not deterministic
|
// keep track of icon changes separately as the generated jpeg's are not deterministic
|
||||||
private boolean _hasChangedIcon = false;
|
private boolean _hasChangedIcon = false;
|
||||||
private IconPack.Icon _selectedIcon;
|
private IconPack.Icon _selectedIcon;
|
||||||
|
private String _pickedMimeType;
|
||||||
private ShapeableImageView _iconView;
|
private ShapeableImageView _iconView;
|
||||||
private ImageView _saveImageButton;
|
private ImageView _saveImageButton;
|
||||||
|
|
||||||
|
@ -140,8 +141,8 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
if (activityResult.getResultCode() != RESULT_OK || data == null || data.getData() == null) {
|
if (activityResult.getResultCode() != RESULT_OK || data == null || data.getData() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String fileType = SafHelper.getMimeType(this, data.getData());
|
_pickedMimeType = SafHelper.getMimeType(this, data.getData());
|
||||||
if (fileType != null && fileType.equals(IconType.SVG.toMimeType())) {
|
if (_pickedMimeType != null && _pickedMimeType.equals(IconType.SVG.toMimeType())) {
|
||||||
ImportFileTask.Params params = new ImportFileTask.Params(data.getData(), "icon", null);
|
ImportFileTask.Params params = new ImportFileTask.Params(data.getData(), "icon", null);
|
||||||
ImportFileTask task = new ImportFileTask(this, result -> {
|
ImportFileTask task = new ImportFileTask(this, result -> {
|
||||||
if (result.getError() == null) {
|
if (result.getError() == null) {
|
||||||
|
@ -804,11 +805,12 @@ public class EditEntryActivity extends AegisActivity {
|
||||||
VaultEntryIcon icon;
|
VaultEntryIcon icon;
|
||||||
if (_selectedIcon == null) {
|
if (_selectedIcon == null) {
|
||||||
Bitmap bitmap = ((BitmapDrawable) _iconView.getDrawable()).getBitmap();
|
Bitmap bitmap = ((BitmapDrawable) _iconView.getDrawable()).getBitmap();
|
||||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
IconType iconType = _pickedMimeType == null
|
||||||
// the quality parameter is ignored for PNG
|
? IconType.INVALID : IconType.fromMimeType(_pickedMimeType);
|
||||||
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
|
if (iconType == IconType.INVALID) {
|
||||||
byte[] data = stream.toByteArray();
|
iconType = bitmap.hasAlpha() ? IconType.PNG : IconType.JPEG;
|
||||||
icon = new VaultEntryIcon(data, IconType.PNG);
|
}
|
||||||
|
icon = BitmapHelper.toVaultEntryIcon(bitmap, iconType);
|
||||||
} else {
|
} else {
|
||||||
byte[] iconBytes;
|
byte[] iconBytes;
|
||||||
try (FileInputStream inStream = new FileInputStream(_selectedIcon.getFile())){
|
try (FileInputStream inStream = new FileInputStream(_selectedIcon.getFile())){
|
||||||
|
|
|
@ -15,17 +15,21 @@ import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
|
import com.beemdevelopment.aegis.helpers.BitmapHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
||||||
|
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
||||||
|
import com.beemdevelopment.aegis.icons.IconType;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
import com.beemdevelopment.aegis.importers.DatabaseImporter;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
|
import com.beemdevelopment.aegis.importers.DatabaseImporterEntryException;
|
||||||
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
import com.beemdevelopment.aegis.importers.DatabaseImporterException;
|
||||||
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
import com.beemdevelopment.aegis.ui.dialogs.Dialogs;
|
||||||
import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
import com.beemdevelopment.aegis.ui.models.ImportEntry;
|
||||||
|
import com.beemdevelopment.aegis.ui.tasks.IconOptimizationTask;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.RootShellTask;
|
import com.beemdevelopment.aegis.ui.tasks.RootShellTask;
|
||||||
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
|
import com.beemdevelopment.aegis.ui.views.ImportEntriesAdapter;
|
||||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||||
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||||
import com.beemdevelopment.aegis.vault.VaultGroup;
|
import com.beemdevelopment.aegis.vault.VaultGroup;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
@ -40,8 +44,10 @@ import java.io.InputStream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
public class ImportEntriesActivity extends AegisActivity {
|
public class ImportEntriesActivity extends AegisActivity {
|
||||||
private View _view;
|
private View _view;
|
||||||
|
@ -172,7 +178,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
state.decrypt(this, new DatabaseImporter.DecryptListener() {
|
state.decrypt(this, new DatabaseImporter.DecryptListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onStateDecrypted(DatabaseImporter.State state) {
|
public void onStateDecrypted(DatabaseImporter.State state) {
|
||||||
importDatabase(state);
|
processDecryptedImporterState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,7 +193,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
importDatabase(state);
|
processDecryptedImporterState(state);
|
||||||
}
|
}
|
||||||
} catch (DatabaseImporterException e) {
|
} catch (DatabaseImporterException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -195,8 +201,7 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void importDatabase(DatabaseImporter.State state) {
|
private void processDecryptedImporterState(DatabaseImporter.State state) {
|
||||||
List<ImportEntry> importEntries = new ArrayList<>();
|
|
||||||
DatabaseImporter.Result result;
|
DatabaseImporter.Result result;
|
||||||
try {
|
try {
|
||||||
result = state.convert();
|
result = state.convert();
|
||||||
|
@ -206,8 +211,29 @@ public class ImportEntriesActivity extends AegisActivity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
UUIDMap<VaultEntry> entries = result.getEntries();
|
Map<UUID, VaultEntryIcon> icons = result.getEntries().getValues().stream()
|
||||||
for (VaultEntry entry : entries.getValues()) {
|
.filter(e -> e.getIcon() != null
|
||||||
|
&& !e.getIcon().getType().equals(IconType.SVG)
|
||||||
|
&& !BitmapHelper.isVaultEntryIconOptimized(e.getIcon()))
|
||||||
|
.collect(Collectors.toMap(VaultEntry::getUUID, VaultEntry::getIcon));
|
||||||
|
if (!icons.isEmpty()) {
|
||||||
|
IconOptimizationTask task = new IconOptimizationTask(this, newIcons -> {
|
||||||
|
for (Map.Entry<UUID, VaultEntryIcon> mapEntry : newIcons.entrySet()) {
|
||||||
|
VaultEntry entry = result.getEntries().getByUUID(mapEntry.getKey());
|
||||||
|
entry.setIcon(mapEntry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
processImporterResult(result);
|
||||||
|
});
|
||||||
|
task.execute(getLifecycle(), icons);
|
||||||
|
} else {
|
||||||
|
processImporterResult(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processImporterResult(DatabaseImporter.Result result) {
|
||||||
|
List<ImportEntry> importEntries = new ArrayList<>();
|
||||||
|
for (VaultEntry entry : result.getEntries().getValues()) {
|
||||||
ImportEntry importEntry = new ImportEntry(entry);
|
ImportEntry importEntry = new ImportEntry(entry);
|
||||||
_adapter.addEntry(importEntry);
|
_adapter.addEntry(importEntry);
|
||||||
importEntries.add(importEntry);
|
importEntries.add(importEntry);
|
||||||
|
|
|
@ -44,9 +44,12 @@ import com.beemdevelopment.aegis.GroupPlaceholderType;
|
||||||
import com.beemdevelopment.aegis.Preferences;
|
import com.beemdevelopment.aegis.Preferences;
|
||||||
import com.beemdevelopment.aegis.R;
|
import com.beemdevelopment.aegis.R;
|
||||||
import com.beemdevelopment.aegis.SortCategory;
|
import com.beemdevelopment.aegis.SortCategory;
|
||||||
|
import com.beemdevelopment.aegis.helpers.BitmapHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
import com.beemdevelopment.aegis.helpers.DropdownHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
import com.beemdevelopment.aegis.helpers.FabScrollHelper;
|
||||||
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
import com.beemdevelopment.aegis.helpers.PermissionHelper;
|
||||||
|
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
||||||
|
import com.beemdevelopment.aegis.icons.IconType;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfo;
|
||||||
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
import com.beemdevelopment.aegis.otp.GoogleAuthInfoException;
|
||||||
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
import com.beemdevelopment.aegis.otp.OtpInfoException;
|
||||||
|
@ -55,12 +58,13 @@ import com.beemdevelopment.aegis.ui.fragments.preferences.BackupsPreferencesFrag
|
||||||
import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment;
|
import com.beemdevelopment.aegis.ui.fragments.preferences.PreferencesFragment;
|
||||||
import com.beemdevelopment.aegis.ui.models.ErrorCardInfo;
|
import com.beemdevelopment.aegis.ui.models.ErrorCardInfo;
|
||||||
import com.beemdevelopment.aegis.ui.models.VaultGroupModel;
|
import com.beemdevelopment.aegis.ui.models.VaultGroupModel;
|
||||||
|
import com.beemdevelopment.aegis.ui.tasks.IconOptimizationTask;
|
||||||
import com.beemdevelopment.aegis.ui.tasks.QrDecodeTask;
|
import com.beemdevelopment.aegis.ui.tasks.QrDecodeTask;
|
||||||
import com.beemdevelopment.aegis.ui.views.EntryListView;
|
import com.beemdevelopment.aegis.ui.views.EntryListView;
|
||||||
import com.beemdevelopment.aegis.util.TimeUtils;
|
import com.beemdevelopment.aegis.util.TimeUtils;
|
||||||
import com.beemdevelopment.aegis.util.UUIDMap;
|
import com.beemdevelopment.aegis.util.UUIDMap;
|
||||||
import com.beemdevelopment.aegis.helpers.ViewHelper;
|
|
||||||
import com.beemdevelopment.aegis.vault.VaultEntry;
|
import com.beemdevelopment.aegis.vault.VaultEntry;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||||
import com.beemdevelopment.aegis.vault.VaultFile;
|
import com.beemdevelopment.aegis.vault.VaultFile;
|
||||||
import com.beemdevelopment.aegis.vault.VaultGroup;
|
import com.beemdevelopment.aegis.vault.VaultGroup;
|
||||||
import com.beemdevelopment.aegis.vault.VaultRepository;
|
import com.beemdevelopment.aegis.vault.VaultRepository;
|
||||||
|
@ -724,6 +728,37 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkIconOptimization() {
|
||||||
|
if (!_vaultManager.getVault().areIconsOptimized()) {
|
||||||
|
Map<UUID, VaultEntryIcon> oldIcons = _vaultManager.getVault().getEntries().stream()
|
||||||
|
.filter(e -> e.getIcon() != null
|
||||||
|
&& !e.getIcon().getType().equals(IconType.SVG)
|
||||||
|
&& !BitmapHelper.isVaultEntryIconOptimized(e.getIcon()))
|
||||||
|
.collect(Collectors.toMap(VaultEntry::getUUID, VaultEntry::getIcon));
|
||||||
|
|
||||||
|
if (!oldIcons.isEmpty()) {
|
||||||
|
IconOptimizationTask task = new IconOptimizationTask(this, this::onIconsOptimized);
|
||||||
|
task.execute(getLifecycle(), oldIcons);
|
||||||
|
} else {
|
||||||
|
onIconsOptimized(Collections.emptyMap());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onIconsOptimized(Map<UUID, VaultEntryIcon> newIcons) {
|
||||||
|
for (Map.Entry<UUID, VaultEntryIcon> mapEntry : newIcons.entrySet()) {
|
||||||
|
VaultEntry entry = _vaultManager.getVault().getEntryByUUID(mapEntry.getKey());
|
||||||
|
entry.setIcon(mapEntry.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
_vaultManager.getVault().setIconsOptimized(true);
|
||||||
|
saveAndBackupVault();
|
||||||
|
|
||||||
|
if (!newIcons.isEmpty()) {
|
||||||
|
_entryListView.setEntries(_vaultManager.getVault().getEntries());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void onDecryptResult() {
|
private void onDecryptResult() {
|
||||||
_auditLogRepository.addVaultUnlockedEvent();
|
_auditLogRepository.addVaultUnlockedEvent();
|
||||||
|
|
||||||
|
@ -912,6 +947,7 @@ public class MainActivity extends AegisActivity implements EntryListView.Listene
|
||||||
} else {
|
} else {
|
||||||
loadEntries();
|
loadEntries();
|
||||||
checkTimeSyncSetting();
|
checkTimeSyncSetting();
|
||||||
|
checkIconOptimization();
|
||||||
}
|
}
|
||||||
|
|
||||||
_lockBackPressHandler.setEnabled(
|
_lockBackPressHandler.setEnabled(
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
package com.beemdevelopment.aegis.ui.tasks;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
|
|
||||||
|
import com.beemdevelopment.aegis.R;
|
||||||
|
import com.beemdevelopment.aegis.helpers.BitmapHelper;
|
||||||
|
import com.beemdevelopment.aegis.icons.IconType;
|
||||||
|
import com.beemdevelopment.aegis.vault.VaultEntryIcon;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class IconOptimizationTask extends ProgressDialogTask<Map<UUID, VaultEntryIcon>, Map<UUID, VaultEntryIcon>> {
|
||||||
|
private final Callback _cb;
|
||||||
|
|
||||||
|
public IconOptimizationTask(Context context, Callback cb) {
|
||||||
|
super(context, context.getString(R.string.optimizing_icon));
|
||||||
|
_cb = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Map<UUID, VaultEntryIcon> doInBackground(Map<UUID, VaultEntryIcon>... params) {
|
||||||
|
Map<UUID, VaultEntryIcon> res = new HashMap<>();
|
||||||
|
Context context = getDialog().getContext();
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
Map<UUID, VaultEntryIcon> icons = params[0];
|
||||||
|
for (Map.Entry<UUID, VaultEntryIcon> entry : icons.entrySet()) {
|
||||||
|
if (icons.size() > 1) {
|
||||||
|
publishProgress(context.getString(R.string.optimizing_icon_multiple, i + 1, icons.size()));
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
|
||||||
|
VaultEntryIcon oldIcon = entry.getValue();
|
||||||
|
if (oldIcon == null || oldIcon.getType().equals(IconType.SVG)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (BitmapHelper.isVaultEntryIconOptimized(oldIcon)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bitmap = BitmapFactory.decodeByteArray(oldIcon.getBytes(), 0, oldIcon.getBytes().length);
|
||||||
|
VaultEntryIcon newIcon = BitmapHelper.toVaultEntryIcon(bitmap, oldIcon.getType());
|
||||||
|
bitmap.recycle();
|
||||||
|
res.put(entry.getKey(), newIcon);
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Map<UUID, VaultEntryIcon> results) {
|
||||||
|
super.onPostExecute(results);
|
||||||
|
_cb.onTaskFinished(results);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
void onTaskFinished(Map<UUID, VaultEntryIcon> results);
|
||||||
|
}
|
||||||
|
}
|
|
@ -15,6 +15,7 @@ public class Vault {
|
||||||
private static final int VERSION = 3;
|
private static final int VERSION = 3;
|
||||||
private final UUIDMap<VaultEntry> _entries = new UUIDMap<>();
|
private final UUIDMap<VaultEntry> _entries = new UUIDMap<>();
|
||||||
private final UUIDMap<VaultGroup> _groups = new UUIDMap<>();
|
private final UUIDMap<VaultGroup> _groups = new UUIDMap<>();
|
||||||
|
private boolean _iconsOptimized = true;
|
||||||
|
|
||||||
// Whether we've migrated the group list to the new format while parsing the vault
|
// Whether we've migrated the group list to the new format while parsing the vault
|
||||||
private boolean _isGroupsMigrationFresh = false;
|
private boolean _isGroupsMigrationFresh = false;
|
||||||
|
@ -42,6 +43,7 @@ public class Vault {
|
||||||
obj.put("version", VERSION);
|
obj.put("version", VERSION);
|
||||||
obj.put("entries", entriesArray);
|
obj.put("entries", entriesArray);
|
||||||
obj.put("groups", groupsArray);
|
obj.put("groups", groupsArray);
|
||||||
|
obj.put("icons_optimized", _iconsOptimized);
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
|
@ -86,6 +88,10 @@ public class Vault {
|
||||||
|
|
||||||
entries.add(entry);
|
entries.add(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!obj.optBoolean("icons_optimized")) {
|
||||||
|
vault.setIconsOptimized(false);
|
||||||
|
}
|
||||||
} catch (VaultEntryException | JSONException e) {
|
} catch (VaultEntryException | JSONException e) {
|
||||||
throw new VaultException(e);
|
throw new VaultException(e);
|
||||||
}
|
}
|
||||||
|
@ -101,6 +107,14 @@ public class Vault {
|
||||||
return _isGroupsMigrationFresh;
|
return _isGroupsMigrationFresh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setIconsOptimized(boolean optimized) {
|
||||||
|
_iconsOptimized = optimized;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean areIconsOptimized() {
|
||||||
|
return _iconsOptimized;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean migrateOldGroup(VaultEntry entry) {
|
public boolean migrateOldGroup(VaultEntry entry) {
|
||||||
if (entry.getOldGroup() != null) {
|
if (entry.getOldGroup() != null) {
|
||||||
Optional<VaultGroup> optGroup = getGroups().getValues()
|
Optional<VaultGroup> optGroup = getGroups().getValues()
|
||||||
|
|
|
@ -103,8 +103,14 @@ public class VaultEntry extends UUIDMap.Value {
|
||||||
entry.setOldGroup(JsonUtils.optString(obj, "group"));
|
entry.setOldGroup(JsonUtils.optString(obj, "group"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Silently ignore any errors that occur when trying to parse the icon of an
|
||||||
|
// entry. This allows us to introduce new icon types in the future (e.g. WebP)
|
||||||
|
// without breaking compatibility with older versions of Aegis.
|
||||||
|
try {
|
||||||
VaultEntryIcon icon = VaultEntryIcon.fromJson(obj);
|
VaultEntryIcon icon = VaultEntryIcon.fromJson(obj);
|
||||||
entry.setIcon(icon);
|
entry.setIcon(icon);
|
||||||
|
} catch (VaultEntryIconException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
} catch (OtpInfoException | JSONException e) {
|
} catch (OtpInfoException | JSONException e) {
|
||||||
|
|
|
@ -23,6 +23,8 @@ public class VaultEntryIcon implements Serializable {
|
||||||
private final byte[] _hash;
|
private final byte[] _hash;
|
||||||
private final IconType _type;
|
private final IconType _type;
|
||||||
|
|
||||||
|
public static final int MAX_DIMENS = 512;
|
||||||
|
|
||||||
public VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type) {
|
public VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type) {
|
||||||
this(bytes, type, generateHash(bytes, type));
|
this(bytes, type, generateHash(bytes, type));
|
||||||
}
|
}
|
||||||
|
@ -70,7 +72,7 @@ public class VaultEntryIcon implements Serializable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
static VaultEntryIcon fromJson(@NonNull JSONObject obj) throws VaultEntryException {
|
static VaultEntryIcon fromJson(@NonNull JSONObject obj) throws VaultEntryIconException {
|
||||||
try {
|
try {
|
||||||
Object icon = obj.get("icon");
|
Object icon = obj.get("icon");
|
||||||
if (icon == JSONObject.NULL) {
|
if (icon == JSONObject.NULL) {
|
||||||
|
@ -80,7 +82,7 @@ public class VaultEntryIcon implements Serializable {
|
||||||
String mime = JsonUtils.optString(obj, "icon_mime");
|
String mime = JsonUtils.optString(obj, "icon_mime");
|
||||||
IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime);
|
IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime);
|
||||||
if (iconType == IconType.INVALID) {
|
if (iconType == IconType.INVALID) {
|
||||||
throw new VaultEntryException(String.format("Bad icon MIME type: %s", mime));
|
throw new VaultEntryIconException(String.format("Bad icon MIME type: %s", mime));
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] iconBytes = Base64.decode((String) icon);
|
byte[] iconBytes = Base64.decode((String) icon);
|
||||||
|
@ -92,7 +94,7 @@ public class VaultEntryIcon implements Serializable {
|
||||||
|
|
||||||
return new VaultEntryIcon(iconBytes, iconType);
|
return new VaultEntryIcon(iconBytes, iconType);
|
||||||
} catch (JSONException | EncodingException e) {
|
} catch (JSONException | EncodingException e) {
|
||||||
throw new VaultEntryException(e);
|
throw new VaultEntryIconException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.beemdevelopment.aegis.vault;
|
||||||
|
|
||||||
|
public class VaultEntryIconException extends Exception {
|
||||||
|
public VaultEntryIconException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public VaultEntryIconException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
}
|
|
@ -333,6 +333,14 @@ public class VaultRepository {
|
||||||
return _vault.isGroupsMigrationFresh();
|
return _vault.isGroupsMigrationFresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean areIconsOptimized() {
|
||||||
|
return _vault.areIconsOptimized();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIconsOptimized(boolean optimized) {
|
||||||
|
_vault.setIconsOptimized(optimized);
|
||||||
|
}
|
||||||
|
|
||||||
public VaultFileCredentials getCredentials() {
|
public VaultFileCredentials getCredentials() {
|
||||||
return _creds == null ? null : _creds.clone();
|
return _creds == null ? null : _creds.clone();
|
||||||
}
|
}
|
||||||
|
|
|
@ -236,6 +236,8 @@
|
||||||
<string name="snackbar_authentication_method">Please select an authentication method</string>
|
<string name="snackbar_authentication_method">Please select an authentication method</string>
|
||||||
<string name="encrypting_vault">Encrypting the vault</string>
|
<string name="encrypting_vault">Encrypting the vault</string>
|
||||||
<string name="exporting_vault">Exporting the vault</string>
|
<string name="exporting_vault">Exporting the vault</string>
|
||||||
|
<string name="optimizing_icon">Optimizing icon</string>
|
||||||
|
<string name="optimizing_icon_multiple">Optimizing icons %1$d/%2$d</string>
|
||||||
<string name="reading_file">Reading file</string>
|
<string name="reading_file">Reading file</string>
|
||||||
<string name="requesting_root_access">Requesting root access</string>
|
<string name="requesting_root_access">Requesting root access</string>
|
||||||
<string name="analyzing_qr">Analyzing QR code</string>
|
<string name="analyzing_qr">Analyzing QR code</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue