From 622d4055d95e49f1d13321643a71251ceb201683 Mon Sep 17 00:00:00 2001 From: tarsin Date: Mon, 13 Jun 2022 12:42:00 +0800 Subject: [PATCH] Reformat code --- .../java/com/hippo/beerbelly/BeerBelly.java | 55 +- .../com/hippo/beerbelly/DiskLruCache.java | 1577 +++++++++-------- .../java/com/hippo/beerbelly/LruCache.java | 24 +- .../com/hippo/beerbelly/SimpleDiskCache.java | 88 +- .../com/hippo/beerbelly/StrictLineReader.java | 272 +-- .../main/java/com/hippo/beerbelly/Util.java | 125 +- .../hippo/ehviewer/ui/GalleryActivity.java | 22 +- .../ehviewer/ui/scene/FavoritesScene.java | 1 - .../ehviewer/ui/scene/GalleryDetailScene.java | 4 - .../ehviewer/ui/scene/GalleryListScene.java | 1 - .../com/hippo/glgallery/DownUpDetector.java | 1 + .../com/hippo/glgallery/GalleryProvider.java | 1 + .../java/com/hippo/glgallery/GalleryView.java | 1 + .../hippo/glgallery/GestureRecognizer.java | 1 + .../hippo/glview/glrenderer/GLES20Canvas.java | 1 + .../com/hippo/glview/widget/GLEdgeView.java | 1 + .../java/com/hippo/scene/StageActivity.java | 22 +- .../java/com/hippo/widget/BatteryView.java | 28 +- .../main/java/com/hippo/widget/TextClock.java | 34 +- .../com/hippo/yorozuya/AnimationUtils.java | 1 + .../java/com/hippo/yorozuya/MathUtils.java | 1 + .../com/hippo/yorozuya/ResourcesUtils.java | 1 + .../thread/InfiniteThreadExecutor.java | 5 +- .../yorozuya/thread/SerialThreadExecutor.java | 10 +- .../main/res/layout/gallery_detail_header.xml | 4 +- 25 files changed, 1158 insertions(+), 1123 deletions(-) diff --git a/app/src/main/java/com/hippo/beerbelly/BeerBelly.java b/app/src/main/java/com/hippo/beerbelly/BeerBelly.java index 90ed5c81b..da9ce4f13 100644 --- a/app/src/main/java/com/hippo/beerbelly/BeerBelly.java +++ b/app/src/main/java/com/hippo/beerbelly/BeerBelly.java @@ -33,15 +33,13 @@ import java.io.OutputStream; public abstract class BeerBelly { private static final String TAG = BeerBelly.class.getSimpleName(); - + private final boolean mHasMemoryCache; + private final boolean mHasDiskCache; @Nullable private MemoryCache mMemoryCache; @Nullable private DiskCache mDiskCache; - private final boolean mHasMemoryCache; - private final boolean mHasDiskCache; - public BeerBelly(BeerBellyParams params) { params.isValid(); mHasMemoryCache = params.hasMemoryCache; @@ -220,7 +218,7 @@ public abstract class BeerBelly { /** * Put value to memory cache * - * @param key the key + * @param key the key * @param value the value * @return false if no memory cache */ @@ -237,7 +235,7 @@ public abstract class BeerBelly { /** * Put value to disk cache * - * @param key the key + * @param key the key * @param value the value * @return false if no disk cache or get error */ @@ -252,7 +250,7 @@ public abstract class BeerBelly { /** * Put value to memory cache and disk cache * - * @param key the key + * @param key the key * @param value the value */ public void put(@NonNull String key, @NonNull V value) { @@ -286,9 +284,8 @@ public abstract class BeerBelly { } /** - * * @param key the key - * @param is the input stream to store + * @param is the input stream to store * @return false if no disk cache or get error */ public boolean putRawToDisk(@NonNull String key, @NonNull InputStream is) { @@ -381,26 +378,6 @@ public abstract class BeerBelly { } } - private class MemoryCache extends LruCache { - - public BeerBelly mParent; - - public MemoryCache(int maxSize, BeerBelly parent) { - super(maxSize); - mParent = parent; - } - - @Override - protected int sizeOf(String key, E value) { - return mParent.sizeOf(key, value); - } - - @Override - protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) { - mParent.memoryEntryRemoved(evicted, key, oldValue, newValue); - } - } - private static class DiskCache { private static final int IO_BUFFER_SIZE = 8 * 1024; @@ -492,4 +469,24 @@ public abstract class BeerBelly { } } } + + private class MemoryCache extends LruCache { + + public BeerBelly mParent; + + public MemoryCache(int maxSize, BeerBelly parent) { + super(maxSize); + mParent = parent; + } + + @Override + protected int sizeOf(String key, E value) { + return mParent.sizeOf(key, value); + } + + @Override + protected void entryRemoved(boolean evicted, String key, E oldValue, E newValue) { + mParent.memoryEntryRemoved(evicted, key, oldValue, newValue); + } + } } diff --git a/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java b/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java index 175537d43..4d8630963 100644 --- a/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java +++ b/app/src/main/java/com/hippo/beerbelly/DiskLruCache.java @@ -86,18 +86,18 @@ import java.util.regex.Pattern; * responding appropriately. */ public final class DiskLruCache implements Closeable { - static final String JOURNAL_FILE = "journal"; - static final String JOURNAL_FILE_TEMP = "journal.tmp"; - static final String JOURNAL_FILE_BACKUP = "journal.bkp"; - static final String MAGIC = "libcore.io.DiskLruCache"; - static final String VERSION_1 = "1"; - static final long ANY_SEQUENCE_NUMBER = -1; - static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}"; - static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN); - private static final String CLEAN = "CLEAN"; - private static final String DIRTY = "DIRTY"; - private static final String REMOVE = "REMOVE"; - private static final String READ = "READ"; + static final String JOURNAL_FILE = "journal"; + static final String JOURNAL_FILE_TEMP = "journal.tmp"; + static final String JOURNAL_FILE_BACKUP = "journal.bkp"; + static final String MAGIC = "libcore.io.DiskLruCache"; + static final String VERSION_1 = "1"; + static final long ANY_SEQUENCE_NUMBER = -1; + static final String STRING_KEY_PATTERN = "[a-z0-9_-]{1,120}"; + static final Pattern LEGAL_KEY_PATTERN = Pattern.compile(STRING_KEY_PATTERN); + private static final String CLEAN = "CLEAN"; + private static final String DIRTY = "DIRTY"; + private static final String REMOVE = "REMOVE"; + private static final String READ = "READ"; /* * This cache uses a journal file named "journal". A typical journal file @@ -138,813 +138,846 @@ public final class DiskLruCache implements Closeable { * "journal.tmp" will be used during compaction; that file should be deleted if * it exists when the cache is opened. */ + private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { + @Override + public void write(int b) { + // Eat all writes silently. Nom nom. + } + }; + /** + * This cache uses a single background thread to evict entries. + */ + final ThreadPoolExecutor executorService = + new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + private final File directory; + private final File journalFile; + private final File journalFileTmp; + private final File journalFileBackup; + private final int appVersion; + private final int valueCount; + private final LinkedHashMap lruEntries = + new LinkedHashMap<>(0, 0.75f, true); + private long maxSize; + private long size = 0; + private Writer journalWriter; + private int redundantOpCount; + /** + * To differentiate between old and current snapshots, each entry is given + * a sequence number each time an edit is committed. A snapshot is stale if + * its sequence number is not equal to its entry's sequence number. + */ + private long nextSequenceNumber = 0; private final Callable cleanupCallable = new Callable<>() { + public Void call() throws Exception { + synchronized (DiskLruCache.this) { + if (journalWriter == null) { + return null; // Closed. + } + trimToSize(); + if (journalRebuildRequired()) { + rebuildJournal(); + redundantOpCount = 0; + } + } + return null; + } + }; - private final File directory; - private final File journalFile; - private final File journalFileTmp; - private final File journalFileBackup; - private final int appVersion; - private long maxSize; - private final int valueCount; - private long size = 0; - private Writer journalWriter; - private final LinkedHashMap lruEntries = - new LinkedHashMap<>(0, 0.75f, true); - private int redundantOpCount; + private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { + this.directory = directory; + this.appVersion = appVersion; + this.journalFile = new File(directory, JOURNAL_FILE); + this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); + this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); + this.valueCount = valueCount; + this.maxSize = maxSize; + } - /** - * To differentiate between old and current snapshots, each entry is given - * a sequence number each time an edit is committed. A snapshot is stale if - * its sequence number is not equal to its entry's sequence number. - */ - private long nextSequenceNumber = 0; + /** + * Opens the cache in {@code directory}, creating a cache if none exists + * there. + * + * @param directory a writable directory + * @param valueCount the number of values per cache entry. Must be positive. + * @param maxSize the maximum number of bytes this cache should use to store + * @throws IOException if reading or writing the cache directory fails + */ + public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) + throws IOException { + if (maxSize <= 0) { + throw new IllegalArgumentException("maxSize <= 0"); + } + if (valueCount <= 0) { + throw new IllegalArgumentException("valueCount <= 0"); + } - /** This cache uses a single background thread to evict entries. */ - final ThreadPoolExecutor executorService = - new ThreadPoolExecutor(0, 1, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); - private final Callable cleanupCallable = new Callable<>() { - public Void call() throws Exception { - synchronized (DiskLruCache.this) { + // If a bkp file exists, use it instead. + File backupFile = new File(directory, JOURNAL_FILE_BACKUP); + if (backupFile.exists()) { + File journalFile = new File(directory, JOURNAL_FILE); + // If journal file also exists just delete backup file. + if (journalFile.exists()) { + backupFile.delete(); + } else { + renameTo(backupFile, journalFile, false); + } + } + + // Prefer to pick up where we left off. + DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + if (cache.journalFile.exists()) { + try { + cache.readJournal(); + cache.processJournal(); + return cache; + } catch (IOException journalIsCorrupt) { + System.out + .println("DiskLruCache " + + directory + + " is corrupt: " + + journalIsCorrupt.getMessage() + + ", removing"); + cache.delete(); + } + } + + // Create a new empty cache. + directory.mkdirs(); + cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); + cache.rebuildJournal(); + return cache; + } + + private static void deleteIfExists(File file) throws IOException { + if (file.exists() && !file.delete()) { + throw new IOException(); + } + } + + private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { + if (deleteDestination) { + deleteIfExists(to); + } + if (!from.renameTo(to)) { + throw new IOException(); + } + } + + private static String inputStreamToString(InputStream in) throws IOException { + return Util.readFully(new InputStreamReader(in, Util.UTF_8)); + } + + private void readJournal() throws IOException { + StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); + try { + String magic = reader.readLine(); + String version = reader.readLine(); + String appVersionString = reader.readLine(); + String valueCountString = reader.readLine(); + String blank = reader.readLine(); + if (!MAGIC.equals(magic) + || !VERSION_1.equals(version) + || !Integer.toString(appVersion).equals(appVersionString) + || !Integer.toString(valueCount).equals(valueCountString) + || !"".equals(blank)) { + throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " + + valueCountString + ", " + blank + "]"); + } + + int lineCount = 0; + while (true) { + try { + readJournalLine(reader.readLine()); + lineCount++; + } catch (EOFException endOfJournal) { + break; + } + } + redundantOpCount = lineCount - lruEntries.size(); + + // If we ended on a truncated line, rebuild the journal before appending to it. + if (reader.hasUnterminatedLine()) { + rebuildJournal(); + } else { + journalWriter = new BufferedWriter(new OutputStreamWriter( + new FileOutputStream(journalFile, true), Util.US_ASCII)); + } + } finally { + Util.closeQuietly(reader); + } + } + + private void readJournalLine(String line) throws IOException { + int firstSpace = line.indexOf(' '); + if (firstSpace == -1) { + throw new IOException("unexpected journal line: " + line); + } + + int keyBegin = firstSpace + 1; + int secondSpace = line.indexOf(' ', keyBegin); + final String key; + if (secondSpace == -1) { + key = line.substring(keyBegin); + if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { + lruEntries.remove(key); + return; + } + } else { + key = line.substring(keyBegin, secondSpace); + } + + Entry entry = lruEntries.get(key); + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } + + if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { + String[] parts = line.substring(secondSpace + 1).split(" "); + entry.readable = true; + entry.currentEditor = null; + entry.setLengths(parts); + } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { + entry.currentEditor = new Editor(entry); + } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { + // This work was already done by calling lruEntries.get(). + } else { + throw new IOException("unexpected journal line: " + line); + } + } + + /** + * Computes the initial size and collects garbage as a part of opening the + * cache. Dirty entries are assumed to be inconsistent and will be deleted. + */ + private void processJournal() throws IOException { + deleteIfExists(journalFileTmp); + for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { + Entry entry = i.next(); + if (entry.currentEditor == null) { + for (int t = 0; t < valueCount; t++) { + size += entry.lengths[t]; + } + } else { + entry.currentEditor = null; + for (int t = 0; t < valueCount; t++) { + deleteIfExists(entry.getCleanFile(t)); + deleteIfExists(entry.getDirtyFile(t)); + } + i.remove(); + } + } + } + + /** + * Creates a new journal that omits redundant information. This replaces the + * current journal if it exists. + */ + private synchronized void rebuildJournal() throws IOException { + if (journalWriter != null) { + journalWriter.close(); + } + + try (Writer writer = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII))) { + writer.write(MAGIC); + writer.write("\n"); + writer.write(VERSION_1); + writer.write("\n"); + writer.write(Integer.toString(appVersion)); + writer.write("\n"); + writer.write(Integer.toString(valueCount)); + writer.write("\n"); + writer.write("\n"); + + for (Entry entry : lruEntries.values()) { + if (entry.currentEditor != null) { + writer.write(DIRTY + ' ' + entry.key + '\n'); + } else { + writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + } + } + } + + if (journalFile.exists()) { + renameTo(journalFile, journalFileBackup, true); + } + renameTo(journalFileTmp, journalFile, false); + journalFileBackup.delete(); + + journalWriter = new BufferedWriter( + new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); + } + + /** + * Return {@code true} if it contains the key + */ + public synchronized boolean contain(String key) { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + return entry != null && entry.readable; + } + + /** + * Returns a snapshot of the entry named {@code key}, or null if it doesn't + * exist is not currently readable. If a value is returned, it is moved to + * the head of the LRU queue. + */ + public synchronized Snapshot get(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null) { + return null; + } + + if (!entry.readable) { + return null; + } + + // Open all streams eagerly to guarantee that we see a single published + // snapshot. If we opened streams lazily then the streams could come + // from different edits. + InputStream[] ins = new InputStream[valueCount]; + try { + for (int i = 0; i < valueCount; i++) { + ins[i] = new FileInputStream(entry.getCleanFile(i)); + } + } catch (FileNotFoundException e) { + // A file must have been deleted manually! + for (int i = 0; i < valueCount; i++) { + if (ins[i] != null) { + Util.closeQuietly(ins[i]); + } else { + break; + } + } + return null; + } + + redundantOpCount++; + journalWriter.append(READ + ' ').append(key).append(String.valueOf('\n')); + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); + } + + /** + * Returns an editor for the entry named {@code key}, or null if another + * edit is in progress. + */ + public Editor edit(String key) throws IOException { + return edit(key, ANY_SEQUENCE_NUMBER); + } + + private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null + || entry.sequenceNumber != expectedSequenceNumber)) { + return null; // Snapshot is stale. + } + if (entry == null) { + entry = new Entry(key); + lruEntries.put(key, entry); + } else if (entry.currentEditor != null) { + return null; // Another edit is in progress. + } + + Editor editor = new Editor(entry); + entry.currentEditor = editor; + + // Flush the journal before creating files to prevent file leaks. + journalWriter.write(DIRTY + ' ' + key + '\n'); + journalWriter.flush(); + return editor; + } + + /** + * Returns the directory where this cache stores its data. + */ + public File getDirectory() { + return directory; + } + + /** + * Returns the maximum number of bytes that this cache should use to store + * its data. + */ + public synchronized long getMaxSize() { + return maxSize; + } + + /** + * Changes the maximum number of bytes the cache can store and queues a job + * to trim the existing store, if necessary. + */ + public synchronized void setMaxSize(long maxSize) { + this.maxSize = maxSize; + executorService.submit(cleanupCallable); + } + + /** + * Returns the number of bytes currently being used to store the values in + * this cache. This may be greater than the max size if a background + * deletion is pending. + */ + public synchronized long size() { + return size; + } + + private synchronized void completeEdit(Editor editor, boolean success) throws IOException { + Entry entry = editor.entry; + if (entry.currentEditor != editor) { + throw new IllegalStateException(); + } + + // If this edit is creating the entry for the first time, every index must have a value. + if (success && !entry.readable) { + for (int i = 0; i < valueCount; i++) { + if (!editor.written[i]) { + editor.abort(); + throw new IllegalStateException("Newly created entry didn't create value for index " + i); + } + if (!entry.getDirtyFile(i).exists()) { + editor.abort(); + return; + } + } + } + + for (int i = 0; i < valueCount; i++) { + File dirty = entry.getDirtyFile(i); + if (success) { + if (dirty.exists()) { + File clean = entry.getCleanFile(i); + dirty.renameTo(clean); + long oldLength = entry.lengths[i]; + long newLength = clean.length(); + entry.lengths[i] = newLength; + size = size - oldLength + newLength; + } + } else { + deleteIfExists(dirty); + } + } + + redundantOpCount++; + entry.currentEditor = null; + if (entry.readable | success) { + entry.readable = true; + journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); + if (success) { + entry.sequenceNumber = nextSequenceNumber++; + } + } else { + lruEntries.remove(entry.key); + journalWriter.write(REMOVE + ' ' + entry.key + '\n'); + } + journalWriter.flush(); + + if (size > maxSize || journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + } + + /** + * We only rebuild the journal when it will halve the size of the journal + * and eliminate at least 2000 ops. + */ + private boolean journalRebuildRequired() { + final int redundantOpCompactThreshold = 2000; + return redundantOpCount >= redundantOpCompactThreshold // + && redundantOpCount >= lruEntries.size(); + } + + /** + * Drops the entry for {@code key} if it exists and can be removed. Entries + * actively being edited cannot be removed. + * + * @return true if an entry was removed. + */ + public synchronized boolean remove(String key) throws IOException { + checkNotClosed(); + validateKey(key); + Entry entry = lruEntries.get(key); + if (entry == null || entry.currentEditor != null) { + return false; + } + + for (int i = 0; i < valueCount; i++) { + File file = entry.getCleanFile(i); + if (file.exists() && !file.delete()) { + throw new IOException("failed to delete " + file); + } + size -= entry.lengths[i]; + entry.lengths[i] = 0; + } + + redundantOpCount++; + journalWriter.append(REMOVE + ' ').append(key).append(String.valueOf('\n')); + lruEntries.remove(key); + + if (journalRebuildRequired()) { + executorService.submit(cleanupCallable); + } + + return true; + } + + /** + * Returns true if this cache has been closed. + */ + public synchronized boolean isClosed() { + return journalWriter == null; + } + + private void checkNotClosed() { if (journalWriter == null) { - return null; // Closed. + throw new IllegalStateException("cache is closed"); + } + } + + /** + * Force buffered operations to the filesystem. + */ + public synchronized void flush() throws IOException { + checkNotClosed(); + trimToSize(); + journalWriter.flush(); + } + + /** + * Closes this cache. Stored values will remain on the filesystem. + */ + public synchronized void close() throws IOException { + if (journalWriter == null) { + return; // Already closed. + } + for (Entry entry : new ArrayList<>(lruEntries.values())) { + if (entry.currentEditor != null) { + entry.currentEditor.abort(); + } } trimToSize(); - if (journalRebuildRequired()) { - rebuildJournal(); - redundantOpCount = 0; + journalWriter.close(); + journalWriter = null; + } + + private void trimToSize() throws IOException { + while (size > maxSize) { + Map.Entry toEvict = lruEntries.entrySet().iterator().next(); + remove(toEvict.getKey()); } - } - return null; - } - }; - - private DiskLruCache(File directory, int appVersion, int valueCount, long maxSize) { - this.directory = directory; - this.appVersion = appVersion; - this.journalFile = new File(directory, JOURNAL_FILE); - this.journalFileTmp = new File(directory, JOURNAL_FILE_TEMP); - this.journalFileBackup = new File(directory, JOURNAL_FILE_BACKUP); - this.valueCount = valueCount; - this.maxSize = maxSize; - } - - /** - * Opens the cache in {@code directory}, creating a cache if none exists - * there. - * - * @param directory a writable directory - * @param valueCount the number of values per cache entry. Must be positive. - * @param maxSize the maximum number of bytes this cache should use to store - * @throws IOException if reading or writing the cache directory fails - */ - public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize) - throws IOException { - if (maxSize <= 0) { - throw new IllegalArgumentException("maxSize <= 0"); - } - if (valueCount <= 0) { - throw new IllegalArgumentException("valueCount <= 0"); - } - - // If a bkp file exists, use it instead. - File backupFile = new File(directory, JOURNAL_FILE_BACKUP); - if (backupFile.exists()) { - File journalFile = new File(directory, JOURNAL_FILE); - // If journal file also exists just delete backup file. - if (journalFile.exists()) { - backupFile.delete(); - } else { - renameTo(backupFile, journalFile, false); - } - } - - // Prefer to pick up where we left off. - DiskLruCache cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); - if (cache.journalFile.exists()) { - try { - cache.readJournal(); - cache.processJournal(); - return cache; - } catch (IOException journalIsCorrupt) { - System.out - .println("DiskLruCache " - + directory - + " is corrupt: " - + journalIsCorrupt.getMessage() - + ", removing"); - cache.delete(); - } - } - - // Create a new empty cache. - directory.mkdirs(); - cache = new DiskLruCache(directory, appVersion, valueCount, maxSize); - cache.rebuildJournal(); - return cache; - } - - private void readJournal() throws IOException { - StrictLineReader reader = new StrictLineReader(new FileInputStream(journalFile), Util.US_ASCII); - try { - String magic = reader.readLine(); - String version = reader.readLine(); - String appVersionString = reader.readLine(); - String valueCountString = reader.readLine(); - String blank = reader.readLine(); - if (!MAGIC.equals(magic) - || !VERSION_1.equals(version) - || !Integer.toString(appVersion).equals(appVersionString) - || !Integer.toString(valueCount).equals(valueCountString) - || !"".equals(blank)) { - throw new IOException("unexpected journal header: [" + magic + ", " + version + ", " - + valueCountString + ", " + blank + "]"); - } - - int lineCount = 0; - while (true) { - try { - readJournalLine(reader.readLine()); - lineCount++; - } catch (EOFException endOfJournal) { - break; - } - } - redundantOpCount = lineCount - lruEntries.size(); - - // If we ended on a truncated line, rebuild the journal before appending to it. - if (reader.hasUnterminatedLine()) { - rebuildJournal(); - } else { - journalWriter = new BufferedWriter(new OutputStreamWriter( - new FileOutputStream(journalFile, true), Util.US_ASCII)); - } - } finally { - Util.closeQuietly(reader); - } - } - - private void readJournalLine(String line) throws IOException { - int firstSpace = line.indexOf(' '); - if (firstSpace == -1) { - throw new IOException("unexpected journal line: " + line); - } - - int keyBegin = firstSpace + 1; - int secondSpace = line.indexOf(' ', keyBegin); - final String key; - if (secondSpace == -1) { - key = line.substring(keyBegin); - if (firstSpace == REMOVE.length() && line.startsWith(REMOVE)) { - lruEntries.remove(key); - return; - } - } else { - key = line.substring(keyBegin, secondSpace); - } - - Entry entry = lruEntries.get(key); - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } - - if (secondSpace != -1 && firstSpace == CLEAN.length() && line.startsWith(CLEAN)) { - String[] parts = line.substring(secondSpace + 1).split(" "); - entry.readable = true; - entry.currentEditor = null; - entry.setLengths(parts); - } else if (secondSpace == -1 && firstSpace == DIRTY.length() && line.startsWith(DIRTY)) { - entry.currentEditor = new Editor(entry); - } else if (secondSpace == -1 && firstSpace == READ.length() && line.startsWith(READ)) { - // This work was already done by calling lruEntries.get(). - } else { - throw new IOException("unexpected journal line: " + line); - } - } - - /** - * Computes the initial size and collects garbage as a part of opening the - * cache. Dirty entries are assumed to be inconsistent and will be deleted. - */ - private void processJournal() throws IOException { - deleteIfExists(journalFileTmp); - for (Iterator i = lruEntries.values().iterator(); i.hasNext(); ) { - Entry entry = i.next(); - if (entry.currentEditor == null) { - for (int t = 0; t < valueCount; t++) { - size += entry.lengths[t]; - } - } else { - entry.currentEditor = null; - for (int t = 0; t < valueCount; t++) { - deleteIfExists(entry.getCleanFile(t)); - deleteIfExists(entry.getDirtyFile(t)); - } - i.remove(); - } - } - } - - /** - * Creates a new journal that omits redundant information. This replaces the - * current journal if it exists. - */ - private synchronized void rebuildJournal() throws IOException { - if (journalWriter != null) { - journalWriter.close(); - } - - try (Writer writer = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(journalFileTmp), Util.US_ASCII))) { - writer.write(MAGIC); - writer.write("\n"); - writer.write(VERSION_1); - writer.write("\n"); - writer.write(Integer.toString(appVersion)); - writer.write("\n"); - writer.write(Integer.toString(valueCount)); - writer.write("\n"); - writer.write("\n"); - - for (Entry entry : lruEntries.values()) { - if (entry.currentEditor != null) { - writer.write(DIRTY + ' ' + entry.key + '\n'); - } else { - writer.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); - } - } - } - - if (journalFile.exists()) { - renameTo(journalFile, journalFileBackup, true); - } - renameTo(journalFileTmp, journalFile, false); - journalFileBackup.delete(); - - journalWriter = new BufferedWriter( - new OutputStreamWriter(new FileOutputStream(journalFile, true), Util.US_ASCII)); - } - - private static void deleteIfExists(File file) throws IOException { - if (file.exists() && !file.delete()) { - throw new IOException(); - } - } - - private static void renameTo(File from, File to, boolean deleteDestination) throws IOException { - if (deleteDestination) { - deleteIfExists(to); - } - if (!from.renameTo(to)) { - throw new IOException(); - } - } - - /** - * Return {@code true} if it contains the key - */ - public synchronized boolean contain(String key) { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - return entry != null && entry.readable; - } - - /** - * Returns a snapshot of the entry named {@code key}, or null if it doesn't - * exist is not currently readable. If a value is returned, it is moved to - * the head of the LRU queue. - */ - public synchronized Snapshot get(String key) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null) { - return null; - } - - if (!entry.readable) { - return null; - } - - // Open all streams eagerly to guarantee that we see a single published - // snapshot. If we opened streams lazily then the streams could come - // from different edits. - InputStream[] ins = new InputStream[valueCount]; - try { - for (int i = 0; i < valueCount; i++) { - ins[i] = new FileInputStream(entry.getCleanFile(i)); - } - } catch (FileNotFoundException e) { - // A file must have been deleted manually! - for (int i = 0; i < valueCount; i++) { - if (ins[i] != null) { - Util.closeQuietly(ins[i]); - } else { - break; - } - } - return null; - } - - redundantOpCount++; - journalWriter.append(READ + ' ').append(key).append(String.valueOf('\n')); - if (journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - - return new Snapshot(key, entry.sequenceNumber, ins, entry.lengths); - } - - /** - * Returns an editor for the entry named {@code key}, or null if another - * edit is in progress. - */ - public Editor edit(String key) throws IOException { - return edit(key, ANY_SEQUENCE_NUMBER); - } - - private synchronized Editor edit(String key, long expectedSequenceNumber) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (expectedSequenceNumber != ANY_SEQUENCE_NUMBER && (entry == null - || entry.sequenceNumber != expectedSequenceNumber)) { - return null; // Snapshot is stale. - } - if (entry == null) { - entry = new Entry(key); - lruEntries.put(key, entry); - } else if (entry.currentEditor != null) { - return null; // Another edit is in progress. - } - - Editor editor = new Editor(entry); - entry.currentEditor = editor; - - // Flush the journal before creating files to prevent file leaks. - journalWriter.write(DIRTY + ' ' + key + '\n'); - journalWriter.flush(); - return editor; - } - - /** Returns the directory where this cache stores its data. */ - public File getDirectory() { - return directory; - } - - /** - * Returns the maximum number of bytes that this cache should use to store - * its data. - */ - public synchronized long getMaxSize() { - return maxSize; - } - - /** - * Changes the maximum number of bytes the cache can store and queues a job - * to trim the existing store, if necessary. - */ - public synchronized void setMaxSize(long maxSize) { - this.maxSize = maxSize; - executorService.submit(cleanupCallable); - } - - /** - * Returns the number of bytes currently being used to store the values in - * this cache. This may be greater than the max size if a background - * deletion is pending. - */ - public synchronized long size() { - return size; - } - - private synchronized void completeEdit(Editor editor, boolean success) throws IOException { - Entry entry = editor.entry; - if (entry.currentEditor != editor) { - throw new IllegalStateException(); - } - - // If this edit is creating the entry for the first time, every index must have a value. - if (success && !entry.readable) { - for (int i = 0; i < valueCount; i++) { - if (!editor.written[i]) { - editor.abort(); - throw new IllegalStateException("Newly created entry didn't create value for index " + i); - } - if (!entry.getDirtyFile(i).exists()) { - editor.abort(); - return; - } - } - } - - for (int i = 0; i < valueCount; i++) { - File dirty = entry.getDirtyFile(i); - if (success) { - if (dirty.exists()) { - File clean = entry.getCleanFile(i); - dirty.renameTo(clean); - long oldLength = entry.lengths[i]; - long newLength = clean.length(); - entry.lengths[i] = newLength; - size = size - oldLength + newLength; - } - } else { - deleteIfExists(dirty); - } - } - - redundantOpCount++; - entry.currentEditor = null; - if (entry.readable | success) { - entry.readable = true; - journalWriter.write(CLEAN + ' ' + entry.key + entry.getLengths() + '\n'); - if (success) { - entry.sequenceNumber = nextSequenceNumber++; - } - } else { - lruEntries.remove(entry.key); - journalWriter.write(REMOVE + ' ' + entry.key + '\n'); - } - journalWriter.flush(); - - if (size > maxSize || journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - } - - /** - * We only rebuild the journal when it will halve the size of the journal - * and eliminate at least 2000 ops. - */ - private boolean journalRebuildRequired() { - final int redundantOpCompactThreshold = 2000; - return redundantOpCount >= redundantOpCompactThreshold // - && redundantOpCount >= lruEntries.size(); - } - - /** - * Drops the entry for {@code key} if it exists and can be removed. Entries - * actively being edited cannot be removed. - * - * @return true if an entry was removed. - */ - public synchronized boolean remove(String key) throws IOException { - checkNotClosed(); - validateKey(key); - Entry entry = lruEntries.get(key); - if (entry == null || entry.currentEditor != null) { - return false; - } - - for (int i = 0; i < valueCount; i++) { - File file = entry.getCleanFile(i); - if (file.exists() && !file.delete()) { - throw new IOException("failed to delete " + file); - } - size -= entry.lengths[i]; - entry.lengths[i] = 0; - } - - redundantOpCount++; - journalWriter.append(REMOVE + ' ').append(key).append(String.valueOf('\n')); - lruEntries.remove(key); - - if (journalRebuildRequired()) { - executorService.submit(cleanupCallable); - } - - return true; - } - - /** Returns true if this cache has been closed. */ - public synchronized boolean isClosed() { - return journalWriter == null; - } - - private void checkNotClosed() { - if (journalWriter == null) { - throw new IllegalStateException("cache is closed"); - } - } - - /** Force buffered operations to the filesystem. */ - public synchronized void flush() throws IOException { - checkNotClosed(); - trimToSize(); - journalWriter.flush(); - } - - /** Closes this cache. Stored values will remain on the filesystem. */ - public synchronized void close() throws IOException { - if (journalWriter == null) { - return; // Already closed. - } - for (Entry entry : new ArrayList<>(lruEntries.values())) { - if (entry.currentEditor != null) { - entry.currentEditor.abort(); - } - } - trimToSize(); - journalWriter.close(); - journalWriter = null; - } - - private void trimToSize() throws IOException { - while (size > maxSize) { - Map.Entry toEvict = lruEntries.entrySet().iterator().next(); - remove(toEvict.getKey()); - } - } - - /** - * Closes the cache and deletes all of its stored values. This will delete - * all files in the cache directory including files that weren't created by - * the cache. - */ - public void delete() throws IOException { - close(); - Util.deleteContents(directory); - } - - private void validateKey(String key) { - Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); - if (!matcher.matches()) { - throw new IllegalArgumentException("keys must match regex " - + STRING_KEY_PATTERN + ": \"" + key + "\""); - } - } - - private static String inputStreamToString(InputStream in) throws IOException { - return Util.readFully(new InputStreamReader(in, Util.UTF_8)); - } - - /** A snapshot of the values for an entry. */ - public final class Snapshot implements Closeable { - private final String key; - private final long sequenceNumber; - private final InputStream[] ins; - private final long[] lengths; - - private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { - this.key = key; - this.sequenceNumber = sequenceNumber; - this.ins = ins; - this.lengths = lengths; } /** - * Returns an editor for this snapshot's entry, or null if either the - * entry has changed since this snapshot was created or if another edit - * is in progress. + * Closes the cache and deletes all of its stored values. This will delete + * all files in the cache directory including files that weren't created by + * the cache. */ - public Editor edit() throws IOException { - return DiskLruCache.this.edit(key, sequenceNumber); + public void delete() throws IOException { + close(); + Util.deleteContents(directory); } - /** Returns the unbuffered stream with the value for {@code index}. */ - public InputStream getInputStream(int index) { - return ins[index]; - } - - /** Returns the string value for {@code index}. */ - public String getString(int index) throws IOException { - return inputStreamToString(getInputStream(index)); - } - - /** Returns the byte length of the value for {@code index}. */ - public long getLength(int index) { - return lengths[index]; - } - - public void close() { - for (InputStream in : ins) { - Util.closeQuietly(in); - } - } - } - - private static final OutputStream NULL_OUTPUT_STREAM = new OutputStream() { - @Override - public void write(int b) { - // Eat all writes silently. Nom nom. - } - }; - - /** Edits the values for an entry. */ - public final class Editor { - private final Entry entry; - private final boolean[] written; - private boolean hasErrors; - private boolean committed; - - private Editor(Entry entry) { - this.entry = entry; - this.written = (entry.readable) ? null : new boolean[valueCount]; + private void validateKey(String key) { + Matcher matcher = LEGAL_KEY_PATTERN.matcher(key); + if (!matcher.matches()) { + throw new IllegalArgumentException("keys must match regex " + + STRING_KEY_PATTERN + ": \"" + key + "\""); + } } /** - * Returns an unbuffered input stream to read the last committed value, - * or null if no value has been committed. + * A snapshot of the values for an entry. */ - public InputStream newInputStream(int index) { - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); + public final class Snapshot implements Closeable { + private final String key; + private final long sequenceNumber; + private final InputStream[] ins; + private final long[] lengths; + + private Snapshot(String key, long sequenceNumber, InputStream[] ins, long[] lengths) { + this.key = key; + this.sequenceNumber = sequenceNumber; + this.ins = ins; + this.lengths = lengths; } - if (!entry.readable) { - return null; + + /** + * Returns an editor for this snapshot's entry, or null if either the + * entry has changed since this snapshot was created or if another edit + * is in progress. + */ + public Editor edit() throws IOException { + return DiskLruCache.this.edit(key, sequenceNumber); } - try { - return new FileInputStream(entry.getCleanFile(index)); - } catch (FileNotFoundException e) { - return null; + + /** + * Returns the unbuffered stream with the value for {@code index}. + */ + public InputStream getInputStream(int index) { + return ins[index]; + } + + /** + * Returns the string value for {@code index}. + */ + public String getString(int index) throws IOException { + return inputStreamToString(getInputStream(index)); + } + + /** + * Returns the byte length of the value for {@code index}. + */ + public long getLength(int index) { + return lengths[index]; + } + + public void close() { + for (InputStream in : ins) { + Util.closeQuietly(in); + } } - } } /** - * Returns the last committed value as a string, or null if no value - * has been committed. + * Edits the values for an entry. */ - public String getString(int index) throws IOException { - InputStream in = newInputStream(index); - return in != null ? inputStreamToString(in) : null; - } + public final class Editor { + private final Entry entry; + private final boolean[] written; + private boolean hasErrors; + private boolean committed; - /** - * Returns a new unbuffered output stream to write the value at - * {@code index}. If the underlying output stream encounters errors - * when writing to the filesystem, this edit will be aborted when - * {@link #commit} is called. The returned output stream does not throw - * IOExceptions. - */ - public OutputStream newOutputStream(int index) throws IOException { - if (index < 0 || index >= valueCount) { - throw new IllegalArgumentException("Expected index " + index + " to " - + "be greater than 0 and less than the maximum value count " - + "of " + valueCount); - } - synchronized (DiskLruCache.this) { - if (entry.currentEditor != this) { - throw new IllegalStateException(); + private Editor(Entry entry) { + this.entry = entry; + this.written = (entry.readable) ? null : new boolean[valueCount]; } - if (!entry.readable) { - written[index] = true; + + /** + * Returns an unbuffered input stream to read the last committed value, + * or null if no value has been committed. + */ + public InputStream newInputStream(int index) { + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + return null; + } + try { + return new FileInputStream(entry.getCleanFile(index)); + } catch (FileNotFoundException e) { + return null; + } + } } - File dirtyFile = entry.getDirtyFile(index); - FileOutputStream outputStream; - try { - outputStream = new FileOutputStream(dirtyFile); - } catch (FileNotFoundException e) { - // Attempt to recreate the cache directory. - directory.mkdirs(); - try { - outputStream = new FileOutputStream(dirtyFile); - } catch (FileNotFoundException e2) { - // We are unable to recover. Silently eat the writes. - return NULL_OUTPUT_STREAM; - } + + /** + * Returns the last committed value as a string, or null if no value + * has been committed. + */ + public String getString(int index) throws IOException { + InputStream in = newInputStream(index); + return in != null ? inputStreamToString(in) : null; } - return new FaultHidingOutputStream(outputStream); - } - } - /** Sets the value at {@code index} to {@code value}. */ - public void set(int index, String value) throws IOException { - Writer writer = null; - try { - writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); - writer.write(value); - } finally { - Util.closeQuietly(writer); - } - } - - /** - * Commits this edit so it is visible to readers. This releases the - * edit lock so another edit may be started on the same key. - */ - public void commit() throws IOException { - if (hasErrors) { - completeEdit(this, false); - remove(entry.key); // The previous entry is stale. - } else { - completeEdit(this, true); - } - committed = true; - } - - /** - * Aborts this edit. This releases the edit lock so another edit may be - * started on the same key. - */ - public void abort() throws IOException { - completeEdit(this, false); - } - - public void abortUnlessCommitted() { - if (!committed) { - try { - abort(); - } catch (IOException ignored) { + /** + * Returns a new unbuffered output stream to write the value at + * {@code index}. If the underlying output stream encounters errors + * when writing to the filesystem, this edit will be aborted when + * {@link #commit} is called. The returned output stream does not throw + * IOExceptions. + */ + public OutputStream newOutputStream(int index) throws IOException { + if (index < 0 || index >= valueCount) { + throw new IllegalArgumentException("Expected index " + index + " to " + + "be greater than 0 and less than the maximum value count " + + "of " + valueCount); + } + synchronized (DiskLruCache.this) { + if (entry.currentEditor != this) { + throw new IllegalStateException(); + } + if (!entry.readable) { + written[index] = true; + } + File dirtyFile = entry.getDirtyFile(index); + FileOutputStream outputStream; + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e) { + // Attempt to recreate the cache directory. + directory.mkdirs(); + try { + outputStream = new FileOutputStream(dirtyFile); + } catch (FileNotFoundException e2) { + // We are unable to recover. Silently eat the writes. + return NULL_OUTPUT_STREAM; + } + } + return new FaultHidingOutputStream(outputStream); + } } - } - } - private class FaultHidingOutputStream extends FilterOutputStream { - private FaultHidingOutputStream(OutputStream out) { - super(out); - } - - @Override public void write(int oneByte) { - try { - out.write(oneByte); - } catch (IOException e) { - hasErrors = true; + /** + * Sets the value at {@code index} to {@code value}. + */ + public void set(int index, String value) throws IOException { + Writer writer = null; + try { + writer = new OutputStreamWriter(newOutputStream(index), Util.UTF_8); + writer.write(value); + } finally { + Util.closeQuietly(writer); + } } - } - @Override public void write(byte[] buffer, int offset, int length) { - try { - out.write(buffer, offset, length); - } catch (IOException e) { - hasErrors = true; + /** + * Commits this edit so it is visible to readers. This releases the + * edit lock so another edit may be started on the same key. + */ + public void commit() throws IOException { + if (hasErrors) { + completeEdit(this, false); + remove(entry.key); // The previous entry is stale. + } else { + completeEdit(this, true); + } + committed = true; } - } - @Override public void close() { - try { - out.close(); - } catch (IOException e) { - hasErrors = true; + /** + * Aborts this edit. This releases the edit lock so another edit may be + * started on the same key. + */ + public void abort() throws IOException { + completeEdit(this, false); } - } - @Override public void flush() { - try { - out.flush(); - } catch (IOException e) { - hasErrors = true; + public void abortUnlessCommitted() { + if (!committed) { + try { + abort(); + } catch (IOException ignored) { + } + } } - } - } - } - private final class Entry { - private final String key; + private class FaultHidingOutputStream extends FilterOutputStream { + private FaultHidingOutputStream(OutputStream out) { + super(out); + } - /** Lengths of this entry's files. */ - private final long[] lengths; + @Override + public void write(int oneByte) { + try { + out.write(oneByte); + } catch (IOException e) { + hasErrors = true; + } + } - /** True if this entry has ever been published. */ - private boolean readable; + @Override + public void write(byte[] buffer, int offset, int length) { + try { + out.write(buffer, offset, length); + } catch (IOException e) { + hasErrors = true; + } + } - /** The ongoing edit or null if this entry is not being edited. */ - private Editor currentEditor; + @Override + public void close() { + try { + out.close(); + } catch (IOException e) { + hasErrors = true; + } + } - /** The sequence number of the most recently committed edit to this entry. */ - private long sequenceNumber; - - private Entry(String key) { - this.key = key; - this.lengths = new long[valueCount]; - } - - public String getLengths() { - StringBuilder result = new StringBuilder(); - for (long size : lengths) { - result.append(' ').append(size); - } - return result.toString(); - } - - /** Set lengths using decimal numbers like "10123". */ - private void setLengths(String[] strings) throws IOException { - if (strings.length != valueCount) { - throw invalidLengths(strings); - } - - try { - for (int i = 0; i < strings.length; i++) { - lengths[i] = Long.parseLong(strings[i]); + @Override + public void flush() { + try { + out.flush(); + } catch (IOException e) { + hasErrors = true; + } + } } - } catch (NumberFormatException e) { - throw invalidLengths(strings); - } } - private IOException invalidLengths(String[] strings) throws IOException { - throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); + private final class Entry { + private final String key; + + /** + * Lengths of this entry's files. + */ + private final long[] lengths; + + /** + * True if this entry has ever been published. + */ + private boolean readable; + + /** + * The ongoing edit or null if this entry is not being edited. + */ + private Editor currentEditor; + + /** + * The sequence number of the most recently committed edit to this entry. + */ + private long sequenceNumber; + + private Entry(String key) { + this.key = key; + this.lengths = new long[valueCount]; + } + + public String getLengths() { + StringBuilder result = new StringBuilder(); + for (long size : lengths) { + result.append(' ').append(size); + } + return result.toString(); + } + + /** + * Set lengths using decimal numbers like "10123". + */ + private void setLengths(String[] strings) throws IOException { + if (strings.length != valueCount) { + throw invalidLengths(strings); + } + + try { + for (int i = 0; i < strings.length; i++) { + lengths[i] = Long.parseLong(strings[i]); + } + } catch (NumberFormatException e) { + throw invalidLengths(strings); + } + } + + private IOException invalidLengths(String[] strings) throws IOException { + throw new IOException("unexpected journal line: " + java.util.Arrays.toString(strings)); + } + + public File getCleanFile(int i) { + return new File(directory, key + "." + i); + } + + public File getDirtyFile(int i) { + return new File(directory, key + "." + i + ".tmp"); + } } - public File getCleanFile(int i) { - return new File(directory, key + "." + i); - } - public File getDirtyFile(int i) { - return new File(directory, key + "." + i + ".tmp"); - } - } } diff --git a/app/src/main/java/com/hippo/beerbelly/LruCache.java b/app/src/main/java/com/hippo/beerbelly/LruCache.java index 5800473fd..0884eeb32 100644 --- a/app/src/main/java/com/hippo/beerbelly/LruCache.java +++ b/app/src/main/java/com/hippo/beerbelly/LruCache.java @@ -33,7 +33,9 @@ import java.util.Map; public class LruCache { private final LinkedHashMap map; - /** Size of this cache in units. Not necessarily the number of elements. */ + /** + * Size of this cache in units. Not necessarily the number of elements. + */ private int size; private int maxSize; @@ -45,8 +47,8 @@ public class LruCache { /** * @param maxSize for caches that do not override {@link #sizeOf}, this is - * the maximum number of entries in the cache. For all other caches, - * this is the maximum sum of the sizes of the entries in this cache. + * the maximum number of entries in the cache. For all other caches, + * this is the maximum sum of the sizes of the entries in this cache. */ public LruCache(int maxSize) { if (maxSize <= 0) { @@ -171,7 +173,7 @@ public class LruCache { * below the requested size. * * @param maxSize the maximum size of the cache before returning. May be -1 - * to evict even 0-sized elements. + * to evict even 0-sized elements. */ public void trimToSize(int maxSize) { while (true) { @@ -233,13 +235,14 @@ public class LruCache { *

The method is called without synchronization: other threads may * access the cache while this method is executing. * - * @param evicted true if the entry is being removed to make space, false - * if the removal was caused by a {@link #put} or {@link #remove}. + * @param evicted true if the entry is being removed to make space, false + * if the removal was caused by a {@link #put} or {@link #remove}. * @param newValue the new value for {@code key}, if it exists. If non-null, - * this removal was caused by a {@link #put}. Otherwise it was caused by - * an eviction or a {@link #remove}. + * this removal was caused by a {@link #put}. Otherwise it was caused by + * an eviction or a {@link #remove}. */ - protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) {} + protected void entryRemoved(boolean evicted, K key, V oldValue, V newValue) { + } /** * Called after a cache miss to compute a value for the corresponding key. @@ -351,7 +354,8 @@ public class LruCache { @NonNull @SuppressLint("DefaultLocale") - @Override public synchronized final String toString() { + @Override + public synchronized final String toString() { int accesses = hitCount + missCount; int hitPercent = accesses != 0 ? (100 * hitCount / accesses) : 0; return String.format("LruCache[maxSize=%d,hits=%d,misses=%d,hitRate=%d%%]", diff --git a/app/src/main/java/com/hippo/beerbelly/SimpleDiskCache.java b/app/src/main/java/com/hippo/beerbelly/SimpleDiskCache.java index dcdcf7bad..b3c162130 100644 --- a/app/src/main/java/com/hippo/beerbelly/SimpleDiskCache.java +++ b/app/src/main/java/com/hippo/beerbelly/SimpleDiskCache.java @@ -55,15 +55,13 @@ public class SimpleDiskCache { private final File mCacheDir; private final int mSize; - - private int mDiskCacheState; - - @Nullable - private DiskLruCache mDiskLruCache; @NonNull private final Map mLockMap = new HashMap<>(); @NonNull private final LockPool mLockPool = new LockPool(); + private int mDiskCacheState; + @Nullable + private DiskLruCache mDiskLruCache; public SimpleDiskCache(File cacheDir, int size) { mCacheDir = cacheDir; @@ -76,6 +74,43 @@ public class SimpleDiskCache { } } + /** + * A hashing method that changes a string (like a URL) into a hash suitable + * for using as a disk filename. + * + * @param key The key used to store the file + */ + private static String hashKeyForDisk(final String key) { + String cacheKey; + try { + final MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(key.getBytes()); + cacheKey = bytesToHexString(digest.digest()); + } catch (final NoSuchAlgorithmException e) { + cacheKey = String.valueOf(key.hashCode()); + } + return cacheKey; + } + + /** + * http://stackoverflow.com/questions/332079 + * + * @param bytes The bytes to convert. + * @return A {@link String} converted from the bytes of a hashable key used + * to store a filename on the disk, to hex digits. + */ + private static String bytesToHexString(final byte[] bytes) { + final StringBuilder builder = new StringBuilder(); + for (final byte b : bytes) { + final String hex = Integer.toHexString(0xFF & b); + if (hex.length() == 1) { + builder.append('0'); + } + builder.append(hex); + } + return builder.toString(); + } + public synchronized boolean isValid() { return mDiskLruCache != null; } @@ -303,43 +338,6 @@ public class SimpleDiskCache { return new CacheOutputStreamPipe(diskKey); } - /** - * A hashing method that changes a string (like a URL) into a hash suitable - * for using as a disk filename. - * - * @param key The key used to store the file - */ - private static String hashKeyForDisk(final String key) { - String cacheKey; - try { - final MessageDigest digest = MessageDigest.getInstance("MD5"); - digest.update(key.getBytes()); - cacheKey = bytesToHexString(digest.digest()); - } catch (final NoSuchAlgorithmException e) { - cacheKey = String.valueOf(key.hashCode()); - } - return cacheKey; - } - - /** - * http://stackoverflow.com/questions/332079 - * - * @param bytes The bytes to convert. - * @return A {@link String} converted from the bytes of a hashable key used - * to store a filename on the disk, to hex digits. - */ - private static String bytesToHexString(final byte[] bytes) { - final StringBuilder builder = new StringBuilder(); - for (final byte b : bytes) { - final String hex = Integer.toHexString(0xFF & b); - if (hex.length() == 1) { - builder.append('0'); - } - builder.append(hex); - } - return builder.toString(); - } - private class CacheInputStreamPipe implements InputStreamPipe { private final String mKey; @@ -372,7 +370,8 @@ public class SimpleDiskCache { } @Override - public @NonNull InputStream open() throws IOException { + public @NonNull + InputStream open() throws IOException { if (mLock == null) { throw new IllegalStateException("Please obtain it first"); } @@ -431,7 +430,8 @@ public class SimpleDiskCache { } @Override - public @NonNull OutputStream open() throws IOException { + public @NonNull + OutputStream open() throws IOException { if (mLock == null) { throw new IllegalStateException("Please obtain it first"); } diff --git a/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java b/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java index ea7995c73..a4e35b604 100644 --- a/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java +++ b/app/src/main/java/com/hippo/beerbelly/StrictLineReader.java @@ -45,155 +45,155 @@ import java.nio.charset.Charset; * The default charset is US_ASCII. */ class StrictLineReader implements Closeable { - private static final byte CR = (byte) '\r'; - private static final byte LF = (byte) '\n'; + private static final byte CR = (byte) '\r'; + private static final byte LF = (byte) '\n'; - private final InputStream in; - private final Charset charset; + private final InputStream in; + private final Charset charset; - /* - * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end - * and the data in the range [pos, end) is buffered for reading. At end of input, if there is - * an unterminated line, we set end == -1, otherwise end == pos. If the underlying - * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. - */ - private byte[] buf; - private int pos; - private int end; + /* + * Buffered data is stored in {@code buf}. As long as no exception occurs, 0 <= pos <= end + * and the data in the range [pos, end) is buffered for reading. At end of input, if there is + * an unterminated line, we set end == -1, otherwise end == pos. If the underlying + * {@code InputStream} throws an {@code IOException}, end may remain as either pos or -1. + */ + private byte[] buf; + private int pos; + private int end; - /** - * Constructs a new {@code LineReader} with the specified charset and the default capacity. - * - * @param in the {@code InputStream} to read data from. - * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are - * supported. - * @throws NullPointerException if {@code in} or {@code charset} is null. - * @throws IllegalArgumentException if the specified charset is not supported. - */ - public StrictLineReader(InputStream in, Charset charset) { - this(in, 8192, charset); - } - - /** - * Constructs a new {@code LineReader} with the specified capacity and charset. - * - * @param in the {@code InputStream} to read data from. - * @param capacity the capacity of the buffer. - * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are - * supported. - * @throws NullPointerException if {@code in} or {@code charset} is null. - * @throws IllegalArgumentException if {@code capacity} is negative or zero - * or the specified charset is not supported. - */ - public StrictLineReader(InputStream in, int capacity, Charset charset) { - if (in == null || charset == null) { - throw new NullPointerException(); - } - if (capacity < 0) { - throw new IllegalArgumentException("capacity <= 0"); - } - if (!(charset.equals(Util.US_ASCII))) { - throw new IllegalArgumentException("Unsupported encoding"); + /** + * Constructs a new {@code LineReader} with the specified charset and the default capacity. + * + * @param in the {@code InputStream} to read data from. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if the specified charset is not supported. + */ + public StrictLineReader(InputStream in, Charset charset) { + this(in, 8192, charset); } - this.in = in; - this.charset = charset; - buf = new byte[capacity]; - } - - /** - * Closes the reader by closing the underlying {@code InputStream} and - * marking this reader as closed. - * - * @throws IOException for errors when closing the underlying {@code InputStream}. - */ - public void close() throws IOException { - synchronized (in) { - if (buf != null) { - buf = null; - in.close(); - } - } - } - - /** - * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, - * this end of line marker is not included in the result. - * - * @return the next line from the input. - * @throws IOException for underlying {@code InputStream} errors. - * @throws EOFException for the end of source stream. - */ - public String readLine() throws IOException { - synchronized (in) { - if (buf == null) { - throw new IOException("LineReader is closed"); - } - - // Read more data if we are at the end of the buffered data. - // Though it's an error to read after an exception, we will let {@code fillBuf()} - // throw again if that happens; thus we need to handle end == -1 as well as end == pos. - if (pos >= end) { - fillBuf(); - } - // Try to find LF in the buffered data and return the line if successful. - for (int i = pos; i != end; ++i) { - if (buf[i] == LF) { - int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; - String res = new String(buf, pos, lineEnd - pos, charset.name()); - pos = i + 1; - return res; + /** + * Constructs a new {@code LineReader} with the specified capacity and charset. + * + * @param in the {@code InputStream} to read data from. + * @param capacity the capacity of the buffer. + * @param charset the charset used to decode data. Only US-ASCII, UTF-8 and ISO-8859-1 are + * supported. + * @throws NullPointerException if {@code in} or {@code charset} is null. + * @throws IllegalArgumentException if {@code capacity} is negative or zero + * or the specified charset is not supported. + */ + public StrictLineReader(InputStream in, int capacity, Charset charset) { + if (in == null || charset == null) { + throw new NullPointerException(); } - } - - // Let's anticipate up to 80 characters on top of those already read. - ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { - @NonNull - @Override - public String toString() { - int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; - try { - return new String(buf, 0, length, charset.name()); - } catch (UnsupportedEncodingException e) { - throw new AssertionError(e); // Since we control the charset this will never happen. - } + if (capacity < 0) { + throw new IllegalArgumentException("capacity <= 0"); + } + if (!(charset.equals(Util.US_ASCII))) { + throw new IllegalArgumentException("Unsupported encoding"); } - }; - while (true) { - out.write(buf, pos, end - pos); - // Mark unterminated line in case fillBuf throws EOFException or IOException. - end = -1; - fillBuf(); - // Try to find LF in the buffered data and return the line if successful. - for (int i = pos; i != end; ++i) { - if (buf[i] == LF) { - if (i != pos) { - out.write(buf, pos, i - pos); + this.in = in; + this.charset = charset; + buf = new byte[capacity]; + } + + /** + * Closes the reader by closing the underlying {@code InputStream} and + * marking this reader as closed. + * + * @throws IOException for errors when closing the underlying {@code InputStream}. + */ + public void close() throws IOException { + synchronized (in) { + if (buf != null) { + buf = null; + in.close(); } - pos = i + 1; - return out.toString(); - } } - } } - } - public boolean hasUnterminatedLine() { - return end == -1; - } + /** + * Reads the next line. A line ends with {@code "\n"} or {@code "\r\n"}, + * this end of line marker is not included in the result. + * + * @return the next line from the input. + * @throws IOException for underlying {@code InputStream} errors. + * @throws EOFException for the end of source stream. + */ + public String readLine() throws IOException { + synchronized (in) { + if (buf == null) { + throw new IOException("LineReader is closed"); + } - /** - * Reads new input data into the buffer. Call only with pos == end or end == -1, - * depending on the desired outcome if the function throws. - */ - private void fillBuf() throws IOException { - int result = in.read(buf, 0, buf.length); - if (result == -1) { - throw new EOFException(); + // Read more data if we are at the end of the buffered data. + // Though it's an error to read after an exception, we will let {@code fillBuf()} + // throw again if that happens; thus we need to handle end == -1 as well as end == pos. + if (pos >= end) { + fillBuf(); + } + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + int lineEnd = (i != pos && buf[i - 1] == CR) ? i - 1 : i; + String res = new String(buf, pos, lineEnd - pos, charset.name()); + pos = i + 1; + return res; + } + } + + // Let's anticipate up to 80 characters on top of those already read. + ByteArrayOutputStream out = new ByteArrayOutputStream(end - pos + 80) { + @NonNull + @Override + public String toString() { + int length = (count > 0 && buf[count - 1] == CR) ? count - 1 : count; + try { + return new String(buf, 0, length, charset.name()); + } catch (UnsupportedEncodingException e) { + throw new AssertionError(e); // Since we control the charset this will never happen. + } + } + }; + + while (true) { + out.write(buf, pos, end - pos); + // Mark unterminated line in case fillBuf throws EOFException or IOException. + end = -1; + fillBuf(); + // Try to find LF in the buffered data and return the line if successful. + for (int i = pos; i != end; ++i) { + if (buf[i] == LF) { + if (i != pos) { + out.write(buf, pos, i - pos); + } + pos = i + 1; + return out.toString(); + } + } + } + } + } + + public boolean hasUnterminatedLine() { + return end == -1; + } + + /** + * Reads new input data into the buffer. Call only with pos == end or end == -1, + * depending on the desired outcome if the function throws. + */ + private void fillBuf() throws IOException { + int result = in.read(buf, 0, buf.length); + if (result == -1) { + throw new EOFException(); + } + pos = 0; + end = result; } - pos = 0; - end = result; - } } diff --git a/app/src/main/java/com/hippo/beerbelly/Util.java b/app/src/main/java/com/hippo/beerbelly/Util.java index 4f2258e3d..25c70ddfc 100644 --- a/app/src/main/java/com/hippo/beerbelly/Util.java +++ b/app/src/main/java/com/hippo/beerbelly/Util.java @@ -26,76 +26,77 @@ import java.io.StringWriter; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -/** Junk drawer of utility methods. */ +/** + * Junk drawer of utility methods. + */ final class Util { - static final Charset US_ASCII = StandardCharsets.US_ASCII; - static final Charset UTF_8 = StandardCharsets.UTF_8; + static final Charset US_ASCII = StandardCharsets.US_ASCII; + static final Charset UTF_8 = StandardCharsets.UTF_8; + private static final int EOF = -1; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; - private Util() { - } - - static String readFully(Reader reader) throws IOException { - try (reader) { - StringWriter writer = new StringWriter(); - char[] buffer = new char[1024]; - int count; - while ((count = reader.read(buffer)) != -1) { - writer.write(buffer, 0, count); - } - return writer.toString(); + private Util() { } - } - /** - * Deletes the contents of {@code dir}. Throws an IOException if any file - * could not be deleted, or if {@code dir} is not a readable directory. - */ - static void deleteContents(File dir) throws IOException { - File[] files = dir.listFiles(); - if (files == null) { - throw new IOException("not a readable directory: " + dir); + static String readFully(Reader reader) throws IOException { + try (reader) { + StringWriter writer = new StringWriter(); + char[] buffer = new char[1024]; + int count; + while ((count = reader.read(buffer)) != -1) { + writer.write(buffer, 0, count); + } + return writer.toString(); + } } - for (File file : files) { - if (file.isDirectory()) { - deleteContents(file); - } - if (!file.delete()) { - throw new IOException("failed to delete file: " + file); - } - } - } - static void closeQuietly(/*Auto*/Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (RuntimeException rethrown) { - throw rethrown; - } catch (Exception ignored) { - } + /** + * Deletes the contents of {@code dir}. Throws an IOException if any file + * could not be deleted, or if {@code dir} is not a readable directory. + */ + static void deleteContents(File dir) throws IOException { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("not a readable directory: " + dir); + } + for (File file : files) { + if (file.isDirectory()) { + deleteContents(file); + } + if (!file.delete()) { + throw new IOException("failed to delete file: " + file); + } + } } - } - private static final int EOF = -1; - private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + static void closeQuietly(/*Auto*/Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (RuntimeException rethrown) { + throw rethrown; + } catch (Exception ignored) { + } + } + } - /** - * Copy bytes from an InputStream to an - * OutputStream. - * - * @param input the InputStream - * @param output the OutputStream - * @return the number of bytes copied - */ - public static long copy(InputStream input, OutputStream output) - throws IOException { - byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; - long count = 0; - int n; - while (EOF != (n = input.read(buffer))) { - output.write(buffer, 0, n); - count += n; + /** + * Copy bytes from an InputStream to an + * OutputStream. + * + * @param input the InputStream + * @param output the OutputStream + * @return the number of bytes copied + */ + public static long copy(InputStream input, OutputStream output) + throws IOException { + byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; + long count = 0; + int n; + while (EOF != (n = input.read(buffer))) { + output.write(buffer, 0, n); + count += n; + } + return count; } - return count; - } } diff --git a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java index 7ff600ab7..fe4672cc8 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/GalleryActivity.java @@ -283,16 +283,7 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan } outState.putInt(KEY_PAGE, mPage); outState.putInt(KEY_CURRENT_INDEX, mCurrentIndex); - } ActivityResultLauncher requestStoragePermissionLauncher = registerForActivityResult( - new ActivityResultContracts.RequestPermission(), - result -> { - if (result && mSavingPage != -1) { - saveImage(mSavingPage); - } else { - Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show(); - } - mSavingPage = -1; - }); + } @Override protected void attachBaseContext(@NonNull Context newBase) { @@ -305,7 +296,16 @@ public class GalleryActivity extends EhActivity implements SeekBar.OnSeekBarChan break; } super.attachBaseContext(newBase); - } + } ActivityResultLauncher requestStoragePermissionLauncher = registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + result -> { + if (result && mSavingPage != -1) { + saveImage(mSavingPage); + } else { + Toast.makeText(this, R.string.error_cant_save_image, Toast.LENGTH_SHORT).show(); + } + mSavingPage = -1; + }); @Override public boolean respectDefaultNightMode() { diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java index e62474fa2..325c90573 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/FavoritesScene.java @@ -68,7 +68,6 @@ import com.hippo.ehviewer.client.data.FavListUrlBuilder; import com.hippo.ehviewer.client.data.GalleryInfo; import com.hippo.ehviewer.client.parser.FavoritesParser; import com.hippo.ehviewer.ui.CommonOperations; -import com.hippo.ehviewer.ui.EhActivity; import com.hippo.ehviewer.ui.MainActivity; import com.hippo.ehviewer.ui.annotation.DrawerLifeCircle; import com.hippo.ehviewer.ui.annotation.ViewLifeCircle; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java index 2c06e85b6..590927a7d 100755 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryDetailScene.java @@ -60,9 +60,6 @@ import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.core.view.ViewCompat; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentTransaction; -import androidx.transition.TransitionInflater; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.progressindicator.CircularProgressIndicator; @@ -103,7 +100,6 @@ import com.hippo.ehviewer.ui.annotation.WholeLifeCircle; import com.hippo.ehviewer.widget.GalleryRatingBar; import com.hippo.scene.Announcer; import com.hippo.scene.SceneFragment; -import com.hippo.scene.TransitionHelper; import com.hippo.text.Html; import com.hippo.text.URLImageGetter; import com.hippo.util.AppHelper; diff --git a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java index b7a519a34..cff548991 100644 --- a/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java +++ b/app/src/main/java/com/hippo/ehviewer/ui/scene/GalleryListScene.java @@ -96,7 +96,6 @@ import com.hippo.ehviewer.dao.DownloadLabel; import com.hippo.ehviewer.dao.QuickSearch; import com.hippo.ehviewer.download.DownloadManager; import com.hippo.ehviewer.ui.CommonOperations; -import com.hippo.ehviewer.ui.EhActivity; import com.hippo.ehviewer.ui.GalleryActivity; import com.hippo.ehviewer.ui.MainActivity; import com.hippo.ehviewer.ui.dialog.SelectItemWithIconAdapter; diff --git a/app/src/main/java/com/hippo/glgallery/DownUpDetector.java b/app/src/main/java/com/hippo/glgallery/DownUpDetector.java index 56f74d7cd..e8139f6f1 100644 --- a/app/src/main/java/com/hippo/glgallery/DownUpDetector.java +++ b/app/src/main/java/com/hippo/glgallery/DownUpDetector.java @@ -21,6 +21,7 @@ import android.view.MotionEvent; class DownUpDetector { private final DownUpListener mListener; private boolean mStillDown; + public DownUpDetector(DownUpListener listener) { mListener = listener; } diff --git a/app/src/main/java/com/hippo/glgallery/GalleryProvider.java b/app/src/main/java/com/hippo/glgallery/GalleryProvider.java index adc2cbc71..f9083cbb3 100644 --- a/app/src/main/java/com/hippo/glgallery/GalleryProvider.java +++ b/app/src/main/java/com/hippo/glgallery/GalleryProvider.java @@ -182,6 +182,7 @@ public abstract class GalleryProvider { private float mPercent; private ImageWrapper mImage; private String mError; + public NotifyTask(Listener listener, ConcurrentPool pool) { mListener = listener; mPool = pool; diff --git a/app/src/main/java/com/hippo/glgallery/GalleryView.java b/app/src/main/java/com/hippo/glgallery/GalleryView.java index 217876ade..36e3a49e8 100644 --- a/app/src/main/java/com/hippo/glgallery/GalleryView.java +++ b/app/src/main/java/com/hippo/glgallery/GalleryView.java @@ -137,6 +137,7 @@ public final class GalleryView extends GLView implements GestureRecognizer.Liste private int mScaleMode = ImageView.SCALE_FIT; private int mStartPosition = ImageView.START_POSITION_TOP_LEFT; private int mIndex; + private GalleryView(Builder build) { mContext = build.mContext; mAdapter = build.mAdapter; diff --git a/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java b/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java index d212488f9..00d74a2cb 100644 --- a/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java +++ b/app/src/main/java/com/hippo/glgallery/GestureRecognizer.java @@ -33,6 +33,7 @@ class GestureRecognizer { private final ScaleGestureDetector mScaleDetector; private final DownUpDetector mDownUpDetector; private final Listener mListener; + public GestureRecognizer(Context context, Listener listener) { mListener = listener; MyGestureListener gestureListener = new MyGestureListener(); diff --git a/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java b/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java index a0ff048b7..ead425565 100644 --- a/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java +++ b/app/src/main/java/com/hippo/glview/glrenderer/GLES20Canvas.java @@ -216,6 +216,7 @@ public class GLES20Canvas implements GLCanvas { private int mCountTextureRect = 0; private int mCountFillRect = 0; private int mCountDrawLine = 0; + public GLES20Canvas() { Matrix.setIdentityM(mTempTextureMatrix, 0); Matrix.setIdentityM(mMatrices, mCurrentMatrixIndex); diff --git a/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java b/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java index 18bc108f7..2be71dc30 100644 --- a/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java +++ b/app/src/main/java/com/hippo/glview/widget/GLEdgeView.java @@ -41,6 +41,7 @@ public final class GLEdgeView extends GLView { private static final int RIGHT_M = RIGHT * 16; private final GLEdgeEffect[] mEffect = new GLEdgeEffect[4]; private final float[] mMatrix = new float[4 * 16]; + public GLEdgeView(int color) { for (int i = 0; i < 4; i++) { mEffect[i] = new GLEdgeEffect(color); diff --git a/app/src/main/java/com/hippo/scene/StageActivity.java b/app/src/main/java/com/hippo/scene/StageActivity.java index 56e5cfadb..c2c7813e2 100644 --- a/app/src/main/java/com/hippo/scene/StageActivity.java +++ b/app/src/main/java/com/hippo/scene/StageActivity.java @@ -511,17 +511,17 @@ public abstract class StageActivity extends EhActivity { FragmentTransaction transaction = fragmentManager.beginTransaction(); if (next != null) { - // Clear shared item - scene.setSharedElementEnterTransition(null); - scene.setSharedElementReturnTransition(null); - scene.setEnterTransition(null); - scene.setExitTransition(null); - next.setSharedElementEnterTransition(null); - next.setSharedElementReturnTransition(null); - next.setEnterTransition(null); - next.setExitTransition(null); - // Do not show animate if it is not the first fragment - transaction.setCustomAnimations(R.anim.scene_close_enter, R.anim.scene_close_exit); + // Clear shared item + scene.setSharedElementEnterTransition(null); + scene.setSharedElementReturnTransition(null); + scene.setEnterTransition(null); + scene.setExitTransition(null); + next.setSharedElementEnterTransition(null); + next.setSharedElementReturnTransition(null); + next.setEnterTransition(null); + next.setExitTransition(null); + // Do not show animate if it is not the first fragment + transaction.setCustomAnimations(R.anim.scene_close_enter, R.anim.scene_close_exit); // Attach fragment transaction.attach(next); } diff --git a/app/src/main/java/com/hippo/widget/BatteryView.java b/app/src/main/java/com/hippo/widget/BatteryView.java index 4b2f94610..f83d3ad79 100644 --- a/app/src/main/java/com/hippo/widget/BatteryView.java +++ b/app/src/main/java/com/hippo/widget/BatteryView.java @@ -66,6 +66,13 @@ public class BatteryView extends AppCompatTextView { mDrawable.setColor(mColor); mDrawable.setWarningColor(mWarningColor); + } + + private void init() { + mDrawable = new BatteryDrawable(); + int height = (int) getTextSize(); + mDrawable.setBounds(0, 0, (int) (height / 0.618f), height); + setCompoundDrawables(mDrawable, null, null, null); } private final Runnable mCharger = new Runnable() { private int level = 0; @@ -81,13 +88,6 @@ public class BatteryView extends AppCompatTextView { } }; - private void init() { - mDrawable = new BatteryDrawable(); - int height = (int) getTextSize(); - mDrawable.setBounds(0, 0, (int) (height / 0.618f), height); - setCompoundDrawables(mDrawable, null, null, null); - } - @Override public void setTextColor(int color) { if (mCurrentColor == color) { @@ -137,7 +137,13 @@ public class BatteryView extends AppCompatTextView { final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BATTERY_CHANGED); getContext().registerReceiver(mIntentReceiver, filter, null, getHandler()); - } private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + } + + private void unregisterReceiver() { + getContext().unregisterReceiver(mIntentReceiver); + } + + private final BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override @SuppressLint("SetTextI18n") @@ -168,12 +174,6 @@ public class BatteryView extends AppCompatTextView { } }; - private void unregisterReceiver() { - getContext().unregisterReceiver(mIntentReceiver); - } - - - diff --git a/app/src/main/java/com/hippo/widget/TextClock.java b/app/src/main/java/com/hippo/widget/TextClock.java index d7c2d4959..2574f4522 100644 --- a/app/src/main/java/com/hippo/widget/TextClock.java +++ b/app/src/main/java/com/hippo/widget/TextClock.java @@ -83,6 +83,12 @@ public class TextClock extends AppCompatTextView { mTimeZone = TimeZone.getDefault().getID(); init(); + } + + private void init() { + createTime(mTimeZone); + // Wait until onAttachedToWindow() to handle the ticker + chooseFormat(false); } private final Runnable mTicker = new Runnable() { @Override public void run() { @@ -95,12 +101,6 @@ public class TextClock extends AppCompatTextView { } }; - private void init() { - createTime(mTimeZone); - // Wait until onAttachedToWindow() to handle the ticker - chooseFormat(false); - } - private void createTime(String timeZone) { if (timeZone != null) { mTime = Calendar.getInstance(TimeZone.getTimeZone(timeZone)); @@ -122,6 +122,17 @@ public class TextClock extends AppCompatTextView { public CharSequence getFormat24Hour() { return mFormat24; + } + + public void setFormat24Hour(CharSequence format) { + mFormat24 = format; + + chooseFormat(); + onTimeChanged(); + } + + public boolean is24HourModeEnabled() { + return DateFormat.is24HourFormat(getContext()); } private final ContentObserver mFormatChangeObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange) { @@ -136,17 +147,6 @@ public class TextClock extends AppCompatTextView { } }; - public void setFormat24Hour(CharSequence format) { - mFormat24 = format; - - chooseFormat(); - onTimeChanged(); - } - - public boolean is24HourModeEnabled() { - return DateFormat.is24HourFormat(getContext()); - } - public String getTimeZone() { return mTimeZone; } diff --git a/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java b/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java index 126fede77..f31f96398 100644 --- a/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/AnimationUtils.java @@ -26,6 +26,7 @@ public final class AnimationUtils { public static final Interpolator FAST_SLOW_INTERPOLATOR = new LinearOutSlowInInterpolator(); public static final Interpolator SLOW_FAST_INTERPOLATOR = new FastOutLinearInInterpolator(); public static final Interpolator SLOW_FAST_SLOW_INTERPOLATOR = new FastOutSlowInInterpolator(); + private AnimationUtils() { } } diff --git a/app/src/main/java/com/hippo/yorozuya/MathUtils.java b/app/src/main/java/com/hippo/yorozuya/MathUtils.java index 669f6d5fc..9e6e2779b 100644 --- a/app/src/main/java/com/hippo/yorozuya/MathUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/MathUtils.java @@ -23,6 +23,7 @@ public final class MathUtils { private static final Random sRandom = new Random(); private static final float DEG_TO_RAD = 3.1415926f / 180.0f; private static final float RAD_TO_DEG = 180.0f / 3.1415926f; + private MathUtils() { } diff --git a/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java b/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java index d425386cc..74a4091ce 100644 --- a/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java +++ b/app/src/main/java/com/hippo/yorozuya/ResourcesUtils.java @@ -25,6 +25,7 @@ import androidx.annotation.AttrRes; public final class ResourcesUtils { private static final Object mAccessLock = new Object(); private static TypedValue mTmpValue = new TypedValue(); + private ResourcesUtils() { } diff --git a/app/src/main/java/com/hippo/yorozuya/thread/InfiniteThreadExecutor.java b/app/src/main/java/com/hippo/yorozuya/thread/InfiniteThreadExecutor.java index ef95d7d8c..38b8a777f 100644 --- a/app/src/main/java/com/hippo/yorozuya/thread/InfiniteThreadExecutor.java +++ b/app/src/main/java/com/hippo/yorozuya/thread/InfiniteThreadExecutor.java @@ -30,9 +30,8 @@ public class InfiniteThreadExecutor implements Executor { private final ThreadFactory mThreadFactory; private final AtomicInteger mThreadCount = new AtomicInteger(); - private int mEmptyThreadCount; - private final Object mLock = new Object(); + private int mEmptyThreadCount; public InfiniteThreadExecutor(long keepAliveMillis, Queue workQueue, ThreadFactory threadFactory) { mKeepAliveMillis = keepAliveMillis; @@ -65,7 +64,7 @@ public class InfiniteThreadExecutor implements Executor { mThreadCount.incrementAndGet(); boolean hasWait = false; - for (;;) { + for (; ; ) { Runnable command; synchronized (mLock) { command = mWorkQueue.poll(); diff --git a/app/src/main/java/com/hippo/yorozuya/thread/SerialThreadExecutor.java b/app/src/main/java/com/hippo/yorozuya/thread/SerialThreadExecutor.java index 9ad37bc07..177b8b7c7 100644 --- a/app/src/main/java/com/hippo/yorozuya/thread/SerialThreadExecutor.java +++ b/app/src/main/java/com/hippo/yorozuya/thread/SerialThreadExecutor.java @@ -26,18 +26,16 @@ import java.util.concurrent.locks.ReentrantLock; public class SerialThreadExecutor implements Executor, Runnable { + private final Lock mThreadLock = new ReentrantLock(); + private final Object mWaitLock = new Object(); private long mKeepAliveMillis; private Queue mWorkQueue; private ThreadFactory mThreadFactory; - // The worker private Thread mThread; - private final Lock mThreadLock = new ReentrantLock(); - private final Object mWaitLock = new Object(); - public SerialThreadExecutor(long keepAliveMillis, - Queue workQueue, ThreadFactory threadFactory) { + Queue workQueue, ThreadFactory threadFactory) { mKeepAliveMillis = keepAliveMillis; mWorkQueue = workQueue; mThreadFactory = threadFactory; @@ -67,7 +65,7 @@ public class SerialThreadExecutor implements Executor, Runnable { // It tell whether has waited boolean hasWaited = false; - for (;;) { + for (; ; ) { mThreadLock.lock(); Runnable runnable = mWorkQueue.poll(); if (runnable == null) { diff --git a/app/src/main/res/layout/gallery_detail_header.xml b/app/src/main/res/layout/gallery_detail_header.xml index 5d179a768..b6212d720 100644 --- a/app/src/main/res/layout/gallery_detail_header.xml +++ b/app/src/main/res/layout/gallery_detail_header.xml @@ -59,9 +59,9 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginLeft="@dimen/keyline_margin" + android:layout_marginTop="@dimen/keyline_margin" android:layout_marginRight="@dimen/keyline_margin" android:layout_marginBottom="@dimen/keyline_margin" - android:layout_marginTop="@dimen/keyline_margin" app:fitsSystemWindowsInsets="top">