diff --git a/app/src/main/java/com/beemdevelopment/aegis/helpers/IconViewHelper.java b/app/src/main/java/com/beemdevelopment/aegis/helpers/IconViewHelper.java deleted file mode 100644 index df740af4..00000000 --- a/app/src/main/java/com/beemdevelopment/aegis/helpers/IconViewHelper.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.beemdevelopment.aegis.helpers; - -import android.os.Build; -import android.widget.ImageView; - -import com.beemdevelopment.aegis.icons.IconType; - -public class IconViewHelper { - private IconViewHelper() { - - } - - /** - * Sets the layer type of the given ImageView based on the given IconType. If the - * icon type is SVG and SDK <= 27, the layer type is set to software. Otherwise, it - * is set to hardware. - */ - public static void setLayerType(ImageView view, IconType iconType) { - if (iconType == IconType.SVG && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { - view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); - return; - } - - view.setLayerType(ImageView.LAYER_TYPE_HARDWARE, null); - } -} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/AssignIconsActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/AssignIconsActivity.java index 0fa36a48..c224d996 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/AssignIconsActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/AssignIconsActivity.java @@ -8,6 +8,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.Toast; + import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -19,19 +20,20 @@ import com.beemdevelopment.aegis.helpers.MetricsHelper; import com.beemdevelopment.aegis.icons.IconPack; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.IconPickerDialog; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.models.AssignIconEntry; import com.beemdevelopment.aegis.ui.views.AssignIconAdapter; import com.beemdevelopment.aegis.ui.views.IconAdapter; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultEntryIcon; import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.util.ViewPreloadSizeProvider; import com.google.android.material.bottomsheet.BottomSheetDialog; + import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; @@ -121,13 +123,14 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap ArrayList uuids = new ArrayList<>(); for (AssignIconEntry selectedEntry : _entries) { VaultEntry entry = selectedEntry.getEntry(); - if(selectedEntry.getNewIcon() != null) { + if (selectedEntry.getNewIcon() != null) { byte[] iconBytes; try (FileInputStream inStream = new FileInputStream(selectedEntry.getNewIcon().getFile())){ iconBytes = IOUtils.readFile(inStream); } - entry.setIcon(iconBytes, selectedEntry.getNewIcon().getIconType()); + VaultEntryIcon icon = new VaultEntryIcon(iconBytes, selectedEntry.getNewIcon().getIconType()); + entry.setIcon(icon); uuids.add(entry.getUUID()); _vaultManager.getVault().replaceEntry(entry); @@ -223,12 +226,9 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap @Nullable @Override public RequestBuilder getPreloadRequestBuilder(@NonNull VaultEntry entry) { - return Glide.with(AssignIconsActivity.this) - .asDrawable() - .load(entry) - .set(IconLoader.ICON_TYPE, entry.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false); + RequestBuilder rb = Glide.with(AssignIconsActivity.this) + .load(entry.getIcon()); + return GlideHelper.setCommonOptions(rb, entry.getIcon().getType()); } } @@ -246,12 +246,9 @@ public class AssignIconsActivity extends AegisActivity implements AssignIconAdap @Nullable @Override public RequestBuilder getPreloadRequestBuilder(@NonNull IconPack.Icon icon) { - return Glide.with(AssignIconsActivity.this) - .asDrawable() - .load(icon.getFile()) - .set(IconLoader.ICON_TYPE, icon.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false); + RequestBuilder rb = Glide.with(AssignIconsActivity.this) + .load(icon.getFile()); + return GlideHelper.setCommonOptions(rb, icon.getIconType()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java index df627de9..91648b42 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/EditEntryActivity.java @@ -37,7 +37,6 @@ import com.beemdevelopment.aegis.encoding.Hex; import com.beemdevelopment.aegis.helpers.AnimationsHelper; import com.beemdevelopment.aegis.helpers.DropdownHelper; import com.beemdevelopment.aegis.helpers.EditTextHelper; -import com.beemdevelopment.aegis.helpers.IconViewHelper; import com.beemdevelopment.aegis.helpers.SafHelper; import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener; import com.beemdevelopment.aegis.helpers.SimpleTextWatcher; @@ -54,13 +53,14 @@ import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.otp.YandexInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; import com.beemdevelopment.aegis.ui.dialogs.IconPickerDialog; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.models.VaultGroupModel; import com.beemdevelopment.aegis.ui.tasks.ImportFileTask; import com.beemdevelopment.aegis.ui.views.IconAdapter; import com.beemdevelopment.aegis.util.Cloner; import com.beemdevelopment.aegis.util.IOUtils; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultEntryIcon; import com.beemdevelopment.aegis.vault.VaultGroup; import com.beemdevelopment.aegis.vault.VaultRepository; import com.bumptech.glide.Glide; @@ -239,19 +239,9 @@ public class EditEntryActivity extends AegisActivity { _advancedSettings = findViewById(R.id.expandableLayout); // fill the fields with values if possible + GlideHelper.loadEntryIcon(Glide.with(this), _origEntry, _iconView); if (_origEntry.hasIcon()) { - IconViewHelper.setLayerType(_iconView, _origEntry.getIconType()); - Glide.with(this) - .asDrawable() - .load(_origEntry.getIcon()) - .set(IconLoader.ICON_TYPE, _origEntry.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_iconView); _hasCustomIcon = true; - } else { - TextDrawable drawable = TextDrawableHelper.generate(_origEntry.getIssuer(), _origEntry.getName(), _iconView); - _iconView.setImageDrawable(drawable); } _textName.setText(_origEntry.getName()); @@ -548,14 +538,7 @@ public class EditEntryActivity extends AegisActivity { _hasCustomIcon = true; _hasChangedIcon = true; - IconViewHelper.setLayerType(_iconView, icon.getIconType()); - Glide.with(EditEntryActivity.this) - .asDrawable() - .load(icon.getFile()) - .set(IconLoader.ICON_TYPE, icon.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_iconView); + GlideHelper.loadIcon(Glide.with(EditEntryActivity.this), icon, _iconView); } private void startEditingIcon(Uri data) { @@ -743,13 +726,14 @@ public class EditEntryActivity extends AegisActivity { if (_hasChangedIcon) { if (_hasCustomIcon) { + VaultEntryIcon icon; if (_selectedIcon == null) { Bitmap bitmap = ((BitmapDrawable) _iconView.getDrawable()).getBitmap(); ByteArrayOutputStream stream = new ByteArrayOutputStream(); // the quality parameter is ignored for PNG bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); byte[] data = stream.toByteArray(); - entry.setIcon(data, IconType.PNG); + icon = new VaultEntryIcon(data, IconType.PNG); } else { byte[] iconBytes; try (FileInputStream inStream = new FileInputStream(_selectedIcon.getFile())){ @@ -757,11 +741,12 @@ public class EditEntryActivity extends AegisActivity { } catch (IOException e) { throw new ParseException(e.getMessage()); } - - entry.setIcon(iconBytes, _selectedIcon.getIconType()); + icon = new VaultEntryIcon(iconBytes, _selectedIcon.getIconType()); } + + entry.setIcon(icon); } else { - entry.setIcon(null, IconType.INVALID); + entry.setIcon(null); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java index ae9ecaa2..d08059ce 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/dialogs/IconPickerDialog.java @@ -18,14 +18,13 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.icons.IconPack; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.views.IconAdapter; import com.beemdevelopment.aegis.ui.views.IconRecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.util.ViewPreloadSizeProvider; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; @@ -76,12 +75,9 @@ public class IconPickerDialog { @Nullable @Override public RequestBuilder getPreloadRequestBuilder(@NonNull IconPack.Icon icon) { - return Glide.with(dialog.getContext()) - .asDrawable() - .load(icon.getFile()) - .set(IconLoader.ICON_TYPE, icon.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false); + RequestBuilder rb = Glide.with(dialog.getContext()) + .load(icon.getFile()); + return GlideHelper.setCommonOptions(rb, icon.getIconType()); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/AegisGlideModule.java b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/AegisGlideModule.java index b6de07b5..509a4f78 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/AegisGlideModule.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/AegisGlideModule.java @@ -6,6 +6,7 @@ import android.graphics.drawable.PictureDrawable; import androidx.annotation.NonNull; import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultEntryIcon; import com.bumptech.glide.Glide; import com.bumptech.glide.Registry; import com.bumptech.glide.annotation.GlideModule; @@ -19,7 +20,7 @@ import java.nio.ByteBuffer; public class AegisGlideModule extends AppGlideModule { @Override public void registerComponents(@NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) { - registry.prepend(VaultEntry.class, ByteBuffer.class, new IconLoader.Factory()); + registry.prepend(VaultEntryIcon.class, ByteBuffer.class, new VaultEntryIconLoader.Factory()); registry.register(SVG.class, PictureDrawable.class, new SvgDrawableTranscoder()) .append(InputStream.class, SVG.class, new SvgDecoder()) .append(ByteBuffer.class, SVG.class, new SvgBytesDecoder()); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/GlideHelper.java b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/GlideHelper.java new file mode 100644 index 00000000..df694c69 --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/GlideHelper.java @@ -0,0 +1,128 @@ +package com.beemdevelopment.aegis.ui.glide; + +import android.graphics.drawable.Drawable; +import android.os.Build; +import android.widget.ImageView; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RawRes; + +import com.amulyakhare.textdrawable.TextDrawable; +import com.beemdevelopment.aegis.helpers.TextDrawableHelper; +import com.beemdevelopment.aegis.icons.IconPack; +import com.beemdevelopment.aegis.icons.IconType; +import com.beemdevelopment.aegis.vault.VaultEntry; +import com.bumptech.glide.RequestBuilder; +import com.bumptech.glide.RequestManager; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.DiskCacheStrategy; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.DrawableImageViewTarget; +import com.bumptech.glide.request.target.Target; + +import java.io.File; + +public class GlideHelper { + private GlideHelper() { + + } + + public static void loadIconFile(RequestManager rm, File file, IconType iconType, ImageView targetView) { + load(rm.load(file), iconType, targetView); + } + + public static void loadIcon(RequestManager rm, IconPack.Icon icon, ImageView targetView) { + loadIconFile(rm, icon.getFile(), icon.getIconType(), targetView); + } + + public static void loadResource(RequestManager rm, @RawRes @DrawableRes @Nullable Integer resourceId, ImageView targetView) { + loadResource(rm, resourceId, null, targetView); + } + + public static void loadResource(RequestManager rm, @RawRes @DrawableRes @Nullable Integer resourceId, @Nullable Integer tint, ImageView targetView) { + setCommonOptions(rm.load(resourceId), null) + .listener(new ViewReadyListener<>(view -> { + if (tint != null) { + view.setColorFilter(tint); + } + setLayerType(targetView, IconType.INVALID); + })) + .into(targetView); + } + + public static void loadEntryIcon(RequestManager rm, VaultEntry entry, ImageView targetView) { + if (entry.hasIcon()) { + setCommonOptions(rm.load(entry.getIcon()), entry.getIcon().getType()).into(targetView); + } else { + // Clear any pending loads for targetView, so that the TextDrawable + // we're about to display doesn't get overwritten when that pending load finishes + rm.clear(targetView); + + setLayerType(targetView, IconType.INVALID); + TextDrawable drawable = TextDrawableHelper.generate(entry.getIssuer(), entry.getName(), targetView); + targetView.setImageDrawable(drawable); + } + } + + private static void load(RequestBuilder rb, IconType iconType, ImageView targetView) { + setCommonOptions(rb, iconType).into(targetView); + } + + public static RequestBuilder setCommonOptions(RequestBuilder rb, IconType iconType) { + if (iconType != null) { + rb = rb.set(VaultEntryIconLoader.ICON_TYPE, iconType) + .listener(new ViewReadyListener<>(targetView -> { + targetView.setImageTintList(null); + setLayerType(targetView, iconType); + })); + } + + return rb.diskCacheStrategy(DiskCacheStrategy.NONE) + .skipMemoryCache(false); + } + + /** + * Sets the layer type of the given ImageView based on the given IconType. If the + * icon type is SVG and SDK <= 27, the layer type is set to software. Otherwise, it + * is set to hardware. + */ + private static void setLayerType(ImageView view, IconType iconType) { + if (iconType == IconType.SVG && Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) { + view.setLayerType(ImageView.LAYER_TYPE_SOFTWARE, null); + return; + } + + view.setLayerType(ImageView.LAYER_TYPE_HARDWARE, null); + } + + private static class ViewReadyListener implements RequestListener { + private final Listener _listener; + + public ViewReadyListener(Listener listener) { + _listener = listener; + } + + @Override + public boolean onLoadFailed(@Nullable GlideException e, @Nullable Object model, @NonNull Target target, boolean isFirstResource) { + return false; + } + + @Override + public boolean onResourceReady(@NonNull T resource, @NonNull Object model, Target target, @NonNull DataSource dataSource, boolean isFirstResource) { + if (target instanceof DrawableImageViewTarget) { + DrawableImageViewTarget viewTarget = (DrawableImageViewTarget) target; + if (_listener != null) { + _listener.onConfigureImageView(viewTarget.getView()); + } + } + return false; + } + + public interface Listener { + void onConfigureImageView(ImageView targetView); + } + } +} diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/SvgDecoder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/SvgDecoder.java index 2eac92ab..99fc236b 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/SvgDecoder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/SvgDecoder.java @@ -22,7 +22,7 @@ public class SvgDecoder implements ResourceDecoder { @Override public boolean handles(@NonNull InputStream source, @NonNull Options options) { - return options.get(IconLoader.ICON_TYPE) == IconType.SVG; + return options.get(VaultEntryIconLoader.ICON_TYPE) == IconType.SVG; } public Resource decode( diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/UUIDKey.java b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconKey.java similarity index 52% rename from app/src/main/java/com/beemdevelopment/aegis/ui/glide/UUIDKey.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconKey.java index 1ff10d59..a816b0cc 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/UUIDKey.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconKey.java @@ -2,30 +2,30 @@ package com.beemdevelopment.aegis.ui.glide; import androidx.annotation.NonNull; +import com.beemdevelopment.aegis.vault.VaultEntryIcon; import com.bumptech.glide.load.Key; import java.security.MessageDigest; -import java.util.UUID; -public class UUIDKey implements Key { - private UUID _uuid; +public class VaultEntryIconKey implements Key { + private final VaultEntryIcon _icon; - public UUIDKey(UUID uuid) { - _uuid = uuid; + public VaultEntryIconKey(VaultEntryIcon icon) { + _icon = icon; } @Override public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { - messageDigest.update(_uuid.toString().getBytes(CHARSET)); + messageDigest.update(_icon.getHash()); } @Override public boolean equals(Object o) { - return _uuid.equals(o); + return _icon.equals(o); } @Override public int hashCode() { - return _uuid.hashCode(); + return _icon.hashCode(); } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/IconLoader.java b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconLoader.java similarity index 70% rename from app/src/main/java/com/beemdevelopment/aegis/ui/glide/IconLoader.java rename to app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconLoader.java index 2eb08036..b8b13c58 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/glide/IconLoader.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/glide/VaultEntryIconLoader.java @@ -3,7 +3,7 @@ package com.beemdevelopment.aegis.ui.glide; import androidx.annotation.NonNull; import com.beemdevelopment.aegis.icons.IconType; -import com.beemdevelopment.aegis.vault.VaultEntry; +import com.beemdevelopment.aegis.vault.VaultEntryIcon; import com.bumptech.glide.Priority; import com.bumptech.glide.load.DataSource; import com.bumptech.glide.load.Option; @@ -15,29 +15,29 @@ import com.bumptech.glide.load.model.MultiModelLoaderFactory; import java.nio.ByteBuffer; -public class IconLoader implements ModelLoader { +public class VaultEntryIconLoader implements ModelLoader { public static final Option ICON_TYPE = Option.memory("ICON_TYPE", IconType.INVALID); @Override - public LoadData buildLoadData(@NonNull VaultEntry model, int width, int height, @NonNull Options options) { - return new LoadData<>(new UUIDKey(model.getUUID()), new Fetcher(model)); + public LoadData buildLoadData(@NonNull VaultEntryIcon icon, int width, int height, @NonNull Options options) { + return new LoadData<>(new VaultEntryIconKey(icon), new Fetcher(icon)); } @Override - public boolean handles(@NonNull VaultEntry model) { + public boolean handles(@NonNull VaultEntryIcon icon) { return true; } public static class Fetcher implements DataFetcher { - private final VaultEntry _model; + private final VaultEntryIcon _icon; - private Fetcher(VaultEntry model) { - _model = model; + private Fetcher(VaultEntryIcon icon) { + _icon = icon; } @Override public void loadData(@NonNull Priority priority, @NonNull DataCallback callback) { - byte[] bytes = _model.getIcon(); + byte[] bytes = _icon.getBytes(); ByteBuffer buf = ByteBuffer.wrap(bytes); callback.onDataReady(buf); } @@ -65,11 +65,11 @@ public class IconLoader implements ModelLoader { } } - public static class Factory implements ModelLoaderFactory { + public static class Factory implements ModelLoaderFactory { @NonNull @Override - public ModelLoader build(@NonNull MultiModelLoaderFactory unused) { - return new IconLoader(); + public ModelLoader build(@NonNull MultiModelLoaderFactory unused) { + return new VaultEntryIconLoader(); } @Override diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java index 658eb152..f2958fa3 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/AssignIconHolder.java @@ -6,15 +6,10 @@ import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; -import com.amulyakhare.textdrawable.TextDrawable; import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.helpers.TextDrawableHelper; -import com.beemdevelopment.aegis.icons.IconType; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.models.AssignIconEntry; - import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; public class AssignIconHolder extends RecyclerView.ViewHolder implements AssignIconEntry.Listener { private View _view; @@ -44,37 +39,15 @@ public class AssignIconHolder extends RecyclerView.ViewHolder implements AssignI _issuer.setText(entry.getEntry().getIssuer()); _accountName.setText(entry.getEntry().getName()); - if(!entry.getEntry().hasIcon()) { - TextDrawable drawable = TextDrawableHelper.generate(entry.getEntry().getIssuer(), entry.getEntry().getName(), _oldIcon); - _oldIcon.setImageDrawable(drawable); - } else { - Glide.with(_view.getContext()) - .asDrawable() - .load(entry.getEntry()) - .set(IconLoader.ICON_TYPE, entry.getEntry().getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .into(_oldIcon); - } - + GlideHelper.loadEntryIcon(Glide.with(_view.getContext()), _entry.getEntry(), _oldIcon); setNewIcon(); } private void setNewIcon() { if (_entry.getNewIcon() != null) { - Glide.with(_view.getContext()) - .asDrawable() - .load(_entry.getNewIcon().getFile()) - .set(IconLoader.ICON_TYPE, _entry.getNewIcon().getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_newIcon); + GlideHelper.loadIcon(Glide.with(_view.getContext()), _entry.getNewIcon(), _newIcon); } else { - Glide.with(_view.getContext()) - .asDrawable() - .load(R.drawable.ic_icon_unselected) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_newIcon); + GlideHelper.loadResource(Glide.with(_view.getContext()), R.drawable.ic_icon_unselected, _newIcon); } _btnReset.setVisibility(_entry.getNewIcon() != null ? View.VISIBLE : View.INVISIBLE); diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java index 1ca87ad3..dc3d1f04 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryHolder.java @@ -11,15 +11,12 @@ import android.widget.TextView; import androidx.fragment.app.Fragment; import androidx.recyclerview.widget.RecyclerView; -import com.amulyakhare.textdrawable.TextDrawable; import com.beemdevelopment.aegis.AccountNamePosition; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.ViewMode; import com.beemdevelopment.aegis.helpers.AnimationsHelper; -import com.beemdevelopment.aegis.helpers.IconViewHelper; import com.beemdevelopment.aegis.helpers.SimpleAnimationEndListener; -import com.beemdevelopment.aegis.helpers.TextDrawableHelper; import com.beemdevelopment.aegis.helpers.ThemeHelper; import com.beemdevelopment.aegis.helpers.UiRefresher; import com.beemdevelopment.aegis.otp.HotpInfo; @@ -28,10 +25,9 @@ import com.beemdevelopment.aegis.otp.OtpInfoException; import com.beemdevelopment.aegis.otp.SteamInfo; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.otp.YandexInfo; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.vault.VaultEntry; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; public class EntryHolder extends RecyclerView.ViewHolder { private static final float DEFAULT_ALPHA = 1.0f; @@ -197,19 +193,7 @@ public class EntryHolder extends RecyclerView.ViewHolder { } public void loadIcon(Fragment fragment) { - if (_entry.hasIcon()) { - IconViewHelper.setLayerType(_profileDrawable, _entry.getIconType()); - Glide.with(fragment) - .asDrawable() - .load(_entry.getIcon()) - .set(IconLoader.ICON_TYPE, _entry.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_profileDrawable); - } else { - TextDrawable drawable = TextDrawableHelper.generate(_entry.getIssuer(), _entry.getName(), _profileDrawable); - _profileDrawable.setImageDrawable(drawable); - } + GlideHelper.loadEntryIcon(Glide.with(fragment), _entry, _profileDrawable); } public ImageView getIconView() { diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java index d67448ec..0b0c6974 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/EntryListView.java @@ -23,8 +23,8 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.beemdevelopment.aegis.CopyBehavior; import com.beemdevelopment.aegis.AccountNamePosition; +import com.beemdevelopment.aegis.CopyBehavior; import com.beemdevelopment.aegis.Preferences; import com.beemdevelopment.aegis.R; import com.beemdevelopment.aegis.SortCategory; @@ -36,7 +36,7 @@ import com.beemdevelopment.aegis.helpers.ThemeHelper; import com.beemdevelopment.aegis.helpers.UiRefresher; import com.beemdevelopment.aegis.otp.TotpInfo; import com.beemdevelopment.aegis.ui.dialogs.Dialogs; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.beemdevelopment.aegis.ui.models.VaultGroupModel; import com.beemdevelopment.aegis.util.UUIDMap; import com.beemdevelopment.aegis.vault.VaultEntry; @@ -45,7 +45,6 @@ import com.bumptech.glide.Glide; import com.bumptech.glide.ListPreloader; import com.bumptech.glide.RequestBuilder; import com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.util.ViewPreloadSizeProvider; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.chip.Chip; @@ -739,18 +738,16 @@ public class EntryListView extends Fragment implements EntryAdapter.Listener { if (!entry.hasIcon()) { return Collections.emptyList(); } + return Collections.singletonList(entry); } @Nullable @Override public RequestBuilder getPreloadRequestBuilder(@NonNull VaultEntry entry) { - return Glide.with(EntryListView.this) - .asDrawable() - .load(entry) - .set(IconLoader.ICON_TYPE, entry.getIconType()) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false); + RequestBuilder rb = Glide.with(EntryListView.this) + .load(entry.getIcon()); + return GlideHelper.setCommonOptions(rb, entry.getIcon().getType()); } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconHolder.java b/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconHolder.java index a583d3fe..70f7de26 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconHolder.java +++ b/app/src/main/java/com/beemdevelopment/aegis/ui/views/IconHolder.java @@ -8,13 +8,11 @@ import android.widget.TextView; import androidx.recyclerview.widget.RecyclerView; import com.beemdevelopment.aegis.R; -import com.beemdevelopment.aegis.helpers.IconViewHelper; import com.beemdevelopment.aegis.helpers.ThemeHelper; import com.beemdevelopment.aegis.icons.IconPack; import com.beemdevelopment.aegis.icons.IconType; -import com.beemdevelopment.aegis.ui.glide.IconLoader; +import com.beemdevelopment.aegis.ui.glide.GlideHelper; import com.bumptech.glide.Glide; -import com.bumptech.glide.load.engine.DiskCacheStrategy; import java.io.File; @@ -42,18 +40,9 @@ public class IconHolder extends RecyclerView.ViewHolder { public void loadIcon(Context context) { if (_isCustom) { int tint = ThemeHelper.getThemeColor(R.attr.iconColorPrimary, context.getTheme()); - _imageView.setColorFilter(tint); - _imageView.setImageResource(R.drawable.ic_plus_black_24dp); + GlideHelper.loadResource(Glide.with(context), R.drawable.ic_plus_black_24dp, tint, _imageView); } else { - _imageView.setImageTintList(null); - IconViewHelper.setLayerType(_imageView, _iconType); - Glide.with(context) - .asDrawable() - .load(_iconFile) - .set(IconLoader.ICON_TYPE, _iconType) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(false) - .into(_imageView); + GlideHelper.loadIconFile(Glide.with(context), _iconFile, _iconType, _imageView); } } } diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java index 46a56b95..90940f2f 100644 --- a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntry.java @@ -1,8 +1,5 @@ package com.beemdevelopment.aegis.vault; -import com.beemdevelopment.aegis.encoding.Base64; -import com.beemdevelopment.aegis.encoding.EncodingException; -import com.beemdevelopment.aegis.icons.IconType; import com.beemdevelopment.aegis.otp.GoogleAuthInfo; import com.beemdevelopment.aegis.otp.OtpInfo; import com.beemdevelopment.aegis.otp.OtpInfoException; @@ -14,7 +11,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.Arrays; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.UUID; @@ -23,8 +20,7 @@ public class VaultEntry extends UUIDMap.Value { private String _name = ""; private String _issuer = ""; private OtpInfo _info; - private byte[] _icon; - private IconType _iconType = IconType.INVALID; + private VaultEntryIcon _icon; private boolean _isFavorite; private int _usageCount; private String _note = ""; @@ -61,8 +57,7 @@ public class VaultEntry extends UUIDMap.Value { obj.put("issuer", _issuer); obj.put("note", _note); obj.put("favorite", _isFavorite); - obj.put("icon", _icon == null ? JSONObject.NULL : Base64.encode(_icon)); - obj.put("icon_mime", _icon == null ? null : _iconType.toMimeType()); + VaultEntryIcon.toJson(_icon, obj); obj.put("info", _info.toJson()); JSONArray groupUuids = new JSONArray(); @@ -107,21 +102,11 @@ public class VaultEntry extends UUIDMap.Value { entry.setOldGroup(JsonUtils.optString(obj, "group")); } - Object icon = obj.get("icon"); - if (icon != JSONObject.NULL) { - String mime = JsonUtils.optString(obj, "icon_mime"); - - IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime); - if (iconType == IconType.INVALID) { - throw new VaultEntryException(String.format("Bad icon MIME type: %s", mime)); - } - - byte[] iconBytes = Base64.decode((String) icon); - entry.setIcon(iconBytes, iconType); - } + VaultEntryIcon icon = VaultEntryIcon.fromJson(obj); + entry.setIcon(icon); return entry; - } catch (OtpInfoException | JSONException | EncodingException e) { + } catch (OtpInfoException | JSONException e) { throw new VaultEntryException(e); } } @@ -138,14 +123,10 @@ public class VaultEntry extends UUIDMap.Value { return _groups; } - public byte[] getIcon() { + public VaultEntryIcon getIcon() { return _icon; } - public IconType getIconType() { - return _iconType; - } - public OtpInfo getInfo() { return _info; } @@ -154,9 +135,15 @@ public class VaultEntry extends UUIDMap.Value { return _usageCount; } - public String getNote() { return _note; } + public String getNote() { + return _note; + } - public boolean isFavorite() { return _isFavorite; }; + public boolean isFavorite() { + return _isFavorite; + } + + ; public void setName(String name) { _name = name; @@ -188,20 +175,25 @@ public class VaultEntry extends UUIDMap.Value { _info = info; } - public void setIcon(byte[] icon, IconType iconType) { + public void setIcon(VaultEntryIcon icon) { _icon = icon; - _iconType = iconType; } public boolean hasIcon() { return _icon != null; } - public void setUsageCount(int usageCount) { _usageCount = usageCount; } + public void setUsageCount(int usageCount) { + _usageCount = usageCount; + } - public void setNote(String note) { _note = note; } + public void setNote(String note) { + _note = note; + } - public void setIsFavorite(boolean isFavorite) { _isFavorite = isFavorite; } + public void setIsFavorite(boolean isFavorite) { + _isFavorite = isFavorite; + } void setOldGroup(String oldGroup) { _oldGroup = oldGroup; @@ -230,8 +222,7 @@ public class VaultEntry extends UUIDMap.Value { return getName().equals(entry.getName()) && getIssuer().equals(entry.getIssuer()) && getInfo().equals(entry.getInfo()) - && Arrays.equals(getIcon(), entry.getIcon()) - && getIconType().equals(entry.getIconType()) + && Objects.equals(getIcon(), entry.getIcon()) && getNote().equals(entry.getNote()) && isFavorite() == entry.isFavorite() && getGroups().equals(entry.getGroups()); diff --git a/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntryIcon.java b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntryIcon.java new file mode 100644 index 00000000..44c7babb --- /dev/null +++ b/app/src/main/java/com/beemdevelopment/aegis/vault/VaultEntryIcon.java @@ -0,0 +1,108 @@ +package com.beemdevelopment.aegis.vault; + +import com.beemdevelopment.aegis.encoding.Base64; +import com.beemdevelopment.aegis.encoding.EncodingException; +import com.beemdevelopment.aegis.encoding.Hex; +import com.beemdevelopment.aegis.icons.IconType; +import com.beemdevelopment.aegis.util.JsonUtils; +import com.google.common.hash.HashCode; + +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +public class VaultEntryIcon implements Serializable { + private final byte[] _bytes; + private final byte[] _hash; + private final IconType _type; + + public VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type) { + this(bytes, type, generateHash(bytes, type)); + } + + VaultEntryIcon(byte @NonNull [] bytes, @NonNull IconType type, byte @NonNull [] hash) { + _bytes = bytes; + _hash = hash; + _type = type; + } + + public byte @NonNull [] getBytes() { + return _bytes; + } + + public byte @NonNull [] getHash() { + return _hash; + } + + @NonNull + public IconType getType() { + return _type; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof VaultEntryIcon)) { + return false; + } + + VaultEntryIcon entry = (VaultEntryIcon) o; + return Arrays.equals(getHash(), entry.getHash()); + } + + @Override + public int hashCode() { + return HashCode.fromBytes(_hash).asInt(); + } + + static void toJson(@Nullable VaultEntryIcon icon, @NonNull JSONObject obj) throws JSONException { + obj.put("icon", icon == null ? JSONObject.NULL : Base64.encode(icon.getBytes())); + if (icon != null) { + obj.put("icon_mime", icon.getType().toMimeType()); + obj.put("icon_hash", Hex.encode(icon.getHash())); + } + } + + @Nullable + static VaultEntryIcon fromJson(@NonNull JSONObject obj) throws VaultEntryException { + try { + Object icon = obj.get("icon"); + if (icon == JSONObject.NULL) { + return null; + } + + String mime = JsonUtils.optString(obj, "icon_mime"); + IconType iconType = mime == null ? IconType.JPEG : IconType.fromMimeType(mime); + if (iconType == IconType.INVALID) { + throw new VaultEntryException(String.format("Bad icon MIME type: %s", mime)); + } + + byte[] iconBytes = Base64.decode((String) icon); + String iconHashStr = JsonUtils.optString(obj, "icon_hash"); + if (iconHashStr != null) { + byte[] iconHash = Hex.decode(iconHashStr); + return new VaultEntryIcon(iconBytes, iconType, iconHash); + } + + return new VaultEntryIcon(iconBytes, iconType); + } catch (JSONException | EncodingException e) { + throw new VaultEntryException(e); + } + } + + private static byte @NonNull [] generateHash(byte @NonNull [] bytes, @NonNull IconType type) { + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(type.toMimeType().getBytes(StandardCharsets.UTF_8)); + return md.digest(bytes); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } +}