and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.filesystem.compressed.sevenz;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.channels.ReadableByteChannel;
+
+import kotlin.io.ByteStreamsKt;
+
+/**
+ * A trimmed down version of original {@link org.apache.commons.compress.utils.IOUtils} but without
+ * the existence of classes that won't exist in older Androids, as well as removing methods that can
+ * be removed.
+ */
+class IOUtils {
+
+ private static final int COPY_BUF_SIZE = 8024;
+ private static final int SKIP_BUF_SIZE = 4096;
+
+ // This buffer does not need to be synchronized because it is write only; the contents are ignored
+ // Does not affect Immutability
+ private static final byte[] SKIP_BUF = new byte[SKIP_BUF_SIZE];
+
+ /** Private constructor to prevent instantiation of this utility class. */
+ private IOUtils() {}
+
+ /**
+ * Copies the content of a InputStream into an OutputStream. Uses a default buffer size of 8024
+ * bytes.
+ *
+ * @param input the InputStream to copy
+ * @param output the target, may be null to simulate output to dev/null on Linux and NUL on
+ * Windows
+ * @return the number of bytes copied
+ * @throws IOException if an error occurs
+ */
+ public static long copy(final InputStream input, final OutputStream output) throws IOException {
+ return copy(input, output, COPY_BUF_SIZE);
+ }
+
+ /**
+ * Copies the content of a InputStream into an OutputStream
+ *
+ * @param input the InputStream to copy
+ * @param output the target, may be null to simulate output to dev/null on Linux and NUL on
+ * Windows
+ * @param buffersize the buffer size to use, must be bigger than 0
+ * @return the number of bytes copied
+ * @throws IOException if an error occurs
+ * @throws IllegalArgumentException if buffersize is smaller than or equal to 0
+ */
+ public static long copy(final InputStream input, final OutputStream output, final int buffersize)
+ throws IOException {
+ return ByteStreamsKt.copyTo(input, output, buffersize);
+ }
+
+ /**
+ * Skips the given number of bytes by repeatedly invoking skip on the given input stream if
+ * necessary.
+ *
+ * In a case where the stream's skip() method returns 0 before the requested number of bytes
+ * has been skip this implementation will fall back to using the read() method.
+ *
+ *
This method will only skip less than the requested number of bytes if the end of the input
+ * stream has been reached.
+ *
+ * @param input stream to skip bytes in
+ * @param numToSkip the number of bytes to skip
+ * @return the number of bytes actually skipped
+ * @throws IOException on error
+ */
+ public static long skip(final InputStream input, long numToSkip) throws IOException {
+ final long available = numToSkip;
+ while (numToSkip > 0) {
+ final long skipped = input.skip(numToSkip);
+ if (skipped == 0) {
+ break;
+ }
+ numToSkip -= skipped;
+ }
+
+ while (numToSkip > 0) {
+ final int read = readFully(input, SKIP_BUF, 0, (int) Math.min(numToSkip, SKIP_BUF_SIZE));
+ if (read < 1) {
+ break;
+ }
+ numToSkip -= read;
+ }
+ return available - numToSkip;
+ }
+
+ /**
+ * Reads as much from input as possible to fill the given array with the given amount of bytes.
+ *
+ *
This method may invoke read repeatedly to read the bytes and only read less bytes than the
+ * requested length if the end of the stream has been reached.
+ *
+ * @param input stream to read from
+ * @param array buffer to fill
+ * @param offset offset into the buffer to start filling at
+ * @param len of bytes to read
+ * @return the number of bytes actually read
+ * @throws IOException if an I/O error has occurred
+ */
+ public static int readFully(
+ final InputStream input, final byte[] array, final int offset, final int len)
+ throws IOException {
+ if (len < 0 || offset < 0 || len + offset > array.length || len + offset < 0) {
+ throw new IndexOutOfBoundsException();
+ }
+ int count = 0, x = 0;
+ while (count != len) {
+ x = input.read(array, offset + count, len - count);
+ if (x == -1) {
+ break;
+ }
+ count += x;
+ }
+ return count;
+ }
+
+ /**
+ * Reads {@code b.remaining()} bytes from the given channel starting at the current channel's
+ * position.
+ *
+ *
This method reads repeatedly from the channel until the requested number of bytes are read.
+ * This method blocks until the requested number of bytes are read, the end of the channel is
+ * detected, or an exception is thrown.
+ *
+ * @param channel the channel to read from
+ * @param byteBuffer the buffer into which the data is read.
+ * @throws IOException - if an I/O error occurs.
+ * @throws EOFException - if the channel reaches the end before reading all the bytes.
+ */
+ public static void readFully(final ReadableByteChannel channel, final ByteBuffer byteBuffer)
+ throws IOException {
+ final int expectedLength = byteBuffer.remaining();
+ int read = 0;
+ while (read < expectedLength) {
+ final int readNow = channel.read(byteBuffer);
+ if (readNow <= 0) {
+ break;
+ }
+ read += readNow;
+ }
+ if (read < expectedLength) {
+ throw new EOFException();
+ }
+ }
+
+ // toByteArray(InputStream) copied from:
+ // commons/proper/io/trunk/src/main/java/org/apache/commons/io/IOUtils.java?revision=1428941
+ // January 8th, 2013
+ //
+ // Assuming our copy() works just as well as theirs! :-)
+
+ /**
+ * Gets the contents of an {@code InputStream} as a {@code byte[]}.
+ *
+ *
This method buffers the input internally, so there is no need to use a {@code
+ * BufferedInputStream}.
+ *
+ * @param input the {@code InputStream} to read from
+ * @return the requested byte array
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.5
+ */
+ public static byte[] toByteArray(final InputStream input) throws IOException {
+ return ByteStreamsKt.readBytes(input);
+ }
+
+ /**
+ * Copies part of the content of a InputStream into an OutputStream. Uses a default buffer size of
+ * 8024 bytes.
+ *
+ * @param input the InputStream to copy
+ * @param output the target Stream
+ * @param len maximum amount of bytes to copy
+ * @return the number of bytes copied
+ * @throws IOException if an error occurs
+ * @since 1.21
+ */
+ public static long copyRange(final InputStream input, final long len, final OutputStream output)
+ throws IOException {
+ return copyRange(input, len, output, COPY_BUF_SIZE);
+ }
+
+ /**
+ * Copies part of the content of a InputStream into an OutputStream
+ *
+ * @param input the InputStream to copy
+ * @param len maximum amount of bytes to copy
+ * @param output the target, may be null to simulate output to dev/null on Linux and NUL on
+ * Windows
+ * @param buffersize the buffer size to use, must be bigger than 0
+ * @return the number of bytes copied
+ * @throws IOException if an error occurs
+ * @throws IllegalArgumentException if buffersize is smaller than or equal to 0
+ * @since 1.21
+ */
+ public static long copyRange(
+ final InputStream input, final long len, final OutputStream output, final int buffersize)
+ throws IOException {
+ if (buffersize < 1) {
+ throw new IllegalArgumentException("buffersize must be bigger than 0");
+ }
+ final byte[] buffer = new byte[(int) Math.min(buffersize, len)];
+ int n = 0;
+ long count = 0;
+ while (count < len
+ && -1 != (n = input.read(buffer, 0, (int) Math.min(len - count, buffer.length)))) {
+ if (output != null) {
+ output.write(buffer, 0, n);
+ }
+ count += n;
+ }
+ return count;
+ }
+
+ /**
+ * Gets part of the contents of an {@code InputStream} as a {@code byte[]}.
+ *
+ * @param input the {@code InputStream} to read from
+ * @param len maximum amount of bytes to copy
+ * @return the requested byte array
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.21
+ */
+ public static byte[] readRange(final InputStream input, final int len) throws IOException {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copyRange(input, len, output);
+ return output.toByteArray();
+ }
+
+ /**
+ * Gets part of the contents of an {@code ReadableByteChannel} as a {@code byte[]}.
+ *
+ * @param input the {@code ReadableByteChannel} to read from
+ * @param len maximum amount of bytes to copy
+ * @return the requested byte array
+ * @throws NullPointerException if the input is null
+ * @throws IOException if an I/O error occurs
+ * @since 1.21
+ */
+ public static byte[] readRange(final ReadableByteChannel input, final int len)
+ throws IOException {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ final ByteBuffer b = ByteBuffer.allocate(Math.min(len, COPY_BUF_SIZE));
+ int read = 0;
+ while (read < len) {
+ // Make sure we never read more than len bytes
+ b.limit(Math.min(len - read, b.capacity()));
+ final int readNow = input.read(b);
+ if (readNow <= 0) {
+ break;
+ }
+ output.write(b.array(), 0, readNow);
+ b.rewind();
+ read += readNow;
+ }
+ return output.toByteArray();
+ }
+}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMA2Decoder.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMA2Decoder.java
index 582b2b5d50..ce8cc422ed 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMA2Decoder.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMA2Decoder.java
@@ -24,6 +24,7 @@
import java.io.InputStream;
import java.io.OutputStream;
+import org.apache.commons.compress.MemoryLimitException;
import org.tukaani.xz.FinishableOutputStream;
import org.tukaani.xz.FinishableWrapperOutputStream;
import org.tukaani.xz.LZMA2InputStream;
@@ -40,12 +41,17 @@ InputStream decode(
final InputStream in,
final long uncompressedLength,
final Coder coder,
- final byte[] password)
+ final byte[] password,
+ final int maxMemoryLimitInKb)
throws IOException {
try {
final int dictionarySize = getDictionarySize(coder);
+ final int memoryUsageInKb = LZMA2InputStream.getMemoryUsage(dictionarySize);
+ if (memoryUsageInKb > maxMemoryLimitInKb) {
+ throw new MemoryLimitException(memoryUsageInKb, maxMemoryLimitInKb);
+ }
return new LZMA2InputStream(in, dictionarySize);
- } catch (final IllegalArgumentException ex) {
+ } catch (final IllegalArgumentException ex) { // NOSONAR
throw new IOException(ex.getMessage());
}
}
@@ -66,7 +72,7 @@ byte[] getOptionsAsProperties(final Object opts) {
}
@Override
- Object getOptionsFromCoder(final Coder coder, final InputStream in) {
+ Object getOptionsFromCoder(final Coder coder, final InputStream in) throws IOException {
return getDictionarySize(coder);
}
@@ -77,13 +83,19 @@ private int getDictSize(final Object opts) {
return numberOptionOrDefault(opts);
}
- private int getDictionarySize(final Coder coder) throws IllegalArgumentException {
+ private int getDictionarySize(final Coder coder) throws IOException {
+ if (coder.properties == null) {
+ throw new IOException("Missing LZMA2 properties");
+ }
+ if (coder.properties.length < 1) {
+ throw new IOException("LZMA2 properties too short");
+ }
final int dictionarySizeBits = 0xff & coder.properties[0];
if ((dictionarySizeBits & (~0x3f)) != 0) {
- throw new IllegalArgumentException("Unsupported LZMA2 property bits");
+ throw new IOException("Unsupported LZMA2 property bits");
}
if (dictionarySizeBits > 40) {
- throw new IllegalArgumentException("Dictionary larger than 4GiB maximum size");
+ throw new IOException("Dictionary larger than 4GiB maximum size");
}
if (dictionarySizeBits == 40) {
return 0xFFFFffff;
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMADecoder.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMADecoder.java
index 3d14e20e9d..6a2924bed1 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMADecoder.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/LZMADecoder.java
@@ -24,6 +24,7 @@
import java.io.InputStream;
import java.io.OutputStream;
+import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.utils.ByteUtils;
import org.apache.commons.compress.utils.FlushShieldFilterOutputStream;
import org.tukaani.xz.LZMA2Options;
@@ -41,14 +42,27 @@ InputStream decode(
final InputStream in,
final long uncompressedLength,
final Coder coder,
- final byte[] password)
+ final byte[] password,
+ final int maxMemoryLimitInKb)
throws IOException {
+ if (coder.properties == null) {
+ throw new IOException("Missing LZMA properties");
+ }
+ if (coder.properties.length < 1) {
+ throw new IOException("LZMA properties too short");
+ }
final byte propsByte = coder.properties[0];
final int dictSize = getDictionarySize(coder);
if (dictSize > LZMAInputStream.DICT_SIZE_MAX) {
throw new IOException("Dictionary larger than 4GiB maximum size used in " + archiveName);
}
- return new LZMAInputStream(in, uncompressedLength, propsByte, dictSize);
+ final int memoryUsageInKb = LZMAInputStream.getMemoryUsage(dictSize, propsByte);
+ if (memoryUsageInKb > maxMemoryLimitInKb) {
+ throw new MemoryLimitException(memoryUsageInKb, maxMemoryLimitInKb);
+ }
+ final LZMAInputStream lzmaIn = new LZMAInputStream(in, uncompressedLength, propsByte, dictSize);
+ lzmaIn.enableRelaxedEndCondition();
+ return lzmaIn;
}
@SuppressWarnings("resource")
@@ -62,8 +76,8 @@ OutputStream encode(final OutputStream out, final Object opts) throws IOExceptio
byte[] getOptionsAsProperties(final Object opts) throws IOException {
final LZMA2Options options = getOptions(opts);
final byte props = (byte) ((options.getPb() * 5 + options.getLp()) * 9 + options.getLc());
- int dictSize = options.getDictSize();
- byte[] o = new byte[5];
+ final int dictSize = options.getDictSize();
+ final byte[] o = new byte[5];
o[0] = props;
ByteUtils.toLittleEndian(o, dictSize, 1, 4);
return o;
@@ -71,13 +85,19 @@ byte[] getOptionsAsProperties(final Object opts) throws IOException {
@Override
Object getOptionsFromCoder(final Coder coder, final InputStream in) throws IOException {
+ if (coder.properties == null) {
+ throw new IOException("Missing LZMA properties");
+ }
+ if (coder.properties.length < 1) {
+ throw new IOException("LZMA properties too short");
+ }
final byte propsByte = coder.properties[0];
int props = propsByte & 0xFF;
- int pb = props / (9 * 5);
+ final int pb = props / (9 * 5);
props -= pb * 9 * 5;
- int lp = props / 9;
- int lc = props - lp * 9;
- LZMA2Options opts = new LZMA2Options();
+ final int lp = props / 9;
+ final int lc = props - lp * 9;
+ final LZMA2Options opts = new LZMA2Options();
opts.setPb(pb);
opts.setLcLp(lc, lp);
opts.setDictSize(getDictionarySize(coder));
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZArchiveEntry.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZArchiveEntry.java
index d69ceaf5d3..0f3e628bd7 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZArchiveEntry.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZArchiveEntry.java
@@ -20,10 +20,13 @@
package com.amaze.filemanager.filesystem.compressed.sevenz;
+import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
+import java.util.Iterator;
import java.util.LinkedList;
+import java.util.Objects;
import java.util.TimeZone;
import org.apache.commons.compress.archivers.ArchiveEntry;
@@ -50,6 +53,7 @@ public class SevenZArchiveEntry implements ArchiveEntry {
private long crc, compressedCrc;
private long size, compressedSize;
private Iterable extends SevenZMethodConfiguration> contentMethods;
+ static final SevenZArchiveEntry[] EMPTY_SEVEN_Z_ARCHIVE_ENTRY_ARRAY = new SevenZArchiveEntry[0];
public SevenZArchiveEntry() {}
@@ -369,7 +373,7 @@ public void setCrc(final int crc) {
/**
* Gets the CRC.
*
- * @since Compress 1.7
+ * @since 1.7
* @return the CRC
*/
public long getCrcValue() {
@@ -379,7 +383,7 @@ public long getCrcValue() {
/**
* Sets the CRC.
*
- * @since Compress 1.7
+ * @since 1.7
* @param crc the CRC
*/
public void setCrcValue(final long crc) {
@@ -411,7 +415,7 @@ void setCompressedCrc(final int crc) {
/**
* Gets the compressed CRC.
*
- * @since Compress 1.7
+ * @since 1.7
* @return the CRC
*/
long getCompressedCrcValue() {
@@ -421,7 +425,7 @@ long getCompressedCrcValue() {
/**
* Sets the compressed CRC.
*
- * @since Compress 1.7
+ * @since 1.7
* @param crc the CRC
*/
void setCompressedCrcValue(final long crc) {
@@ -488,6 +492,21 @@ public void setContentMethods(final Iterable extends SevenZMethodConfiguration
}
}
+ /**
+ * Sets the (compression) methods to use for entry's content - the default is LZMA2.
+ *
+ *
Currently only {@link SevenZMethod#COPY}, {@link SevenZMethod#LZMA2}, {@link
+ * SevenZMethod#BZIP2} and {@link SevenZMethod#DEFLATE} are supported when writing archives.
+ *
+ *
The methods will be consulted in iteration order to create the final output.
+ *
+ * @param methods the methods to use for the content
+ * @since 1.22
+ */
+ public void setContentMethods(SevenZMethodConfiguration... methods) {
+ setContentMethods(Arrays.asList(methods));
+ }
+
/**
* Gets the (compression) methods to use for entry's content - the default is LZMA2.
*
@@ -503,6 +522,41 @@ public Iterable extends SevenZMethodConfiguration> getContentMethods() {
return contentMethods;
}
+ @Override
+ public int hashCode() {
+ final String n = getName();
+ return n == null ? 0 : n.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final SevenZArchiveEntry other = (SevenZArchiveEntry) obj;
+ return Objects.equals(name, other.name)
+ && hasStream == other.hasStream
+ && isDirectory == other.isDirectory
+ && isAntiItem == other.isAntiItem
+ && hasCreationDate == other.hasCreationDate
+ && hasLastModifiedDate == other.hasLastModifiedDate
+ && hasAccessDate == other.hasAccessDate
+ && creationDate == other.creationDate
+ && lastModifiedDate == other.lastModifiedDate
+ && accessDate == other.accessDate
+ && hasWindowsAttributes == other.hasWindowsAttributes
+ && windowsAttributes == other.windowsAttributes
+ && hasCrc == other.hasCrc
+ && crc == other.crc
+ && compressedCrc == other.compressedCrc
+ && size == other.size
+ && compressedSize == other.compressedSize
+ && equalSevenZMethods(contentMethods, other.contentMethods);
+ }
+
/**
* Converts NTFS time (100 nanosecond units since 1 January 1601) to Java time.
*
@@ -531,4 +585,25 @@ public static long javaTimeToNtfsTime(final Date date) {
ntfsEpoch.set(Calendar.MILLISECOND, 0);
return ((date.getTime() - ntfsEpoch.getTimeInMillis()) * 1000 * 10);
}
+
+ private boolean equalSevenZMethods(
+ final Iterable extends SevenZMethodConfiguration> c1,
+ final Iterable extends SevenZMethodConfiguration> c2) {
+ if (c1 == null) {
+ return c2 == null;
+ }
+ if (c2 == null) {
+ return false;
+ }
+ final Iterator extends SevenZMethodConfiguration> i2 = c2.iterator();
+ for (SevenZMethodConfiguration element : c1) {
+ if (!i2.hasNext()) {
+ return false;
+ }
+ if (!element.equals(i2.next())) {
+ return false;
+ }
+ }
+ return !i2.hasNext();
+ }
}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFile.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFile.java
index d0fd38e8c5..6b0039da58 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFile.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFile.java
@@ -24,6 +24,7 @@
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.DataInputStream;
+import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
@@ -32,23 +33,27 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
+import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.charset.Charset;
-import java.nio.charset.CharsetEncoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
+import java.util.LinkedHashMap;
import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
import java.util.zip.CRC32;
+import java.util.zip.CheckedInputStream;
+import org.apache.commons.compress.MemoryLimitException;
import org.apache.commons.compress.utils.BoundedInputStream;
+import org.apache.commons.compress.utils.ByteUtils;
import org.apache.commons.compress.utils.CRC32VerifyingInputStream;
-import org.apache.commons.compress.utils.CharsetNames;
-import org.apache.commons.compress.utils.IOUtils;
import org.apache.commons.compress.utils.InputStreamStatistics;
/**
- * Reads a 7z file, using FileChannel under the covers.
+ * Reads a 7z file, using SeekableByteChannel under the covers.
*
*
The 7z file format is a flexible container that can contain many compression and encryption
* types, but at the moment only only Copy, LZMA, LZMA2, BZIP2, Deflate and AES-256 + SHA-256 are
@@ -61,20 +66,27 @@
*
*
Both the header and file contents may be compressed and/or encrypted. With both encrypted,
* neither file names nor file contents can be read, but the use of encryption isn't plausibly
- * deniable. @NotThreadSafe
+ * deniable.
+ *
+ *
Multi volume archives can be read by concatenating the parts in correct order - either
+ * manually or by using {link org.apache.commons.compress.utils.MultiReadOnlySeekableByteChannel}
+ * for example. @NotThreadSafe
*
* @since 1.6
*/
public class SevenZFile implements Closeable {
static final int SIGNATURE_HEADER_SIZE = 32;
+ private static final String DEFAULT_FILE_NAME = "unknown archive";
+
private final String fileName;
private FileChannel channel;
private final Archive archive;
private int currentEntryIndex = -1;
private int currentFolderIndex = -1;
- private InputStream currentFolderInputStream = null;
+ private InputStream currentFolderInputStream;
private byte[] password;
+ private final SevenZFileOptions options;
private long compressedBytesReadFromCurrentEntry;
private long uncompressedBytesReadFromCurrentEntry;
@@ -89,34 +101,55 @@ public class SevenZFile implements Closeable {
/**
* Reads a file as 7z archive
*
- * @param filename the file to read
+ * @param fileName the file to read
* @param password optional password if the archive is encrypted
* @throws IOException if reading the archive fails
* @since 1.17
*/
- public SevenZFile(final File filename, final char[] password) throws IOException {
+ public SevenZFile(final File fileName, final char[] password) throws IOException {
+ this(fileName, password, SevenZFileOptions.DEFAULT);
+ }
+
+ /**
+ * Reads a file as 7z archive with additional options.
+ *
+ * @param fileName the file to read
+ * @param password optional password if the archive is encrypted
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(final File fileName, final char[] password, final SevenZFileOptions options)
+ throws IOException {
this(
- new FileInputStream(filename).getChannel(),
- filename.getAbsolutePath(),
+ new FileInputStream(fileName).getChannel(), // NOSONAR
+ fileName.getAbsolutePath(),
utf16Decode(password),
- true);
+ true,
+ options);
}
/**
* Reads a file as 7z archive
*
- * @param filename the file to read
+ * @param fileName the file to read
* @param password optional password if the archive is encrypted - the byte array is supposed to
* be the UTF16-LE encoded representation of the password.
* @throws IOException if reading the archive fails
* @deprecated use the char[]-arg version for the password instead
*/
- public SevenZFile(final File filename, final byte[] password) throws IOException {
- this(new FileInputStream(filename).getChannel(), filename.getAbsolutePath(), password, true);
+ @Deprecated
+ public SevenZFile(final File fileName, final byte[] password) throws IOException {
+ this(
+ new FileInputStream(fileName).getChannel(),
+ fileName.getAbsolutePath(),
+ password,
+ true,
+ SevenZFileOptions.DEFAULT);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
@@ -126,11 +159,26 @@ public SevenZFile(final File filename, final byte[] password) throws IOException
* @since 1.13
*/
public SevenZFile(final FileChannel channel) throws IOException {
- this(channel, "unknown archive", (char[]) null);
+ this(channel, SevenZFileOptions.DEFAULT);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive with addtional options.
+ *
+ *
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
+ * from an in-memory archive.
+ *
+ * @param channel the channel to read
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(final FileChannel channel, final SevenZFileOptions options) throws IOException {
+ this(channel, DEFAULT_FILE_NAME, null, options);
+ }
+
+ /**
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
@@ -141,43 +189,101 @@ public SevenZFile(final FileChannel channel) throws IOException {
* @since 1.17
*/
public SevenZFile(final FileChannel channel, final char[] password) throws IOException {
- this(channel, "unknown archive", utf16Decode(password));
+ this(channel, password, SevenZFileOptions.DEFAULT);
+ }
+
+ /**
+ * Reads a SeekableByteChannel as 7z archive with additional options.
+ *
+ *
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
+ * from an in-memory archive.
+ *
+ * @param channel the channel to read
+ * @param password optional password if the archive is encrypted
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(
+ final FileChannel channel, final char[] password, final SevenZFileOptions options)
+ throws IOException {
+ this(channel, DEFAULT_FILE_NAME, password, options);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
*
* @param channel the channel to read
- * @param filename name of the archive - only used for error reporting
+ * @param fileName name of the archive - only used for error reporting
* @param password optional password if the archive is encrypted
* @throws IOException if reading the archive fails
* @since 1.17
*/
- public SevenZFile(final FileChannel channel, String filename, final char[] password)
+ public SevenZFile(final FileChannel channel, final String fileName, final char[] password)
+ throws IOException {
+ this(channel, fileName, password, SevenZFileOptions.DEFAULT);
+ }
+
+ /**
+ * Reads a SeekableByteChannel as 7z archive with addtional options.
+ *
+ *
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
+ * from an in-memory archive.
+ *
+ * @param channel the channel to read
+ * @param fileName name of the archive - only used for error reporting
+ * @param password optional password if the archive is encrypted
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(
+ final FileChannel channel,
+ final String fileName,
+ final char[] password,
+ final SevenZFileOptions options)
throws IOException {
- this(channel, filename, utf16Decode(password), false);
+ this(channel, fileName, utf16Decode(password), false, options);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
*
* @param channel the channel to read
- * @param filename name of the archive - only used for error reporting
+ * @param fileName name of the archive - only used for error reporting
* @throws IOException if reading the archive fails
* @since 1.17
*/
- public SevenZFile(final FileChannel channel, String filename) throws IOException {
- this(channel, filename, null, false);
+ public SevenZFile(final FileChannel channel, final String fileName) throws IOException {
+ this(channel, fileName, SevenZFileOptions.DEFAULT);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive with additional options.
+ *
+ *
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
+ * from an in-memory archive.
+ *
+ * @param channel the channel to read
+ * @param fileName name of the archive - only used for error reporting
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(
+ final FileChannel channel, final String fileName, final SevenZFileOptions options)
+ throws IOException {
+ this(channel, fileName, null, false, options);
+ }
+
+ /**
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
@@ -189,35 +295,42 @@ public SevenZFile(final FileChannel channel, String filename) throws IOException
* @since 1.13
* @deprecated use the char[]-arg version for the password instead
*/
+ @Deprecated
public SevenZFile(final FileChannel channel, final byte[] password) throws IOException {
- this(channel, "unknown archive", password);
+ this(channel, DEFAULT_FILE_NAME, password);
}
/**
- * Reads a FileChannel as 7z archive
+ * Reads a SeekableByteChannel as 7z archive
*
*
{@link org.apache.commons.compress.utils.SeekableInMemoryByteChannel} allows you to read
* from an in-memory archive.
*
* @param channel the channel to read
- * @param filename name of the archive - only used for error reporting
+ * @param fileName name of the archive - only used for error reporting
* @param password optional password if the archive is encrypted - the byte array is supposed to
* be the UTF16-LE encoded representation of the password.
* @throws IOException if reading the archive fails
* @since 1.13
* @deprecated use the char[]-arg version for the password instead
*/
- public SevenZFile(final FileChannel channel, String filename, final byte[] password)
+ @Deprecated
+ public SevenZFile(final FileChannel channel, final String fileName, final byte[] password)
throws IOException {
- this(channel, filename, password, false);
+ this(channel, fileName, password, false, SevenZFileOptions.DEFAULT);
}
private SevenZFile(
- final FileChannel channel, String filename, final byte[] password, boolean closeOnError)
+ final FileChannel channel,
+ final String filename,
+ final byte[] password,
+ final boolean closeOnError,
+ final SevenZFileOptions options)
throws IOException {
boolean succeeded = false;
this.channel = channel;
this.fileName = filename;
+ this.options = options;
try {
archive = readHeaders(password);
if (password != null) {
@@ -236,11 +349,23 @@ private SevenZFile(
/**
* Reads a file as unencrypted 7z archive
*
- * @param filename the file to read
+ * @param fileName the file to read
* @throws IOException if reading the archive fails
*/
- public SevenZFile(final File filename) throws IOException {
- this(filename, (char[]) null);
+ public SevenZFile(final File fileName) throws IOException {
+ this(fileName, SevenZFileOptions.DEFAULT);
+ }
+
+ /**
+ * Reads a file as unencrypted 7z archive
+ *
+ * @param fileName the file to read
+ * @param options the options to apply
+ * @throws IOException if reading the archive fails or the memory limit (if set) is too small
+ * @since 1.19
+ */
+ public SevenZFile(final File fileName, final SevenZFileOptions options) throws IOException {
+ this(fileName, null, options);
}
/**
@@ -275,13 +400,16 @@ public SevenZArchiveEntry getNextEntry() throws IOException {
}
++currentEntryIndex;
final SevenZArchiveEntry entry = archive.files[currentEntryIndex];
- buildDecodingStream();
+ if (entry.getName() == null && options.getUseDefaultNameForUnnamedEntries()) {
+ entry.setName(getDefaultName());
+ }
+ buildDecodingStream(currentEntryIndex, false);
uncompressedBytesReadFromCurrentEntry = compressedBytesReadFromCurrentEntry = 0;
return entry;
}
/**
- * Returns meta-data of all archive entries.
+ * Returns a copy of meta-data of all archive entries.
*
*
This method only provides meta-data, the entries can not be used to read the contents, you
* still need to process all entries in order using {@link #getNextEntry} for that.
@@ -289,15 +417,15 @@ public SevenZArchiveEntry getNextEntry() throws IOException {
*
The content methods are only available for entries that have already been reached via {@link
* #getNextEntry}.
*
- * @return meta-data of all archive entries.
+ * @return a copy of meta-data of all archive entries.
* @since 1.11
*/
public Iterable getEntries() {
- return Arrays.asList(archive.files);
+ return new ArrayList<>(Arrays.asList(archive.files));
}
private Archive readHeaders(final byte[] password) throws IOException {
- ByteBuffer buf =
+ final ByteBuffer buf =
ByteBuffer.allocate(12 /* signature + 2 bytes version + 4 bytes CRC */)
.order(ByteOrder.LITTLE_ENDIAN);
readFully(buf);
@@ -315,23 +443,104 @@ private Archive readHeaders(final byte[] password) throws IOException {
"Unsupported 7z version (%d,%d)", archiveVersionMajor, archiveVersionMinor));
}
+ boolean headerLooksValid =
+ false; // See https://www.7-zip.org/recover.html - "There is no correct End Header at the
+ // end of archive"
final long startHeaderCrc = 0xffffFFFFL & buf.getInt();
- final StartHeader startHeader = readStartHeader(startHeaderCrc);
+ if (startHeaderCrc == 0) {
+ // This is an indication of a corrupt header - peek the next 20 bytes
+ final long currentPosition = channel.position();
+ final ByteBuffer peekBuf = ByteBuffer.allocate(20);
+ readFully(peekBuf);
+ channel.position(currentPosition);
+ // Header invalid if all data is 0
+ while (peekBuf.hasRemaining()) {
+ if (peekBuf.get() != 0) {
+ headerLooksValid = true;
+ break;
+ }
+ }
+ } else {
+ headerLooksValid = true;
+ }
- final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
- if (nextHeaderSizeInt != startHeader.nextHeaderSize) {
- throw new IOException("cannot handle nextHeaderSize " + startHeader.nextHeaderSize);
+ if (headerLooksValid) {
+ return initializeArchive(readStartHeader(startHeaderCrc), password, true);
}
- channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
- buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
- readFully(buf);
- final CRC32 crc = new CRC32();
- crc.update(buf.array());
- if (startHeader.nextHeaderCrc != crc.getValue()) {
- throw new IOException("NextHeader CRC mismatch");
+ // No valid header found - probably first file of multipart archive was removed too early. Scan
+ // for end header.
+ if (options.getTryToRecoverBrokenArchives()) {
+ return tryToLocateEndHeader(password);
}
+ throw new IOException(
+ "archive seems to be invalid.\nYou may want to retry and enable the"
+ + " tryToRecoverBrokenArchives if the archive could be a multi volume archive that has been closed"
+ + " prematurely.");
+ }
+
+ private Archive tryToLocateEndHeader(final byte[] password) throws IOException {
+ final ByteBuffer nidBuf = ByteBuffer.allocate(1);
+ final long searchLimit = 1024L * 1024 * 1;
+ // Main header, plus bytes that readStartHeader would read
+ final long previousDataSize = channel.position() + 20;
+ final long minPos;
+ // Determine minimal position - can't start before current position
+ if (channel.position() + searchLimit > channel.size()) {
+ minPos = channel.position();
+ } else {
+ minPos = channel.size() - searchLimit;
+ }
+ long pos = channel.size() - 1;
+ // Loop: Try from end of archive
+ while (pos > minPos) {
+ pos--;
+ channel.position(pos);
+ nidBuf.rewind();
+ if (channel.read(nidBuf) < 1) {
+ throw new EOFException();
+ }
+ final int nid = nidBuf.array()[0];
+ // First indicator: Byte equals one of these header identifiers
+ if (nid == NID.kEncodedHeader || nid == NID.kHeader) {
+ try {
+ // Try to initialize Archive structure from here
+ final StartHeader startHeader = new StartHeader();
+ startHeader.nextHeaderOffset = pos - previousDataSize;
+ startHeader.nextHeaderSize = channel.size() - pos;
+ final Archive result = initializeArchive(startHeader, password, false);
+ // Sanity check: There must be some data...
+ if (result.packSizes.length > 0 && result.files.length > 0) {
+ return result;
+ }
+ } catch (final Exception ignore) {
+ // Wrong guess...
+ }
+ }
+ }
+ throw new IOException("Start header corrupt and unable to guess end header");
+ }
+ private Archive initializeArchive(
+ final StartHeader startHeader, final byte[] password, final boolean verifyCrc)
+ throws IOException {
+ assertFitsIntoNonNegativeInt("nextHeaderSize", startHeader.nextHeaderSize);
+ final int nextHeaderSizeInt = (int) startHeader.nextHeaderSize;
+ channel.position(SIGNATURE_HEADER_SIZE + startHeader.nextHeaderOffset);
+ if (verifyCrc) {
+ final long position = channel.position();
+ CheckedInputStream cis =
+ new CheckedInputStream(Channels.newInputStream(channel), new CRC32());
+ if (cis.skip(nextHeaderSizeInt) != nextHeaderSizeInt) {
+ throw new IOException("Problem computing NextHeader CRC-32");
+ }
+ if (startHeader.nextHeaderCrc != cis.getChecksum().getValue()) {
+ throw new IOException("NextHeader CRC-32 mismatch");
+ }
+ channel.position(position);
+ }
Archive archive = new Archive();
+ ByteBuffer buf = ByteBuffer.allocate(nextHeaderSizeInt).order(ByteOrder.LITTLE_ENDIAN);
+ readFully(buf);
int nid = getUnsignedByte(buf);
if (nid == NID.kEncodedHeader) {
buf = readEncodedHeader(buf, archive, password);
@@ -339,11 +548,11 @@ private Archive readHeaders(final byte[] password) throws IOException {
archive = new Archive();
nid = getUnsignedByte(buf);
}
- if (nid == NID.kHeader) {
- readHeader(buf, archive);
- } else {
+ if (nid != NID.kHeader) {
throw new IOException("Broken or unsupported archive: no Header");
}
+ readHeader(buf, archive);
+ archive.subStreamsInfo = null;
return archive;
}
@@ -351,17 +560,35 @@ private StartHeader readStartHeader(final long startHeaderCrc) throws IOExceptio
final StartHeader startHeader = new StartHeader();
// using Stream rather than ByteBuffer for the benefit of the
// built-in CRC check
- DataInputStream dataInputStream =
+ try (DataInputStream dataInputStream =
new DataInputStream(
new CRC32VerifyingInputStream(
- new BoundedFileChannelInputStream(channel, 20), 20, startHeaderCrc));
- startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
- startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
- startHeader.nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
- return startHeader;
+ new BoundedFileChannelInputStream(channel, 20), 20, startHeaderCrc))) {
+ startHeader.nextHeaderOffset = Long.reverseBytes(dataInputStream.readLong());
+ if (startHeader.nextHeaderOffset < 0
+ || startHeader.nextHeaderOffset + SIGNATURE_HEADER_SIZE > channel.size()) {
+ throw new IOException("nextHeaderOffset is out of bounds");
+ }
+
+ startHeader.nextHeaderSize = Long.reverseBytes(dataInputStream.readLong());
+ final long nextHeaderEnd = startHeader.nextHeaderOffset + startHeader.nextHeaderSize;
+ if (nextHeaderEnd < startHeader.nextHeaderOffset
+ || nextHeaderEnd + SIGNATURE_HEADER_SIZE > channel.size()) {
+ throw new IOException("nextHeaderSize is out of bounds");
+ }
+
+ startHeader.nextHeaderCrc = 0xffffFFFFL & Integer.reverseBytes(dataInputStream.readInt());
+
+ return startHeader;
+ }
}
private void readHeader(final ByteBuffer header, final Archive archive) throws IOException {
+ final int pos = header.position();
+ final ArchiveStatistics stats = sanityCheckAndCollectStatistics(header);
+ stats.assertValidity(options.getMaxMemoryLimitInKb());
+ header.position(pos);
+
int nid = getUnsignedByte(header);
if (nid == NID.kArchiveProperties) {
@@ -371,7 +598,7 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I
if (nid == NID.kAdditionalStreamsInfo) {
throw new IOException("Additional streams unsupported");
- // nid = header.readUnsignedByte();
+ // nid = getUnsignedByte(header);
}
if (nid == NID.kMainStreamsInfo) {
@@ -383,10 +610,39 @@ private void readHeader(final ByteBuffer header, final Archive archive) throws I
readFilesInfo(header, archive);
nid = getUnsignedByte(header);
}
+ }
+
+ private ArchiveStatistics sanityCheckAndCollectStatistics(final ByteBuffer header)
+ throws IOException {
+ final ArchiveStatistics stats = new ArchiveStatistics();
+
+ int nid = getUnsignedByte(header);
+
+ if (nid == NID.kArchiveProperties) {
+ sanityCheckArchiveProperties(header);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kAdditionalStreamsInfo) {
+ throw new IOException("Additional streams unsupported");
+ // nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kMainStreamsInfo) {
+ sanityCheckStreamsInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kFilesInfo) {
+ sanityCheckFilesInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
if (nid != NID.kEnd) {
throw new IOException("Badly terminated header, found " + nid);
}
+
+ return stats;
}
private void readArchiveProperties(final ByteBuffer input) throws IOException {
@@ -395,15 +651,39 @@ private void readArchiveProperties(final ByteBuffer input) throws IOException {
while (nid != NID.kEnd) {
final long propertySize = readUint64(input);
final byte[] property = new byte[(int) propertySize];
- input.get(property);
+ get(input, property);
nid = getUnsignedByte(input);
}
}
+ private void sanityCheckArchiveProperties(final ByteBuffer header) throws IOException {
+ int nid = getUnsignedByte(header);
+ while (nid != NID.kEnd) {
+ final int propertySize = assertFitsIntoNonNegativeInt("propertySize", readUint64(header));
+ if (skipBytesFully(header, propertySize) < propertySize) {
+ throw new IOException("invalid property size");
+ }
+ nid = getUnsignedByte(header);
+ }
+ }
+
private ByteBuffer readEncodedHeader(
final ByteBuffer header, final Archive archive, final byte[] password) throws IOException {
+ final int pos = header.position();
+ final ArchiveStatistics stats = new ArchiveStatistics();
+ sanityCheckStreamsInfo(header, stats);
+ stats.assertValidity(options.getMaxMemoryLimitInKb());
+ header.position(pos);
+
readStreamsInfo(header, archive);
+ if (archive.folders == null || archive.folders.length == 0) {
+ throw new IOException("no folders, can't read encoded header");
+ }
+ if (archive.packSizes == null || archive.packSizes.length == 0) {
+ throw new IOException("no packed streams, can't read encoded header");
+ }
+
// FIXME: merge with buildDecodingStream()/buildDecoderStack() at some stage?
final Folder folder = archive.folders[0];
final int firstPackStreamIndex = 0;
@@ -422,18 +702,46 @@ private ByteBuffer readEncodedHeader(
inputStreamStack, // NOSONAR
folder.getUnpackSizeForCoder(coder),
coder,
- password);
+ password,
+ options.getMaxMemoryLimitInKb());
}
if (folder.hasCrc) {
inputStreamStack =
new CRC32VerifyingInputStream(inputStreamStack, folder.getUnpackSize(), folder.crc);
}
- final byte[] nextHeader = new byte[(int) folder.getUnpackSize()];
- DataInputStream nextHeaderInputStream = new DataInputStream(inputStreamStack);
- nextHeaderInputStream.readFully(nextHeader);
+ final int unpackSize = assertFitsIntoNonNegativeInt("unpackSize", folder.getUnpackSize());
+ final byte[] nextHeader = IOUtils.readRange(inputStreamStack, unpackSize);
+ if (nextHeader.length < unpackSize) {
+ throw new IOException("premature end of stream");
+ }
+ inputStreamStack.close();
return ByteBuffer.wrap(nextHeader).order(ByteOrder.LITTLE_ENDIAN);
}
+ private void sanityCheckStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
+ int nid = getUnsignedByte(header);
+
+ if (nid == NID.kPackInfo) {
+ sanityCheckPackInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kUnpackInfo) {
+ sanityCheckUnpackInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kSubStreamsInfo) {
+ sanityCheckSubStreamsInfo(header, stats);
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid != NID.kEnd) {
+ throw new IOException("Badly terminated StreamsInfo");
+ }
+ }
+
private void readStreamsInfo(final ByteBuffer header, final Archive archive) throws IOException {
int nid = getUnsignedByte(header);
@@ -447,25 +755,58 @@ private void readStreamsInfo(final ByteBuffer header, final Archive archive) thr
nid = getUnsignedByte(header);
} else {
// archive without unpack/coders info
- archive.folders = new Folder[0];
+ archive.folders = Folder.EMPTY_FOLDER_ARRAY;
}
if (nid == NID.kSubStreamsInfo) {
readSubStreamsInfo(header, archive);
nid = getUnsignedByte(header);
}
+ }
+
+ private void sanityCheckPackInfo(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
+ final long packPos = readUint64(header);
+ if (packPos < 0
+ || SIGNATURE_HEADER_SIZE + packPos > channel.size()
+ || SIGNATURE_HEADER_SIZE + packPos < 0) {
+ throw new IOException("packPos (" + packPos + ") is out of range");
+ }
+ final long numPackStreams = readUint64(header);
+ stats.numberOfPackedStreams = assertFitsIntoNonNegativeInt("numPackStreams", numPackStreams);
+ int nid = getUnsignedByte(header);
+ if (nid == NID.kSize) {
+ long totalPackSizes = 0;
+ for (int i = 0; i < stats.numberOfPackedStreams; i++) {
+ final long packSize = readUint64(header);
+ totalPackSizes += packSize;
+ final long endOfPackStreams = SIGNATURE_HEADER_SIZE + packPos + totalPackSizes;
+ if (packSize < 0 || endOfPackStreams > channel.size() || endOfPackStreams < packPos) {
+ throw new IOException("packSize (" + packSize + ") is out of range");
+ }
+ }
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid == NID.kCRC) {
+ final int crcsDefined = readAllOrBits(header, stats.numberOfPackedStreams).cardinality();
+ if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
+ throw new IOException("invalid number of CRCs in PackInfo");
+ }
+ nid = getUnsignedByte(header);
+ }
if (nid != NID.kEnd) {
- throw new IOException("Badly terminated StreamsInfo");
+ throw new IOException("Badly terminated PackInfo (" + nid + ")");
}
}
private void readPackInfo(final ByteBuffer header, final Archive archive) throws IOException {
archive.packPos = readUint64(header);
- final long numPackStreams = readUint64(header);
+ final int numPackStreamsInt = (int) readUint64(header);
int nid = getUnsignedByte(header);
if (nid == NID.kSize) {
- archive.packSizes = new long[(int) numPackStreams];
+ archive.packSizes = new long[numPackStreamsInt];
for (int i = 0; i < archive.packSizes.length; i++) {
archive.packSizes[i] = readUint64(header);
}
@@ -473,43 +814,84 @@ private void readPackInfo(final ByteBuffer header, final Archive archive) throws
}
if (nid == NID.kCRC) {
- archive.packCrcsDefined = readAllOrBits(header, (int) numPackStreams);
- archive.packCrcs = new long[(int) numPackStreams];
- for (int i = 0; i < (int) numPackStreams; i++) {
+ archive.packCrcsDefined = readAllOrBits(header, numPackStreamsInt);
+ archive.packCrcs = new long[numPackStreamsInt];
+ for (int i = 0; i < numPackStreamsInt; i++) {
if (archive.packCrcsDefined.get(i)) {
- archive.packCrcs[i] = 0xffffFFFFL & header.getInt();
+ archive.packCrcs[i] = 0xffffFFFFL & getInt(header);
}
}
nid = getUnsignedByte(header);
}
-
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated PackInfo (" + nid + ")");
- }
}
- private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ private void sanityCheckUnpackInfo(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
int nid = getUnsignedByte(header);
if (nid != NID.kFolder) {
throw new IOException("Expected kFolder, got " + nid);
}
final long numFolders = readUint64(header);
- final Folder[] folders = new Folder[(int) numFolders];
- archive.folders = folders;
+ stats.numberOfFolders = assertFitsIntoNonNegativeInt("numFolders", numFolders);
final int external = getUnsignedByte(header);
if (external != 0) {
throw new IOException("External unsupported");
}
- for (int i = 0; i < (int) numFolders; i++) {
- folders[i] = readFolder(header);
+
+ final List numberOfOutputStreamsPerFolder = new LinkedList<>();
+ for (int i = 0; i < stats.numberOfFolders; i++) {
+ numberOfOutputStreamsPerFolder.add(sanityCheckFolder(header, stats));
+ }
+
+ final long totalNumberOfBindPairs = stats.numberOfOutStreams - stats.numberOfFolders;
+ final long packedStreamsRequiredByFolders = stats.numberOfInStreams - totalNumberOfBindPairs;
+ if (packedStreamsRequiredByFolders < stats.numberOfPackedStreams) {
+ throw new IOException("archive doesn't contain enough packed streams");
}
nid = getUnsignedByte(header);
if (nid != NID.kCodersUnpackSize) {
throw new IOException("Expected kCodersUnpackSize, got " + nid);
}
+
+ for (final int numberOfOutputStreams : numberOfOutputStreamsPerFolder) {
+ for (int i = 0; i < numberOfOutputStreams; i++) {
+ final long unpackSize = readUint64(header);
+ if (unpackSize < 0) {
+ throw new IllegalArgumentException("negative unpackSize");
+ }
+ }
+ }
+
+ nid = getUnsignedByte(header);
+ if (nid == NID.kCRC) {
+ stats.folderHasCrc = readAllOrBits(header, stats.numberOfFolders);
+ final int crcsDefined = stats.folderHasCrc.cardinality();
+ if (skipBytesFully(header, 4 * crcsDefined) < 4 * crcsDefined) {
+ throw new IOException("invalid number of CRCs in UnpackInfo");
+ }
+ nid = getUnsignedByte(header);
+ }
+
+ if (nid != NID.kEnd) {
+ throw new IOException("Badly terminated UnpackInfo");
+ }
+ }
+
+ private void readUnpackInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ int nid = getUnsignedByte(header);
+ final int numFoldersInt = (int) readUint64(header);
+ final Folder[] folders = new Folder[numFoldersInt];
+ archive.folders = folders;
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFoldersInt; i++) {
+ folders[i] = readFolder(header);
+ }
+
+ nid = getUnsignedByte(header);
for (final Folder folder : folders) {
+ assertFitsIntoNonNegativeInt("totalOutputStreams", folder.totalOutputStreams);
folder.unpackSizes = new long[(int) folder.totalOutputStreams];
for (int i = 0; i < folder.totalOutputStreams; i++) {
folder.unpackSizes[i] = readUint64(header);
@@ -518,11 +900,11 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro
nid = getUnsignedByte(header);
if (nid == NID.kCRC) {
- final BitSet crcsDefined = readAllOrBits(header, (int) numFolders);
- for (int i = 0; i < (int) numFolders; i++) {
+ final BitSet crcsDefined = readAllOrBits(header, numFoldersInt);
+ for (int i = 0; i < numFoldersInt; i++) {
if (crcsDefined.get(i)) {
folders[i].hasCrc = true;
- folders[i].crc = 0xffffFFFFL & header.getInt();
+ folders[i].crc = 0xffffFFFFL & getInt(header);
} else {
folders[i].hasCrc = false;
}
@@ -530,9 +912,71 @@ private void readUnpackInfo(final ByteBuffer header, final Archive archive) thro
nid = getUnsignedByte(header);
}
+ }
+
+ private void sanityCheckSubStreamsInfo(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
+
+ int nid = getUnsignedByte(header);
+ final List numUnpackSubStreamsPerFolder = new LinkedList<>();
+ if (nid == NID.kNumUnpackStream) {
+ for (int i = 0; i < stats.numberOfFolders; i++) {
+ numUnpackSubStreamsPerFolder.add(
+ assertFitsIntoNonNegativeInt("numStreams", readUint64(header)));
+ }
+ for (Integer n : numUnpackSubStreamsPerFolder) {
+ stats.numberOfUnpackSubStreams += n.longValue();
+ }
+ nid = getUnsignedByte(header);
+ } else {
+ stats.numberOfUnpackSubStreams = stats.numberOfFolders;
+ }
+
+ assertFitsIntoNonNegativeInt("totalUnpackStreams", stats.numberOfUnpackSubStreams);
+
+ if (nid == NID.kSize) {
+ for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
+ if (numUnpackSubStreams == 0) {
+ continue;
+ }
+ for (int i = 0; i < numUnpackSubStreams - 1; i++) {
+ final long size = readUint64(header);
+ if (size < 0) {
+ throw new IOException("negative unpackSize");
+ }
+ }
+ }
+ nid = getUnsignedByte(header);
+ }
+
+ int numDigests = 0;
+ if (numUnpackSubStreamsPerFolder.isEmpty()) {
+ numDigests =
+ stats.folderHasCrc == null
+ ? stats.numberOfFolders
+ : stats.numberOfFolders - stats.folderHasCrc.cardinality();
+ } else {
+ int folderIdx = 0;
+ for (final int numUnpackSubStreams : numUnpackSubStreamsPerFolder) {
+ if (numUnpackSubStreams != 1
+ || stats.folderHasCrc == null
+ || !stats.folderHasCrc.get(folderIdx++)) {
+ numDigests += numUnpackSubStreams;
+ }
+ }
+ }
+
+ if (nid == NID.kCRC) {
+ assertFitsIntoNonNegativeInt("numDigests", numDigests);
+ final int missingCrcs = readAllOrBits(header, numDigests).cardinality();
+ if (skipBytesFully(header, 4 * missingCrcs) < 4 * missingCrcs) {
+ throw new IOException("invalid number of missing CRCs in SubStreamInfo");
+ }
+ nid = getUnsignedByte(header);
+ }
if (nid != NID.kEnd) {
- throw new IOException("Badly terminated UnpackInfo");
+ throw new IOException("Badly terminated SubStreamsInfo");
}
}
@@ -541,19 +985,20 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
for (final Folder folder : archive.folders) {
folder.numUnpackSubStreams = 1;
}
- int totalUnpackStreams = archive.folders.length;
+ long unpackStreamsCount = archive.folders.length;
int nid = getUnsignedByte(header);
if (nid == NID.kNumUnpackStream) {
- totalUnpackStreams = 0;
+ unpackStreamsCount = 0;
for (final Folder folder : archive.folders) {
final long numStreams = readUint64(header);
folder.numUnpackSubStreams = (int) numStreams;
- totalUnpackStreams += numStreams;
+ unpackStreamsCount += numStreams;
}
nid = getUnsignedByte(header);
}
+ final int totalUnpackStreams = (int) unpackStreamsCount;
final SubStreamsInfo subStreamsInfo = new SubStreamsInfo();
subStreamsInfo.unpackSizes = new long[totalUnpackStreams];
subStreamsInfo.hasCrc = new BitSet(totalUnpackStreams);
@@ -572,6 +1017,9 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
sum += size;
}
}
+ if (sum > folder.getUnpackSize()) {
+ throw new IOException("sum of unpack sizes of folder exceeds total unpack size");
+ }
subStreamsInfo.unpackSizes[nextUnpackStream++] = folder.getUnpackSize() - sum;
}
if (nid == NID.kSize) {
@@ -590,7 +1038,7 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
final long[] missingCrcs = new long[numDigests];
for (int i = 0; i < numDigests; i++) {
if (hasMissingCrc.get(i)) {
- missingCrcs[i] = 0xffffFFFFL & header.getInt();
+ missingCrcs[i] = 0xffffFFFFL & getInt(header);
}
}
int nextCrc = 0;
@@ -610,14 +1058,98 @@ private void readSubStreamsInfo(final ByteBuffer header, final Archive archive)
}
}
- nid = getUnsignedByte(header);
+ nid = getUnsignedByte(header);
+ }
+
+ archive.subStreamsInfo = subStreamsInfo;
+ }
+
+ private int sanityCheckFolder(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
+
+ final int numCoders = assertFitsIntoNonNegativeInt("numCoders", readUint64(header));
+ if (numCoders == 0) {
+ throw new IOException("Folder without coders");
+ }
+ stats.numberOfCoders += numCoders;
+
+ long totalOutStreams = 0;
+ long totalInStreams = 0;
+ for (int i = 0; i < numCoders; i++) {
+ final int bits = getUnsignedByte(header);
+ final int idSize = bits & 0xf;
+ get(header, new byte[idSize]);
+
+ final boolean isSimple = (bits & 0x10) == 0;
+ final boolean hasAttributes = (bits & 0x20) != 0;
+ final boolean moreAlternativeMethods = (bits & 0x80) != 0;
+ if (moreAlternativeMethods) {
+ throw new IOException(
+ "Alternative methods are unsupported, please report. "
+ + // NOSONAR
+ "The reference implementation doesn't support them either.");
+ }
+
+ if (isSimple) {
+ totalInStreams++;
+ totalOutStreams++;
+ } else {
+ totalInStreams += assertFitsIntoNonNegativeInt("numInStreams", readUint64(header));
+ totalOutStreams += assertFitsIntoNonNegativeInt("numOutStreams", readUint64(header));
+ }
+
+ if (hasAttributes) {
+ final int propertiesSize =
+ assertFitsIntoNonNegativeInt("propertiesSize", readUint64(header));
+ if (skipBytesFully(header, propertiesSize) < propertiesSize) {
+ throw new IOException("invalid propertiesSize in folder");
+ }
+ }
+ }
+ assertFitsIntoNonNegativeInt("totalInStreams", totalInStreams);
+ assertFitsIntoNonNegativeInt("totalOutStreams", totalOutStreams);
+ stats.numberOfOutStreams += totalOutStreams;
+ stats.numberOfInStreams += totalInStreams;
+
+ if (totalOutStreams == 0) {
+ throw new IOException("Total output streams can't be 0");
}
- if (nid != NID.kEnd) {
- throw new IOException("Badly terminated SubStreamsInfo");
+ final int numBindPairs = assertFitsIntoNonNegativeInt("numBindPairs", totalOutStreams - 1);
+ if (totalInStreams < numBindPairs) {
+ throw new IOException("Total input streams can't be less than the number of bind pairs");
+ }
+ final BitSet inStreamsBound = new BitSet((int) totalInStreams);
+ for (int i = 0; i < numBindPairs; i++) {
+ final int inIndex = assertFitsIntoNonNegativeInt("inIndex", readUint64(header));
+ if (totalInStreams <= inIndex) {
+ throw new IOException("inIndex is bigger than number of inStreams");
+ }
+ inStreamsBound.set(inIndex);
+ final int outIndex = assertFitsIntoNonNegativeInt("outIndex", readUint64(header));
+ if (totalOutStreams <= outIndex) {
+ throw new IOException("outIndex is bigger than number of outStreams");
+ }
}
- archive.subStreamsInfo = subStreamsInfo;
+ final int numPackedStreams =
+ assertFitsIntoNonNegativeInt("numPackedStreams", totalInStreams - numBindPairs);
+
+ if (numPackedStreams == 1) {
+ if (inStreamsBound.nextClearBit(0) == -1) {
+ throw new IOException("Couldn't find stream's bind pair index");
+ }
+ } else {
+ for (int i = 0; i < numPackedStreams; i++) {
+ final int packedStreamIndex =
+ assertFitsIntoNonNegativeInt("packedStreamIndex", readUint64(header));
+ if (packedStreamIndex >= totalInStreams) {
+ throw new IOException("packedStreamIndex is bigger than number of totalInStreams");
+ }
+ }
+ }
+
+ return (int) totalOutStreams;
}
private Folder readFolder(final ByteBuffer header) throws IOException {
@@ -636,7 +1168,7 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
final boolean moreAlternativeMethods = (bits & 0x80) != 0;
coders[i].decompressionMethodId = new byte[idSize];
- header.get(coders[i].decompressionMethodId);
+ get(header, coders[i].decompressionMethodId);
if (isSimple) {
coders[i].numInStreams = 1;
coders[i].numOutStreams = 1;
@@ -649,22 +1181,20 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
if (hasAttributes) {
final long propertiesSize = readUint64(header);
coders[i].properties = new byte[(int) propertiesSize];
- header.get(coders[i].properties);
+ get(header, coders[i].properties);
}
// would need to keep looping as above:
- while (moreAlternativeMethods) {
+ if (moreAlternativeMethods) {
throw new IOException(
"Alternative methods are unsupported, please report. "
- + "The reference implementation doesn't support them either.");
+ + // NOSONAR
+ "The reference implementation doesn't support them either.");
}
}
folder.coders = coders;
folder.totalInputStreams = totalInStreams;
folder.totalOutputStreams = totalOutStreams;
- if (totalOutStreams == 0) {
- throw new IOException("Total output streams can't be 0");
- }
final long numBindPairs = totalOutStreams - 1;
final BindPair[] bindPairs = new BindPair[(int) numBindPairs];
for (int i = 0; i < bindPairs.length; i++) {
@@ -674,11 +1204,8 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
}
folder.bindPairs = bindPairs;
- if (totalInStreams < numBindPairs) {
- throw new IOException("Total input streams can't be less than the number of bind pairs");
- }
final long numPackedStreams = totalInStreams - numBindPairs;
- final long packedStreams[] = new long[(int) numPackedStreams];
+ final long[] packedStreams = new long[(int) numPackedStreams];
if (numPackedStreams == 1) {
int i;
for (i = 0; i < (int) totalInStreams; i++) {
@@ -686,9 +1213,6 @@ private Folder readFolder(final ByteBuffer header) throws IOException {
break;
}
}
- if (i == (int) totalInStreams) {
- throw new IOException("Couldn't find stream's bind pair index");
- }
packedStreams[0] = i;
} else {
for (int i = 0; i < (int) numPackedStreams; i++) {
@@ -729,15 +1253,11 @@ private BitSet readBits(final ByteBuffer header, final int size) throws IOExcept
return bits;
}
- private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
- final long numFiles = readUint64(header);
- final SevenZArchiveEntry[] files = new SevenZArchiveEntry[(int) numFiles];
- for (int i = 0; i < files.length; i++) {
- files[i] = new SevenZArchiveEntry();
- }
- BitSet isEmptyStream = null;
- BitSet isEmptyFile = null;
- BitSet isAnti = null;
+ private void sanityCheckFilesInfo(final ByteBuffer header, final ArchiveStatistics stats)
+ throws IOException {
+ stats.numberOfEntries = assertFitsIntoNonNegativeInt("numFiles", readUint64(header));
+
+ int emptyStreams = -1;
while (true) {
final int propertyType = getUnsignedByte(header);
if (propertyType == 0) {
@@ -747,24 +1267,24 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
switch (propertyType) {
case NID.kEmptyStream:
{
- isEmptyStream = readBits(header, files.length);
+ emptyStreams = readBits(header, stats.numberOfEntries).cardinality();
break;
}
case NID.kEmptyFile:
{
- if (isEmptyStream == null) { // protect against NPE
+ if (emptyStreams == -1) {
throw new IOException(
"Header format error: kEmptyStream must appear before kEmptyFile");
}
- isEmptyFile = readBits(header, isEmptyStream.cardinality());
+ readBits(header, emptyStreams);
break;
}
case NID.kAnti:
{
- if (isEmptyStream == null) { // protect against NPE
+ if (emptyStreams == -1) {
throw new IOException("Header format error: kEmptyStream must appear before kAnti");
}
- isAnti = readBits(header, isEmptyStream.cardinality());
+ readBits(header, emptyStreams);
break;
}
case NID.kName:
@@ -773,82 +1293,74 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
if (external != 0) {
throw new IOException("Not implemented");
}
- if (((size - 1) & 1) != 0) {
+ final int namesLength = assertFitsIntoNonNegativeInt("file names length", size - 1);
+ if ((namesLength & 1) != 0) {
throw new IOException("File names length invalid");
}
- final byte[] names = new byte[(int) (size - 1)];
- header.get(names);
- int nextFile = 0;
- int nextName = 0;
- for (int i = 0; i < names.length; i += 2) {
- if (names[i] == 0 && names[i + 1] == 0) {
- files[nextFile++].setName(
- new String(names, nextName, i - nextName, CharsetNames.UTF_16LE));
- nextName = i + 2;
+
+ int filesSeen = 0;
+ for (int i = 0; i < namesLength; i += 2) {
+ final char c = getChar(header);
+ if (c == 0) {
+ filesSeen++;
}
}
- if (nextName != names.length || nextFile != files.length) {
- throw new IOException("Error parsing file names");
+ if (filesSeen != stats.numberOfEntries) {
+ throw new IOException(
+ "Invalid number of file names ("
+ + filesSeen
+ + " instead of "
+ + stats.numberOfEntries
+ + ")");
}
break;
}
case NID.kCTime:
{
- final BitSet timesDefined = readAllOrBits(header, files.length);
+ final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new IOException("Unimplemented");
+ throw new IOException("Not implemented");
}
- for (int i = 0; i < files.length; i++) {
- files[i].setHasCreationDate(timesDefined.get(i));
- if (files[i].getHasCreationDate()) {
- files[i].setCreationDate(header.getLong());
- }
+ if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
+ throw new IOException("invalid creation dates size");
}
break;
}
case NID.kATime:
{
- final BitSet timesDefined = readAllOrBits(header, files.length);
+ final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new IOException("Unimplemented");
+ throw new IOException("Not implemented");
}
- for (int i = 0; i < files.length; i++) {
- files[i].setHasAccessDate(timesDefined.get(i));
- if (files[i].getHasAccessDate()) {
- files[i].setAccessDate(header.getLong());
- }
+ if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
+ throw new IOException("invalid access dates size");
}
break;
}
case NID.kMTime:
{
- final BitSet timesDefined = readAllOrBits(header, files.length);
+ final int timesDefined = readAllOrBits(header, stats.numberOfEntries).cardinality();
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new IOException("Unimplemented");
+ throw new IOException("Not implemented");
}
- for (int i = 0; i < files.length; i++) {
- files[i].setHasLastModifiedDate(timesDefined.get(i));
- if (files[i].getHasLastModifiedDate()) {
- files[i].setLastModifiedDate(header.getLong());
- }
+ if (skipBytesFully(header, 8 * timesDefined) < 8 * timesDefined) {
+ throw new IOException("invalid modification dates size");
}
break;
}
case NID.kWinAttributes:
{
- final BitSet attributesDefined = readAllOrBits(header, files.length);
+ final int attributesDefined =
+ readAllOrBits(header, stats.numberOfEntries).cardinality();
final int external = getUnsignedByte(header);
if (external != 0) {
- throw new IOException("Unimplemented");
+ throw new IOException("Not implemented");
}
- for (int i = 0; i < files.length; i++) {
- files[i].setHasWindowsAttributes(attributesDefined.get(i));
- if (files[i].getHasWindowsAttributes()) {
- files[i].setWindowsAttributes(header.getInt());
- }
+ if (skipBytesFully(header, 4 * attributesDefined) < 4 * attributesDefined) {
+ throw new IOException("invalid windows attributes size");
}
break;
}
@@ -877,29 +1389,179 @@ private void readFilesInfo(final ByteBuffer header, final Archive archive) throw
}
}
}
+ stats.numberOfEntriesWithStream = stats.numberOfEntries - Math.max(emptyStreams, 0);
+ }
+
+ private void readFilesInfo(final ByteBuffer header, final Archive archive) throws IOException {
+ final int numFilesInt = (int) readUint64(header);
+ final Map fileMap = new LinkedHashMap<>();
+ BitSet isEmptyStream = null;
+ BitSet isEmptyFile = null;
+ BitSet isAnti = null;
+ while (true) {
+ final int propertyType = getUnsignedByte(header);
+ if (propertyType == 0) {
+ break;
+ }
+ final long size = readUint64(header);
+ switch (propertyType) {
+ case NID.kEmptyStream:
+ {
+ isEmptyStream = readBits(header, numFilesInt);
+ break;
+ }
+ case NID.kEmptyFile:
+ {
+ isEmptyFile = readBits(header, isEmptyStream.cardinality());
+ break;
+ }
+ case NID.kAnti:
+ {
+ isAnti = readBits(header, isEmptyStream.cardinality());
+ break;
+ }
+ case NID.kName:
+ {
+ /* final int external = */ getUnsignedByte(header);
+ final byte[] names = new byte[(int) (size - 1)];
+ final int namesLength = names.length;
+ get(header, names);
+ int nextFile = 0;
+ int nextName = 0;
+ for (int i = 0; i < namesLength; i += 2) {
+ if (names[i] == 0 && names[i + 1] == 0) {
+ checkEntryIsInitialized(fileMap, nextFile);
+ fileMap
+ .get(nextFile)
+ .setName(new String(names, nextName, i - nextName, "UTF-16LE"));
+ nextName = i + 2;
+ nextFile++;
+ }
+ }
+ if (nextName != namesLength || nextFile != numFilesInt) {
+ throw new IOException("Error parsing file names");
+ }
+ break;
+ }
+ case NID.kCTime:
+ {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasCreationDate(timesDefined.get(i));
+ if (entryAtIndex.getHasCreationDate()) {
+ entryAtIndex.setCreationDate(getLong(header));
+ }
+ }
+ break;
+ }
+ case NID.kATime:
+ {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasAccessDate(timesDefined.get(i));
+ if (entryAtIndex.getHasAccessDate()) {
+ entryAtIndex.setAccessDate(getLong(header));
+ }
+ }
+ break;
+ }
+ case NID.kMTime:
+ {
+ final BitSet timesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasLastModifiedDate(timesDefined.get(i));
+ if (entryAtIndex.getHasLastModifiedDate()) {
+ entryAtIndex.setLastModifiedDate(getLong(header));
+ }
+ }
+ break;
+ }
+ case NID.kWinAttributes:
+ {
+ final BitSet attributesDefined = readAllOrBits(header, numFilesInt);
+ /* final int external = */ getUnsignedByte(header);
+ for (int i = 0; i < numFilesInt; i++) {
+ checkEntryIsInitialized(fileMap, i);
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ entryAtIndex.setHasWindowsAttributes(attributesDefined.get(i));
+ if (entryAtIndex.getHasWindowsAttributes()) {
+ entryAtIndex.setWindowsAttributes(getInt(header));
+ }
+ }
+ break;
+ }
+ case NID.kDummy:
+ {
+ // 7z 9.20 asserts the content is all zeros and ignores the property
+ // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
+
+ skipBytesFully(header, size);
+ break;
+ }
+
+ default:
+ {
+ // Compress up to 1.8.1 would throw an exception, now we ignore it (see COMPRESS-287
+ skipBytesFully(header, size);
+ break;
+ }
+ }
+ }
int nonEmptyFileCounter = 0;
int emptyFileCounter = 0;
- for (int i = 0; i < files.length; i++) {
- files[i].setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
- if (files[i].hasStream()) {
- files[i].setDirectory(false);
- files[i].setAntiItem(false);
- files[i].setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
- files[i].setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
- files[i].setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
+ for (int i = 0; i < numFilesInt; i++) {
+ final SevenZArchiveEntry entryAtIndex = fileMap.get(i);
+ if (entryAtIndex == null) {
+ continue;
+ }
+ entryAtIndex.setHasStream(isEmptyStream == null || !isEmptyStream.get(i));
+ if (entryAtIndex.hasStream()) {
+ if (archive.subStreamsInfo == null) {
+ throw new IOException("Archive contains file with streams but no subStreamsInfo");
+ }
+ entryAtIndex.setDirectory(false);
+ entryAtIndex.setAntiItem(false);
+ entryAtIndex.setHasCrc(archive.subStreamsInfo.hasCrc.get(nonEmptyFileCounter));
+ entryAtIndex.setCrcValue(archive.subStreamsInfo.crcs[nonEmptyFileCounter]);
+ entryAtIndex.setSize(archive.subStreamsInfo.unpackSizes[nonEmptyFileCounter]);
+ if (entryAtIndex.getSize() < 0) {
+ throw new IOException("broken archive, entry with negative size");
+ }
++nonEmptyFileCounter;
} else {
- files[i].setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
- files[i].setAntiItem(isAnti != null && isAnti.get(emptyFileCounter));
- files[i].setHasCrc(false);
- files[i].setSize(0);
+ entryAtIndex.setDirectory(isEmptyFile == null || !isEmptyFile.get(emptyFileCounter));
+ entryAtIndex.setAntiItem(isAnti != null && isAnti.get(emptyFileCounter));
+ entryAtIndex.setHasCrc(false);
+ entryAtIndex.setSize(0);
++emptyFileCounter;
}
}
- archive.files = files;
+ List entries = new LinkedList<>();
+ for (SevenZArchiveEntry entry : fileMap.values()) {
+ if (entry != null) {
+ entries.add(entry);
+ }
+ }
+ archive.files = entries.toArray(new SevenZArchiveEntry[entries.size()]);
calculateStreamMap(archive);
}
+ private void checkEntryIsInitialized(
+ final Map archiveEntries, final int index) {
+ if (archiveEntries.get(index) == null) {
+ archiveEntries.put(index, new SevenZArchiveEntry());
+ }
+ }
+
private void calculateStreamMap(final Archive archive) throws IOException {
final StreamMap streamMap = new StreamMap();
@@ -912,7 +1574,7 @@ private void calculateStreamMap(final Archive archive) throws IOException {
}
long nextPackStreamOffset = 0;
- final int numPackSizes = archive.packSizes != null ? archive.packSizes.length : 0;
+ final int numPackSizes = archive.packSizes.length;
streamMap.packStreamOffsets = new long[numPackSizes];
for (int i = 0; i < numPackSizes; i++) {
streamMap.packStreamOffsets[i] = nextPackStreamOffset;
@@ -953,40 +1615,69 @@ private void calculateStreamMap(final Archive archive) throws IOException {
archive.streamMap = streamMap;
}
- private void buildDecodingStream() throws IOException {
- final int folderIndex = archive.streamMap.fileFolderIndex[currentEntryIndex];
+ /**
+ * Build the decoding stream for the entry to be read. This method may be called from a random
+ * access(getInputStream) or sequential access(getNextEntry). If this method is called from a
+ * random access, some entries may need to be skipped(we put them to the deferredBlockStreams and
+ * skip them when actually needed to improve the performance)
+ *
+ * @param entryIndex the index of the entry to be read
+ * @param isRandomAccess is this called in a random access
+ * @throws IOException if there are exceptions when reading the file
+ */
+ private void buildDecodingStream(final int entryIndex, final boolean isRandomAccess)
+ throws IOException {
+ if (archive.streamMap == null) {
+ throw new IOException("Archive doesn't contain stream information to read entries");
+ }
+ final int folderIndex = archive.streamMap.fileFolderIndex[entryIndex];
if (folderIndex < 0) {
deferredBlockStreams.clear();
// TODO: previously it'd return an empty stream?
- // new BoundedInputStream(new ByteArrayInputStream(new byte[0]), 0);
+ // new BoundedInputStream(new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY), 0);
return;
}
- final SevenZArchiveEntry file = archive.files[currentEntryIndex];
+ final SevenZArchiveEntry file = archive.files[entryIndex];
+ boolean isInSameFolder = false;
if (currentFolderIndex == folderIndex) {
// (COMPRESS-320).
// The current entry is within the same (potentially opened) folder. The
// previous stream has to be fully decoded before we can start reading
// but don't do it eagerly -- if the user skips over the entire folder nothing
// is effectively decompressed.
+ if (entryIndex > 0) {
+ file.setContentMethods(archive.files[entryIndex - 1].getContentMethods());
+ }
- file.setContentMethods(archive.files[currentEntryIndex - 1].getContentMethods());
+ // if this is called in a random access, then the content methods of previous entry may be
+ // null
+ // the content methods should be set to methods of the first entry as it must not be null,
+ // and the content methods would only be set if the content methods was not set
+ if (isRandomAccess && file.getContentMethods() == null) {
+ final int folderFirstFileIndex = archive.streamMap.folderFirstFileIndex[folderIndex];
+ final SevenZArchiveEntry folderFirstFile = archive.files[folderFirstFileIndex];
+ file.setContentMethods(folderFirstFile.getContentMethods());
+ }
+ isInSameFolder = true;
} else {
- // We're opening a new folder. Discard any queued streams/ folder stream.
currentFolderIndex = folderIndex;
- deferredBlockStreams.clear();
- if (currentFolderInputStream != null) {
- currentFolderInputStream.close();
- currentFolderInputStream = null;
- }
+ // We're opening a new folder. Discard any queued streams/ folder stream.
+ reopenFolderInputStream(folderIndex, file);
+ }
+
+ boolean haveSkippedEntries = false;
+ if (isRandomAccess) {
+ // entries will only need to be skipped if it's a random access
+ haveSkippedEntries = skipEntriesWhenNeeded(entryIndex, isInSameFolder, folderIndex);
+ }
- final Folder folder = archive.folders[folderIndex];
- final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
- final long folderOffset =
- SIGNATURE_HEADER_SIZE
- + archive.packPos
- + archive.streamMap.packStreamOffsets[firstPackStreamIndex];
- currentFolderInputStream =
- buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file);
+ if (isRandomAccess && currentEntryIndex == entryIndex && !haveSkippedEntries) {
+ // we don't need to add another entry to the deferredBlockStreams when :
+ // 1. If this method is called in a random access and the entry index
+ // to be read equals to the current entry index, the input stream
+ // has already been put in the deferredBlockStreams
+ // 2. If this entry has not been read(which means no entries are skipped)
+ return;
}
InputStream fileStream = new BoundedInputStream(currentFolderInputStream, file.getSize());
@@ -997,6 +1688,128 @@ private void buildDecodingStream() throws IOException {
deferredBlockStreams.add(fileStream);
}
+ /**
+ * Discard any queued streams/ folder stream, and reopen the current folder input stream.
+ *
+ * @param folderIndex the index of the folder to reopen
+ * @param file the 7z entry to read
+ * @throws IOException if exceptions occur when reading the 7z file
+ */
+ private void reopenFolderInputStream(final int folderIndex, final SevenZArchiveEntry file)
+ throws IOException {
+ deferredBlockStreams.clear();
+ if (currentFolderInputStream != null) {
+ currentFolderInputStream.close();
+ currentFolderInputStream = null;
+ }
+ final Folder folder = archive.folders[folderIndex];
+ final int firstPackStreamIndex = archive.streamMap.folderFirstPackStreamIndex[folderIndex];
+ final long folderOffset =
+ SIGNATURE_HEADER_SIZE
+ + archive.packPos
+ + archive.streamMap.packStreamOffsets[firstPackStreamIndex];
+
+ currentFolderInputStream = buildDecoderStack(folder, folderOffset, firstPackStreamIndex, file);
+ }
+
+ /**
+ * Skip all the entries if needed. Entries need to be skipped when:
+ *
+ * 1. it's a random access 2. one of these 2 condition is meet :
+ *
+ *
2.1 currentEntryIndex != entryIndex : this means there are some entries to be
+ * skipped(currentEntryIndex < entryIndex) or the entry has already been read(currentEntryIndex >
+ * entryIndex)
+ *
+ *
2.2 currentEntryIndex == entryIndex && !hasCurrentEntryBeenRead: if the entry to be read is
+ * the current entry, but some data of it has been read before, then we need to reopen the stream
+ * of the folder and skip all the entries before the current entries
+ *
+ * @param entryIndex the entry to be read
+ * @param isInSameFolder are the entry to be read and the current entry in the same folder
+ * @param folderIndex the index of the folder which contains the entry
+ * @return true if there are entries actually skipped
+ * @throws IOException there are exceptions when skipping entries
+ * @since 1.21
+ */
+ private boolean skipEntriesWhenNeeded(
+ final int entryIndex, final boolean isInSameFolder, final int folderIndex)
+ throws IOException {
+ final SevenZArchiveEntry file = archive.files[entryIndex];
+ // if the entry to be read is the current entry, and the entry has not
+ // been read yet, then there's nothing we need to do
+ if (currentEntryIndex == entryIndex && !hasCurrentEntryBeenRead()) {
+ return false;
+ }
+
+ // 1. if currentEntryIndex < entryIndex :
+ // this means there are some entries to be skipped(currentEntryIndex < entryIndex)
+ // 2. if currentEntryIndex > entryIndex || (currentEntryIndex == entryIndex &&
+ // hasCurrentEntryBeenRead) :
+ // this means the entry has already been read before, and we need to reopen the
+ // stream of the folder and skip all the entries before the current entries
+ int filesToSkipStartIndex = archive.streamMap.folderFirstFileIndex[currentFolderIndex];
+ if (isInSameFolder) {
+ if (currentEntryIndex < entryIndex) {
+ // the entries between filesToSkipStartIndex and currentEntryIndex had already been skipped
+ filesToSkipStartIndex = currentEntryIndex + 1;
+ } else {
+ // the entry is in the same folder of current entry, but it has already been read before, we
+ // need to reset
+ // the position of the currentFolderInputStream to the beginning of folder, and then skip
+ // the files
+ // from the start entry of the folder again
+ reopenFolderInputStream(folderIndex, file);
+ }
+ }
+
+ for (int i = filesToSkipStartIndex; i < entryIndex; i++) {
+ final SevenZArchiveEntry fileToSkip = archive.files[i];
+ InputStream fileStreamToSkip =
+ new BoundedInputStream(currentFolderInputStream, fileToSkip.getSize());
+ if (fileToSkip.getHasCrc()) {
+ fileStreamToSkip =
+ new CRC32VerifyingInputStream(
+ fileStreamToSkip, fileToSkip.getSize(), fileToSkip.getCrcValue());
+ }
+ deferredBlockStreams.add(fileStreamToSkip);
+
+ // set the content methods as well, it equals to file.getContentMethods() because they are in
+ // same folder
+ fileToSkip.setContentMethods(file.getContentMethods());
+ }
+ return true;
+ }
+
+ /**
+ * Find out if any data of current entry has been read or not. This is achieved by comparing the
+ * bytes remaining to read and the size of the file.
+ *
+ * @return true if any data of current entry has been read
+ * @since 1.21
+ */
+ private boolean hasCurrentEntryBeenRead() {
+ boolean hasCurrentEntryBeenRead = false;
+ if (!deferredBlockStreams.isEmpty()) {
+ final InputStream currentEntryInputStream =
+ deferredBlockStreams.get(deferredBlockStreams.size() - 1);
+ // get the bytes remaining to read, and compare it with the size of
+ // the file to figure out if the file has been read
+ if (currentEntryInputStream instanceof CRC32VerifyingInputStream) {
+ hasCurrentEntryBeenRead =
+ ((CRC32VerifyingInputStream) currentEntryInputStream).getBytesRemaining()
+ != archive.files[currentEntryIndex].getSize();
+ }
+
+ if (currentEntryInputStream instanceof BoundedInputStream) {
+ hasCurrentEntryBeenRead =
+ ((BoundedInputStream) currentEntryInputStream).getBytesRemaining()
+ != archive.files[currentEntryIndex].getSize();
+ }
+ }
+ return hasCurrentEntryBeenRead;
+ }
+
private InputStream buildDecoderStack(
final Folder folder,
final long folderOffset,
@@ -1025,6 +1838,9 @@ public int read(final byte[] b) throws IOException {
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
+ if (len == 0) {
+ return 0;
+ }
final int r = in.read(b, off, len);
if (r >= 0) {
count(r);
@@ -1032,7 +1848,7 @@ public int read(final byte[] b, final int off, final int len) throws IOException
return r;
}
- private void count(int c) {
+ private void count(final int c) {
compressedBytesReadFromCurrentEntry += c;
}
};
@@ -1044,7 +1860,12 @@ private void count(int c) {
final SevenZMethod method = SevenZMethod.byId(coder.decompressionMethodId);
inputStreamStack =
Coders.addDecoder(
- fileName, inputStreamStack, folder.getUnpackSizeForCoder(coder), coder, password);
+ fileName,
+ inputStreamStack,
+ folder.getUnpackSizeForCoder(coder),
+ coder,
+ password,
+ options.getMaxMemoryLimitInKb());
methods.addFirst(
new SevenZMethodConfiguration(
method, Coders.findByMethod(method).getOptionsFromCoder(coder, inputStreamStack)));
@@ -1063,7 +1884,7 @@ private void count(int c) {
* @throws IOException if an I/O error has occurred
*/
public int read() throws IOException {
- int b = getCurrentStream().read();
+ final int b = getCurrentStream().read();
if (b >= 0) {
uncompressedBytesReadFromCurrentEntry++;
}
@@ -1072,7 +1893,7 @@ public int read() throws IOException {
private InputStream getCurrentStream() throws IOException {
if (archive.files[currentEntryIndex].getSize() == 0) {
- return new ByteArrayInputStream(new byte[0]);
+ return new ByteArrayInputStream(ByteUtils.EMPTY_BYTE_ARRAY);
}
if (deferredBlockStreams.isEmpty()) {
throw new IllegalStateException("No current 7z entry (call getNextEntry() first).");
@@ -1082,14 +1903,46 @@ private InputStream getCurrentStream() throws IOException {
// In solid compression mode we need to decompress all leading folder'
// streams to get access to an entry. We defer this until really needed
// so that entire blocks can be skipped without wasting time for decompression.
- final InputStream stream = deferredBlockStreams.remove(0);
- IOUtils.skip(stream, Long.MAX_VALUE);
+ try (final InputStream stream = deferredBlockStreams.remove(0)) {
+ IOUtils.skip(stream, Long.MAX_VALUE);
+ }
compressedBytesReadFromCurrentEntry = 0;
}
return deferredBlockStreams.get(0);
}
+ /**
+ * Returns an InputStream for reading the contents of the given entry.
+ *
+ *
For archives using solid compression randomly accessing entries will be significantly slower
+ * than reading the archive sequentially.
+ *
+ * @param entry the entry to get the stream for.
+ * @return a stream to read the entry from.
+ * @throws IOException if unable to create an input stream from the zipentry
+ * @since 1.20
+ */
+ public InputStream getInputStream(final SevenZArchiveEntry entry) throws IOException {
+ int entryIndex = -1;
+ for (int i = 0; i < this.archive.files.length; i++) {
+ if (entry == this.archive.files[i]) {
+ entryIndex = i;
+ break;
+ }
+ }
+
+ if (entryIndex < 0) {
+ throw new IllegalArgumentException(
+ "Can not find " + entry.getName() + " in " + this.fileName);
+ }
+
+ buildDecodingStream(entryIndex, true);
+ currentEntryIndex = entryIndex;
+ currentFolderIndex = archive.streamMap.fileFolderIndex[entryIndex];
+ return getCurrentStream();
+ }
+
/**
* Reads data into an array of bytes.
*
@@ -1111,7 +1964,10 @@ public int read(final byte[] b) throws IOException {
* @throws IOException if an I/O error has occurred
*/
public int read(final byte[] b, final int off, final int len) throws IOException {
- int cnt = getCurrentStream().read(b, off, len);
+ if (len == 0) {
+ return 0;
+ }
+ final int cnt = getCurrentStream().read(b, off, len);
if (cnt > 0) {
uncompressedBytesReadFromCurrentEntry += cnt;
}
@@ -1145,16 +2001,47 @@ private static long readUint64(final ByteBuffer in) throws IOException {
long value = 0;
for (int i = 0; i < 8; i++) {
if ((firstByte & mask) == 0) {
- return value | ((firstByte & (mask - 1)) << (8 * i));
+ return value | (firstByte & mask - 1) << 8 * i;
}
final long nextByte = getUnsignedByte(in);
- value |= nextByte << (8 * i);
+ value |= nextByte << 8 * i;
mask >>>= 1;
}
return value;
}
- private static int getUnsignedByte(ByteBuffer buf) {
+ private static char getChar(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 2) {
+ throw new EOFException();
+ }
+ return buf.getChar();
+ }
+
+ private static int getInt(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 4) {
+ throw new EOFException();
+ }
+ return buf.getInt();
+ }
+
+ private static long getLong(final ByteBuffer buf) throws IOException {
+ if (buf.remaining() < 8) {
+ throw new EOFException();
+ }
+ return buf.getLong();
+ }
+
+ private static void get(final ByteBuffer buf, final byte[] to) throws IOException {
+ if (buf.remaining() < to.length) {
+ throw new EOFException();
+ }
+ buf.get(to);
+ }
+
+ private static int getUnsignedByte(final ByteBuffer buf) throws IOException {
+ if (!buf.hasRemaining()) {
+ throw new EOFException();
+ }
return buf.get() & 0xff;
}
@@ -1179,12 +2066,12 @@ public static boolean matches(final byte[] signature, final int length) {
return true;
}
- private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) throws IOException {
+ private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) {
if (bytesToSkip < 1) {
return 0;
}
- int current = input.position();
- int maxSkip = input.remaining();
+ final int current = input.position();
+ final int maxSkip = input.remaining();
if (maxSkip < bytesToSkip) {
bytesToSkip = maxSkip;
}
@@ -1192,8 +2079,9 @@ private static long skipBytesFully(final ByteBuffer input, long bytesToSkip) thr
return bytesToSkip;
}
- private void readFully(ByteBuffer buf) throws IOException {
+ private void readFully(final ByteBuffer buf) throws IOException {
buf.rewind();
+
IOUtils.readFully(channel, buf);
buf.flip();
}
@@ -1203,18 +2091,128 @@ public String toString() {
return archive.toString();
}
- private static final CharsetEncoder PASSWORD_ENCODER = Charset.forName("UTF-16LE").newEncoder();
+ /**
+ * Derives a default file name from the archive name - if known.
+ *
+ *
This implements the same heuristics the 7z tools use. In 7z's case if an archive contains
+ * entries without a name - i.e. {@link SevenZArchiveEntry#getName} returns {@code null} - then
+ * its command line and GUI tools will use this default name when extracting the entries.
+ *
+ * @return null if the name of the archive is unknown. Otherwise if the name of the archive has
+ * got any extension, it is stripped and the remainder returned. Finally if the name of the
+ * archive hasn't got any extension then a {@code ~} character is appended to the archive
+ * name.
+ * @since 1.19
+ */
+ public String getDefaultName() {
+ if (DEFAULT_FILE_NAME.equals(fileName) || fileName == null) {
+ return null;
+ }
+
+ final String lastSegment = new File(fileName).getName();
+ final int dotPos = lastSegment.lastIndexOf(".");
+ if (dotPos > 0) { // if the file starts with a dot then this is not an extension
+ return lastSegment.substring(0, dotPos);
+ }
+ return lastSegment + "~";
+ }
- private static byte[] utf16Decode(char[] chars) throws IOException {
+ private static byte[] utf16Decode(final char[] chars) {
if (chars == null) {
return null;
}
- ByteBuffer encoded = PASSWORD_ENCODER.encode(CharBuffer.wrap(chars));
+ final ByteBuffer encoded = Charset.forName("UTF-16LE").encode(CharBuffer.wrap(chars));
if (encoded.hasArray()) {
return encoded.array();
}
- byte[] e = new byte[encoded.remaining()];
+ final byte[] e = new byte[encoded.remaining()];
encoded.get(e);
return e;
}
+
+ private static int assertFitsIntoNonNegativeInt(final String what, final long value)
+ throws IOException {
+ if (value > Integer.MAX_VALUE || value < 0) {
+ throw new IOException("Cannot handle " + what + " " + value);
+ }
+ return (int) value;
+ }
+
+ private static class ArchiveStatistics {
+ private int numberOfPackedStreams;
+ private long numberOfCoders;
+ private long numberOfOutStreams;
+ private long numberOfInStreams;
+ private long numberOfUnpackSubStreams;
+ private int numberOfFolders;
+ private BitSet folderHasCrc;
+ private int numberOfEntries;
+ private int numberOfEntriesWithStream;
+
+ @Override
+ public String toString() {
+ return "Archive with "
+ + numberOfEntries
+ + " entries in "
+ + numberOfFolders
+ + " folders. Estimated size "
+ + estimateSize() / 1024L
+ + " kB.";
+ }
+
+ long estimateSize() {
+ final long lowerBound =
+ 16L * numberOfPackedStreams /* packSizes, packCrcs in Archive */
+ + numberOfPackedStreams / 8 /* packCrcsDefined in Archive */
+ + numberOfFolders * folderSize() /* folders in Archive */
+ + numberOfCoders * coderSize() /* coders in Folder */
+ + (numberOfOutStreams - numberOfFolders) * bindPairSize() /* bindPairs in Folder */
+ + 8L
+ * (numberOfInStreams
+ - numberOfOutStreams
+ + numberOfFolders) /* packedStreams in Folder */
+ + 8L * numberOfOutStreams /* unpackSizes in Folder */
+ + numberOfEntries * entrySize() /* files in Archive */
+ + streamMapSize();
+ return 2 * lowerBound /* conservative guess */;
+ }
+
+ void assertValidity(final int maxMemoryLimitInKb) throws IOException {
+ if (numberOfEntriesWithStream > 0 && numberOfFolders == 0) {
+ throw new IOException("archive with entries but no folders");
+ }
+ if (numberOfEntriesWithStream > numberOfUnpackSubStreams) {
+ throw new IOException("archive doesn't contain enough substreams for entries");
+ }
+
+ final long memoryNeededInKb = estimateSize() / 1024;
+ if (maxMemoryLimitInKb < memoryNeededInKb) {
+ throw new MemoryLimitException(memoryNeededInKb, maxMemoryLimitInKb);
+ }
+ }
+
+ private long folderSize() {
+ return 30; /* nested arrays are accounted for separately */
+ }
+
+ private long coderSize() {
+ return 2 /* methodId is between 1 and four bytes currently, COPY and LZMA2 are the most common with 1 */
+ + 16
+ + 4 /* properties, guess */;
+ }
+
+ private long bindPairSize() {
+ return 16;
+ }
+
+ private long entrySize() {
+ return 100; /* real size depends on name length, everything without name is about 70 bytes */
+ }
+
+ private long streamMapSize() {
+ return 8 * numberOfFolders /* folderFirstPackStreamIndex, folderFirstFileIndex */
+ + 8 * numberOfPackedStreams /* packStreamOffsets */
+ + 4 * numberOfEntries /* fileFolderIndex */;
+ }
+ }
}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFileOptions.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFileOptions.java
new file mode 100644
index 0000000000..77df73350a
--- /dev/null
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZFileOptions.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra ,
+ * Emmanuel Messulam, Raymond Lai and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.filesystem.compressed.sevenz;
+
+/**
+ * Collects options for reading 7z archives.
+ *
+ * @since 1.19 @Immutable
+ */
+public class SevenZFileOptions {
+ private static final int DEFAUL_MEMORY_LIMIT_IN_KB = Integer.MAX_VALUE;
+ private static final boolean DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES = false;
+ private static final boolean DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES = false;
+
+ private final int maxMemoryLimitInKb;
+ private final boolean useDefaultNameForUnnamedEntries;
+ private final boolean tryToRecoverBrokenArchives;
+
+ private SevenZFileOptions(
+ final int maxMemoryLimitInKb,
+ final boolean useDefaultNameForUnnamedEntries,
+ final boolean tryToRecoverBrokenArchives) {
+ this.maxMemoryLimitInKb = maxMemoryLimitInKb;
+ this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
+ this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
+ }
+
+ /**
+ * The default options.
+ *
+ *
+ * - no memory limit
+ *
- don't modify the name of unnamed entries
+ *
+ */
+ public static final SevenZFileOptions DEFAULT =
+ new SevenZFileOptions(
+ DEFAUL_MEMORY_LIMIT_IN_KB,
+ DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES,
+ DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES);
+
+ /**
+ * Obtains a builder for SevenZFileOptions.
+ *
+ * @return a builder for SevenZFileOptions.
+ */
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Gets the maximum amount of memory to use for parsing the archive and during extraction.
+ *
+ * Not all codecs will honor this setting. Currently only lzma and lzma2 are supported.
+ *
+ * @return the maximum amount of memory to use for extraction
+ */
+ public int getMaxMemoryLimitInKb() {
+ return maxMemoryLimitInKb;
+ }
+
+ /**
+ * Gets whether entries without a name should get their names set to the archive's default file
+ * name.
+ *
+ * @return whether entries without a name should get their names set to the archive's default file
+ * name
+ */
+ public boolean getUseDefaultNameForUnnamedEntries() {
+ return useDefaultNameForUnnamedEntries;
+ }
+
+ /**
+ * Whether {@link SevenZFile} shall try to recover from a certain type of broken archive.
+ *
+ * @return whether SevenZFile shall try to recover from a certain type of broken archive.
+ * @since 1.21
+ */
+ public boolean getTryToRecoverBrokenArchives() {
+ return tryToRecoverBrokenArchives;
+ }
+
+ /**
+ * Mutable builder for the immutable {@link SevenZFileOptions}.
+ *
+ * @since 1.19
+ */
+ public static class Builder {
+ private int maxMemoryLimitInKb = DEFAUL_MEMORY_LIMIT_IN_KB;
+ private boolean useDefaultNameForUnnamedEntries = DEFAULT_USE_DEFAULTNAME_FOR_UNNAMED_ENTRIES;
+ private boolean tryToRecoverBrokenArchives = DEFAULT_TRY_TO_RECOVER_BROKEN_ARCHIVES;
+
+ /**
+ * Sets the maximum amount of memory to use for parsing the archive and during extraction.
+ *
+ *
Not all codecs will honor this setting. Currently only lzma and lzma2 are supported.
+ *
+ * @param maxMemoryLimitInKb limit of the maximum amount of memory to use
+ * @return the reconfigured builder
+ */
+ public Builder withMaxMemoryLimitInKb(final int maxMemoryLimitInKb) {
+ this.maxMemoryLimitInKb = maxMemoryLimitInKb;
+ return this;
+ }
+
+ /**
+ * Sets whether entries without a name should get their names set to the archive's default file
+ * name.
+ *
+ * @param useDefaultNameForUnnamedEntries if true the name of unnamed entries will be set to the
+ * archive's default name
+ * @return the reconfigured builder
+ */
+ public Builder withUseDefaultNameForUnnamedEntries(
+ final boolean useDefaultNameForUnnamedEntries) {
+ this.useDefaultNameForUnnamedEntries = useDefaultNameForUnnamedEntries;
+ return this;
+ }
+
+ /**
+ * Sets whether {@link SevenZFile} will try to revover broken archives where the CRC of the
+ * file's metadata is 0.
+ *
+ *
This special kind of broken archive is encountered when mutli volume archives are closed
+ * prematurely. If you enable this option SevenZFile will trust data that looks as if it could
+ * contain metadata of an archive and allocate big amounts of memory. It is strongly recommended
+ * to not enable this option without setting {@link #withMaxMemoryLimitInKb} at the same time.
+ *
+ * @param tryToRecoverBrokenArchives if true SevenZFile will try to recover archives that are
+ * broken in the specific way
+ * @return the reconfigured builder
+ * @since 1.21
+ */
+ public Builder withTryToRecoverBrokenArchives(final boolean tryToRecoverBrokenArchives) {
+ this.tryToRecoverBrokenArchives = tryToRecoverBrokenArchives;
+ return this;
+ }
+
+ /**
+ * Create the {@link SevenZFileOptions}.
+ *
+ * @return configured {@link SevenZFileOptions}.
+ */
+ public SevenZFileOptions build() {
+ return new SevenZFileOptions(
+ maxMemoryLimitInKb, useDefaultNameForUnnamedEntries, tryToRecoverBrokenArchives);
+ }
+ }
+}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethod.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethod.java
index 4709caff5c..aa5da65a6e 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethod.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethod.java
@@ -106,8 +106,9 @@ public enum SevenZMethod {
}
byte[] getId() {
- final byte[] copy = new byte[id.length];
- System.arraycopy(id, 0, copy, 0, id.length);
+ final int idLength = id.length;
+ final byte[] copy = new byte[idLength];
+ System.arraycopy(id, 0, copy, 0, idLength);
return copy;
}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethodConfiguration.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethodConfiguration.java
index 1145ddc600..1db39d9b35 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethodConfiguration.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZMethodConfiguration.java
@@ -20,13 +20,16 @@
package com.amaze.filemanager.filesystem.compressed.sevenz;
+import java.util.Objects;
+
/**
* Combines a SevenZMethod with configuration options for the method.
*
*
The exact type and interpretation of options depends on the method being configured. Currently
* supported are:
*
- *
+ *
+ * Options
* | Method | Option Type | Description |
* | BZIP2 | Number | Block Size - an number between 1 and 9 |
* | DEFLATE | Number | Compression Level - an number between 1 and 9 |
@@ -85,4 +88,21 @@ public SevenZMethod getMethod() {
public Object getOptions() {
return options;
}
+
+ @Override
+ public int hashCode() {
+ return method == null ? 0 : method.hashCode();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+ final SevenZMethodConfiguration other = (SevenZMethodConfiguration) obj;
+ return Objects.equals(method, other.method) && Objects.equals(options, other.options);
+ }
}
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZOutputFile.java b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZOutputFile.java
index 685aa0bc7c..13b3017c29 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZOutputFile.java
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/SevenZOutputFile.java
@@ -26,6 +26,7 @@
import java.io.DataOutputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
@@ -51,11 +52,11 @@
public class SevenZOutputFile implements Closeable {
private final RandomAccessFile channel;
private final List files = new ArrayList<>();
- private int numNonEmptyStreams = 0;
+ private int numNonEmptyStreams;
private final CRC32 crc32 = new CRC32();
private final CRC32 compressedCrc32 = new CRC32();
- private long fileBytesWritten = 0;
- private boolean finished = false;
+ private long fileBytesWritten;
+ private boolean finished;
private CountingOutputStream currentOutputStream;
private CountingOutputStream[] additionalCountingStreams;
private Iterable extends SevenZMethodConfiguration> contentMethods =
@@ -65,11 +66,11 @@ public class SevenZOutputFile implements Closeable {
/**
* Opens file to write a 7z archive to.
*
- * @param filename the file to write to
+ * @param fileName the file to write to
* @throws IOException if opening the file fails
*/
- public SevenZOutputFile(final File filename) throws IOException {
- this(new RandomAccessFile(filename, ""));
+ public SevenZOutputFile(final File fileName) throws IOException {
+ this(new RandomAccessFile(fileName, "rwd"));
}
/**
@@ -138,10 +139,8 @@ public void close() throws IOException {
* @param inputFile file to create an entry from
* @param entryName the name to use
* @return the ArchiveEntry set up with details from the file
- * @throws IOException on error
*/
- public SevenZArchiveEntry createArchiveEntry(final File inputFile, final String entryName)
- throws IOException {
+ public SevenZArchiveEntry createArchiveEntry(final File inputFile, final String entryName) {
final SevenZArchiveEntry entry = new SevenZArchiveEntry();
entry.setDirectory(inputFile.isDirectory());
entry.setName(entryName);
@@ -156,9 +155,8 @@ public SevenZArchiveEntry createArchiveEntry(final File inputFile, final String
* to complete the process.
*
* @param archiveEntry describes the entry
- * @throws IOException on error
*/
- public void putArchiveEntry(final ArchiveEntry archiveEntry) throws IOException {
+ public void putArchiveEntry(final ArchiveEntry archiveEntry) {
final SevenZArchiveEntry entry = (SevenZArchiveEntry) archiveEntry;
files.add(entry);
}
@@ -237,6 +235,21 @@ public void write(final byte[] b, final int off, final int len) throws IOExcepti
}
}
+ /**
+ * Writes all of the given input stream to the current archive entry.
+ *
+ * @param inputStream the data source.
+ * @throws IOException if an I/O error occurs.
+ * @since 1.21
+ */
+ public void write(final InputStream inputStream) throws IOException {
+ final byte[] buffer = new byte[8024];
+ int n = 0;
+ while (-1 != (n = inputStream.read(buffer))) {
+ write(buffer, 0, n);
+ }
+ }
+
/**
* Finishes the addition of entries to this archive, without closing it.
*
@@ -261,7 +274,7 @@ public void finish() throws IOException {
final CRC32 crc32 = new CRC32();
crc32.update(headerBytes);
- ByteBuffer bb =
+ final ByteBuffer bb =
ByteBuffer.allocate(
SevenZFile.sevenZSignature.length
+ 2 /* version */
@@ -307,7 +320,8 @@ private CountingOutputStream setupFileOutputStream() throws IOException {
throw new IllegalStateException("No current 7z entry");
}
- OutputStream out = new OutputStreamWrapper();
+ // doesn't need to be closed, just wraps the instance field channel
+ OutputStream out = new OutputStreamWrapper(); // NOSONAR
final ArrayList moreStreams = new ArrayList<>();
boolean first = true;
for (final SevenZMethodConfiguration m : getContentMethods(files.get(files.size() - 1))) {
@@ -320,7 +334,7 @@ private CountingOutputStream setupFileOutputStream() throws IOException {
first = false;
}
if (!moreStreams.isEmpty()) {
- additionalCountingStreams = moreStreams.toArray(new CountingOutputStream[moreStreams.size()]);
+ additionalCountingStreams = moreStreams.toArray(new CountingOutputStream[0]);
}
return new CountingOutputStream(out) {
@Override
diff --git a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/package.html b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/package.html
index 975703b385..c5756f2336 100644
--- a/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/package.html
+++ b/commons_compress_7z/src/main/java/com/amaze/filemanager/filesystem/compressed/sevenz/package.html
@@ -1,4 +1,5 @@
-
+
+
+
+ 7z package
+
Provides classes for reading and writing archives using
the 7z format.
From a645ca2f4be8fd63ba4abb697b5fe6feb26848e3 Mon Sep 17 00:00:00 2001
From: VishnuSanal
Date: Sat, 31 Dec 2022 19:07:45 +0530
Subject: [PATCH 037/384] rename `BackupPrefsFragment#putBoolean` to
`BackupPrefsFragment#storePreference` :see_no_evil:
---
.../ui/fragments/preferencefragments/BackupPrefsFragment.kt | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
index 9f74d9a963..a6030391b3 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
+++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
@@ -148,7 +148,7 @@ class BackupPrefsFragment : BasePrefsFragment() {
PreferenceManager.getDefaultSharedPreferences(requireActivity()).edit()
for ((key, value) in map)
- putBoolean(editor, key, value)
+ storePreference(editor, key, value)
editor?.apply()
@@ -178,7 +178,7 @@ class BackupPrefsFragment : BasePrefsFragment() {
}
@Suppress("UNCHECKED_CAST")
- private fun putBoolean(editor: SharedPreferences.Editor?, key: String?, value: Any) {
+ private fun storePreference(editor: SharedPreferences.Editor?, key: String?, value: Any) {
try {
when (value::class.simpleName) {
"Boolean" -> editor?.putBoolean(key, value as Boolean)
From 47c065d0f01b0522bbd17513cfa65ada3b8afe3d Mon Sep 17 00:00:00 2001
From: VishnuSanal
Date: Sat, 31 Dec 2022 19:10:45 +0530
Subject: [PATCH 038/384] use slf4j logger instead of `e.printstacktrace()`
---
.../fragments/preferencefragments/BackupPrefsFragment.kt | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
index a6030391b3..e727e1d922 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
+++ b/app/src/main/java/com/amaze/filemanager/ui/fragments/preferencefragments/BackupPrefsFragment.kt
@@ -36,11 +36,14 @@ import com.amaze.filemanager.TagsHelper
import com.amaze.filemanager.ui.activities.MainActivity
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
import java.io.*
class BackupPrefsFragment : BasePrefsFragment() {
private val TAG: String = TagsHelper.getTag(BasePrefsFragment::class.java)
+ private val log: Logger = LoggerFactory.getLogger(BackupPrefsFragment::class.java)
companion object {
val IMPORT_BACKUP_FILE: Int = 2
@@ -83,7 +86,7 @@ class BackupPrefsFragment : BasePrefsFragment() {
startActivity(intent)
} catch (e: IOException) {
Toast.makeText(context, getString(R.string.exporting_failed), Toast.LENGTH_SHORT).show()
- e.printStackTrace()
+ log.error(getString(R.string.exporting_failed), e)
}
}
@@ -170,7 +173,7 @@ class BackupPrefsFragment : BasePrefsFragment() {
getString(R.string.importing_failed),
Toast.LENGTH_SHORT
).show()
- e.printStackTrace()
+ log.error(getString(R.string.importing_failed), e)
}
} else {
Toast.makeText(context, getString(R.string.unknown_error), Toast.LENGTH_SHORT).show()
@@ -194,7 +197,7 @@ class BackupPrefsFragment : BasePrefsFragment() {
"${getString(R.string.import_failed_for)} $key",
Toast.LENGTH_SHORT
).show()
- e.printStackTrace()
+ log.error("${getString(R.string.import_failed_for)} $key", e)
}
}
From b6965d5d22568eec16362bb63795455760aa7534 Mon Sep 17 00:00:00 2001
From: Raymond Lai
Date: Sat, 7 Jan 2023 17:56:35 +0800
Subject: [PATCH 039/384] Make app minimal requirement to Android 4.4
Fixes #3189
---
app/build.gradle | 2 +-
.../java/com/amaze/filemanager/application/AppConfigTest.java | 3 +--
.../asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt | 3 +--
.../filemanager/asynchronous/asynctasks/DbViewerTaskTest.java | 3 +--
.../compress/AbstractCompressedHelperCallableTest.kt | 3 +--
.../asynctasks/compress/CompressedHelperForBadArchiveTest.kt | 2 +-
.../asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt | 3 +--
.../asynctasks/texteditor/read/ReadTextFileCallableTest.kt | 3 +--
.../texteditor/write/WriteTextFileCallableTest.java | 3 +--
.../filemanager/asynchronous/services/EncryptServiceTest.kt | 3 +--
.../amaze/filemanager/asynchronous/services/ZipServiceTest.kt | 4 +---
.../filemanager/database/ExplorerDatabaseMigrationTest.kt | 3 +--
.../filemanager/database/UtilitiesDatabaseMigrationTest.kt | 3 +--
.../java/com/amaze/filemanager/database/UtilsHandlerTest.kt | 3 +--
.../typeconverters/EncryptedStringTypeConverterTest.kt | 3 +--
.../filemanager/filesystem/AbstractOperationsTestBase.kt | 3 +--
.../filemanager/filesystem/EditableFileAbstractionTest.java | 3 +--
.../java/com/amaze/filemanager/filesystem/HybridFileTest.kt | 3 +--
.../java/com/amaze/filemanager/filesystem/OperationsTest.java | 3 +--
.../java/com/amaze/filemanager/filesystem/RootHelperTest.java | 3 +--
.../filemanager/filesystem/compressed/B0rkenZipTest.java | 3 +--
.../filesystem/compressed/CompressedHelperTest.java | 3 +--
.../compressed/extractcontents/AbstractExtractorTest.kt | 3 +--
.../filemanager/filesystem/files/FileListSorterTest.java | 3 +--
.../com/amaze/filemanager/filesystem/files/FileUtilsTest.kt | 3 +--
.../filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt | 3 +--
.../ftpserver/commands/AbstractFtpserverCommandTest.kt | 3 +--
.../amaze/filemanager/filesystem/root/ListFilesCommandTest.kt | 3 +--
.../filemanager/filesystem/root/ListFilesCommandTest2.kt | 3 +--
.../amaze/filemanager/filesystem/smb/CifsContextsTest.java | 3 +--
.../com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt | 3 +--
.../filemanager/filesystem/ssh/AbstractSftpServerTest.java | 3 +--
.../filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt | 3 +--
.../com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt | 3 +--
.../com/amaze/filemanager/ui/activities/MainActivityTest.java | 3 +--
.../filemanager/ui/activities/TextEditorActivityTest.java | 3 +--
.../filemanager/ui/dialogs/AbstractEncryptDialogTests.kt | 3 +--
.../filemanager/ui/fragments/CloudSheetFragmentTest.java | 3 +--
.../test/java/com/amaze/filemanager/ui/icons/IconsTest.java | 3 +--
.../test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt | 3 +--
.../filemanager/ui/views/WarnableTextInputValidatorTest.java | 3 +--
.../test/java/com/amaze/filemanager/utils/CryptUtilTest.kt | 3 +--
.../java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt | 3 +--
app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt | 3 +--
app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java | 3 +--
app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java | 3 +--
.../com/amaze/filemanager/utils/X509CertificateUtilTest.kt | 3 +--
.../compressed/extractcontents/MultipartRarExtractorTest.kt | 2 +-
.../amaze/filemanager/utils/PackageInstallValidationTest.kt | 3 +--
.../filesystem/cloud/CloudStreamSourceTest.java | 3 +--
.../filesystem/smbstreamer/StreamSourceTest.java | 3 +--
.../com/amaze/filemanager/test/ShadowPasswordUtilTest.java | 3 +--
52 files changed, 52 insertions(+), 102 deletions(-)
diff --git a/app/build.gradle b/app/build.gradle
index 6334fe80cc..cfc5ccdc71 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -16,7 +16,7 @@ android {
defaultConfig {
applicationId "com.amaze.filemanager"
- minSdkVersion 14
+ minSdkVersion 19
targetSdkVersion 31
versionCode 117
versionName "3.8.4"
diff --git a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
index b623cc39df..7d4f53312e 100644
--- a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
+++ b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.application;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Looper.getMainLooper;
@@ -51,7 +50,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
public class AppConfigTest {
@After
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
index 32a755dff1..ee2be561bc 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
@@ -22,7 +22,6 @@ package com.amaze.filemanager.asynchronous.asynctasks
import android.content.Context
import android.os.Build
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper
@@ -61,7 +60,7 @@ import org.robolectric.shadows.ShadowToast
ShadowTabHandler::class,
ShadowPasswordUtil::class
],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
abstract class AbstractDeleteTaskTestBase {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
index 811533fc6a..3ee43925d9 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.asynchronous.asynctasks;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static android.os.Looper.getMainLooper;
@@ -58,7 +57,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class DbViewerTaskTest {
private WebView webView;
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
index 4c24574f56..e66761685e 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.asynchronous.asynctasks.compress
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -37,7 +36,7 @@ import java.io.FileOutputStream
import java.util.*
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
abstract class AbstractCompressedHelperCallableTest {
private lateinit var systemTz: TimeZone
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperForBadArchiveTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperForBadArchiveTest.kt
index c213f7f31d..908f1cc79f 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperForBadArchiveTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/CompressedHelperForBadArchiveTest.kt
@@ -41,7 +41,7 @@ import java.io.FileOutputStream
* Test behaviour of CompressedHelpers in handling corrupt archives.
*/
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
class CompressedHelperForBadArchiveTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
index f5257d2e34..ac450a557a 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.asynchronous.asynctasks.ssh
import android.content.Context
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -61,7 +60,7 @@ import java.net.SocketException
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
@Suppress("StringLiteralDuplication")
class SshAuthenticationTaskTest {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
index 78b2790f75..f6e3cd814b 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
@@ -22,7 +22,6 @@ package com.amaze.filemanager.asynchronous.asynctasks.texteditor.read
import android.content.Context
import android.net.Uri
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -46,7 +45,7 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class ReadTextFileCallableTest {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
index d9b2f1430a..93b7e20fee 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.asynchronous.asynctasks.texteditor.write;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -65,7 +64,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class, ShadowContentResolver.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class WriteTextFileCallableTest {
private static final String contents = "This is modified data";
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
index 99566ba153..8d7d34938d 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
@@ -24,7 +24,6 @@ import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.M
import android.os.Build.VERSION_CODES.P
@@ -68,7 +67,7 @@ import java.util.concurrent.TimeUnit
import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
class EncryptServiceTest {
private lateinit var service: EncryptService
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
index 73d5643a6f..7127f13ff6 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
@@ -22,7 +22,6 @@ package com.amaze.filemanager.asynchronous.services
import android.content.Context
import android.content.Intent
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper.getMainLooper
@@ -53,12 +52,11 @@ import java.time.ZoneId.of
import java.time.format.DateTimeFormatter
import java.util.*
import java.util.concurrent.TimeUnit
-import kotlin.collections.ArrayList
import kotlin.random.Random
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
class ZipServiceTest {
val dt = DateTimeFormatter.ofPattern("yyyyMMddkkmm")
diff --git a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
index dd7a97b338..b5ab096ba3 100644
--- a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.database
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.room.Room
@@ -44,7 +43,7 @@ import java.io.IOException
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
@Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod")
class ExplorerDatabaseMigrationTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
index 5d529b7ab3..e482ab901c 100644
--- a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.database
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.util.Base64
@@ -54,7 +53,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class UtilitiesDatabaseMigrationTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
index 52be6456df..fe566db72e 100644
--- a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.database
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -53,7 +52,7 @@ import java.io.File
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class UtilsHandlerTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt b/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
index 27b54d2aad..fa63d9c2ba 100644
--- a/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.database.typeconverters
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -38,7 +37,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class EncryptedStringTypeConverterTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt b/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
index a0d6093001..76e47ceff9 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
@@ -22,7 +22,6 @@ package com.amaze.filemanager.filesystem
import android.content.Context
import android.os.Build
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper
@@ -61,7 +60,7 @@ import org.robolectric.shadows.ShadowSQLiteConnection
ShadowTabHandler::class,
ShadowPasswordUtil::class
],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
abstract class AbstractOperationsTestBase {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
index 352bb5a0f7..51bf089efa 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static com.amaze.filemanager.filesystem.EditableFileAbstraction.Scheme.CONTENT;
@@ -49,7 +48,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class EditableFileAbstractionTest {
@Test(expected = IllegalArgumentException.class)
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
index 4d2daafd78..ce7829f160 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -39,7 +38,7 @@ import kotlin.random.Random
/* ktlint-disable max-line-length */
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
class HybridFileTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
index 84da62de0c..a37422200d 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertFalse;
@@ -45,7 +44,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class OperationsTest {
private File storageRoot = Environment.getExternalStorageDirectory();
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
index f576ed94e8..dd87605eb5 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.fail;
@@ -51,7 +50,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
@Ignore("FIXME: should not ignore - please implement a shadow")
public class RootHelperTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
index dcb7b61e66..26483a4475 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.compressed;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static kotlin.io.ConstantsKt.DEFAULT_BUFFER_SIZE;
@@ -56,7 +55,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class B0rkenZipTest {
private File zipfile1 = new File(Environment.getExternalStorageDirectory(), "zip-slip.zip");
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
index 5612a99c4c..bffc1b832f 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.compressed;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -66,7 +65,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class CompressedHelperTest {
private Context context;
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
index fd129da301..c5aea2b62f 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.filesystem.compressed.extractcontents
import android.content.Context
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -45,7 +44,7 @@ import java.nio.file.Paths
import java.util.*
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
abstract class AbstractExtractorTest {
protected abstract fun extractorClass(): Class
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
index 79cc0edc1c..b2c89ab3de 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.files;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.hamcrest.Matchers.greaterThan;
@@ -46,7 +45,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class FileListSorterTest {
/**
* Purpose: when dirsOnTop is 0, if file1 is directory && file2 is not directory, result is -1
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
index 80085c4017..10fddf8378 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.files
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -36,7 +35,7 @@ import java.util.*
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
-@Config(sdk = [JELLY_BEAN, KITKAT, P])
+@Config(sdk = [KITKAT, P])
@Suppress("TooManyFunctions", "StringLiteralDuplication")
class FileUtilsTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
index 2103f4c842..7160b58ea3 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.ftp
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -61,7 +60,7 @@ import java.io.IOException
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class NetCopyClientConnectionPoolFtpTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
index e3dcd089f8..67101cfc40 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.ftpserver.commands
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -38,7 +37,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
abstract class AbstractFtpserverCommandTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
index e922d54ba0..6db5b63adb 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.filesystem.root
import android.content.SharedPreferences
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.preference.PreferenceManager
@@ -54,7 +53,7 @@ import java.io.InputStreamReader
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowNativeOperations::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class ListFilesCommandTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
index da9ad65a4e..eb4175459d 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.filesystem.root
import android.content.SharedPreferences
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.preference.PreferenceManager
@@ -59,7 +58,7 @@ import java.io.InputStreamReader
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowNativeOperations::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class ListFilesCommandTest2 {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
index 00ef3f47af..cbd28cc06d 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.smb;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -43,7 +42,7 @@
import jcifs.ResolverType;
import jcifs.context.BaseContext;
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
@RunWith(AndroidJUnit4.class)
public class CifsContextsTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
index e21fbd3536..8a4be016fa 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.filesystem.smb
import android.content.Context
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -45,7 +44,7 @@ import org.robolectric.shadows.ShadowSQLiteConnection
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowSmbUtil::class, ShadowMultiDex::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
@LooperMode(LooperMode.Mode.PAUSED)
class SmbHybridFileTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
index 71af20c968..1a30671ef4 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.ssh;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.SSH_URI_PREFIX;
@@ -58,7 +57,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public abstract class AbstractSftpServerTest {
protected SshServer server;
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
index f65799ae00..687d29ebc6 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.filesystem.ssh
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -70,7 +69,7 @@ import java.security.KeyPair
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class NetCopyClientConnectionPoolSshTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
index 68bb7cf910..cb85826740 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.filesystem.ssh
import android.content.Context
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -46,7 +45,7 @@ import org.robolectric.annotation.LooperMode
@LooperMode(LooperMode.Mode.PAUSED)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class SshHybridFileTest {
diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
index c2dc76569c..3b9ce346ac 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.ui.activities;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.P;
@@ -79,7 +78,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
- sdk = {JELLY_BEAN, KITKAT, P},
+ sdk = {KITKAT, P},
shadows = {
ShadowMultiDex.class,
ShadowStorageManager.class,
diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java b/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
index ddc5e65526..0ba4a68d69 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.ui.activities;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -58,7 +57,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class TextEditorActivityTest {
private final String fileContents = "fsdfsdfs";
diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
index 99dd8f1102..13cf133e23 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
+++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
@@ -21,7 +21,6 @@
package com.amaze.filemanager.ui.dialogs
import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
@@ -41,7 +40,7 @@ import org.robolectric.annotation.Config
* Base class for various tests related to file encryption.
*/
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class, ShadowTabHandler::class], sdk = [JELLY_BEAN, KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class, ShadowTabHandler::class], sdk = [KITKAT, P])
abstract class AbstractEncryptDialogTests {
protected lateinit var scenario: ActivityScenario
diff --git a/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java b/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
index 7e066a8620..7e2018a67c 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.ui.fragments;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertFalse;
@@ -39,7 +38,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
public class CloudSheetFragmentTest {
@Test
diff --git a/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java b/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
index 7042034aa6..f8873caf22 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.ui.icons;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -41,7 +40,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class IconsTest {
@Before
diff --git a/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt b/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
index 19be9db0f4..8e49c7e214 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
@@ -22,7 +22,6 @@ package com.amaze.filemanager.ui.theme
import android.content.Context
import android.content.res.Configuration
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -37,7 +36,7 @@ import java.util.*
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [JELLY_BEAN, KITKAT, P],
+ sdk = [KITKAT, P],
shadows = [ShadowMultiDex::class]
)
class AppThemeTest {
diff --git a/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java b/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
index c2b8ec3d23..032526308b 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.ui.views;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertEquals;
@@ -46,7 +45,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
public class WarnableTextInputValidatorTest {
private Context context;
diff --git a/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
index e9c376a570..e0494d24de 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils
-import android.os.Build.VERSION_CODES.JELLY_BEAN_MR2
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -48,7 +47,7 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [JELLY_BEAN_MR2, KITKAT, P]
+ sdk = [KITKAT, P]
)
class CryptUtilTest {
diff --git a/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt b/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
index 6c5df77c95..10017c5985 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.text.SpannedString
@@ -32,7 +31,7 @@ import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
-@Config(sdk = [JELLY_BEAN, KITKAT, P])
+@Config(sdk = [KITKAT, P])
class MinMaxInputFilterTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
index 03f8dda12b..df499decb9 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -47,7 +46,7 @@ import org.robolectric.annotation.Config
@Suppress("StringLiteralDuplication")
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [JELLY_BEAN, KITKAT, P],
+ sdk = [KITKAT, P],
shadows = [ShadowPasswordUtil::class, ShadowSmbUtil::class]
)
class SmbUtilTest {
diff --git a/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java b/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
index c0bb401d98..db778827d3 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
+++ b/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertArrayEquals;
@@ -38,7 +37,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
public class TinyDBTest {
private SharedPreferences prefs;
diff --git a/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java b/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
index ed39e757a1..a53083910f 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.N;
import static android.os.Build.VERSION_CODES.P;
@@ -57,7 +56,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {JELLY_BEAN, KITKAT, P})
+@Config(sdk = {KITKAT, P})
public class UtilsTest {
@Test
diff --git a/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
index c31cf66ea5..3b4aee022e 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
@@ -20,7 +20,6 @@
package com.amaze.filemanager.utils
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -43,7 +42,7 @@ import javax.security.cert.X509Certificate
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P]
)
class X509CertificateUtilTest {
diff --git a/app/src/testPlay/java/com/amaze/filemanager/filesystem/compressed/extractcontents/MultipartRarExtractorTest.kt b/app/src/testPlay/java/com/amaze/filemanager/filesystem/compressed/extractcontents/MultipartRarExtractorTest.kt
index 00ee84c522..e087520174 100644
--- a/app/src/testPlay/java/com/amaze/filemanager/filesystem/compressed/extractcontents/MultipartRarExtractorTest.kt
+++ b/app/src/testPlay/java/com/amaze/filemanager/filesystem/compressed/extractcontents/MultipartRarExtractorTest.kt
@@ -44,7 +44,7 @@ import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [Build.VERSION_CODES.JELLY_BEAN, Build.VERSION_CODES.KITKAT, Build.VERSION_CODES.P]
+ sdk = [Build.VERSION_CODES.KITKAT, Build.VERSION_CODES.P]
)
class MultipartRarExtractorTest {
diff --git a/app/src/testPlayRelease/java/com/amaze/filemanager/utils/PackageInstallValidationTest.kt b/app/src/testPlayRelease/java/com/amaze/filemanager/utils/PackageInstallValidationTest.kt
index 5bdd17ab76..3f69dd66b1 100644
--- a/app/src/testPlayRelease/java/com/amaze/filemanager/utils/PackageInstallValidationTest.kt
+++ b/app/src/testPlayRelease/java/com/amaze/filemanager/utils/PackageInstallValidationTest.kt
@@ -24,7 +24,6 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageInfo
import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.JELLY_BEAN
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
@@ -66,7 +65,7 @@ import java.util.concurrent.TimeUnit
@SuppressLint("SdCardPath")
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [JELLY_BEAN, KITKAT, P],
+ sdk = [KITKAT, P],
shadows = [ShadowPackageManager::class, ShadowMultiDex::class, ShadowTabHandler::class]
)
class PackageInstallValidationTest {
diff --git a/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/cloud/CloudStreamSourceTest.java b/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/cloud/CloudStreamSourceTest.java
index d42ddfbf21..d0e87e353f 100644
--- a/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/cloud/CloudStreamSourceTest.java
+++ b/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/cloud/CloudStreamSourceTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.fileoperations.filesystem.cloud;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertArrayEquals;
@@ -51,7 +50,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class CloudStreamSourceTest {
private CloudStreamSource cs;
private String testFilePath;
diff --git a/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/smbstreamer/StreamSourceTest.java b/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/smbstreamer/StreamSourceTest.java
index e89e5d0115..c8b012fa04 100644
--- a/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/smbstreamer/StreamSourceTest.java
+++ b/file_operations/src/test/java/com/amaze/filemanager/fileoperations/filesystem/smbstreamer/StreamSourceTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.fileoperations.filesystem.smbstreamer;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.junit.Assert.assertArrayEquals;
@@ -52,7 +51,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class, ShadowSmbFile.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class StreamSourceTest {
private SmbFile file;
private StreamSource ss;
diff --git a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
index 2ce8dcbd6e..12e9be1360 100644
--- a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
+++ b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
@@ -20,7 +20,6 @@
package com.amaze.filemanager.test;
-import static android.os.Build.VERSION_CODES.JELLY_BEAN;
import static android.os.Build.VERSION_CODES.KITKAT;
import static android.os.Build.VERSION_CODES.P;
import static org.awaitility.Awaitility.await;
@@ -56,7 +55,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class, ShadowPasswordUtil.class},
- sdk = {JELLY_BEAN, KITKAT, P})
+ sdk = {KITKAT, P})
public class ShadowPasswordUtilTest {
@Before
From bf147c8a99956560407dbb9d3513fcd227358b9b Mon Sep 17 00:00:00 2001
From: Raymond Lai
Date: Mon, 9 Jan 2023 22:34:11 +0800
Subject: [PATCH 040/384] Unit test adjustments
- Robolectric Test against Android 4.4, 8.0 and 11.0
- Add GrantPermissionRule to unit tests that involve MainActivity, to grant MANAGE_EXTERNAL_STORAGE before starting
---
.../application/AppConfigTest.java | 3 +-
.../asynctasks/AbstractDeleteTaskTestBase.kt | 12 +-
.../asynctasks/DbViewerTaskTest.java | 3 +-
.../AbstractCompressedHelperCallableTest.kt | 3 +-
.../ssh/SshAuthenticationTaskTest.kt | 3 +-
.../read/ReadTextFileCallableTest.kt | 3 +-
.../write/WriteTextFileCallableTest.java | 3 +-
.../services/DecryptServiceTest.kt | 3 +-
.../services/EncryptServiceTest.kt | 3 +-
.../services/ExtractServiceTest.kt | 11 ++
.../asynchronous/services/ZipServiceTest.kt | 3 +-
.../database/ExplorerDatabaseMigrationTest.kt | 3 +-
.../UtilitiesDatabaseMigrationTest.kt | 3 +-
.../filemanager/database/UtilsHandlerTest.kt | 3 +-
.../EncryptedStringTypeConverterTest.kt | 3 +-
.../filesystem/AbstractOperationsTestBase.kt | 16 +-
.../EditableFileAbstractionTest.java | 3 +-
.../filemanager/filesystem/HybridFileTest.kt | 3 +-
.../filesystem/OperationsTest.java | 3 +-
.../filesystem/RootHelperTest.java | 3 +-
.../filesystem/compressed/B0rkenZipTest.java | 3 +-
.../compressed/CompressedHelperTest.java | 3 +-
.../extractcontents/AbstractExtractorTest.kt | 3 +-
.../filesystem/files/FileListSorterTest.java | 4 +-
.../filesystem/files/FileUtilsTest.kt | 3 +-
.../ftp/NetCopyClientConnectionPoolFtpTest.kt | 3 +-
.../commands/AbstractFtpserverCommandTest.kt | 3 +-
.../filesystem/root/ListFilesCommandTest.kt | 3 +-
.../filesystem/root/ListFilesCommandTest2.kt | 3 +-
.../filesystem/smb/CifsContextsTest.java | 4 +-
.../filesystem/smb/SmbHybridFileTest.kt | 3 +-
.../ssh/AbstractSftpServerTest.java | 3 +-
.../ssh/NetCopyClientConnectionPoolSshTest.kt | 3 +-
.../filesystem/ssh/SshHybridFileTest.kt | 3 +-
.../ui/activities/MainActivityTest.java | 11 +-
.../ui/activities/PermissionsActivityTest.kt | 155 ++++++++++++++++++
.../ui/activities/TextEditorActivityTest.java | 3 +-
.../ui/dialogs/AbstractEncryptDialogTests.kt | 16 +-
.../ui/fragments/CloudSheetFragmentTest.java | 3 +-
.../amaze/filemanager/ui/icons/IconsTest.java | 3 +-
.../NotificationConstantsTest.java | 2 +-
.../filemanager/ui/theme/AppThemeTest.kt | 3 +-
.../views/WarnableTextInputValidatorTest.java | 3 +-
.../amaze/filemanager/utils/AESCryptTest.kt | 3 +-
.../filemanager/utils/AnimUtilsTest.java | 3 +-
.../amaze/filemanager/utils/CryptUtilTest.kt | 3 +-
.../utils/MinMaxInputFilterTest.kt | 3 +-
.../amaze/filemanager/utils/SmbUtilTest.kt | 3 +-
.../amaze/filemanager/utils/TinyDBTest.java | 3 +-
.../amaze/filemanager/utils/UtilsTest.java | 2 +-
.../utils/X509CertificateUtilTest.kt | 3 +-
.../test/ShadowPasswordUtilTest.java | 3 +-
.../com/amaze/filemanager/test/TestUtils.kt | 11 ++
53 files changed, 318 insertions(+), 52 deletions(-)
create mode 100644 app/src/test/java/com/amaze/filemanager/ui/activities/PermissionsActivityTest.kt
diff --git a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
index 7d4f53312e..b92d24725a 100644
--- a/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
+++ b/app/src/test/java/com/amaze/filemanager/application/AppConfigTest.java
@@ -44,13 +44,14 @@
import com.bumptech.glide.Glide;
import com.bumptech.glide.MemoryCategory;
+import android.os.Build;
import android.os.StrictMode;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class AppConfigTest {
@After
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
index ee2be561bc..bf85edb25a 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/AbstractDeleteTaskTestBase.kt
@@ -20,16 +20,19 @@
package com.amaze.filemanager.asynchronous.asynctasks
+import android.Manifest
import android.content.Context
import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper
import android.os.storage.StorageManager
+import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.GrantPermissionRule
import com.amaze.filemanager.R
import com.amaze.filemanager.filesystem.HybridFileParcelable
import com.amaze.filemanager.shadows.ShadowMultiDex
@@ -44,6 +47,7 @@ import io.reactivex.schedulers.Schedulers
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
+import org.junit.Rule
import org.junit.runner.RunWith
import org.robolectric.Shadows.shadowOf
import org.robolectric.annotation.Config
@@ -60,12 +64,18 @@ import org.robolectric.shadows.ShadowToast
ShadowTabHandler::class,
ShadowPasswordUtil::class
],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
abstract class AbstractDeleteTaskTestBase {
private var ctx: Context? = null
+ @Rule
+ @JvmField
+ @RequiresApi(Build.VERSION_CODES.R)
+ val allFilesPermissionRule = GrantPermissionRule
+ .grant(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+
/**
* Test case setup.
*
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
index 3ee43925d9..8e3e775138 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/DbViewerTaskTest.java
@@ -47,6 +47,7 @@
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.os.Build;
import android.view.View;
import android.webkit.WebView;
import android.widget.TextView;
@@ -57,7 +58,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class DbViewerTaskTest {
private WebView webView;
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
index e66761685e..a9d4b780c2 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/compress/AbstractCompressedHelperCallableTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.asynchronous.asynctasks.compress
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -36,7 +37,7 @@ import java.io.FileOutputStream
import java.util.*
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
abstract class AbstractCompressedHelperCallableTest {
private lateinit var systemTz: TimeZone
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
index ac450a557a..491b7dd717 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/SshAuthenticationTaskTest.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.asynchronous.asynctasks.ssh
import android.content.Context
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -60,7 +61,7 @@ import java.net.SocketException
import java.util.concurrent.CountDownLatch
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
@Suppress("StringLiteralDuplication")
class SshAuthenticationTaskTest {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
index f6e3cd814b..b4805a0ce4 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/read/ReadTextFileCallableTest.kt
@@ -22,6 +22,7 @@ package com.amaze.filemanager.asynchronous.asynctasks.texteditor.read
import android.content.Context
import android.net.Uri
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -45,7 +46,7 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class ReadTextFileCallableTest {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
index 93b7e20fee..ca7e6d6b6d 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/texteditor/write/WriteTextFileCallableTest.java
@@ -56,6 +56,7 @@
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import androidx.test.core.app.ApplicationProvider;
@@ -64,7 +65,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class, ShadowContentResolver.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class WriteTextFileCallableTest {
private static final String contents = "This is modified data";
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/DecryptServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/DecryptServiceTest.kt
index 4dc503ed1d..cc3cca4bae 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/DecryptServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/DecryptServiceTest.kt
@@ -23,6 +23,7 @@ package com.amaze.filemanager.asynchronous.services
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.M
@@ -63,7 +64,7 @@ import java.util.concurrent.TimeUnit
import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
@Suppress("StringLiteralDuplication")
class DecryptServiceTest {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
index 8d7d34938d..bbf62280f7 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/EncryptServiceTest.kt
@@ -23,6 +23,7 @@ package com.amaze.filemanager.asynchronous.services
import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.M
@@ -67,7 +68,7 @@ import java.util.concurrent.TimeUnit
import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
class EncryptServiceTest {
private lateinit var service: EncryptService
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ExtractServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ExtractServiceTest.kt
index 5539306b07..8765894503 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ExtractServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ExtractServiceTest.kt
@@ -20,16 +20,20 @@
package com.amaze.filemanager.asynchronous.services
+import android.Manifest
import android.content.Context
import android.content.Intent
import android.os.Build.VERSION.SDK_INT
+import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
import android.os.Environment
+import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.GrantPermissionRule
import com.amaze.filemanager.BuildConfig
import com.amaze.filemanager.R
import com.amaze.filemanager.application.AppConfig
@@ -47,6 +51,7 @@ import org.junit.Assert.assertNull
import org.junit.Assert.fail
import org.junit.Before
import org.junit.Ignore
+import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.Robolectric
@@ -93,6 +98,12 @@ class ExtractServiceTest {
private val multiVolumeRarFileV5Part2: File
private val multiVolumeRarFileV5Part3: File
+ @Rule
+ @JvmField
+ @RequiresApi(VERSION_CODES.R)
+ val allFilesPermissionRule = GrantPermissionRule
+ .grant(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+
init {
Environment.getExternalStorageDirectory().run {
zipfile1 = File(this, "zip-slip.zip")
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
index 7127f13ff6..d77db736ca 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/services/ZipServiceTest.kt
@@ -22,6 +22,7 @@ package com.amaze.filemanager.asynchronous.services
import android.content.Context
import android.content.Intent
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper.getMainLooper
@@ -56,7 +57,7 @@ import kotlin.random.Random
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
class ZipServiceTest {
val dt = DateTimeFormatter.ofPattern("yyyyMMddkkmm")
diff --git a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
index b5ab096ba3..37932fd892 100644
--- a/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/ExplorerDatabaseMigrationTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.database
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.room.Room
@@ -43,7 +44,7 @@ import java.io.IOException
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
@Suppress("StringLiteralDuplication", "ComplexMethod", "LongMethod")
class ExplorerDatabaseMigrationTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
index e482ab901c..e94a850483 100644
--- a/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/UtilitiesDatabaseMigrationTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.database
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.util.Base64
@@ -53,7 +54,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class UtilitiesDatabaseMigrationTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
index fe566db72e..e5bc68ad1f 100644
--- a/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/UtilsHandlerTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.database
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -52,7 +53,7 @@ import java.io.File
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class UtilsHandlerTest {
diff --git a/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt b/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
index fa63d9c2ba..50019217b0 100644
--- a/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/database/typeconverters/EncryptedStringTypeConverterTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.database.typeconverters
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -37,7 +38,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class EncryptedStringTypeConverterTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt b/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
index 76e47ceff9..ff553eaec1 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/AbstractOperationsTestBase.kt
@@ -20,16 +20,19 @@
package com.amaze.filemanager.filesystem
+import android.Manifest
import android.content.Context
import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Looper
import android.os.storage.StorageManager
+import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.GrantPermissionRule
import com.amaze.filemanager.fileoperations.filesystem.OpenMode
import com.amaze.filemanager.shadows.ShadowMultiDex
import com.amaze.filemanager.shadows.ShadowSmbUtil
@@ -43,6 +46,7 @@ import io.reactivex.schedulers.Schedulers
import org.junit.After
import org.junit.Assert.*
import org.junit.Before
+import org.junit.Rule
import org.junit.runner.RunWith
import org.robolectric.Shadows
import org.robolectric.android.util.concurrent.InlineExecutorService
@@ -60,13 +64,13 @@ import org.robolectric.shadows.ShadowSQLiteConnection
ShadowTabHandler::class,
ShadowPasswordUtil::class
],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
abstract class AbstractOperationsTestBase {
- protected var ctx: Context? = null
+ private var ctx: Context? = null
- protected val blankCallback = object : Operations.ErrorCallBack {
+ private val blankCallback = object : Operations.ErrorCallBack {
override fun exists(file: HybridFile?) = Unit
override fun launchSAF(file: HybridFile?) = Unit
override fun launchSAF(file: HybridFile?, file1: HybridFile?) = Unit
@@ -74,6 +78,12 @@ abstract class AbstractOperationsTestBase {
override fun invalidName(file: HybridFile?) = Unit
}
+ @Rule
+ @JvmField
+ @RequiresApi(Build.VERSION_CODES.R)
+ val allFilesPermissionRule = GrantPermissionRule
+ .grant(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+
/**
* Test case setup.
*
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
index 51bf089efa..eac28874d3 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/EditableFileAbstractionTest.java
@@ -39,6 +39,7 @@
import android.content.ContentResolver;
import android.content.ContentValues;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.provider.OpenableColumns;
@@ -48,7 +49,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class EditableFileAbstractionTest {
@Test(expected = IllegalArgumentException.class)
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
index ce7829f160..4993edce5e 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/HybridFileTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.filesystem
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -38,7 +39,7 @@ import kotlin.random.Random
/* ktlint-disable max-line-length */
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
class HybridFileTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
index a37422200d..a5ddee4c1a 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/OperationsTest.java
@@ -36,6 +36,7 @@
import com.amaze.filemanager.fileoperations.filesystem.OpenMode;
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
import android.os.Environment;
import androidx.test.core.app.ApplicationProvider;
@@ -44,7 +45,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class OperationsTest {
private File storageRoot = Environment.getExternalStorageDirectory();
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
index dd87605eb5..3e01e85d4a 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/RootHelperTest.java
@@ -43,6 +43,7 @@
import com.amaze.filemanager.filesystem.root.ListFilesCommand;
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
import android.os.Environment;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -50,7 +51,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
@Ignore("FIXME: should not ignore - please implement a shadow")
public class RootHelperTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
index 26483a4475..99bb6b9944 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/B0rkenZipTest.java
@@ -45,6 +45,7 @@
import com.amaze.filemanager.filesystem.compressed.extractcontents.helpers.ZipExtractor;
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
import android.os.Environment;
import androidx.test.core.app.ApplicationProvider;
@@ -55,7 +56,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class B0rkenZipTest {
private File zipfile1 = new File(Environment.getExternalStorageDirectory(), "zip-slip.zip");
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
index bffc1b832f..85b15ab939 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/CompressedHelperTest.java
@@ -58,6 +58,7 @@
import com.amaze.filemanager.shadows.ShadowMultiDex;
import android.content.Context;
+import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -65,7 +66,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class CompressedHelperTest {
private Context context;
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
index c5aea2b62f..dbc2444af7 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/compressed/extractcontents/AbstractExtractorTest.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.filesystem.compressed.extractcontents
import android.content.Context
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -44,7 +45,7 @@ import java.nio.file.Paths
import java.util.*
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P])
+@Config(shadows = [ShadowMultiDex::class], sdk = [KITKAT, P, Build.VERSION_CODES.R])
abstract class AbstractExtractorTest {
protected abstract fun extractorClass(): Class
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
index b2c89ab3de..35aa967cd1 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileListSorterTest.java
@@ -35,6 +35,8 @@
import com.amaze.filemanager.fileoperations.filesystem.OpenMode;
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
+
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -45,7 +47,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class FileListSorterTest {
/**
* Purpose: when dirsOnTop is 0, if file1 is directory && file2 is not directory, result is -1
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
index 10fddf8378..5b1ba3133c 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/files/FileUtilsTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.filesystem.files
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,7 +36,7 @@ import java.util.*
@RunWith(AndroidJUnit4::class)
@LooperMode(LooperMode.Mode.PAUSED)
-@Config(sdk = [KITKAT, P])
+@Config(sdk = [KITKAT, P, Build.VERSION_CODES.R])
@Suppress("TooManyFunctions", "StringLiteralDuplication")
class FileUtilsTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
index 7160b58ea3..2f3bc5223a 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPoolFtpTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.filesystem.ftp
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -60,7 +61,7 @@ import java.io.IOException
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class NetCopyClientConnectionPoolFtpTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
index 67101cfc40..6a9bbde6e9 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ftpserver/commands/AbstractFtpserverCommandTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.filesystem.ftpserver.commands
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -37,7 +38,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
abstract class AbstractFtpserverCommandTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
index 6db5b63adb..7f80ba0802 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.filesystem.root
import android.content.SharedPreferences
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.preference.PreferenceManager
@@ -53,7 +54,7 @@ import java.io.InputStreamReader
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowNativeOperations::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class ListFilesCommandTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
index eb4175459d..5aa9403f76 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/root/ListFilesCommandTest2.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.filesystem.root
import android.content.SharedPreferences
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.preference.PreferenceManager
@@ -58,7 +59,7 @@ import java.io.InputStreamReader
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowNativeOperations::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class ListFilesCommandTest2 {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
index cbd28cc06d..8695fdf445 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/smb/CifsContextsTest.java
@@ -36,13 +36,15 @@
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
+import android.os.Build;
+
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import jcifs.ResolverType;
import jcifs.context.BaseContext;
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
@RunWith(AndroidJUnit4.class)
public class CifsContextsTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
index 8a4be016fa..64e6376701 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/smb/SmbHybridFileTest.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.filesystem.smb
import android.content.Context
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -44,7 +45,7 @@ import org.robolectric.shadows.ShadowSQLiteConnection
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowSmbUtil::class, ShadowMultiDex::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
@LooperMode(LooperMode.Mode.PAUSED)
class SmbHybridFileTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
index 1a30671ef4..cf2ddf36ec 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/AbstractSftpServerTest.java
@@ -46,6 +46,7 @@
import com.amaze.filemanager.filesystem.ssh.test.TestKeyProvider;
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
import android.os.Environment;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -57,7 +58,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public abstract class AbstractSftpServerTest {
protected SshServer server;
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
index 687d29ebc6..4847947be9 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/NetCopyClientConnectionPoolSshTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.filesystem.ssh
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -69,7 +70,7 @@ import java.security.KeyPair
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class NetCopyClientConnectionPoolSshTest {
diff --git a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
index cb85826740..8b317fda6e 100644
--- a/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/filesystem/ssh/SshHybridFileTest.kt
@@ -21,6 +21,7 @@
package com.amaze.filemanager.filesystem.ssh
import android.content.Context
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -45,7 +46,7 @@ import org.robolectric.annotation.LooperMode
@LooperMode(LooperMode.Mode.PAUSED)
@Config(
shadows = [ShadowMultiDex::class, ShadowPasswordUtil::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class SshHybridFileTest {
diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
index 3b9ce346ac..443dfe4d6c 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/activities/MainActivityTest.java
@@ -41,6 +41,7 @@
import org.junit.After;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockedConstruction;
@@ -62,15 +63,18 @@
import com.amaze.filemanager.utils.PasswordUtil;
import com.amaze.filemanager.utils.SmbUtil;
+import android.Manifest;
import android.os.Build;
import android.os.Bundle;
import android.os.storage.StorageManager;
import android.util.Base64;
+import androidx.annotation.RequiresApi;
import androidx.lifecycle.Lifecycle;
import androidx.test.core.app.ActivityScenario;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.rule.GrantPermissionRule;
import io.reactivex.android.plugins.RxAndroidPlugins;
import io.reactivex.plugins.RxJavaPlugins;
@@ -78,7 +82,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
- sdk = {KITKAT, P},
+ sdk = {KITKAT, P, Build.VERSION_CODES.R},
shadows = {
ShadowMultiDex.class,
ShadowStorageManager.class,
@@ -97,6 +101,11 @@ public class MainActivityTest {
"address", "port", "keypairName", "name", "username", "password", "edit"
};
+ @Rule
+ @RequiresApi(Build.VERSION_CODES.R)
+ public final GrantPermissionRule allFilesPermissionRule =
+ GrantPermissionRule.grant(Manifest.permission.MANAGE_EXTERNAL_STORAGE);
+
private MockedConstruction mc;
@Before
diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/PermissionsActivityTest.kt b/app/src/test/java/com/amaze/filemanager/ui/activities/PermissionsActivityTest.kt
new file mode 100644
index 0000000000..69741eb77e
--- /dev/null
+++ b/app/src/test/java/com/amaze/filemanager/ui/activities/PermissionsActivityTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014-2023 Arpit Khurana , Vishal Nehra ,
+ * Emmanuel Messulam, Raymond Lai and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.ui.activities
+
+import android.app.AppOpsManager
+import android.content.Context
+import android.net.Uri
+import android.os.Build
+import android.os.Build.VERSION_CODES.KITKAT
+import android.os.Build.VERSION_CODES.P
+import android.os.Build.VERSION_CODES.R
+import android.os.storage.StorageManager
+import android.provider.Settings
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.amaze.filemanager.shadows.ShadowMultiDex
+import com.amaze.filemanager.test.TestUtils.initializeInternalStorage
+import io.reactivex.android.plugins.RxAndroidPlugins
+import io.reactivex.plugins.RxJavaPlugins
+import io.reactivex.schedulers.Schedulers
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RuntimeEnvironment
+import org.robolectric.Shadows
+import org.robolectric.Shadows.shadowOf
+import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowDialog
+import org.robolectric.shadows.ShadowSQLiteConnection
+import org.robolectric.shadows.ShadowStorageManager
+
+/**
+ * Tests MainActivity's superclass, PermissionsActivity.
+ *
+ * Cannot instantiate itself, hence still uses MainActivity to trigger its actions.
+ */
+@RunWith(AndroidJUnit4::class)
+@Config(
+ sdk = [KITKAT, P, Build.VERSION_CODES.R],
+ shadows = [ShadowMultiDex::class, ShadowStorageManager::class]
+)
+class PermissionsActivityTest {
+
+ private lateinit var scenario: ActivityScenario
+
+ /**
+ * Pre-test setup
+ */
+ @Before
+ fun setUp() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) initializeInternalStorage()
+ RxJavaPlugins.reset()
+ RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
+ RxAndroidPlugins.reset()
+ RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
+ ShadowSQLiteConnection.reset()
+ }
+
+ /**
+ * Post-test cleanup
+ */
+ @After
+ fun tearDown() {
+ scenario.moveToState(Lifecycle.State.DESTROYED)
+ scenario.close()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Shadows.shadowOf(
+ ApplicationProvider.getApplicationContext().getSystemService(
+ StorageManager::class.java
+ )
+ ).resetStorageVolumeList()
+ }
+ }
+
+ /**
+ * Test grant all files access dialog.
+ */
+ @Test
+ @Config(sdk = [R])
+ fun testDisplayAllFilesPermissionDialog() {
+ scenario = ActivityScenario.launch(MainActivity::class.java)
+ scenario.moveToState(Lifecycle.State.STARTED)
+ scenario.onActivity { activity ->
+ // Make Environment.isExternalStorageManager() returns false.
+ val shadowApplication = shadowOf(RuntimeEnvironment.getApplication())
+ shadowOf(
+ activity.getSystemService(
+ AppOpsManager::class.java
+ )
+ ).setMode(
+ 92,
+ activity.applicationInfo.uid,
+ activity.packageName,
+ AppOpsManager.MODE_IGNORED
+ )
+ activity.requestAllFilesAccess { }
+ assertNotNull(ShadowDialog.getLatestDialog())
+ ShadowDialog.getLatestDialog().run {
+ assertTrue(this is MaterialDialog)
+ (this as MaterialDialog).run {
+ assertEquals(
+ activity.getString(com.amaze.filemanager.R.string.grantper),
+ this.titleView.text
+ )
+ assertEquals(
+ activity.getString(
+ com.amaze.filemanager.R.string.grant_all_files_permission
+ ),
+ this.contentView?.text.toString()
+ )
+ this.getActionButton(DialogAction.POSITIVE).run {
+ assertEquals(
+ activity.getString(com.amaze.filemanager.R.string.grant),
+ this.text
+ )
+ performClick()
+ }
+ val intent = shadowApplication.nextStartedActivity
+ assertNotNull(intent)
+ assertEquals(
+ Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,
+ intent.action
+ )
+ assertEquals(Uri.parse("package:${activity.packageName}"), intent.data)
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java b/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
index 0ba4a68d69..e49fa6975b 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/activities/TextEditorActivityTest.java
@@ -48,6 +48,7 @@
import android.content.ContentResolver;
import android.content.Intent;
import android.net.Uri;
+import android.os.Build;
import android.os.Environment;
import android.widget.TextView;
@@ -57,7 +58,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class TextEditorActivityTest {
private final String fileContents = "fsdfsdfs";
diff --git a/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt b/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
index 13cf133e23..ba3ac95807 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
+++ b/app/src/test/java/com/amaze/filemanager/ui/dialogs/AbstractEncryptDialogTests.kt
@@ -20,19 +20,24 @@
package com.amaze.filemanager.ui.dialogs
+import android.Manifest
+import android.os.Build
import android.os.Build.VERSION.SDK_INT
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
+import androidx.annotation.RequiresApi
import androidx.lifecycle.Lifecycle
import androidx.test.core.app.ActivityScenario
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.rule.GrantPermissionRule
import com.amaze.filemanager.shadows.ShadowMultiDex
import com.amaze.filemanager.test.ShadowTabHandler
import com.amaze.filemanager.test.TestUtils.initializeInternalStorage
import com.amaze.filemanager.ui.activities.MainActivity
import org.junit.After
import org.junit.Before
+import org.junit.Rule
import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@@ -40,11 +45,20 @@ import org.robolectric.annotation.Config
* Base class for various tests related to file encryption.
*/
@RunWith(AndroidJUnit4::class)
-@Config(shadows = [ShadowMultiDex::class, ShadowTabHandler::class], sdk = [KITKAT, P])
+@Config(
+ shadows = [ShadowMultiDex::class, ShadowTabHandler::class],
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
+)
abstract class AbstractEncryptDialogTests {
protected lateinit var scenario: ActivityScenario
+ @Rule
+ @JvmField
+ @RequiresApi(Build.VERSION_CODES.R)
+ var allFilesPermissionRule = GrantPermissionRule
+ .grant(Manifest.permission.MANAGE_EXTERNAL_STORAGE)
+
/**
* MainActivity setup.
*/
diff --git a/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java b/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
index 7e2018a67c..2ae29be76a 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/fragments/CloudSheetFragmentTest.java
@@ -33,12 +33,13 @@
import com.amaze.filemanager.database.CloudContract;
import android.content.pm.PackageInfo;
+import android.os.Build;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class CloudSheetFragmentTest {
@Test
diff --git a/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java b/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
index f8873caf22..bc377b3a30 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/icons/IconsTest.java
@@ -33,6 +33,7 @@
import com.amaze.filemanager.shadows.ShadowMultiDex;
+import android.os.Build;
import android.webkit.MimeTypeMap;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -40,7 +41,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class IconsTest {
@Before
diff --git a/app/src/test/java/com/amaze/filemanager/ui/notifications/NotificationConstantsTest.java b/app/src/test/java/com/amaze/filemanager/ui/notifications/NotificationConstantsTest.java
index 84dac3f0b5..c2780642c3 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/notifications/NotificationConstantsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/notifications/NotificationConstantsTest.java
@@ -54,7 +54,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class NotificationConstantsTest {
private Context context;
diff --git a/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt b/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
index 8e49c7e214..372d3360f9 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/ui/theme/AppThemeTest.kt
@@ -22,6 +22,7 @@ package com.amaze.filemanager.ui.theme
import android.content.Context
import android.content.res.Configuration
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -36,7 +37,7 @@ import java.util.*
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [KITKAT, P],
+ sdk = [KITKAT, P, Build.VERSION_CODES.R],
shadows = [ShadowMultiDex::class]
)
class AppThemeTest {
diff --git a/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java b/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
index 032526308b..ecf1352a65 100644
--- a/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
+++ b/app/src/test/java/com/amaze/filemanager/ui/views/WarnableTextInputValidatorTest.java
@@ -36,6 +36,7 @@
import com.amaze.filemanager.R;
import android.content.Context;
+import android.os.Build;
import android.widget.Button;
import android.widget.EditText;
@@ -45,7 +46,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class WarnableTextInputValidatorTest {
private Context context;
diff --git a/app/src/test/java/com/amaze/filemanager/utils/AESCryptTest.kt b/app/src/test/java/com/amaze/filemanager/utils/AESCryptTest.kt
index 662598e6ef..a1c7cbbcad 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/AESCryptTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/AESCryptTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.utils
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -35,7 +36,7 @@ import kotlin.random.Random
* Unit test for [AESCrypt]
*/
@RunWith(AndroidJUnit4::class)
-@Config(sdk = [KITKAT, P])
+@Config(sdk = [KITKAT, P, Build.VERSION_CODES.R])
class AESCryptTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/utils/AnimUtilsTest.java b/app/src/test/java/com/amaze/filemanager/utils/AnimUtilsTest.java
index 4434414b14..b5cbfc2003 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/AnimUtilsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/utils/AnimUtilsTest.java
@@ -38,13 +38,14 @@
import com.amaze.filemanager.ui.views.ThemedTextView;
+import android.os.Build;
import android.view.animation.Interpolator;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class AnimUtilsTest {
@Test
diff --git a/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
index e0494d24de..2cd21af630 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/CryptUtilTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.utils
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.os.Environment
@@ -47,7 +48,7 @@ import kotlin.random.Random
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class CryptUtilTest {
diff --git a/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt b/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
index 10017c5985..9ecadd5fab 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/MinMaxInputFilterTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.utils
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import android.text.SpannedString
@@ -31,7 +32,7 @@ import org.junit.runner.RunWith
import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
-@Config(sdk = [KITKAT, P])
+@Config(sdk = [KITKAT, P, Build.VERSION_CODES.R])
class MinMaxInputFilterTest {
/**
diff --git a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
index df499decb9..635547e8f0 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/SmbUtilTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.utils
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.core.app.ApplicationProvider
@@ -46,7 +47,7 @@ import org.robolectric.annotation.Config
@Suppress("StringLiteralDuplication")
@RunWith(AndroidJUnit4::class)
@Config(
- sdk = [KITKAT, P],
+ sdk = [KITKAT, P, Build.VERSION_CODES.R],
shadows = [ShadowPasswordUtil::class, ShadowSmbUtil::class]
)
class SmbUtilTest {
diff --git a/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java b/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
index db778827d3..67b03d58b2 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
+++ b/app/src/test/java/com/amaze/filemanager/utils/TinyDBTest.java
@@ -31,13 +31,14 @@
import org.robolectric.annotation.Config;
import android.content.SharedPreferences;
+import android.os.Build;
import androidx.preference.PreferenceManager;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class TinyDBTest {
private SharedPreferences prefs;
diff --git a/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java b/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
index a53083910f..0c8d6dd710 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
+++ b/app/src/test/java/com/amaze/filemanager/utils/UtilsTest.java
@@ -56,7 +56,7 @@
import androidx.test.ext.junit.runners.AndroidJUnit4;
@RunWith(AndroidJUnit4.class)
-@Config(sdk = {KITKAT, P})
+@Config(sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class UtilsTest {
@Test
diff --git a/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt b/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
index 3b4aee022e..9aebb4de92 100644
--- a/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/utils/X509CertificateUtilTest.kt
@@ -20,6 +20,7 @@
package com.amaze.filemanager.utils
+import android.os.Build
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -42,7 +43,7 @@ import javax.security.cert.X509Certificate
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class],
- sdk = [KITKAT, P]
+ sdk = [KITKAT, P, Build.VERSION_CODES.R]
)
class X509CertificateUtilTest {
diff --git a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
index 12e9be1360..0f161a8256 100644
--- a/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
+++ b/testShared/src/test/java/com/amaze/filemanager/test/ShadowPasswordUtilTest.java
@@ -43,6 +43,7 @@
import com.amaze.filemanager.shadows.ShadowMultiDex;
import com.amaze.filemanager.utils.PasswordUtil;
+import android.os.Build;
import android.util.Base64;
import androidx.test.core.app.ApplicationProvider;
@@ -55,7 +56,7 @@
@RunWith(AndroidJUnit4.class)
@Config(
shadows = {ShadowMultiDex.class, ShadowPasswordUtil.class},
- sdk = {KITKAT, P})
+ sdk = {KITKAT, P, Build.VERSION_CODES.R})
public class ShadowPasswordUtilTest {
@Before
diff --git a/testShared/src/test/java/com/amaze/filemanager/test/TestUtils.kt b/testShared/src/test/java/com/amaze/filemanager/test/TestUtils.kt
index c094b62a9b..e0ce8a764b 100644
--- a/testShared/src/test/java/com/amaze/filemanager/test/TestUtils.kt
+++ b/testShared/src/test/java/com/amaze/filemanager/test/TestUtils.kt
@@ -22,6 +22,7 @@ package com.amaze.filemanager.test
import android.content.Context
import android.os.Build
+import android.os.Build.VERSION_CODES
import android.os.Environment
import android.os.Parcel
import android.os.UserHandle
@@ -33,6 +34,7 @@ import com.amaze.filemanager.BuildConfig
import com.amaze.filemanager.application.AppConfig
import com.amaze.filemanager.filesystem.compressed.CompressedHelper
import org.robolectric.Shadows
+import org.robolectric.shadows.ShadowEnvironment
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import kotlin.random.Random
@@ -99,6 +101,15 @@ object TestUtils {
parcel.writeString("1234-5678")
parcel.writeString(Environment.MEDIA_MOUNTED)
addVolumeToStorageManager(parcel)
+
+ /*
+ * Monkey-patch ShadowEnvironment for Environment.isExternalStorageManager() to work.
+ *
+ * See https://github.com/robolectric/robolectric/issues/7300
+ */
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.R) {
+ ShadowEnvironment.addExternalDir(Environment.getExternalStorageDirectory().absolutePath)
+ }
}
/**
From 15c32ef28b406f2b5a73a8a5a652473ebbcb621e Mon Sep 17 00:00:00 2001
From: Raymond Lai
Date: Sun, 25 Dec 2022 01:57:17 +0800
Subject: [PATCH 041/384] Convert PemToKeyPairTask to PemToKeyPairObservable
Fixes #1364.
At code level it's reusing the same class, but with RxJava's Observable should do things better than AsyncTasks anyway.
---
.github/workflows/android-feature.yml | 7 -
...yPairTask.kt => PemToKeyPairObservable.kt} | 149 ++++-----
.../ftp/NetCopyClientConnectionPool.kt | 23 +-
.../ui/dialogs/SftpConnectDialog.kt | 46 ++-
.../main/res/layout/dialog_singleedittext.xml | 2 +-
app/src/main/res/values/strings.xml | 1 +
.../ssh/PemToKeyPairObservableEd25519Test.kt | 124 ++++++++
.../ssh/PemToKeyPairObservableRsaTest.kt | 301 ++++++++++++++++++
.../asynctasks/ssh/PemToKeyPairTaskTest.java | 131 --------
.../asynctasks/ssh/PemToKeyPairTaskTest2.java | 98 ------
10 files changed, 536 insertions(+), 346 deletions(-)
rename app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/{PemToKeyPairTask.kt => PemToKeyPairObservable.kt} (60%)
create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
create mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
delete mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest.java
delete mode 100644 app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest2.java
diff --git a/.github/workflows/android-feature.yml b/.github/workflows/android-feature.yml
index 271679d775..dc1cc3f8f5 100644
--- a/.github/workflows/android-feature.yml
+++ b/.github/workflows/android-feature.yml
@@ -45,10 +45,3 @@ jobs:
uses: gradle/gradle-build-action@v2
with:
arguments: jacocoTestPlayDebugUnitTestReport --stacktrace --info
- - name: Publish test results
- uses: dorny/test-reporter@v1
- if: always()
- with:
- name: test-results
- path: 'app/build/test-results/testPlayDebugUnitTest/TEST-*.xml'
- reporter: java-junit
\ No newline at end of file
diff --git a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTask.kt b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt
similarity index 60%
rename from app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTask.kt
rename to app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt
index aa15cc986f..501be08903 100644
--- a/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTask.kt
+++ b/app/src/main/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservable.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014-2021 Arpit Khurana , Vishal Nehra ,
+ * Copyright (C) 2014-2022 Arpit Khurana , Vishal Nehra ,
* Emmanuel Messulam, Raymond Lai and Contributors.
*
* This file is part of Amaze File Manager.
@@ -20,22 +20,19 @@
package com.amaze.filemanager.asynchronous.asynctasks.ssh
-import android.os.AsyncTask
import android.text.InputType
-import android.view.View
-import android.widget.EditText
+import android.view.LayoutInflater
import android.widget.Toast
import com.afollestad.materialdialogs.DialogAction
import com.afollestad.materialdialogs.MaterialDialog
import com.amaze.filemanager.R
import com.amaze.filemanager.application.AppConfig
-import com.amaze.filemanager.asynchronous.asynctasks.AsyncTaskResult
+import com.amaze.filemanager.databinding.DialogSingleedittextBinding
import com.amaze.filemanager.ui.views.WarnableTextInputLayout
import com.amaze.filemanager.ui.views.WarnableTextInputValidator
-import com.amaze.filemanager.ui.views.WarnableTextInputValidator.ReturnState
import com.hierynomus.sshj.userauth.keyprovider.OpenSSHKeyV1KeyFile
-import net.schmizz.sshj.common.IOUtils
-import net.schmizz.sshj.userauth.keyprovider.KeyProvider
+import io.reactivex.ObservableEmitter
+import io.reactivex.ObservableOnSubscribe
import net.schmizz.sshj.userauth.keyprovider.OpenSSHKeyFile
import net.schmizz.sshj.userauth.keyprovider.PuTTYKeyFile
import net.schmizz.sshj.userauth.password.PasswordFinder
@@ -50,86 +47,67 @@ import java.io.InputStream
import java.io.StringReader
import java.security.KeyPair
-/**
- * [AsyncTask] to convert given [InputStream] into [KeyPair] which is requird by
- * sshj, using [JcaPEMKeyConverter].
- *
- * @see JcaPEMKeyConverter
- *
- * @see KeyProvider
- *
- * @see OpenSSHKeyV1KeyFile
- *
- * @see PuTTYKeyFile
- *
- * @see com.amaze.filemanager.filesystem.ftp.NetCopyClientConnectionPool.create
- * @see net.schmizz.sshj.SSHClient.authPublickey
- */
-class PemToKeyPairTask(
- private val pemFile: ByteArray,
- private val callback: AsyncTaskResult.Callback
-) :
- AsyncTask() {
+class PemToKeyPairObservable(private val pemFile: ByteArray) : ObservableOnSubscribe {
+
private val converters = arrayOf(
JcaPemToKeyPairConverter(),
OpenSshPemToKeyPairConverter(),
OpenSshV1PemToKeyPairConverter(),
PuttyPrivateKeyToKeyPairConverter()
)
- private val log: Logger = LoggerFactory.getLogger(PemToKeyPairTask::class.java)
-
- private var paused = false
private var passwordFinder: PasswordFinder? = null
private var errorMessage: String? = null
- constructor(pemFile: InputStream, callback: AsyncTaskResult.Callback) :
- this(IOUtils.readFully(pemFile).toByteArray(), callback)
- constructor(pemContent: String, callback: AsyncTaskResult.Callback) :
- this(pemContent.toByteArray(), callback)
+ companion object {
+ private val log: Logger = LoggerFactory.getLogger(PemToKeyPairObservable::class.java)
+ }
- override fun doInBackground(vararg voids: Void): KeyPair? {
- while (true) {
- if (isCancelled) {
- return null
- }
- if (paused) {
- continue
- }
- for (converter in converters) {
- val keyPair = converter.convert(String(pemFile))
- if (keyPair != null) {
- paused = false
- return keyPair
- }
- }
- if (passwordFinder != null) {
- errorMessage = AppConfig
- .getInstance()
- .getString(R.string.ssh_key_invalid_passphrase)
+ constructor(pemFile: InputStream) : this(pemFile.readBytes())
+ constructor(pemContent: String) : this(pemContent.toByteArray())
+
+ override fun subscribe(emitter: ObservableEmitter) {
+ for (converter in converters) {
+ val keyPair = converter.convert(String(pemFile))
+ if (keyPair != null) {
+ emitter.onNext(keyPair)
+ emitter.onComplete()
+ return
}
- paused = true
- publishProgress(IOException("No converter available to parse selected PEM"))
}
+ if (passwordFinder != null) {
+ errorMessage = AppConfig
+ .getInstance()
+ .getString(R.string.ssh_key_invalid_passphrase)
+ } else {
+ errorMessage = AppConfig
+ .getInstance()
+ .getString(R.string.ssh_key_no_decoder_decrypt)
+ }
+ emitter.onError(IOException(errorMessage))
}
- override fun onProgressUpdate(vararg values: IOException?) {
- super.onProgressUpdate(*values)
- if (values.isEmpty()) {
- return
- }
- val result = values[0]
- val builder = MaterialDialog.Builder(AppConfig.getInstance().mainActivityContext!!)
- val dialogLayout = View.inflate(
- AppConfig.getInstance().mainActivityContext,
- R.layout.dialog_singleedittext,
- null
+ /**
+ * For generating the callback when decoding the PEM failed. Opens dialog and prompt for
+ * password.
+ */
+ fun displayPassphraseDialog(
+ exception: Throwable,
+ positiveCallback: (() -> Unit),
+ negativeCallback: (() -> Unit)
+ ) {
+ val builder = MaterialDialog.Builder(
+ AppConfig.getInstance().mainActivityContext!!
+ )
+ val dialogLayout = DialogSingleedittextBinding.inflate(
+ LayoutInflater.from(AppConfig.getInstance().mainActivityContext)
)
val wilTextfield: WarnableTextInputLayout =
- dialogLayout.findViewById(R.id.singleedittext_warnabletextinputlayout)
- val textfield = dialogLayout.findViewById(R.id.singleedittext_input)
- textfield.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
+ dialogLayout.singleedittextWarnabletextinputlayout
+ val textfield = dialogLayout.singleedittextInput
+ textfield.inputType = InputType.TYPE_CLASS_TEXT or
+ InputType.TYPE_TEXT_VARIATION_PASSWORD
builder
- .customView(dialogLayout, false)
+ .customView(dialogLayout.root, false)
.autoDismiss(false)
.title(R.string.ssh_key_prompt_passphrase)
.positiveText(R.string.ok)
@@ -138,19 +116,18 @@ class PemToKeyPairTask(
override fun reqPassword(resource: Resource<*>?): CharArray {
return textfield.text.toString().toCharArray()
}
-
override fun shouldRetry(resource: Resource<*>?): Boolean {
return false
}
}
- paused = false
dialog.dismiss()
+ positiveCallback.invoke()
}
.negativeText(R.string.cancel)
.onNegative { dialog: MaterialDialog, which: DialogAction? ->
dialog.dismiss()
- toastOnParseError(result!!)
- cancel(true)
+ toastOnParseError(exception)
+ negativeCallback.invoke()
}
val dialog = builder.show()
WarnableTextInputValidator(
@@ -160,12 +137,12 @@ class PemToKeyPairTask(
dialog.getActionButton(DialogAction.POSITIVE)
) { text: String ->
if (text.isEmpty()) {
- ReturnState(
- ReturnState.STATE_ERROR,
+ WarnableTextInputValidator.ReturnState(
+ WarnableTextInputValidator.ReturnState.STATE_ERROR,
R.string.field_empty
)
}
- ReturnState()
+ WarnableTextInputValidator.ReturnState()
}
if (errorMessage != null) {
wilTextfield.error = errorMessage
@@ -173,11 +150,7 @@ class PemToKeyPairTask(
}
}
- override fun onPostExecute(result: KeyPair?) {
- callback.onResult(result)
- }
-
- private fun toastOnParseError(result: IOException) {
+ private fun toastOnParseError(result: Throwable) {
Toast.makeText(
AppConfig.getInstance().mainActivityContext,
AppConfig.getInstance()
@@ -189,18 +162,16 @@ class PemToKeyPairTask(
}
private abstract inner class PemToKeyPairConverter {
- fun convert(source: String?): KeyPair? = runCatching {
+ fun convert(source: String): KeyPair? = runCatching {
throwingConvert(source)
}.onFailure {
log.warn("failed to convert pem to keypair", it)
}.getOrNull()
- @Throws(Exception::class)
protected abstract fun throwingConvert(source: String?): KeyPair?
}
private inner class JcaPemToKeyPairConverter : PemToKeyPairConverter() {
- @Throws(Exception::class)
override fun throwingConvert(source: String?): KeyPair? {
val pemParser = PEMParser(StringReader(source))
val keyPair = pemParser.readObject() as PEMKeyPair?
@@ -210,8 +181,7 @@ class PemToKeyPairTask(
}
private inner class OpenSshPemToKeyPairConverter : PemToKeyPairConverter() {
- @Throws(Exception::class)
- public override fun throwingConvert(source: String?): KeyPair {
+ override fun throwingConvert(source: String?): KeyPair {
val converter = OpenSSHKeyFile()
converter.init(StringReader(source), passwordFinder)
return KeyPair(converter.public, converter.private)
@@ -219,8 +189,7 @@ class PemToKeyPairTask(
}
private inner class OpenSshV1PemToKeyPairConverter : PemToKeyPairConverter() {
- @Throws(Exception::class)
- public override fun throwingConvert(source: String?): KeyPair {
+ override fun throwingConvert(source: String?): KeyPair {
val converter = OpenSSHKeyV1KeyFile()
converter.init(StringReader(source), passwordFinder)
return KeyPair(converter.public, converter.private)
diff --git a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt
index 56374a6693..f3fd9b928d 100644
--- a/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt
+++ b/app/src/main/java/com/amaze/filemanager/filesystem/ftp/NetCopyClientConnectionPool.kt
@@ -23,13 +23,15 @@ package com.amaze.filemanager.filesystem.ftp
import android.annotation.SuppressLint
import com.amaze.filemanager.application.AppConfig
import com.amaze.filemanager.asynchronous.asynctasks.ftp.auth.FtpAuthenticationTask
-import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairTask
+import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairObservable
import com.amaze.filemanager.asynchronous.asynctasks.ssh.SshAuthenticationTask
import com.amaze.filemanager.filesystem.ftp.NetCopyClientUtils.extractBaseUriFrom
import com.amaze.filemanager.utils.PasswordUtil
import io.reactivex.Flowable
import io.reactivex.Maybe
+import io.reactivex.Observable.create
import io.reactivex.Single
+import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import net.schmizz.sshj.Config
import net.schmizz.sshj.SSHClient
@@ -254,10 +256,23 @@ object NetCopyClientConnectionPool {
val pem = utilsHandler.getSshAuthPrivateKey(url)
val keyPair = AtomicReference(null)
if (true == pem?.isNotEmpty()) {
+ val observable = PemToKeyPairObservable(pem)
keyPair.set(
- PemToKeyPairTask(
- pem
- ) { }.execute().get()
+ create(observable)
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .retryWhen { exceptions ->
+ exceptions.flatMap { exception ->
+ create { subscriber ->
+ observable.displayPassphraseDialog(exception, {
+ subscriber.onNext(Unit)
+ }, {
+ subscriber.onError(exception)
+ })
+ }
+ }
+ }
+ .blockingFirst()
)
}
val hostKey = utilsHandler.getRemoteHostKey(url) ?: return null
diff --git a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt
index 1d2e125298..bbef37767d 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt
+++ b/app/src/main/java/com/amaze/filemanager/ui/dialogs/SftpConnectDialog.kt
@@ -46,7 +46,7 @@ import com.amaze.filemanager.application.AppConfig
import com.amaze.filemanager.asynchronous.asynctasks.ftp.AbstractGetHostInfoTask
import com.amaze.filemanager.asynchronous.asynctasks.ftp.hostcert.FtpsGetHostCertificateTask
import com.amaze.filemanager.asynchronous.asynctasks.ssh.GetSshHostFingerprintTask
-import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairTask
+import com.amaze.filemanager.asynchronous.asynctasks.ssh.PemToKeyPairObservable
import com.amaze.filemanager.database.UtilsHandler
import com.amaze.filemanager.database.models.OperationData
import com.amaze.filemanager.databinding.SftpDialogBinding
@@ -66,6 +66,8 @@ import com.amaze.filemanager.utils.MinMaxInputFilter
import com.amaze.filemanager.utils.SimpleTextWatcher
import com.amaze.filemanager.utils.X509CertificateUtil.FINGERPRINT
import com.google.android.material.snackbar.Snackbar
+import io.reactivex.Observable
+import io.reactivex.Observable.create
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@@ -579,19 +581,33 @@ class SftpConnectDialog : DialogFragment() {
runCatching {
requireContext().contentResolver.openInputStream(this)?.let {
selectedKeyContent ->
- PemToKeyPairTask(selectedKeyContent) { result: KeyPair? ->
- selectedParsedKeyPair = result
- selectedParsedKeyPairName = this
- .lastPathSegment!!
- .substring(
- this.lastPathSegment!!
- .indexOf('/') + 1
- )
- val okBTN = (dialog as MaterialDialog)
- .getActionButton(DialogAction.POSITIVE)
- okBTN.isEnabled = okBTN.isEnabled || true
- binding.selectPemBTN.text = selectedParsedKeyPairName
- }.execute()
+ val observable = PemToKeyPairObservable(selectedKeyContent)
+ create(observable).subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .retryWhen { exceptions ->
+ exceptions.flatMap { exception ->
+ Observable.create { subscriber ->
+ observable.displayPassphraseDialog(exception, {
+ subscriber.onNext(Unit)
+ }, {
+ subscriber.onError(exception)
+ })
+ }
+ }
+ }
+ .subscribe({ result ->
+ selectedParsedKeyPair = result
+ selectedParsedKeyPairName = this
+ .lastPathSegment!!
+ .substring(
+ this.lastPathSegment!!
+ .indexOf('/') + 1
+ )
+ val okBTN = (dialog as MaterialDialog)
+ .getActionButton(DialogAction.POSITIVE)
+ okBTN.isEnabled = okBTN.isEnabled || true
+ binding.selectPemBTN.text = selectedParsedKeyPairName
+ }, {})
}
}.onFailure {
log.error("Error reading PEM key", it)
@@ -694,7 +710,7 @@ class SftpConnectDialog : DialogFragment() {
): Boolean {
DataUtils.getInstance().removeServer(DataUtils.getInstance().containsServer(oldPath))
DataUtils.getInstance().addServer(arrayOf(connectionName, encryptedPath))
- Collections.sort(DataUtils.getInstance().servers, BookSorter())
+ DataUtils.getInstance().servers.sortWith(BookSorter())
(activity as MainActivity).drawer.refreshDrawer()
AppConfig.getInstance().runInBackground {
AppConfig.getInstance().utilsHandler.updateSsh(
diff --git a/app/src/main/res/layout/dialog_singleedittext.xml b/app/src/main/res/layout/dialog_singleedittext.xml
index 9f7b3eca8c..63489887e5 100644
--- a/app/src/main/res/layout/dialog_singleedittext.xml
+++ b/app/src/main/res/layout/dialog_singleedittext.xml
@@ -11,7 +11,7 @@
android:paddingLeft="@dimen/md_dialog_frame_margin"
android:paddingRight="@dimen/md_dialog_frame_margin">
- It\'s recommended to use \".txt\" as the file extension.
Select private key for authentication
Invalid key passphrase.
+ No valid decoder to decrypt PEM.
Failed to update encryption entry in database
Extracted
Compressed
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
new file mode 100644
index 0000000000..f58cfdcc01
--- /dev/null
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra ,
+ * Emmanuel Messulam, Raymond Lai and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.asynchronous.asynctasks.ssh
+
+import android.os.Build.VERSION_CODES.JELLY_BEAN
+import android.os.Build.VERSION_CODES.KITKAT
+import android.os.Build.VERSION_CODES.P
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.amaze.filemanager.shadows.ShadowMultiDex
+import com.amaze.filemanager.test.ShadowTabHandler
+import io.reactivex.Observable
+import io.reactivex.android.plugins.RxAndroidPlugins
+import io.reactivex.plugins.RxJavaPlugins
+import io.reactivex.schedulers.Schedulers
+import net.schmizz.sshj.userauth.password.PasswordFinder
+import net.schmizz.sshj.userauth.password.Resource
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+
+/**
+ * Test [PemToKeyPairObservable] for ed25519 keys.
+ */
+@RunWith(AndroidJUnit4::class)
+@Config(
+ shadows = [ShadowMultiDex::class, ShadowTabHandler::class],
+ sdk = [JELLY_BEAN, KITKAT, P]
+)
+class PemToKeyPairObservableEd25519Test {
+
+ companion object {
+ // public key for authorized_keys: ssh-ed25519
+ // AAAAC3NzaC1lZDI1NTE5AAAAIGxJHFewxU9tJn9hUq9e2C/+ELFw83NpmJ5NLFOzU7O3 test-openssh-key
+ private const val unencryptedOpenSshKey = (
+ "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+ "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" +
+ "QyNTUxOQAAACBsSRxXsMVPbSZ/YVKvXtgv/hCxcPNzaZieTSxTs1OztwAAAJhX2WUxV9ll\n" +
+ "MQAAAAtzc2gtZWQyNTUxOQAAACBsSRxXsMVPbSZ/YVKvXtgv/hCxcPNzaZieTSxTs1Oztw\n" +
+ "AAAECjSjwwMXPzbZWq/EBoA4HA9Lr7B1/Tw78K+k1zqAJwA2xJHFewxU9tJn9hUq9e2C/+\n" +
+ "ELFw83NpmJ5NLFOzU7O3AAAADmFpcndhdmVAaHN2MDEwAQIDBAUGBw==\n" +
+ "-----END OPENSSH PRIVATE KEY-----"
+ )
+
+ // Passphrase = 12345678
+ // public key for authorized_keys: ssh-ed25519
+ // AAAAC3NzaC1lZDI1NTE5AAAAIHio1/33U0XoewL1qGLmTzxyVNeYP5b0Tunv/SQrQi92 test-openssh-key
+ private const val encryptedOpenSshKey = (
+ "-----BEGIN OPENSSH PRIVATE KEY-----\n" +
+ "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCwlfECA9\n" +
+ "+EGLwKVApTmomnAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHio1/33U0XoewL1\n" +
+ "qGLmTzxyVNeYP5b0Tunv/SQrQi92AAAAoD2dysYInLaJgXIv6k/xFv7OblU9vWkCwcYnDW\n" +
+ "8Zj5+ke8QL2/r7EUBEvY1H02GenlEH1Ufct8ce7/eAWwd7aWukaSQXlKW9IBt5YrxW8+P/\n" +
+ "wrHcd/Z92eQ0E7NV6b6LnghGYlyCjpSBW+mxa0AAYPD21c95d/HvJF6zxQl/IKCCLdOrr/\n" +
+ "ilMCSIGQEdg71hA3MMZsRbUvazsnZTZXD9PLI=\n" +
+ "-----END OPENSSH PRIVATE KEY-----"
+ )
+ }
+
+ /**
+ * Pre test setup.
+ */
+ @Before
+ fun setUp() {
+ RxJavaPlugins.reset()
+ RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
+ RxAndroidPlugins.reset()
+ RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
+ }
+
+ /**
+ * Test decrypt unencrypted key pair.
+ */
+ @Test
+ fun testUnencryptedKeyToKeyPair() {
+ val task = PemToKeyPairObservable(unencryptedOpenSshKey)
+ val result = Observable.create(task).subscribeOn(Schedulers.single()).blockingFirst()
+ Assert.assertNotNull(result)
+ Assert.assertNotNull(result.public)
+ Assert.assertNotNull(result.private)
+ }
+
+ /**
+ * Test decrypt passphrase protected key pair.
+ */
+ @Test
+ fun testEncryptedKeyToKeyPair() {
+ val task = PemToKeyPairObservable(encryptedOpenSshKey)
+ val field = PemToKeyPairObservable::class.java.getDeclaredField("passwordFinder")
+ field.isAccessible = true
+ field[task] = object : PasswordFinder {
+ override fun reqPassword(resource: Resource<*>?): CharArray {
+ return "12345678".toCharArray()
+ }
+
+ override fun shouldRetry(resource: Resource<*>?): Boolean {
+ return false
+ }
+ }
+ val result = Observable.create(task).subscribeOn(Schedulers.single()).blockingFirst()
+ Assert.assertNotNull(result)
+ Assert.assertNotNull(result.public)
+ Assert.assertNotNull(result.private)
+ }
+}
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
new file mode 100644
index 0000000000..1192f5e6ad
--- /dev/null
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra ,
+ * Emmanuel Messulam, Raymond Lai and Contributors.
+ *
+ * This file is part of Amaze File Manager.
+ *
+ * Amaze File Manager is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package com.amaze.filemanager.asynchronous.asynctasks.ssh
+
+import android.os.Build.VERSION.SDK_INT
+import android.os.Build.VERSION_CODES.JELLY_BEAN
+import android.os.Build.VERSION_CODES.KITKAT
+import android.os.Build.VERSION_CODES.N
+import android.os.Build.VERSION_CODES.P
+import android.widget.EditText
+import androidx.lifecycle.Lifecycle
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.afollestad.materialdialogs.DialogAction
+import com.afollestad.materialdialogs.MaterialDialog
+import com.amaze.filemanager.R
+import com.amaze.filemanager.application.AppConfig
+import com.amaze.filemanager.shadows.ShadowMultiDex
+import com.amaze.filemanager.test.ShadowTabHandler
+import com.amaze.filemanager.test.TestUtils
+import com.amaze.filemanager.ui.activities.MainActivity
+import io.reactivex.Observable
+import io.reactivex.android.plugins.RxAndroidPlugins
+import io.reactivex.android.schedulers.AndroidSchedulers
+import io.reactivex.plugins.RxJavaPlugins
+import io.reactivex.schedulers.Schedulers
+import net.schmizz.sshj.userauth.password.PasswordFinder
+import net.schmizz.sshj.userauth.password.Resource
+import org.awaitility.Awaitility.await
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.shadows.ShadowDialog
+import org.robolectric.shadows.ShadowToast
+import java.security.KeyPair
+import java.util.concurrent.TimeUnit
+
+/**
+ * Test [PemToKeyPairObservable].
+ */
+@RunWith(AndroidJUnit4::class)
+@Config(
+ shadows = [ShadowMultiDex::class, ShadowTabHandler::class],
+ sdk = [JELLY_BEAN, KITKAT, P]
+)
+class PemToKeyPairObservableRsaTest {
+
+ companion object {
+ private const val unencryptedPuttyKey = (
+ "PuTTY-User-Key-File-2: ssh-rsa\n" +
+ "Encryption: none\n" +
+ "Comment: Key for test only\n" +
+ "Public-Lines: 6\n" +
+ "AAAAB3NzaC1yc2EAAAABJQAAAQEA6ZhWkS0Xpb1riC5r3dulviIwVFUP4uXnnapv\n" +
+ "eqVwB/7aklhu3SnOlBwRMoan+AohhHogy2cjNMqW6x/xLwH9Cbo4kMTeJTPR7ca2\n" +
+ "lxmCtGgdFRheR84if6T+i2fb1ADUmncJEkL2H1Q7RG7+opoerDpwdGjopsP7s7H4\n" +
+ "ZTvGGXcudhrOFOf/gW8hR8m9wJ05ON8qfKiRWIKDxFpectFOpJC/NGP4F53EHNAk\n" +
+ "HIhNPSW5voytGvj4VaS/xRAs2HLmj7jTor/Le/vJlndmnyJkGIwEJbVpp5HsZG7R\n" +
+ "VyhqZwCcI6ZiMXvYSH6oplffUGZz5HXkskBmreMauZC1beN31Q==\n" +
+ "Private-Lines: 14\n" +
+ "AAABAD8iQOj3bizLaSu5hO/af9KFx99w74lukaA75sczobz4xXOpMrhQfQVvXpgI\n" +
+ "t8Z/R1Q8rurdms//Zw8dY8eD/zMPvELNbHjB5bXireOmB6ZhU/fdEo/yhd1PMAoA\n" +
+ "ZO0wqCm/To9QXjH7Fu/mpa9n7J1AOhGf0C0SX7QGlikyv+s0c4ib+ipR6TEoLRHD\n" +
+ "Oa85soSZGjPvPckkfSNncemEWuo4Hp0jiJOU/Gd37YNY4Jc9FhRRuBlTUvhBdg5g\n" +
+ "FsvHZD2fZ+5J0Z2Gm9tJL6Uq6rvNWVC9sFlnornPXM9/UvXQ1Q59rIk0CQNEOVr7\n" +
+ "kdYpYeUhYrrwhCVQrjbV8CxyRi0AAACBAP1T4/nXa/+tA9f3orMBkxzQF0FnPI/+\n" +
+ "e8YXvdHtkHl3/uJEEy9FmliJecKNtNBDN6Tu3iAs5ne/btvyduMgRAxqOyXxdQCq\n" +
+ "uR2iNoHLCDqgOUbXCh+swVHPXsdbbhv/54aWjLBbbfZ6S1CwTmPV3eAVJRb78JwS\n" +
+ "uBi8Sq/5ZnhFAAAAgQDsDyjp1Bm6nonVwaGCHRsH5JFuiHbP8cyIQzdnC6PrMFvS\n" +
+ "EU1PfMKSeudywnEKyljct7Njw5FnMt1InWj1X80adK/gWTmppAPRr0u0ipT4J9ry\n" +
+ "1yCj4zeNS/cylZZoP/rOCq8Z2mbzqIO28jN5e7xDMrutNdXhqrOwmMgM0AliUQAA\n" +
+ "AIEAjMoM3mw2YXE7U1X2H/hfYymMWC+6XU4XHCI2Fk+CWGUPxvDT3uqUtoEXOXkY\n" +
+ "THdPSgA2f6EmqCOPR1VAA4jdQkK8VkN3/O3zWFdfRGqN5Kka7a7cmcyd93sq3LIU\n" +
+ "EYe4EYW7BQwe1W5ZCO+lRzjquGAB5rMhdAnzYfvkPc7sfJ8=\n" +
+ "Private-MAC: 2cd5ec740c5dd854e8a6bea3773f98697670bdc6"
+ )
+
+ // Passphrase = test
+ private const val encryptedPuttyKey = (
+ "PuTTY-User-Key-File-2: ssh-rsa\n" +
+ "Encryption: aes256-cbc\n" +
+ "Comment: Key for test only\n" +
+ "Public-Lines: 6\n" +
+ "AAAAB3NzaC1yc2EAAAABJQAAAQEAoArpfCYeHImHcTELKVzVjyS6N6viAN4lzkWC\n" +
+ "EDCyBX6x4wwgVXRYQTbd6xNCpVb/TdBTN/aVF9EXtMW2TXyvntxGblE+ilK8b3GL\n" +
+ "zRfxjrjGsjqffwlHn3JaWpCOYtEqgGOLeKkofbKBXGn1aK6tvowPsY89Dl4aK857\n" +
+ "mwisAvCIxmd8b6f2aBy4MLQ7AdmZXxPq6YD9CDXPyQkNG4RH5RGAIAw7jD+O6tUo\n" +
+ "g69voQudjy3D51VQDGNxOJVojQiQvmRUR2qkSazPJqE/hFsdN6rKh+Pbe8h6z+rU\n" +
+ "bym37+sTK5JwWKHDQ7/ezLdNR38wAPHdz2VW5+0rqKm8LAtCGQ==\n" +
+ "Private-Lines: 14\n" +
+ "erS8mfUDEme+ujzF1k0GA7d2P4umHriMFQjBZIdvht6amZXoF1L+bkJp82/vG9lv\n" +
+ "YYNYQqk5tHezkV3sJncPwr3RI/0Y1L+WtKWSfE6OzSKdYJoX3WpAuMTeMlVrxu0t\n" +
+ "RXnjbfSz7Z1czryxO4NgAK3NsYQK6h290uq5/mpqP6fIhT3/tn+mH8kihAt8+uum\n" +
+ "1RW9ucNi3TZTG4I00Z3LWHw1VaqYFeCYh3yp8Canv3mKGn5ISqsd5ehNXW1TYvJF\n" +
+ "Bd742+JlxK8dhrAr2R+g+erSA0ac8Df3wH72syVPzdewnh+21zff3NGI9GWN799X\n" +
+ "CnVtf+psDPuebGQIHewNTGsaziNkAT5rGXdNMo3Xln/B1Wr9l8tIJAtDWSNqjDLp\n" +
+ "kAcLQ+Z1wTPZehZKBi0oTsLVm4tEcPQsbnuK9h+Y/d9EWcmBEiHTGM6otesNZNA8\n" +
+ "i247YZpyrG3azeRFBVMNSzKJ+vS2rKpgvm8nbYKy+nO5uNZDEm9oARr9QPCxdzXK\n" +
+ "dmI9F8IT3tLd4qCekD4DI9MKxJLjzFmyGOHc4zMxgUyL5BT5suVDIAWL/hiekwjt\n" +
+ "T1+V+TRScq4c+pIWxfVu4kY+HpLUpSR3RAVaRFar7jaB+YEEXw+gqEVTCyZeXGFf\n" +
+ "dWU+8BkhFBF24v3Qoi9SmuWYrSQGl9O8smIHW0H2JCNF+8oqpQG8dwx37L3VyMNq\n" +
+ "ApJh0LnRhoRwKo2YaAZKInaFTYS8Gnj7DvZ+l7lxPRfCV5yl9U2How2BI9YPRDDu\n" +
+ "Gs4agAG3InJnMiuIOzaNOIFLGM9STtYNyvG411rj6tR4EEQ6cJCxIlVe5a1mEt7M\n" +
+ "GVfbB5wUvow0o0a56OBmFMZOCxV2Vpxu6PuGTD8QQ0O0YzNDWFk3Fj2RRnnLCBLF\n" +
+ "Private-MAC: f742e2954fbb0c98984db0d9855a0f15507ecc0a"
+ )
+ }
+
+ private lateinit var scenario: ActivityScenario
+
+ /**
+ * Pre test setup.
+ */
+ @Before
+ fun setUp() {
+ RxJavaPlugins.reset()
+ RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
+ RxAndroidPlugins.reset()
+ RxAndroidPlugins.setInitMainThreadSchedulerHandler { Schedulers.trampoline() }
+ if (SDK_INT >= N) TestUtils.initializeInternalStorage()
+ scenario = ActivityScenario.launch(MainActivity::class.java)
+ scenario.moveToState(Lifecycle.State.STARTED)
+ }
+
+ /**
+ * Post test clean up.
+ */
+ @After
+ fun tearDown() {
+ scenario.close()
+ }
+
+ /**
+ * Test decrypt unencrypted key pair.
+ */
+ @Test
+ fun testUnencryptedKeyToKeyPair() {
+ val task = PemToKeyPairObservable(unencryptedPuttyKey)
+ val result = Observable.create(task).subscribeOn(Schedulers.single()).blockingFirst()
+ assertNotNull(result)
+ assertNotNull(result?.public)
+ assertNotNull(result?.private)
+ }
+
+ /**
+ * Test decrypt passphrase protected key pair.
+ */
+ @Test
+ fun testEncryptedKeyToKeyPair() {
+ val task = PemToKeyPairObservable(encryptedPuttyKey)
+ val field = PemToKeyPairObservable::class.java.getDeclaredField("passwordFinder")
+ field.isAccessible = true
+ field[task] = object : PasswordFinder {
+ override fun reqPassword(resource: Resource<*>): CharArray = "test".toCharArray()
+ override fun shouldRetry(resource: Resource<*>): Boolean = false
+ }
+ val result = Observable.create(task).subscribeOn(Schedulers.single()).blockingFirst()
+ assertNotNull(result)
+ assertNotNull(result?.public)
+ assertNotNull(result?.private)
+ }
+
+ /**
+ * Test decrypt passphrase protected key pair with wrong passphrase, then a correct passphrase.
+ */
+ @Test
+ fun testEncryptedKeyToKeyPairWithWrongPassphrase() {
+ performTestInActivity {
+ var lap = 0
+ val task = PemToKeyPairObservable(encryptedPuttyKey)
+ val field = PemToKeyPairObservable::class.java.getDeclaredField("passwordFinder")
+ var result: KeyPair? = null
+ field.isAccessible = true
+ field[task] = object : PasswordFinder {
+ override fun reqPassword(resource: Resource<*>): CharArray = "foobar".toCharArray()
+ override fun shouldRetry(resource: Resource<*>): Boolean = false
+ }
+ Observable.create(task).subscribeOn(Schedulers.io())
+ .retryWhen { exceptions ->
+ exceptions.flatMap { exception ->
+ Observable.create { subscriber ->
+ task.displayPassphraseDialog(exception, {
+ subscriber.onNext(Unit)
+ }, {
+ subscriber.onError(exception)
+ })
+ }
+ }
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ if (lap == 0) {
+ fail("Should not return KeyPair")
+ } else {
+ result = it
+ }
+ }, {
+ if (lap > 0) {
+ fail("Cannot decrypt keypair")
+ }
+ })
+ await().atMost(10, TimeUnit.SECONDS).until {
+ ShadowDialog.getLatestDialog() != null
+ }
+ (ShadowDialog.getLatestDialog() as MaterialDialog).let { dialog ->
+ assertEquals(
+ AppConfig.getInstance().resources.getText(R.string.ssh_key_prompt_passphrase),
+ dialog.titleView.text
+ )
+ dialog.customView?.run {
+ lap++
+ findViewById(R.id.singleedittext_input)?.run {
+ this.setText("test")
+ } ?: fail("Text field not found")
+ } ?: fail("No view found at dialog")
+ dialog.getActionButton(DialogAction.POSITIVE).performClick()
+ }
+ await().atMost(30, TimeUnit.SECONDS).until { result != null }
+ assertNotNull(result?.public)
+ assertNotNull(result?.private)
+ }
+ }
+
+ /**
+ * Test decrypt passphrase protected key pair with wrong passphrase, then cancel.
+ */
+ @Test
+ fun testEncryptedKeyToKeyPairWithWrongPassphraseThenCancel() {
+ performTestInActivity {
+ val task = PemToKeyPairObservable(encryptedPuttyKey)
+ Observable.create(task).subscribeOn(Schedulers.io())
+ .retryWhen { exceptions ->
+ exceptions.flatMap { exception ->
+ Observable.create { subscriber ->
+ task.displayPassphraseDialog(exception, {
+ subscriber.onNext(Unit)
+ }, {
+ subscriber.onError(exception)
+ })
+ }
+ }
+ }
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({
+ fail("Should not return KeyPair")
+ }, {})
+ await().atMost(10, TimeUnit.SECONDS).until {
+ ShadowDialog.getLatestDialog() != null
+ }
+ (ShadowDialog.getLatestDialog() as MaterialDialog).let { dialog ->
+ assertEquals(
+ AppConfig.getInstance().resources.getText(R.string.ssh_key_prompt_passphrase),
+ dialog.titleView.text
+ )
+ dialog.getActionButton(DialogAction.NEGATIVE).performClick()
+ }
+ await().atMost(30, TimeUnit.SECONDS).until {
+ ShadowToast.getLatestToast() != null
+ }
+ assertEquals(
+ AppConfig.getInstance().resources.getString(
+ R.string.ssh_pem_key_parse_error,
+ AppConfig.getInstance().resources.getString(
+ R.string.ssh_key_no_decoder_decrypt
+ )
+ ),
+ ShadowToast.getTextOfLatestToast()
+ )
+ }
+ }
+
+ private fun performTestInActivity(test: () -> Unit) {
+ scenario.onActivity { activity ->
+ AppConfig.getInstance().setMainActivityContext(activity)
+ test.invoke()
+ }
+ }
+}
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest.java
deleted file mode 100644
index 3dfbaecf9b..0000000000
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra ,
- * Emmanuel Messulam, Raymond Lai and Contributors.
- *
- * This file is part of Amaze File Manager.
- *
- * Amaze File Manager is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.amaze.filemanager.asynchronous.asynctasks.ssh;
-
-import static org.junit.Assert.assertNotNull;
-
-import java.lang.reflect.Field;
-import java.security.KeyPair;
-import java.util.concurrent.ExecutionException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import net.schmizz.sshj.userauth.password.PasswordFinder;
-import net.schmizz.sshj.userauth.password.Resource;
-
-@RunWith(AndroidJUnit4.class)
-public class PemToKeyPairTaskTest {
-
- private static final String unencryptedPuttyKey =
- "PuTTY-User-Key-File-2: ssh-rsa\n"
- + "Encryption: none\n"
- + "Comment: Key for test only\n"
- + "Public-Lines: 6\n"
- + "AAAAB3NzaC1yc2EAAAABJQAAAQEA6ZhWkS0Xpb1riC5r3dulviIwVFUP4uXnnapv\n"
- + "eqVwB/7aklhu3SnOlBwRMoan+AohhHogy2cjNMqW6x/xLwH9Cbo4kMTeJTPR7ca2\n"
- + "lxmCtGgdFRheR84if6T+i2fb1ADUmncJEkL2H1Q7RG7+opoerDpwdGjopsP7s7H4\n"
- + "ZTvGGXcudhrOFOf/gW8hR8m9wJ05ON8qfKiRWIKDxFpectFOpJC/NGP4F53EHNAk\n"
- + "HIhNPSW5voytGvj4VaS/xRAs2HLmj7jTor/Le/vJlndmnyJkGIwEJbVpp5HsZG7R\n"
- + "VyhqZwCcI6ZiMXvYSH6oplffUGZz5HXkskBmreMauZC1beN31Q==\n"
- + "Private-Lines: 14\n"
- + "AAABAD8iQOj3bizLaSu5hO/af9KFx99w74lukaA75sczobz4xXOpMrhQfQVvXpgI\n"
- + "t8Z/R1Q8rurdms//Zw8dY8eD/zMPvELNbHjB5bXireOmB6ZhU/fdEo/yhd1PMAoA\n"
- + "ZO0wqCm/To9QXjH7Fu/mpa9n7J1AOhGf0C0SX7QGlikyv+s0c4ib+ipR6TEoLRHD\n"
- + "Oa85soSZGjPvPckkfSNncemEWuo4Hp0jiJOU/Gd37YNY4Jc9FhRRuBlTUvhBdg5g\n"
- + "FsvHZD2fZ+5J0Z2Gm9tJL6Uq6rvNWVC9sFlnornPXM9/UvXQ1Q59rIk0CQNEOVr7\n"
- + "kdYpYeUhYrrwhCVQrjbV8CxyRi0AAACBAP1T4/nXa/+tA9f3orMBkxzQF0FnPI/+\n"
- + "e8YXvdHtkHl3/uJEEy9FmliJecKNtNBDN6Tu3iAs5ne/btvyduMgRAxqOyXxdQCq\n"
- + "uR2iNoHLCDqgOUbXCh+swVHPXsdbbhv/54aWjLBbbfZ6S1CwTmPV3eAVJRb78JwS\n"
- + "uBi8Sq/5ZnhFAAAAgQDsDyjp1Bm6nonVwaGCHRsH5JFuiHbP8cyIQzdnC6PrMFvS\n"
- + "EU1PfMKSeudywnEKyljct7Njw5FnMt1InWj1X80adK/gWTmppAPRr0u0ipT4J9ry\n"
- + "1yCj4zeNS/cylZZoP/rOCq8Z2mbzqIO28jN5e7xDMrutNdXhqrOwmMgM0AliUQAA\n"
- + "AIEAjMoM3mw2YXE7U1X2H/hfYymMWC+6XU4XHCI2Fk+CWGUPxvDT3uqUtoEXOXkY\n"
- + "THdPSgA2f6EmqCOPR1VAA4jdQkK8VkN3/O3zWFdfRGqN5Kka7a7cmcyd93sq3LIU\n"
- + "EYe4EYW7BQwe1W5ZCO+lRzjquGAB5rMhdAnzYfvkPc7sfJ8=\n"
- + "Private-MAC: 2cd5ec740c5dd854e8a6bea3773f98697670bdc6";
-
- // Passphrase = test
- private static final String encryptedPuttyKey =
- "PuTTY-User-Key-File-2: ssh-rsa\n"
- + "Encryption: aes256-cbc\n"
- + "Comment: Key for test only\n"
- + "Public-Lines: 6\n"
- + "AAAAB3NzaC1yc2EAAAABJQAAAQEAoArpfCYeHImHcTELKVzVjyS6N6viAN4lzkWC\n"
- + "EDCyBX6x4wwgVXRYQTbd6xNCpVb/TdBTN/aVF9EXtMW2TXyvntxGblE+ilK8b3GL\n"
- + "zRfxjrjGsjqffwlHn3JaWpCOYtEqgGOLeKkofbKBXGn1aK6tvowPsY89Dl4aK857\n"
- + "mwisAvCIxmd8b6f2aBy4MLQ7AdmZXxPq6YD9CDXPyQkNG4RH5RGAIAw7jD+O6tUo\n"
- + "g69voQudjy3D51VQDGNxOJVojQiQvmRUR2qkSazPJqE/hFsdN6rKh+Pbe8h6z+rU\n"
- + "bym37+sTK5JwWKHDQ7/ezLdNR38wAPHdz2VW5+0rqKm8LAtCGQ==\n"
- + "Private-Lines: 14\n"
- + "erS8mfUDEme+ujzF1k0GA7d2P4umHriMFQjBZIdvht6amZXoF1L+bkJp82/vG9lv\n"
- + "YYNYQqk5tHezkV3sJncPwr3RI/0Y1L+WtKWSfE6OzSKdYJoX3WpAuMTeMlVrxu0t\n"
- + "RXnjbfSz7Z1czryxO4NgAK3NsYQK6h290uq5/mpqP6fIhT3/tn+mH8kihAt8+uum\n"
- + "1RW9ucNi3TZTG4I00Z3LWHw1VaqYFeCYh3yp8Canv3mKGn5ISqsd5ehNXW1TYvJF\n"
- + "Bd742+JlxK8dhrAr2R+g+erSA0ac8Df3wH72syVPzdewnh+21zff3NGI9GWN799X\n"
- + "CnVtf+psDPuebGQIHewNTGsaziNkAT5rGXdNMo3Xln/B1Wr9l8tIJAtDWSNqjDLp\n"
- + "kAcLQ+Z1wTPZehZKBi0oTsLVm4tEcPQsbnuK9h+Y/d9EWcmBEiHTGM6otesNZNA8\n"
- + "i247YZpyrG3azeRFBVMNSzKJ+vS2rKpgvm8nbYKy+nO5uNZDEm9oARr9QPCxdzXK\n"
- + "dmI9F8IT3tLd4qCekD4DI9MKxJLjzFmyGOHc4zMxgUyL5BT5suVDIAWL/hiekwjt\n"
- + "T1+V+TRScq4c+pIWxfVu4kY+HpLUpSR3RAVaRFar7jaB+YEEXw+gqEVTCyZeXGFf\n"
- + "dWU+8BkhFBF24v3Qoi9SmuWYrSQGl9O8smIHW0H2JCNF+8oqpQG8dwx37L3VyMNq\n"
- + "ApJh0LnRhoRwKo2YaAZKInaFTYS8Gnj7DvZ+l7lxPRfCV5yl9U2How2BI9YPRDDu\n"
- + "Gs4agAG3InJnMiuIOzaNOIFLGM9STtYNyvG411rj6tR4EEQ6cJCxIlVe5a1mEt7M\n"
- + "GVfbB5wUvow0o0a56OBmFMZOCxV2Vpxu6PuGTD8QQ0O0YzNDWFk3Fj2RRnnLCBLF\n"
- + "Private-MAC: f742e2954fbb0c98984db0d9855a0f15507ecc0a";
-
- @Test
- public void testUnencryptedKeyToKeyPair() throws InterruptedException, ExecutionException {
- PemToKeyPairTask task = new PemToKeyPairTask(unencryptedPuttyKey, result -> {});
- KeyPair result = task.execute().get();
- assertNotNull(result);
- assertNotNull(result.getPublic());
- assertNotNull(result.getPrivate());
- }
-
- @Test
- public void testEncryptedKeyToKeyPair()
- throws InterruptedException, NoSuchFieldException, IllegalAccessException,
- ExecutionException {
- PemToKeyPairTask task = new PemToKeyPairTask(encryptedPuttyKey, result -> {});
- Field field = PemToKeyPairTask.class.getDeclaredField("passwordFinder");
- field.setAccessible(true);
- field.set(
- task,
- new PasswordFinder() {
- @Override
- public char[] reqPassword(Resource> resource) {
- return "test".toCharArray();
- }
-
- @Override
- public boolean shouldRetry(Resource> resource) {
- return false;
- }
- });
- KeyPair result = task.execute().get();
- assertNotNull(result);
- assertNotNull(result.getPublic());
- assertNotNull(result.getPrivate());
- }
-}
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest2.java b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest2.java
deleted file mode 100644
index a2cbad5176..0000000000
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairTaskTest2.java
+++ /dev/null
@@ -1,98 +0,0 @@
-/*
- * Copyright (C) 2014-2020 Arpit Khurana , Vishal Nehra ,
- * Emmanuel Messulam, Raymond Lai and Contributors.
- *
- * This file is part of Amaze File Manager.
- *
- * Amaze File Manager is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package com.amaze.filemanager.asynchronous.asynctasks.ssh;
-
-import static org.junit.Assert.assertNotNull;
-
-import java.lang.reflect.Field;
-import java.security.KeyPair;
-import java.util.concurrent.ExecutionException;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import net.schmizz.sshj.userauth.password.PasswordFinder;
-import net.schmizz.sshj.userauth.password.Resource;
-
-@RunWith(AndroidJUnit4.class)
-public class PemToKeyPairTaskTest2 {
-
- // public key for authorized_keys: ssh-ed25519
- // AAAAC3NzaC1lZDI1NTE5AAAAIGxJHFewxU9tJn9hUq9e2C/+ELFw83NpmJ5NLFOzU7O3 test-openssh-key
- private static final String unencryptedOpenSshKey =
- "-----BEGIN OPENSSH PRIVATE KEY-----\n"
- + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n"
- + "QyNTUxOQAAACBsSRxXsMVPbSZ/YVKvXtgv/hCxcPNzaZieTSxTs1OztwAAAJhX2WUxV9ll\n"
- + "MQAAAAtzc2gtZWQyNTUxOQAAACBsSRxXsMVPbSZ/YVKvXtgv/hCxcPNzaZieTSxTs1Oztw\n"
- + "AAAECjSjwwMXPzbZWq/EBoA4HA9Lr7B1/Tw78K+k1zqAJwA2xJHFewxU9tJn9hUq9e2C/+\n"
- + "ELFw83NpmJ5NLFOzU7O3AAAADmFpcndhdmVAaHN2MDEwAQIDBAUGBw==\n"
- + "-----END OPENSSH PRIVATE KEY-----";
-
- // Passphrase = 12345678
- // public key for authorized_keys: ssh-ed25519
- // AAAAC3NzaC1lZDI1NTE5AAAAIHio1/33U0XoewL1qGLmTzxyVNeYP5b0Tunv/SQrQi92 test-openssh-key
- private static final String encryptedOpenSshKey =
- "-----BEGIN OPENSSH PRIVATE KEY-----\n"
- + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCwlfECA9\n"
- + "+EGLwKVApTmomnAAAAZAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIHio1/33U0XoewL1\n"
- + "qGLmTzxyVNeYP5b0Tunv/SQrQi92AAAAoD2dysYInLaJgXIv6k/xFv7OblU9vWkCwcYnDW\n"
- + "8Zj5+ke8QL2/r7EUBEvY1H02GenlEH1Ufct8ce7/eAWwd7aWukaSQXlKW9IBt5YrxW8+P/\n"
- + "wrHcd/Z92eQ0E7NV6b6LnghGYlyCjpSBW+mxa0AAYPD21c95d/HvJF6zxQl/IKCCLdOrr/\n"
- + "ilMCSIGQEdg71hA3MMZsRbUvazsnZTZXD9PLI=\n"
- + "-----END OPENSSH PRIVATE KEY-----";
-
- @Test
- public void testUnencryptedKeyToKeyPair() throws ExecutionException, InterruptedException {
- PemToKeyPairTask task = new PemToKeyPairTask(unencryptedOpenSshKey, result -> {});
- KeyPair result = task.execute().get();
- assertNotNull(result);
- assertNotNull(result.getPublic());
- assertNotNull(result.getPrivate());
- }
-
- @Test
- public void testEncryptedKeyToKeyPair()
- throws InterruptedException, NoSuchFieldException, IllegalAccessException,
- ExecutionException {
- PemToKeyPairTask task = new PemToKeyPairTask(encryptedOpenSshKey, result -> {});
- Field field = PemToKeyPairTask.class.getDeclaredField("passwordFinder");
- field.setAccessible(true);
- field.set(
- task,
- new PasswordFinder() {
- @Override
- public char[] reqPassword(Resource> resource) {
- return "12345678".toCharArray();
- }
-
- @Override
- public boolean shouldRetry(Resource> resource) {
- return false;
- }
- });
- KeyPair result = task.execute().get();
- assertNotNull(result);
- assertNotNull(result.getPublic());
- assertNotNull(result.getPrivate());
- }
-}
From c70ffad82580f395fa3d7a8b7dccce858188867d Mon Sep 17 00:00:00 2001
From: peerzadaburhan
Date: Thu, 2 Feb 2023 11:38:35 +0530
Subject: [PATCH 042/384] Issue #3394
---
app/src/main/AndroidManifest.xml | 4 +-
.../ui/activities/MainActivity.java | 4315 +++++++++--------
.../ui/views/appbar/SearchView.java | 1 +
3 files changed, 2180 insertions(+), 2140 deletions(-)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index bd1b7c8b06..42e6e9ae77 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -62,7 +62,9 @@
android:launchMode="singleInstance"
android:name=".ui.activities.MainActivity"
android:theme="@style/appCompatBlack"
- android:configChanges="uiMode" >
+ android:configChanges="uiMode"
+ android:windowSoftInputMode="adjustPan"
+ >
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index ff6c36469c..e1f509521e 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -162,6 +162,7 @@
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
+import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.hardware.usb.UsbManager;
import android.media.RingtoneManager;
@@ -175,12 +176,19 @@
import android.os.storage.StorageVolume;
import android.service.quicksettings.TileService;
import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
+import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowInsets;
import android.view.animation.DecelerateInterpolator;
+import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
@@ -191,6 +199,9 @@
import androidx.annotation.StringRes;
import androidx.arch.core.util.Function;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
+import androidx.core.view.ViewCompat;
+import androidx.core.view.WindowInsetsCompat;
+import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
@@ -205,7 +216,7 @@
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends PermissionsActivity
- implements SmbConnectionListener,
+ implements SmbConnectionListener,
BookmarkCallback,
SearchWorkerFragment.HelperCallbacks,
CloudConnectionCallbacks,
@@ -213,788 +224,803 @@ public class MainActivity extends PermissionsActivity
FolderChooserDialog.FolderCallback,
PermissionsActivity.OnPermissionGranted {
- private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
-
- public static final Pattern DIR_SEPARATOR = Pattern.compile("/");
- public static final String TAG_ASYNC_HELPER = "async_helper";
-
- private DataUtils dataUtils;
-
- public String path = "";
- public boolean mReturnIntent = false;
- public boolean isCompressedOpen = false;
- public boolean mRingtonePickerIntent = false;
- public int skinStatusBar;
-
- private SpeedDialView floatingActionButton;
-
- public MainActivityHelper mainActivityHelper;
-
- public int operation = -1;
- public ArrayList oparrayList;
- public ArrayList> oparrayListList;
-
- // oppathe - the path at which certain operation needs to be performed
- // oppathe1 - the new path which user wants to create/modify
- // oppathList - the paths at which certain operation needs to be performed (pairs with
- // oparrayList)
- public String oppathe, oppathe1;
- public ArrayList oppatheList;
-
- // This holds the Uris to be written at initFabToSave()
- private ArrayList urisToBeSaved;
-
- public static final String PASTEHELPER_BUNDLE = "pasteHelper";
-
- private static final String KEY_DRAWER_SELECTED = "selectitem";
- private static final String KEY_OPERATION_PATH = "oppathe";
- private static final String KEY_OPERATED_ON_PATH = "oppathe1";
- private static final String KEY_OPERATIONS_PATH_LIST = "oparraylist";
- private static final String KEY_OPERATION = "operation";
- private static final String KEY_SELECTED_LIST_ITEM = "select_list_item";
-
- private AppBar appbar;
- private Drawer drawer;
- // private HistoryManager history, grid;
- private MainActivity mainActivity = this;
- private String pathInCompressedArchive;
- private boolean openProcesses = false;
- private MaterialDialog materialDialog;
- private boolean backPressedToExitOnce = false;
- private WeakReference toast = new WeakReference<>(null);
- private Intent intent;
- private View indicator_layout;
+ private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
+
+ public static final Pattern DIR_SEPARATOR = Pattern.compile("/");
+ public static final String TAG_ASYNC_HELPER = "async_helper";
+
+ private DataUtils dataUtils;
+
+ public String path = "";
+ public boolean mReturnIntent = false;
+ public boolean isCompressedOpen = false;
+ public boolean mRingtonePickerIntent = false;
+ public int skinStatusBar;
+
+ private SpeedDialView floatingActionButton;
+
+ public MainActivityHelper mainActivityHelper;
+
+ public int operation = -1;
+ public ArrayList oparrayList;
+ public ArrayList> oparrayListList;
+
+ // oppathe - the path at which certain operation needs to be performed
+ // oppathe1 - the new path which user wants to create/modify
+ // oppathList - the paths at which certain operation needs to be performed (pairs with
+ // oparrayList)
+ public String oppathe, oppathe1;
+ public ArrayList oppatheList;
+
+ // This holds the Uris to be written at initFabToSave()
+ private ArrayList urisToBeSaved;
+
+ public static final String PASTEHELPER_BUNDLE = "pasteHelper";
+
+ private static final String KEY_DRAWER_SELECTED = "selectitem";
+ private static final String KEY_OPERATION_PATH = "oppathe";
+ private static final String KEY_OPERATED_ON_PATH = "oppathe1";
+ private static final String KEY_OPERATIONS_PATH_LIST = "oparraylist";
+ private static final String KEY_OPERATION = "operation";
+ private static final String KEY_SELECTED_LIST_ITEM = "select_list_item";
+
+ private AppBar appbar;
+ private Drawer drawer;
+ // private HistoryManager history, grid;
+ private MainActivity mainActivity = this;
+ private String pathInCompressedArchive;
+ private boolean openProcesses = false;
+ private MaterialDialog materialDialog;
+ private boolean backPressedToExitOnce = false;
+ private WeakReference toast = new WeakReference<>(null);
+ private Intent intent;
+ private View indicator_layout;
+
+ private AppBarLayout appBarLayout;
+
+ private SpeedDialOverlayLayout fabBgView;
+ private UtilsHandler utilsHandler;
+ private CloudHandler cloudHandler;
+ private CloudLoaderAsyncTask cloudLoaderAsyncTask;
+ /**
+ * This is for a hack.
+ *
+ * @see MainActivity#onLoadFinished(Loader, Cursor)
+ */
+ private Cursor cloudCursorData = null;
- private AppBarLayout appBarLayout;
+ public static final int REQUEST_CODE_SAF = 223;
- private SpeedDialOverlayLayout fabBgView;
- private UtilsHandler utilsHandler;
- private CloudHandler cloudHandler;
- private CloudLoaderAsyncTask cloudLoaderAsyncTask;
- /**
- * This is for a hack.
- *
- * @see MainActivity#onLoadFinished(Loader, Cursor)
- */
- private Cursor cloudCursorData = null;
+ public static final String KEY_INTENT_PROCESS_VIEWER = "openprocesses";
+ public static final String TAG_INTENT_FILTER_FAILED_OPS = "failedOps";
+ public static final String TAG_INTENT_FILTER_GENERAL = "general_communications";
+ public static final String ARGS_KEY_LOADER = "loader_cloud_args_service";
- public static final int REQUEST_CODE_SAF = 223;
+ /**
+ * Broadcast which will be fired after every file operation, will denote list loading Registered
+ * by {@link MainFragment}
+ */
+ public static final String KEY_INTENT_LOAD_LIST = "loadlist";
- public static final String KEY_INTENT_PROCESS_VIEWER = "openprocesses";
- public static final String TAG_INTENT_FILTER_FAILED_OPS = "failedOps";
- public static final String TAG_INTENT_FILTER_GENERAL = "general_communications";
- public static final String ARGS_KEY_LOADER = "loader_cloud_args_service";
+ /**
+ * Extras carried by the list loading intent Contains path of parent directory in which operation
+ * took place, so that we can run media scanner on it
+ */
+ public static final String KEY_INTENT_LOAD_LIST_FILE = "loadlist_file";
- /**
- * Broadcast which will be fired after every file operation, will denote list loading Registered
- * by {@link MainFragment}
- */
- public static final String KEY_INTENT_LOAD_LIST = "loadlist";
+ /**
+ * Mime type in intent that apps need to pass when trying to open file manager from a specific
+ * directory Should be clubbed with {@link Intent#ACTION_VIEW} and send in path to open in intent
+ * data field
+ */
+ public static final String ARGS_INTENT_ACTION_VIEW_MIME_FOLDER = "resource/folder";
- /**
- * Extras carried by the list loading intent Contains path of parent directory in which operation
- * took place, so that we can run media scanner on it
- */
- public static final String KEY_INTENT_LOAD_LIST_FILE = "loadlist_file";
+ public static final String ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL = "application/*";
- /**
- * Mime type in intent that apps need to pass when trying to open file manager from a specific
- * directory Should be clubbed with {@link Intent#ACTION_VIEW} and send in path to open in intent
- * data field
- */
- public static final String ARGS_INTENT_ACTION_VIEW_MIME_FOLDER = "resource/folder";
+ public static final String CLOUD_AUTHENTICATOR_GDRIVE = "android.intent.category.BROWSABLE";
+ public static final String CLOUD_AUTHENTICATOR_REDIRECT_URI = "com.amaze.filemanager:/auth";
- public static final String ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL = "application/*";
+ // the current visible tab, either 0 or 1
+ public static int currentTab;
+ private boolean listItemSelected = false;
- public static final String CLOUD_AUTHENTICATOR_GDRIVE = "android.intent.category.BROWSABLE";
- public static final String CLOUD_AUTHENTICATOR_REDIRECT_URI = "com.amaze.filemanager:/auth";
+ private String scrollToFileName = null;
- // the current visible tab, either 0 or 1
- public static int currentTab;
- private boolean listItemSelected = false;
+ public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463;
+ public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472;
- private String scrollToFileName = null;
+ private PasteHelper pasteHelper;
+ private MainActivityActionMode mainActivityActionMode;
- public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463;
- public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472;
+ private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0";
+ private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage";
+ private static final String INTENT_ACTION_OPEN_QUICK_ACCESS =
+ "com.amaze.filemanager.openQuickAccess";
+ private static final String INTENT_ACTION_OPEN_RECENT = "com.amaze.filemanager.openRecent";
+ private static final String INTENT_ACTION_OPEN_FTP_SERVER = "com.amaze.filemanager.openFTPServer";
+ private static final String INTENT_ACTION_OPEN_APP_MANAGER =
+ "com.amaze.filemanager.openAppManager";
- private PasteHelper pasteHelper;
- private MainActivityActionMode mainActivityActionMode;
-
- private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0";
- private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage";
- private static final String INTENT_ACTION_OPEN_QUICK_ACCESS =
- "com.amaze.filemanager.openQuickAccess";
- private static final String INTENT_ACTION_OPEN_RECENT = "com.amaze.filemanager.openRecent";
- private static final String INTENT_ACTION_OPEN_FTP_SERVER = "com.amaze.filemanager.openFTPServer";
- private static final String INTENT_ACTION_OPEN_APP_MANAGER =
- "com.amaze.filemanager.openAppManager";
-
- /** Called when the activity is first created. */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main_toolbar);
-
- intent = getIntent();
-
- dataUtils = DataUtils.getInstance();
- if (savedInstanceState != null) {
- listItemSelected = savedInstanceState.getBoolean(KEY_SELECTED_LIST_ITEM, false);
- }
-
- initialisePreferences();
- initializeInteractiveShell();
-
- dataUtils.registerOnDataChangedListener(new SaveOnDataUtilsChange(drawer));
-
- AppConfig.getInstance().setMainActivityContext(this);
-
- initialiseViews();
- utilsHandler = AppConfig.getInstance().getUtilsHandler();
- cloudHandler = new CloudHandler(this, AppConfig.getInstance().getExplorerDatabase());
-
- initialiseFab(); // TODO: 7/12/2017 not init when actionIntent != null
- mainActivityHelper = new MainActivityHelper(this);
- mainActivityActionMode = new MainActivityActionMode(new WeakReference<>(MainActivity.this));
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_toolbar);
- if (CloudSheetFragment.isCloudProviderAvailable(this)) {
- LoaderManager.getInstance(this).initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
- }
+ intent = getIntent();
- path = intent.getStringExtra("path");
- openProcesses = intent.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false);
- if (intent.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- intent.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, this);
- }
- }
+ dataUtils = DataUtils.getInstance();
+ if (savedInstanceState != null) {
+ listItemSelected = savedInstanceState.getBoolean(KEY_SELECTED_LIST_ITEM, false);
+ }
- checkForExternalIntent(intent);
-
- drawer.setDrawerIndicatorEnabled();
-
- if (!getBoolean(PREFERENCE_BOOKMARKS_ADDED)) {
- utilsHandler.addCommonBookmarks();
- getPrefs().edit().putBoolean(PREFERENCE_BOOKMARKS_ADDED, true).commit();
- }
-
- checkForExternalPermission();
-
- Completable.fromRunnable(
- () -> {
- dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
- dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
- dataUtils.setGridfiles(utilsHandler.getGridViewList());
- dataUtils.setListfiles(utilsHandler.getListViewList());
- dataUtils.setBooks(utilsHandler.getBookmarksList());
- ArrayList servers = new ArrayList<>();
- servers.addAll(utilsHandler.getSmbList());
- servers.addAll(utilsHandler.getSftpList());
- dataUtils.setServers(servers);
-
- ExtensionsKt.updateAUAlias(
- this,
- !PackageUtils.Companion.appInstalledOrNot(
- AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager())
- && !getBoolean(
- PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS));
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- new CompletableObserver() {
- @Override
- public void onSubscribe(@NonNull Disposable d) {}
-
- @Override
- public void onComplete() {
- drawer.refreshDrawer();
- invalidateFragmentAndBundle(savedInstanceState, false);
- }
-
- @Override
- public void onError(@NonNull Throwable e) {
- LOG.error("Error setting up DataUtils", e);
- drawer.refreshDrawer();
- invalidateFragmentAndBundle(savedInstanceState, false);
- }
- });
- initStatusBarResources(findViewById(R.id.drawer_layout));
- }
-
- public void invalidateFragmentAndBundle(Bundle savedInstanceState, boolean isCloudRefresh) {
- if (savedInstanceState == null) {
- if (openProcesses) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(
- R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
- // transaction.addToBackStack(null);
- openProcesses = false;
- // title.setText(utils.getString(con, R.string.process_viewer));
- // Commit the transaction
- transaction.commit();
- supportInvalidateOptionsMenu();
- } else if (intent.getAction() != null
- && (intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)
- || INTENT_ACTION_OPEN_FTP_SERVER.equals(intent.getAction()))) {
- // tile preferences, open ftp fragment
-
- FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
- transaction2.replace(R.id.content_frame, new FtpServerFragment());
- appBarLayout
- .animate()
- .translationY(0)
- .setInterpolator(new DecelerateInterpolator(2))
- .start();
-
- drawer.deselectEverything();
- transaction2.commit();
- } else if (intent.getAction() != null
- && INTENT_ACTION_OPEN_APP_MANAGER.equals(intent.getAction())) {
- FragmentTransaction transaction3 = getSupportFragmentManager().beginTransaction();
- transaction3.replace(R.id.content_frame, new AppsListFragment());
- appBarLayout
- .animate()
- .translationY(0)
- .setInterpolator(new DecelerateInterpolator(2))
- .start();
-
- drawer.deselectEverything();
- transaction3.commit();
- } else {
- if (path != null && path.length() > 0) {
- HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
- file.generateMode(MainActivity.this);
- if (file.isCloudDriveFile() && dataUtils.getAccounts().size() == 0) {
- // not ready to serve cloud files
- goToMain(null);
- } else if (file.isDirectory(MainActivity.this) && !isCloudRefresh) {
- goToMain(path);
- } else {
- if (!isCloudRefresh) {
- goToMain(null);
- }
- if (file.isSmb() || file.isSftp()) {
- String authorisedPath =
- SshClientUtils.formatPlainServerPathToAuthorised(dataUtils.getServers(), path);
- file.setPath(authorisedPath);
- LOG.info(
- "Opening smb file from deeplink, modify plain path to authorised path {}",
- authorisedPath);
+ initialisePreferences();
+ initializeInteractiveShell();
+
+
+ dataUtils.registerOnDataChangedListener(new SaveOnDataUtilsChange(drawer));
+
+ AppConfig.getInstance().setMainActivityContext(this);
+
+ initialiseViews();
+ utilsHandler = AppConfig.getInstance().getUtilsHandler();
+ cloudHandler = new CloudHandler(this, AppConfig.getInstance().getExplorerDatabase());
+
+ initialiseFab(); // TODO: 7/12/2017 not init when actionIntent != null
+ mainActivityHelper = new MainActivityHelper(this);
+ mainActivityActionMode = new MainActivityActionMode(new WeakReference<>(MainActivity.this));
+
+ if (CloudSheetFragment.isCloudProviderAvailable(this)) {
+
+ LoaderManager.getInstance(this).initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
+ }
+
+ path = intent.getStringExtra("path");
+ openProcesses = intent.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false);
+
+ if (intent.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ intent.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, this);
}
- file.openFile(this, true);
- }
- } else if (!isCloudRefresh) {
- goToMain(null);
}
- }
- } else {
- pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
- oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
- oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
- oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
- operation = savedInstanceState.getInt(KEY_OPERATION);
- int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
- getDrawer().selectCorrectDrawerItem(selectedStorage);
- }
- }
-
- @Override
- public void onPermissionGranted() {
- drawer.refreshDrawer();
- TabFragment tabFragment = getTabFragment();
- boolean b = getBoolean(PREFERENCE_NEED_TO_SET_HOME);
- // reset home and current paths according to new storages
- if (b) {
- TabHandler tabHandler = TabHandler.getInstance();
- tabHandler
- .clear()
- .subscribe(
- () -> {
- if (tabFragment != null) {
- tabFragment.refactorDrawerStorages(false);
- Fragment main = tabFragment.getFragmentAtIndex(0);
- if (main != null) ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1));
- Fragment main1 = tabFragment.getFragmentAtIndex(1);
- if (main1 != null) ((MainFragment) main1).updateTabWithDb(tabHandler.findTab(2));
+
+ checkForExternalIntent(intent);
+
+ drawer.setDrawerIndicatorEnabled();
+
+
+ if (!getBoolean(PREFERENCE_BOOKMARKS_ADDED)) {
+ utilsHandler.addCommonBookmarks();
+ getPrefs().edit().putBoolean(PREFERENCE_BOOKMARKS_ADDED, true).commit();
+ }
+
+ checkForExternalPermission();
+
+ Completable.fromRunnable(
+ () -> {
+ dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
+ dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
+ dataUtils.setGridfiles(utilsHandler.getGridViewList());
+ dataUtils.setListfiles(utilsHandler.getListViewList());
+ dataUtils.setBooks(utilsHandler.getBookmarksList());
+ ArrayList servers = new ArrayList<>();
+ servers.addAll(utilsHandler.getSmbList());
+ servers.addAll(utilsHandler.getSftpList());
+ dataUtils.setServers(servers);
+
+ ExtensionsKt.updateAUAlias(
+ this,
+ !PackageUtils.Companion.appInstalledOrNot(
+ AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager())
+ && !getBoolean(
+ PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS));
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ new CompletableObserver() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {
+ }
+
+ @Override
+ public void onComplete() {
+ drawer.refreshDrawer();
+ invalidateFragmentAndBundle(savedInstanceState, false);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ LOG.error("Error setting up DataUtils", e);
+ drawer.refreshDrawer();
+ invalidateFragmentAndBundle(savedInstanceState, false);
+ }
+ });
+ initStatusBarResources(findViewById(R.id.drawer_layout));
+ }
+
+ public void invalidateFragmentAndBundle(Bundle savedInstanceState, boolean isCloudRefresh) {
+ if (savedInstanceState == null) {
+ if (openProcesses) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(
+ R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
+ // transaction.addToBackStack(null);
+ openProcesses = false;
+ // title.setText(utils.getString(con, R.string.process_viewer));
+ // Commit the transaction
+ transaction.commit();
+ supportInvalidateOptionsMenu();
+ } else if (intent.getAction() != null
+ && (intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)
+ || INTENT_ACTION_OPEN_FTP_SERVER.equals(intent.getAction()))) {
+ // tile preferences, open ftp fragment
+
+ FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
+ transaction2.replace(R.id.content_frame, new FtpServerFragment());
+ appBarLayout
+ .animate()
+ .translationY(0)
+ .setInterpolator(new DecelerateInterpolator(2))
+ .start();
+
+ drawer.deselectEverything();
+ transaction2.commit();
+ } else if (intent.getAction() != null
+ && INTENT_ACTION_OPEN_APP_MANAGER.equals(intent.getAction())) {
+ FragmentTransaction transaction3 = getSupportFragmentManager().beginTransaction();
+ transaction3.replace(R.id.content_frame, new AppsListFragment());
+ appBarLayout
+ .animate()
+ .translationY(0)
+ .setInterpolator(new DecelerateInterpolator(2))
+ .start();
+
+ drawer.deselectEverything();
+ transaction3.commit();
+ } else {
+ if (path != null && path.length() > 0) {
+ HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
+ file.generateMode(MainActivity.this);
+ if (file.isCloudDriveFile() && dataUtils.getAccounts().size() == 0) {
+ // not ready to serve cloud files
+ goToMain(null);
+ } else if (file.isDirectory(MainActivity.this) && !isCloudRefresh) {
+ goToMain(path);
+ } else {
+ if (!isCloudRefresh) {
+ goToMain(null);
+ }
+ if (file.isSmb() || file.isSftp()) {
+ String authorisedPath =
+ SshClientUtils.formatPlainServerPathToAuthorised(dataUtils.getServers(), path);
+ file.setPath(authorisedPath);
+ LOG.info(
+ "Opening smb file from deeplink, modify plain path to authorised path {}",
+ authorisedPath);
+ }
+ file.openFile(this, true);
+ }
+ } else if (!isCloudRefresh) {
+ goToMain(null);
}
- getPrefs().edit().putBoolean(PREFERENCE_NEED_TO_SET_HOME, false).commit();
- });
- } else {
- // just refresh list
- if (tabFragment != null) {
- Fragment main = tabFragment.getFragmentAtIndex(0);
- if (main != null) ((MainFragment) main).updateList(false);
- Fragment main1 = tabFragment.getFragmentAtIndex(1);
- if (main1 != null) ((MainFragment) main1).updateList(false);
- }
+ }
+ } else {
+ pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
+ oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
+ oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
+ oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
+ operation = savedInstanceState.getInt(KEY_OPERATION);
+ int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
+ getDrawer().selectCorrectDrawerItem(selectedStorage);
+ }
}
- }
- private void checkForExternalPermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (!checkStoragePermission()) {
- requestStoragePermission(this, true);
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- requestAllFilesAccess(this);
- }
+ @Override
+ public void onPermissionGranted() {
+ drawer.refreshDrawer();
+ TabFragment tabFragment = getTabFragment();
+ boolean b = getBoolean(PREFERENCE_NEED_TO_SET_HOME);
+ // reset home and current paths according to new storages
+ if (b) {
+ TabHandler tabHandler = TabHandler.getInstance();
+ tabHandler
+ .clear()
+ .subscribe(
+ () -> {
+ if (tabFragment != null) {
+ tabFragment.refactorDrawerStorages(false);
+ Fragment main = tabFragment.getFragmentAtIndex(0);
+ if (main != null)
+ ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1));
+ Fragment main1 = tabFragment.getFragmentAtIndex(1);
+ if (main1 != null)
+ ((MainFragment) main1).updateTabWithDb(tabHandler.findTab(2));
+ }
+ getPrefs().edit().putBoolean(PREFERENCE_NEED_TO_SET_HOME, false).commit();
+ });
+ } else {
+ // just refresh list
+ if (tabFragment != null) {
+ Fragment main = tabFragment.getFragmentAtIndex(0);
+ if (main != null) ((MainFragment) main).updateList(false);
+ Fragment main1 = tabFragment.getFragmentAtIndex(1);
+ if (main1 != null) ((MainFragment) main1).updateList(false);
+ }
+ }
}
- }
- /** Checks for the action to take when Amaze receives an intent from external source */
- private void checkForExternalIntent(Intent intent) {
- final String actionIntent = intent.getAction();
- if (actionIntent == null) {
- return;
+ private void checkForExternalPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!checkStoragePermission()) {
+ requestStoragePermission(this, true);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ requestAllFilesAccess(this);
+ }
+ }
}
- final String type = intent.getType();
+ /**
+ * Checks for the action to take when Amaze receives an intent from external source
+ */
+ private void checkForExternalIntent(Intent intent) {
+ final String actionIntent = intent.getAction();
+ if (actionIntent == null) {
+ return;
+ }
- if (actionIntent.equals(Intent.ACTION_GET_CONTENT)) {
- // file picker intent
- mReturnIntent = true;
- Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
+ final String type = intent.getType();
+
+ if (actionIntent.equals(Intent.ACTION_GET_CONTENT)) {
+ // file picker intent
+ mReturnIntent = true;
+ Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
+
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when picking file
+ Utils.disableScreenRotation(this);
+ } else if (actionIntent.equals(RingtoneManager.ACTION_RINGTONE_PICKER)) {
+ // ringtone picker intent
+ mReturnIntent = true;
+ mRingtonePickerIntent = true;
+ Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
+
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when picking file
+ Utils.disableScreenRotation(this);
+ } else if (actionIntent.equals(Intent.ACTION_VIEW)) {
+ // zip viewer intent
+ Uri uri = intent.getData();
+
+ if (type != null
+ && (type.equals(ARGS_INTENT_ACTION_VIEW_MIME_FOLDER)
+ || type.equals(ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL))) {
+ // support for syncting or intents from external apps that
+ // need to start file manager from a specific path
+
+ if (uri != null) {
+
+ path = Utils.sanitizeInput(FileUtils.fromContentUri(uri).getAbsolutePath());
+ scrollToFileName = intent.getStringExtra("com.amaze.fileutilities.AFM_LOCATE_FILE_NAME");
+ } else {
+ // no data field, open home for the tab in later processing
+ path = null;
+ }
+ } else if (FileUtils.isCompressedFile(Utils.sanitizeInput(uri.toString()))) {
+ // we don't have folder resource mime type set, supposed to be zip/rar
+ isCompressedOpen = true;
+ pathInCompressedArchive = Utils.sanitizeInput(uri.toString());
+ openCompressed(pathInCompressedArchive);
+ } else if (uri.getPath().startsWith("/open_file")) {
+ /**
+ * Deeplink to open files directly through amaze using following format:
+ * http://teamamaze.xyz/open_file?path=path-to-file
+ */
+ path = Utils.sanitizeInput(uri.getQueryParameter("path"));
+ } else {
+ LOG.warn(getString(R.string.error_cannot_find_way_open));
+ }
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when picking file
- Utils.disableScreenRotation(this);
- } else if (actionIntent.equals(RingtoneManager.ACTION_RINGTONE_PICKER)) {
- // ringtone picker intent
- mReturnIntent = true;
- mRingtonePickerIntent = true;
- Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
+ } else if (actionIntent.equals(Intent.ACTION_SEND)) {
+ if ("text/plain".equals(type)) {
+ initFabToSave(null);
+ } else {
+ // save a single file to filesystem
+ Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ ArrayList uris = new ArrayList<>();
+ uris.add(uri);
+ initFabToSave(uris);
+ }
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when saving a file
+ Utils.disableScreenRotation(this);
+
+ } else if (actionIntent.equals(Intent.ACTION_SEND_MULTIPLE) && type != null) {
+ // save multiple files to filesystem
+
+ ArrayList arrayList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ initFabToSave(arrayList);
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when picking file
- Utils.disableScreenRotation(this);
- } else if (actionIntent.equals(Intent.ACTION_VIEW)) {
- // zip viewer intent
- Uri uri = intent.getData();
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when saving a file
+ Utils.disableScreenRotation(this);
+ }
+ }
+
+ /**
+ * Initializes the floating action button to act as to save data from an external intent
+ */
+ private void initFabToSave(final ArrayList uris) {
+ Utils.showThemedSnackbar(
+ this,
+ getString(R.string.select_save_location),
+ BaseTransientBottomBar.LENGTH_INDEFINITE,
+ R.string.save,
+ () -> saveExternalIntent(uris));
+ }
- if (type != null
- && (type.equals(ARGS_INTENT_ACTION_VIEW_MIME_FOLDER)
- || type.equals(ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL))) {
- // support for syncting or intents from external apps that
- // need to start file manager from a specific path
+ private void saveExternalIntent(final ArrayList uris) {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (uris != null && uris.size() > 0) {
+ if (SDK_INT >= LOLLIPOP) {
+ File folder = new File(mainFragment.getCurrentPath());
+ int result = mainActivityHelper.checkFolder(folder, MainActivity.this);
+ if (result == WRITABLE_OR_ON_SDCARD) {
+ FileUtil.writeUriToStorage(
+ MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
+ finish();
+ } else {
+ // Trigger SAF intent, keep uri until finish
+ operation = SAVE_FILE;
+ urisToBeSaved = uris;
+ mainActivityHelper.checkFolder(folder, MainActivity.this);
+ }
+ } else {
+ FileUtil.writeUriToStorage(
+ MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
+ }
+ } else {
+ saveExternalIntentExtras();
+ }
+ Toast.makeText(
+ MainActivity.this,
+ getResources().getString(R.string.saving)
+ + " to "
+ + mainFragment.getCurrentPath(),
+ Toast.LENGTH_LONG)
+ .show();
+ finish();
+ return null;
+ });
+ }
- if (uri != null) {
+ private void saveExternalIntentExtras() {
+ executeWithMainFragment(
+ mainFragment -> {
+ Bundle extras = intent.getExtras();
+ StringBuilder data = new StringBuilder();
+ if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_SUBJECT))) {
+ data.append(extras.getString(Intent.EXTRA_SUBJECT));
+ }
+ if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_TEXT))) {
+ data.append(AppConstants.NEW_LINE).append(extras.getString(Intent.EXTRA_TEXT));
+ }
+ String fileName = Long.toString(System.currentTimeMillis());
+ AppConfig.getInstance()
+ .runInBackground(
+ () ->
+ MakeFileOperation.mktextfile(
+ data.toString(), mainFragment.getCurrentPath(), fileName));
+ return null;
+ });
+ }
- path = Utils.sanitizeInput(FileUtils.fromContentUri(uri).getAbsolutePath());
- scrollToFileName = intent.getStringExtra("com.amaze.fileutilities.AFM_LOCATE_FILE_NAME");
+ public void clearFabActionItems() {
+ floatingActionButton.removeActionItemById(R.id.menu_new_folder);
+ floatingActionButton.removeActionItemById(R.id.menu_new_file);
+ floatingActionButton.removeActionItemById(R.id.menu_new_cloud);
+ }
+
+ /**
+ * Initializes an interactive shell, which will stay throughout the app lifecycle.
+ */
+ private void initializeInteractiveShell() {
+ if (isRootExplorer()) {
+ // Enable mount-master flag when invoking su command, to force su run in the global mount
+ // namespace. See https://github.com/topjohnwu/libsu/issues/75
+ Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
+ Shell.getShell();
+ }
+ }
+
+ /**
+ * @return paths to all available volumes in the system (include emulated)
+ */
+ public synchronized ArrayList getStorageDirectories() {
+ ArrayList volumes;
+ if (SDK_INT >= N) {
+ volumes = getStorageDirectoriesNew();
} else {
- // no data field, open home for the tab in later processing
- path = null;
- }
- } else if (FileUtils.isCompressedFile(Utils.sanitizeInput(uri.toString()))) {
- // we don't have folder resource mime type set, supposed to be zip/rar
- isCompressedOpen = true;
- pathInCompressedArchive = Utils.sanitizeInput(uri.toString());
- openCompressed(pathInCompressedArchive);
- } else if (uri.getPath().startsWith("/open_file")) {
- /**
- * Deeplink to open files directly through amaze using following format:
- * http://teamamaze.xyz/open_file?path=path-to-file
- */
- path = Utils.sanitizeInput(uri.getQueryParameter("path"));
- } else {
- LOG.warn(getString(R.string.error_cannot_find_way_open));
- }
+ volumes = getStorageDirectoriesLegacy();
+ }
+ if (isRootExplorer()) {
+ volumes.add(
+ new StorageDirectoryParcelable(
+ "/",
+ getResources().getString(R.string.root_directory),
+ R.drawable.ic_drawer_root_white));
+ }
+ return volumes;
+ }
- } else if (actionIntent.equals(Intent.ACTION_SEND)) {
- if ("text/plain".equals(type)) {
- initFabToSave(null);
- } else {
- // save a single file to filesystem
- Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- ArrayList uris = new ArrayList<>();
- uris.add(uri);
- initFabToSave(uris);
- }
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when saving a file
- Utils.disableScreenRotation(this);
-
- } else if (actionIntent.equals(Intent.ACTION_SEND_MULTIPLE) && type != null) {
- // save multiple files to filesystem
-
- ArrayList arrayList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
- initFabToSave(arrayList);
-
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when saving a file
- Utils.disableScreenRotation(this);
- }
- }
-
- /** Initializes the floating action button to act as to save data from an external intent */
- private void initFabToSave(final ArrayList uris) {
- Utils.showThemedSnackbar(
- this,
- getString(R.string.select_save_location),
- BaseTransientBottomBar.LENGTH_INDEFINITE,
- R.string.save,
- () -> saveExternalIntent(uris));
- }
-
- private void saveExternalIntent(final ArrayList uris) {
- executeWithMainFragment(
- mainFragment -> {
- if (uris != null && uris.size() > 0) {
- if (SDK_INT >= LOLLIPOP) {
- File folder = new File(mainFragment.getCurrentPath());
- int result = mainActivityHelper.checkFolder(folder, MainActivity.this);
- if (result == WRITABLE_OR_ON_SDCARD) {
- FileUtil.writeUriToStorage(
- MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
- finish();
- } else {
- // Trigger SAF intent, keep uri until finish
- operation = SAVE_FILE;
- urisToBeSaved = uris;
- mainActivityHelper.checkFolder(folder, MainActivity.this);
- }
+ /**
+ * @return All available storage volumes (including internal storage, SD-Cards and USB devices)
+ */
+ @TargetApi(N)
+ public synchronized ArrayList getStorageDirectoriesNew() {
+ // Final set of paths
+ ArrayList volumes = new ArrayList<>();
+ StorageManager sm = getSystemService(StorageManager.class);
+ for (StorageVolume volume : sm.getStorageVolumes()) {
+ if (!volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)
+ && !volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+ continue;
+ }
+ File path = Utils.getVolumeDirectory(volume);
+ String name = volume.getDescription(this);
+ if (INTERNAL_SHARED_STORAGE.equalsIgnoreCase(name)) {
+ name = getString(R.string.storage_internal);
+ }
+ int icon;
+ if (!volume.isRemovable()) {
+ icon = R.drawable.ic_phone_android_white_24dp;
} else {
- FileUtil.writeUriToStorage(
- MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
+ // HACK: There is no reliable way to distinguish USB and SD external storage
+ // However it is often enough to check for "USB" String
+ if (name.toUpperCase().contains("USB") || path.getPath().toUpperCase().contains("USB")) {
+ icon = R.drawable.ic_usb_white_24dp;
+ } else {
+ icon = R.drawable.ic_sd_storage_white_24dp;
+ }
}
- } else {
- saveExternalIntentExtras();
- }
- Toast.makeText(
- MainActivity.this,
- getResources().getString(R.string.saving)
- + " to "
- + mainFragment.getCurrentPath(),
- Toast.LENGTH_LONG)
- .show();
- finish();
- return null;
- });
- }
-
- private void saveExternalIntentExtras() {
- executeWithMainFragment(
- mainFragment -> {
- Bundle extras = intent.getExtras();
- StringBuilder data = new StringBuilder();
- if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_SUBJECT))) {
- data.append(extras.getString(Intent.EXTRA_SUBJECT));
- }
- if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_TEXT))) {
- data.append(AppConstants.NEW_LINE).append(extras.getString(Intent.EXTRA_TEXT));
- }
- String fileName = Long.toString(System.currentTimeMillis());
- AppConfig.getInstance()
- .runInBackground(
- () ->
- MakeFileOperation.mktextfile(
- data.toString(), mainFragment.getCurrentPath(), fileName));
- return null;
- });
- }
-
- public void clearFabActionItems() {
- floatingActionButton.removeActionItemById(R.id.menu_new_folder);
- floatingActionButton.removeActionItemById(R.id.menu_new_file);
- floatingActionButton.removeActionItemById(R.id.menu_new_cloud);
- }
-
- /** Initializes an interactive shell, which will stay throughout the app lifecycle. */
- private void initializeInteractiveShell() {
- if (isRootExplorer()) {
- // Enable mount-master flag when invoking su command, to force su run in the global mount
- // namespace. See https://github.com/topjohnwu/libsu/issues/75
- Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
- Shell.getShell();
- }
- }
-
- /**
- * @return paths to all available volumes in the system (include emulated)
- */
- public synchronized ArrayList getStorageDirectories() {
- ArrayList volumes;
- if (SDK_INT >= N) {
- volumes = getStorageDirectoriesNew();
- } else {
- volumes = getStorageDirectoriesLegacy();
- }
- if (isRootExplorer()) {
- volumes.add(
- new StorageDirectoryParcelable(
- "/",
- getResources().getString(R.string.root_directory),
- R.drawable.ic_drawer_root_white));
- }
- return volumes;
- }
-
- /**
- * @return All available storage volumes (including internal storage, SD-Cards and USB devices)
- */
- @TargetApi(N)
- public synchronized ArrayList getStorageDirectoriesNew() {
- // Final set of paths
- ArrayList volumes = new ArrayList<>();
- StorageManager sm = getSystemService(StorageManager.class);
- for (StorageVolume volume : sm.getStorageVolumes()) {
- if (!volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)
- && !volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED_READ_ONLY)) {
- continue;
- }
- File path = Utils.getVolumeDirectory(volume);
- String name = volume.getDescription(this);
- if (INTERNAL_SHARED_STORAGE.equalsIgnoreCase(name)) {
- name = getString(R.string.storage_internal);
- }
- int icon;
- if (!volume.isRemovable()) {
- icon = R.drawable.ic_phone_android_white_24dp;
- } else {
- // HACK: There is no reliable way to distinguish USB and SD external storage
- // However it is often enough to check for "USB" String
- if (name.toUpperCase().contains("USB") || path.getPath().toUpperCase().contains("USB")) {
- icon = R.drawable.ic_usb_white_24dp;
- } else {
- icon = R.drawable.ic_sd_storage_white_24dp;
+ volumes.add(new StorageDirectoryParcelable(path.getPath(), name, icon));
}
- }
- volumes.add(new StorageDirectoryParcelable(path.getPath(), name, icon));
- }
- return volumes;
- }
-
- /**
- * Returns all available SD-Cards in the system (include emulated)
- *
- * Warning: Hack! Based on Android source code of version 4.3 (API 18) Because there was no
- * standard way to get it before android N
- *
- * @return All available SD-Cards in the system (include emulated)
- */
- public synchronized ArrayList getStorageDirectoriesLegacy() {
- List rv = new ArrayList<>();
-
- // Primary physical SD-CARD (not emulated)
- final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
- // All Secondary SD-CARDs (all exclude primary) separated by ":"
- final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
- // Primary emulated SD-CARD
- final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
- if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
- // Device has physical external storage; use plain paths.
- if (TextUtils.isEmpty(rawExternalStorage)) {
- // EXTERNAL_STORAGE undefined; falling back to default.
- // Check for actual existence of the directory before adding to list
- if (new File(DEFAULT_FALLBACK_STORAGE_PATH).exists()) {
- rv.add(DEFAULT_FALLBACK_STORAGE_PATH);
+ return volumes;
+ }
+
+ /**
+ * Returns all available SD-Cards in the system (include emulated)
+ *
+ * Warning: Hack! Based on Android source code of version 4.3 (API 18) Because there was no
+ * standard way to get it before android N
+ *
+ * @return All available SD-Cards in the system (include emulated)
+ */
+ public synchronized ArrayList getStorageDirectoriesLegacy() {
+ List rv = new ArrayList<>();
+
+ // Primary physical SD-CARD (not emulated)
+ final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
+ // All Secondary SD-CARDs (all exclude primary) separated by ":"
+ final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
+ // Primary emulated SD-CARD
+ final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
+ if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ // Device has physical external storage; use plain paths.
+ if (TextUtils.isEmpty(rawExternalStorage)) {
+ // EXTERNAL_STORAGE undefined; falling back to default.
+ // Check for actual existence of the directory before adding to list
+ if (new File(DEFAULT_FALLBACK_STORAGE_PATH).exists()) {
+ rv.add(DEFAULT_FALLBACK_STORAGE_PATH);
+ } else {
+ // We know nothing else, use Environment's fallback
+ rv.add(Environment.getExternalStorageDirectory().getAbsolutePath());
+ }
+ } else {
+ rv.add(rawExternalStorage);
+ }
} else {
- // We know nothing else, use Environment's fallback
- rv.add(Environment.getExternalStorageDirectory().getAbsolutePath());
+ // Device has emulated storage; external storage paths should have
+ // userId burned into them.
+ final String rawUserId;
+ if (SDK_INT < JELLY_BEAN_MR1) {
+ rawUserId = "";
+ } else {
+ final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
+ final String[] folders = DIR_SEPARATOR.split(path);
+ final String lastFolder = folders[folders.length - 1];
+ boolean isDigit = false;
+ try {
+ Integer.valueOf(lastFolder);
+ isDigit = true;
+ } catch (NumberFormatException ignored) {
+ }
+ rawUserId = isDigit ? lastFolder : "";
+ }
+ // /storage/emulated/0[1,2,...]
+ if (TextUtils.isEmpty(rawUserId)) {
+ rv.add(rawEmulatedStorageTarget);
+ } else {
+ rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
+ }
}
- } else {
- rv.add(rawExternalStorage);
- }
- } else {
- // Device has emulated storage; external storage paths should have
- // userId burned into them.
- final String rawUserId;
- if (SDK_INT < JELLY_BEAN_MR1) {
- rawUserId = "";
- } else {
- final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
- final String[] folders = DIR_SEPARATOR.split(path);
- final String lastFolder = folders[folders.length - 1];
- boolean isDigit = false;
- try {
- Integer.valueOf(lastFolder);
- isDigit = true;
- } catch (NumberFormatException ignored) {
+ // Add all secondary storages
+ if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
+ // All Secondary SD-CARDs splited into array
+ final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
+ Collections.addAll(rv, rawSecondaryStorages);
}
- rawUserId = isDigit ? lastFolder : "";
- }
- // /storage/emulated/0[1,2,...]
- if (TextUtils.isEmpty(rawUserId)) {
- rv.add(rawEmulatedStorageTarget);
- } else {
- rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
- }
- }
- // Add all secondary storages
- if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
- // All Secondary SD-CARDs splited into array
- final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
- Collections.addAll(rv, rawSecondaryStorages);
- }
- if (SDK_INT >= M && checkStoragePermission()) rv.clear();
- if (SDK_INT >= KITKAT) {
- String strings[] = ExternalSdCardOperation.getExtSdCardPathsForActivity(this);
- for (String s : strings) {
- File f = new File(s);
- if (!rv.contains(s) && FileUtils.canListFiles(f)) rv.add(s);
- }
- }
- File usb = getUsbDrive();
- if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath());
+ if (SDK_INT >= M && checkStoragePermission()) rv.clear();
+ if (SDK_INT >= KITKAT) {
+ String strings[] = ExternalSdCardOperation.getExtSdCardPathsForActivity(this);
+ for (String s : strings) {
+ File f = new File(s);
+ if (!rv.contains(s) && FileUtils.canListFiles(f)) rv.add(s);
+ }
+ }
+ File usb = getUsbDrive();
+ if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath());
- if (SDK_INT >= KITKAT) {
- if (SingletonUsbOtg.getInstance().isDeviceConnected()) {
- rv.add(OTGUtil.PREFIX_OTG + "/");
- }
- }
+ if (SDK_INT >= KITKAT) {
+ if (SingletonUsbOtg.getInstance().isDeviceConnected()) {
+ rv.add(OTGUtil.PREFIX_OTG + "/");
+ }
+ }
- // Assign a label and icon to each directory
- ArrayList volumes = new ArrayList<>();
- for (String file : rv) {
- File f = new File(file);
- @DrawableRes int icon;
-
- if ("/storage/emulated/legacy".equals(file)
- || "/storage/emulated/0".equals(file)
- || "/mnt/sdcard".equals(file)) {
- icon = R.drawable.ic_phone_android_white_24dp;
- } else if ("/storage/sdcard1".equals(file)) {
- icon = R.drawable.ic_sd_storage_white_24dp;
- } else if ("/".equals(file)) {
- icon = R.drawable.ic_drawer_root_white;
- } else {
- icon = R.drawable.ic_sd_storage_white_24dp;
- }
+ // Assign a label and icon to each directory
+ ArrayList volumes = new ArrayList<>();
+ for (String file : rv) {
+ File f = new File(file);
+ @DrawableRes int icon;
+
+ if ("/storage/emulated/legacy".equals(file)
+ || "/storage/emulated/0".equals(file)
+ || "/mnt/sdcard".equals(file)) {
+ icon = R.drawable.ic_phone_android_white_24dp;
+ } else if ("/storage/sdcard1".equals(file)) {
+ icon = R.drawable.ic_sd_storage_white_24dp;
+ } else if ("/".equals(file)) {
+ icon = R.drawable.ic_drawer_root_white;
+ } else {
+ icon = R.drawable.ic_sd_storage_white_24dp;
+ }
- @StorageNaming.DeviceDescription
- int deviceDescription = StorageNaming.getDeviceDescriptionLegacy(f);
- String name = StorageNamingHelper.getNameForDeviceDescription(this, f, deviceDescription);
+ @StorageNaming.DeviceDescription
+ int deviceDescription = StorageNaming.getDeviceDescriptionLegacy(f);
+ String name = StorageNamingHelper.getNameForDeviceDescription(this, f, deviceDescription);
- volumes.add(new StorageDirectoryParcelable(file, name, icon));
+ volumes.add(new StorageDirectoryParcelable(file, name, icon));
+ }
+
+ return volumes;
}
- return volumes;
- }
+ @Override
+ public void onBackPressed() {
+ if (!drawer.isLocked() && drawer.isOpen()) {
+ drawer.close();
+ return;
+ }
- @Override
- public void onBackPressed() {
- if (!drawer.isLocked() && drawer.isOpen()) {
- drawer.close();
- return;
+ Fragment fragment = getFragmentAtFrame();
+ if (getAppbar().getSearchView().isShown()) {
+ // hide search view if visible, with an animation
+ getAppbar().getSearchView().hideSearchView();
+ } else if (fragment instanceof TabFragment) {
+ if (floatingActionButton.isOpen()) {
+ floatingActionButton.close(true);
+ } else {
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.goBack();
+ return null;
+ });
+ }
+ } else if (fragment instanceof CompressedExplorerFragment) {
+ CompressedExplorerFragment compressedExplorerFragment =
+ (CompressedExplorerFragment) getFragmentAtFrame();
+ if (compressedExplorerFragment.mActionMode == null) {
+ if (compressedExplorerFragment.canGoBack()) {
+ compressedExplorerFragment.goBack();
+ } else if (isCompressedOpen) {
+ isCompressedOpen = false;
+ finish();
+ } else {
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.setCustomAnimations(R.anim.slide_out_bottom, R.anim.slide_out_bottom);
+ fragmentTransaction.remove(compressedExplorerFragment);
+ fragmentTransaction.commit();
+ supportInvalidateOptionsMenu();
+ floatingActionButton.show();
+ }
+ } else {
+ compressedExplorerFragment.mActionMode.finish();
+ }
+ } else if (fragment instanceof FtpServerFragment) {
+ // returning back from FTP server
+ if (path != null && path.length() > 0) {
+ HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
+ file.generateMode(this);
+ if (file.isDirectory(this)) goToMain(path);
+ else {
+ goToMain(null);
+ FileUtils.openFile(new File(path), this, getPrefs());
+ }
+ } else {
+ goToMain(null);
+ }
+ } else {
+ goToMain(null);
+ }
}
- Fragment fragment = getFragmentAtFrame();
- if (getAppbar().getSearchView().isShown()) {
- // hide search view if visible, with an animation
- getAppbar().getSearchView().hideSearchView();
- } else if (fragment instanceof TabFragment) {
- if (floatingActionButton.isOpen()) {
- floatingActionButton.close(true);
- } else {
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.goBack();
- return null;
- });
- }
- } else if (fragment instanceof CompressedExplorerFragment) {
- CompressedExplorerFragment compressedExplorerFragment =
- (CompressedExplorerFragment) getFragmentAtFrame();
- if (compressedExplorerFragment.mActionMode == null) {
- if (compressedExplorerFragment.canGoBack()) {
- compressedExplorerFragment.goBack();
- } else if (isCompressedOpen) {
- isCompressedOpen = false;
- finish();
- } else {
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
- fragmentTransaction.setCustomAnimations(R.anim.slide_out_bottom, R.anim.slide_out_bottom);
- fragmentTransaction.remove(compressedExplorerFragment);
- fragmentTransaction.commit();
- supportInvalidateOptionsMenu();
- floatingActionButton.show();
- }
- } else {
- compressedExplorerFragment.mActionMode.finish();
- }
- } else if (fragment instanceof FtpServerFragment) {
- // returning back from FTP server
- if (path != null && path.length() > 0) {
- HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
- file.generateMode(this);
- if (file.isDirectory(this)) goToMain(path);
- else {
- goToMain(null);
- FileUtils.openFile(new File(path), this, getPrefs());
- }
- } else {
- goToMain(null);
- }
- } else {
- goToMain(null);
+ public void invalidatePasteSnackbar(boolean showSnackbar) {
+ if (pasteHelper != null) {
+ pasteHelper.invalidateSnackbar(this, showSnackbar);
+ }
}
- }
- public void invalidatePasteSnackbar(boolean showSnackbar) {
- if (pasteHelper != null) {
- pasteHelper.invalidateSnackbar(this, showSnackbar);
+ public void exit() {
+ if (backPressedToExitOnce) {
+ NetCopyClientConnectionPool.INSTANCE.shutdown();
+ finish();
+ if (isRootExplorer()) {
+ closeInteractiveShell();
+ }
+ } else {
+ this.backPressedToExitOnce = true;
+ final Toast toast = Toast.makeText(this, getString(R.string.press_again), Toast.LENGTH_SHORT);
+ this.toast = new WeakReference<>(toast);
+ toast.show();
+ new Handler()
+ .postDelayed(
+ () -> {
+ backPressedToExitOnce = false;
+ },
+ 2000);
+ }
}
- }
- public void exit() {
- if (backPressedToExitOnce) {
- NetCopyClientConnectionPool.INSTANCE.shutdown();
- finish();
- if (isRootExplorer()) {
- closeInteractiveShell();
- }
- } else {
- this.backPressedToExitOnce = true;
- final Toast toast = Toast.makeText(this, getString(R.string.press_again), Toast.LENGTH_SHORT);
- this.toast = new WeakReference<>(toast);
- toast.show();
- new Handler()
- .postDelayed(
- () -> {
- backPressedToExitOnce = false;
- },
- 2000);
- }
- }
-
- public void goToMain(String path) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- // title.setText(R.string.app_name);
- TabFragment tabFragment = new TabFragment();
- if (intent != null && intent.getAction() != null) {
- if (INTENT_ACTION_OPEN_QUICK_ACCESS.equals(intent.getAction())) {
- path = "5";
- } else if (INTENT_ACTION_OPEN_RECENT.equals(intent.getAction())) {
- path = "6";
- }
+ public void goToMain(String path) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ // title.setText(R.string.app_name);
+ TabFragment tabFragment = new TabFragment();
+ if (intent != null && intent.getAction() != null) {
+ if (INTENT_ACTION_OPEN_QUICK_ACCESS.equals(intent.getAction())) {
+ path = "5";
+ } else if (INTENT_ACTION_OPEN_RECENT.equals(intent.getAction())) {
+ path = "6";
+ }
+ }
+ if (path != null && path.length() > 0) {
+ Bundle b = new Bundle();
+ b.putString("path", path);
+ tabFragment.setArguments(b);
+ }
+ transaction.replace(R.id.content_frame, tabFragment);
+ // Commit the transaction
+ transaction.addToBackStack("tabt" + 1);
+ transaction.commitAllowingStateLoss();
+ appbar.setTitle(null);
+ floatingActionButton.show();
+ if (isCompressedOpen && pathInCompressedArchive != null) {
+ openCompressed(pathInCompressedArchive);
+ pathInCompressedArchive = null;
+ }
}
- if (path != null && path.length() > 0) {
- Bundle b = new Bundle();
- b.putString("path", path);
- tabFragment.setArguments(b);
- }
- transaction.replace(R.id.content_frame, tabFragment);
- // Commit the transaction
- transaction.addToBackStack("tabt" + 1);
- transaction.commitAllowingStateLoss();
- appbar.setTitle(null);
- floatingActionButton.show();
- if (isCompressedOpen && pathInCompressedArchive != null) {
- openCompressed(pathInCompressedArchive);
- pathInCompressedArchive = null;
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater menuInflater = getMenuInflater();
- menuInflater.inflate(R.menu.activity_extra, menu);
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.activity_extra, menu);
/*
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
@@ -1018,236 +1044,236 @@ public boolean onMenuItemActionCollapse(MenuItem item) {
}
});
*/
- return super.onCreateOptionsMenu(menu);
- }
+ return super.onCreateOptionsMenu(menu);
+ }
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- MenuItem s = menu.findItem(R.id.view);
- MenuItem search = menu.findItem(R.id.search);
- Fragment fragment = getFragmentAtFrame();
- if (fragment instanceof TabFragment) {
- appbar.setTitle(R.string.appbar_name);
- if (getBoolean(PREFERENCE_VIEW)) {
- s.setTitle(getResources().getString(R.string.gridview));
- } else {
- s.setTitle(getResources().getString(R.string.listview));
- }
- try {
- executeWithMainFragment(
- mainFragment -> {
- if (mainFragment.getMainFragmentViewModel().isList()) {
- s.setTitle(R.string.gridview);
- } else {
- s.setTitle(R.string.listview);
- }
- appbar
- .getBottomBar()
- .updatePath(
- mainFragment.getCurrentPath(),
- mainFragment.getMainFragmentViewModel().getResults(),
- MainActivityHelper.SEARCH_TEXT,
- mainFragment.getMainFragmentViewModel().getOpenMode(),
- mainFragment.getMainFragmentViewModel().getFolderCount(),
- mainFragment.getMainFragmentViewModel().getFileCount(),
- mainFragment);
- return null;
- });
- } catch (Exception e) {
- LOG.warn("failure while preparing options menu", e);
- }
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem s = menu.findItem(R.id.view);
+ MenuItem search = menu.findItem(R.id.search);
+ Fragment fragment = getFragmentAtFrame();
+ if (fragment instanceof TabFragment) {
+ appbar.setTitle(R.string.appbar_name);
+ if (getBoolean(PREFERENCE_VIEW)) {
+ s.setTitle(getResources().getString(R.string.gridview));
+ } else {
+ s.setTitle(getResources().getString(R.string.listview));
+ }
+ try {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (mainFragment.getMainFragmentViewModel().isList()) {
+ s.setTitle(R.string.gridview);
+ } else {
+ s.setTitle(R.string.listview);
+ }
+ appbar
+ .getBottomBar()
+ .updatePath(
+ mainFragment.getCurrentPath(),
+ mainFragment.getMainFragmentViewModel().getResults(),
+ MainActivityHelper.SEARCH_TEXT,
+ mainFragment.getMainFragmentViewModel().getOpenMode(),
+ mainFragment.getMainFragmentViewModel().getFolderCount(),
+ mainFragment.getMainFragmentViewModel().getFileCount(),
+ mainFragment);
+ return null;
+ });
+ } catch (Exception e) {
+ LOG.warn("failure while preparing options menu", e);
+ }
- appbar.getBottomBar().setClickListener();
-
- search.setVisible(true);
- if (indicator_layout != null) indicator_layout.setVisibility(View.VISIBLE);
- menu.findItem(R.id.search).setVisible(true);
- menu.findItem(R.id.home).setVisible(true);
- menu.findItem(R.id.history).setVisible(true);
- menu.findItem(R.id.sethome).setVisible(true);
- menu.findItem(R.id.sort).setVisible(true);
- menu.findItem(R.id.hiddenitems).setVisible(true);
- menu.findItem(R.id.view).setVisible(true);
- menu.findItem(R.id.extract).setVisible(false);
- invalidatePasteSnackbar(true);
- findViewById(R.id.buttonbarframe).setVisibility(View.VISIBLE);
- } else if (fragment instanceof AppsListFragment
- || fragment instanceof ProcessViewerFragment
- || fragment instanceof FtpServerFragment) {
- appBarLayout.setExpanded(true);
- menu.findItem(R.id.sethome).setVisible(false);
- if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
- findViewById(R.id.buttonbarframe).setVisibility(View.GONE);
- menu.findItem(R.id.search).setVisible(false);
- menu.findItem(R.id.home).setVisible(false);
- menu.findItem(R.id.history).setVisible(false);
- menu.findItem(R.id.extract).setVisible(false);
- if (fragment instanceof ProcessViewerFragment) {
- menu.findItem(R.id.sort).setVisible(false);
- } else if (fragment instanceof FtpServerFragment) {
- menu.findItem(R.id.sort).setVisible(false);
- } else {
- menu.findItem(R.id.dsort).setVisible(false);
- menu.findItem(R.id.sortby).setVisible(false);
- }
- menu.findItem(R.id.hiddenitems).setVisible(false);
- menu.findItem(R.id.view).setVisible(false);
- invalidatePasteSnackbar(false);
- } else if (fragment instanceof CompressedExplorerFragment) {
- appbar.setTitle(R.string.appbar_name);
- menu.findItem(R.id.sethome).setVisible(false);
- if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
- getAppbar().getBottomBar().resetClickListener();
- menu.findItem(R.id.search).setVisible(false);
- menu.findItem(R.id.home).setVisible(false);
- menu.findItem(R.id.history).setVisible(false);
- menu.findItem(R.id.sort).setVisible(false);
- menu.findItem(R.id.hiddenitems).setVisible(false);
- menu.findItem(R.id.view).setVisible(false);
- menu.findItem(R.id.extract).setVisible(true);
- invalidatePasteSnackbar(false);
- }
- return super.onPrepareOptionsMenu(menu);
- }
-
- // called when the user exits the action mode
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // The action bar home/up action should open or close the drawer.
- // ActionBarDrawerToggle will take care of this.
- if (drawer.onOptionsItemSelected(item)) return true;
- // Same thing goes to other Fragments loaded.
- // If they have handled the options, we don't need to.
- if (getFragmentAtFrame().onOptionsItemSelected(item)) return true;
-
- // Handle action buttons
- executeWithMainFragment(
- mainFragment -> {
- switch (item.getItemId()) {
- case R.id.home:
- mainFragment.home();
- break;
- case R.id.history:
- HistoryDialog.showHistoryDialog(this, mainFragment);
- break;
- case R.id.sethome:
- if (mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.FILE
- && mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.ROOT) {
- Toast.makeText(mainActivity, R.string.not_allowed, Toast.LENGTH_SHORT).show();
- break;
- }
- final MaterialDialog dialog =
- GeneralDialogCreation.showBasicDialog(
- mainActivity,
- R.string.question_set_path_as_home,
- R.string.set_as_home,
- R.string.yes,
- R.string.no);
- dialog
- .getActionButton(DialogAction.POSITIVE)
- .setOnClickListener(
- (v) -> {
- mainFragment
- .getMainFragmentViewModel()
- .setHome(mainFragment.getCurrentPath());
- updatePaths(mainFragment.getMainFragmentViewModel().getNo());
- dialog.dismiss();
- });
- dialog.show();
- break;
- case R.id.exit:
- finish();
- break;
- case R.id.sortby:
- GeneralDialogCreation.showSortDialog(mainFragment, getAppTheme(), getPrefs());
- break;
- case R.id.dsort:
- String[] sort = getResources().getStringArray(R.array.directorysortmode);
- MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity);
- builder.theme(getAppTheme().getMaterialDialogTheme(this));
- builder.title(R.string.directorysort);
- int current =
- Integer.parseInt(
- getPrefs()
- .getString(PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "0"));
-
- builder
- .items(sort)
- .itemsCallbackSingleChoice(
- current,
- (dialog1, view, which, text) -> {
- getPrefs()
- .edit()
- .putString(
- PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "" + which)
- .commit();
- mainFragment
- .getMainFragmentViewModel()
- .initSortModes(
- SortHandler.getSortType(
- this, mainFragment.getMainFragmentViewModel().getCurrentPath()),
- getPrefs());
- mainFragment.updateList(false);
- dialog1.dismiss();
- return true;
- });
- builder.build().show();
- break;
- case R.id.hiddenitems:
- HiddenFilesDialog.showHiddenDialog(this, mainFragment);
- break;
- case R.id.view:
- int pathLayout =
- dataUtils.getListOrGridForPath(mainFragment.getCurrentPath(), DataUtils.LIST);
- if (mainFragment.getMainFragmentViewModel().isList()) {
- if (pathLayout == DataUtils.LIST) {
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(
- UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
- });
- }
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
-
- dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.GRID);
- } else {
- if (pathLayout == DataUtils.GRID) {
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(
- UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
- });
- }
+ appbar.getBottomBar().setClickListener();
+
+ search.setVisible(true);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.VISIBLE);
+ menu.findItem(R.id.search).setVisible(true);
+ menu.findItem(R.id.home).setVisible(true);
+ menu.findItem(R.id.history).setVisible(true);
+ menu.findItem(R.id.sethome).setVisible(true);
+ menu.findItem(R.id.sort).setVisible(true);
+ menu.findItem(R.id.hiddenitems).setVisible(true);
+ menu.findItem(R.id.view).setVisible(true);
+ menu.findItem(R.id.extract).setVisible(false);
+ invalidatePasteSnackbar(true);
+ findViewById(R.id.buttonbarframe).setVisibility(View.VISIBLE);
+ } else if (fragment instanceof AppsListFragment
+ || fragment instanceof ProcessViewerFragment
+ || fragment instanceof FtpServerFragment) {
+ appBarLayout.setExpanded(true);
+ menu.findItem(R.id.sethome).setVisible(false);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
+ findViewById(R.id.buttonbarframe).setVisibility(View.GONE);
+ menu.findItem(R.id.search).setVisible(false);
+ menu.findItem(R.id.home).setVisible(false);
+ menu.findItem(R.id.history).setVisible(false);
+ menu.findItem(R.id.extract).setVisible(false);
+ if (fragment instanceof ProcessViewerFragment) {
+ menu.findItem(R.id.sort).setVisible(false);
+ } else if (fragment instanceof FtpServerFragment) {
+ menu.findItem(R.id.sort).setVisible(false);
+ } else {
+ menu.findItem(R.id.dsort).setVisible(false);
+ menu.findItem(R.id.sortby).setVisible(false);
+ }
+ menu.findItem(R.id.hiddenitems).setVisible(false);
+ menu.findItem(R.id.view).setVisible(false);
+ invalidatePasteSnackbar(false);
+ } else if (fragment instanceof CompressedExplorerFragment) {
+ appbar.setTitle(R.string.appbar_name);
+ menu.findItem(R.id.sethome).setVisible(false);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
+ getAppbar().getBottomBar().resetClickListener();
+ menu.findItem(R.id.search).setVisible(false);
+ menu.findItem(R.id.home).setVisible(false);
+ menu.findItem(R.id.history).setVisible(false);
+ menu.findItem(R.id.sort).setVisible(false);
+ menu.findItem(R.id.hiddenitems).setVisible(false);
+ menu.findItem(R.id.view).setVisible(false);
+ menu.findItem(R.id.extract).setVisible(true);
+ invalidatePasteSnackbar(false);
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
-
- dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.LIST);
- }
- mainFragment.switchView();
- break;
- case R.id.extract:
- Fragment fragment1 = getFragmentAtFrame();
- if (fragment1 instanceof CompressedExplorerFragment) {
- mainActivityHelper.extractFile(
- ((CompressedExplorerFragment) fragment1).compressedFile);
- }
- break;
- case R.id.search:
- getAppbar().getSearchView().revealSearchView();
- break;
- }
- return null;
- },
- false);
-
- return super.onOptionsItemSelected(item);
- }
+ // called when the user exits the action mode
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // The action bar home/up action should open or close the drawer.
+ // ActionBarDrawerToggle will take care of this.
+ if (drawer.onOptionsItemSelected(item)) return true;
+ // Same thing goes to other Fragments loaded.
+ // If they have handled the options, we don't need to.
+ if (getFragmentAtFrame().onOptionsItemSelected(item)) return true;
+
+ // Handle action buttons
+ executeWithMainFragment(
+ mainFragment -> {
+ switch (item.getItemId()) {
+ case R.id.home:
+ mainFragment.home();
+ break;
+ case R.id.history:
+ HistoryDialog.showHistoryDialog(this, mainFragment);
+ break;
+ case R.id.sethome:
+ if (mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.FILE
+ && mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.ROOT) {
+ Toast.makeText(mainActivity, R.string.not_allowed, Toast.LENGTH_SHORT).show();
+ break;
+ }
+ final MaterialDialog dialog =
+ GeneralDialogCreation.showBasicDialog(
+ mainActivity,
+ R.string.question_set_path_as_home,
+ R.string.set_as_home,
+ R.string.yes,
+ R.string.no);
+ dialog
+ .getActionButton(DialogAction.POSITIVE)
+ .setOnClickListener(
+ (v) -> {
+ mainFragment
+ .getMainFragmentViewModel()
+ .setHome(mainFragment.getCurrentPath());
+ updatePaths(mainFragment.getMainFragmentViewModel().getNo());
+ dialog.dismiss();
+ });
+ dialog.show();
+ break;
+ case R.id.exit:
+ finish();
+ break;
+ case R.id.sortby:
+ GeneralDialogCreation.showSortDialog(mainFragment, getAppTheme(), getPrefs());
+ break;
+ case R.id.dsort:
+ String[] sort = getResources().getStringArray(R.array.directorysortmode);
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity);
+ builder.theme(getAppTheme().getMaterialDialogTheme(this));
+ builder.title(R.string.directorysort);
+ int current =
+ Integer.parseInt(
+ getPrefs()
+ .getString(PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "0"));
+
+ builder
+ .items(sort)
+ .itemsCallbackSingleChoice(
+ current,
+ (dialog1, view, which, text) -> {
+ getPrefs()
+ .edit()
+ .putString(
+ PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "" + which)
+ .commit();
+ mainFragment
+ .getMainFragmentViewModel()
+ .initSortModes(
+ SortHandler.getSortType(
+ this, mainFragment.getMainFragmentViewModel().getCurrentPath()),
+ getPrefs());
+ mainFragment.updateList(false);
+ dialog1.dismiss();
+ return true;
+ });
+ builder.build().show();
+ break;
+ case R.id.hiddenitems:
+ HiddenFilesDialog.showHiddenDialog(this, mainFragment);
+ break;
+ case R.id.view:
+ int pathLayout =
+ dataUtils.getListOrGridForPath(mainFragment.getCurrentPath(), DataUtils.LIST);
+ if (mainFragment.getMainFragmentViewModel().isList()) {
+ if (pathLayout == DataUtils.LIST) {
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(
+ UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
+ });
+ }
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
+
+ dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.GRID);
+ } else {
+ if (pathLayout == DataUtils.GRID) {
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(
+ UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
+ });
+ }
+
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
+
+ dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.LIST);
+ }
+ mainFragment.switchView();
+ break;
+ case R.id.extract:
+ Fragment fragment1 = getFragmentAtFrame();
+ if (fragment1 instanceof CompressedExplorerFragment) {
+ mainActivityHelper.extractFile(
+ ((CompressedExplorerFragment) fragment1).compressedFile);
+ }
+ break;
+ case R.id.search:
+ getAppbar().getSearchView().revealSearchView();
+ break;
+ }
+ return null;
+ },
+ false);
+
+ return super.onOptionsItemSelected(item);
+ }
/*@Override
public void onRestoreInstanceState(Bundle savedInstanceState){
@@ -1261,1254 +1287,1265 @@ public void onRestoreInstanceState(Bundle savedInstanceState){
selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
}*/
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- // Sync the toggle state after onRestoreInstanceState has occurred.
- drawer.syncState();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- // Pass any configuration change to the drawer toggls
- drawer.onConfigurationChanged(newConfig);
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(KEY_DRAWER_SELECTED, getDrawer().getDrawerSelectedItem());
- outState.putBoolean(KEY_SELECTED_LIST_ITEM, listItemSelected);
- if (pasteHelper != null) {
- outState.putParcelable(PASTEHELPER_BUNDLE, pasteHelper);
- }
-
- if (oppathe != null) {
- outState.putString(KEY_OPERATION_PATH, oppathe);
- outState.putString(KEY_OPERATED_ON_PATH, oppathe1);
- outState.putParcelableArrayList(KEY_OPERATIONS_PATH_LIST, (oparrayList));
- outState.putInt(KEY_OPERATION, operation);
- }
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- unregisterReceiver(mainActivityHelper.mNotificationReceiver);
- unregisterReceiver(receiver2);
-
- if (SDK_INT >= KITKAT) {
- unregisterReceiver(mOtgReceiver);
- }
-
- final Toast toast = this.toast.get();
- if (toast != null) {
- toast.cancel();
- }
- this.toast = new WeakReference<>(null);
- }
-
- @Override
- public void onResume() {
- super.onResume();
- if (materialDialog != null && !materialDialog.isShowing()) {
- materialDialog.show();
- materialDialog = null;
- }
-
- drawer.refreshDrawer();
- drawer.refactorDrawerLockMode();
-
- IntentFilter newFilter = new IntentFilter();
- newFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- newFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
- newFilter.addDataScheme(ContentResolver.SCHEME_FILE);
- registerReceiver(mainActivityHelper.mNotificationReceiver, newFilter);
- registerReceiver(receiver2, new IntentFilter(TAG_INTENT_FILTER_GENERAL));
-
- if (SDK_INT >= Build.VERSION_CODES.KITKAT) {
- updateUsbInformation();
- }
- }
-
- /** Updates everything related to USB devices MUST ALWAYS be called after onResume() */
- @RequiresApi(api = Build.VERSION_CODES.KITKAT)
- private void updateUsbInformation() {
- boolean isInformationUpdated = false;
- List connectedDevices = OTGUtil.getMassStorageDevicesConnected(this);
-
- if (!connectedDevices.isEmpty()) {
- if (SingletonUsbOtg.getInstance().getUsbOtgRoot() != null
- && OTGUtil.isUsbUriAccessible(this)) {
- for (UsbOtgRepresentation device : connectedDevices) {
- if (SingletonUsbOtg.getInstance().checkIfRootIsFromDevice(device)) {
- isInformationUpdated = true;
- break;
- }
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ drawer.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ drawer.onConfigurationChanged(newConfig);
+ // Pass any configuration change to the drawer toggls
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_DRAWER_SELECTED, getDrawer().getDrawerSelectedItem());
+ outState.putBoolean(KEY_SELECTED_LIST_ITEM, listItemSelected);
+ if (pasteHelper != null) {
+ outState.putParcelable(PASTEHELPER_BUNDLE, pasteHelper);
}
- if (!isInformationUpdated) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ if (oppathe != null) {
+ outState.putString(KEY_OPERATION_PATH, oppathe);
+ outState.putString(KEY_OPERATED_ON_PATH, oppathe1);
+ outState.putParcelableArrayList(KEY_OPERATIONS_PATH_LIST, (oparrayList));
+ outState.putInt(KEY_OPERATION, operation);
}
- }
+ }
- if (!isInformationUpdated) {
- SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
- isInformationUpdated = true;
- }
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mainActivityHelper.mNotificationReceiver);
+ unregisterReceiver(receiver2);
+
+ if (SDK_INT >= KITKAT) {
+ unregisterReceiver(mOtgReceiver);
+ }
+
+ final Toast toast = this.toast.get();
+ if (toast != null) {
+ toast.cancel();
+ }
+ this.toast = new WeakReference<>(null);
}
- if (!isInformationUpdated) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- drawer.refreshDrawer();
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (materialDialog != null && !materialDialog.isShowing()) {
+ materialDialog.show();
+ materialDialog = null;
+ }
+
+ drawer.refreshDrawer();
+ drawer.refactorDrawerLockMode();
+
+ IntentFilter newFilter = new IntentFilter();
+ newFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+ newFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ newFilter.addDataScheme(ContentResolver.SCHEME_FILE);
+ registerReceiver(mainActivityHelper.mNotificationReceiver, newFilter);
+ registerReceiver(receiver2, new IntentFilter(TAG_INTENT_FILTER_GENERAL));
+
+ if (SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ updateUsbInformation();
+ }
}
- // Registering intent filter for OTG
- IntentFilter otgFilter = new IntentFilter();
- otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
- registerReceiver(mOtgReceiver, otgFilter);
- }
+ /**
+ * Updates everything related to USB devices MUST ALWAYS be called after onResume()
+ */
+ @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+ private void updateUsbInformation() {
+ boolean isInformationUpdated = false;
+ List connectedDevices = OTGUtil.getMassStorageDevicesConnected(this);
+
+ if (!connectedDevices.isEmpty()) {
+ if (SingletonUsbOtg.getInstance().getUsbOtgRoot() != null
+ && OTGUtil.isUsbUriAccessible(this)) {
+ for (UsbOtgRepresentation device : connectedDevices) {
+ if (SingletonUsbOtg.getInstance().checkIfRootIsFromDevice(device)) {
+ isInformationUpdated = true;
+ break;
+ }
+ }
- /** Receiver to check if a USB device is connected at the runtime of application */
- BroadcastReceiver mOtgReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
- List connectedDevices =
- OTGUtil.getMassStorageDevicesConnected(MainActivity.this);
- if (!connectedDevices.isEmpty()) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
- drawer.refreshDrawer();
+ if (!isInformationUpdated) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ }
}
- } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+
+ if (!isInformationUpdated) {
+ SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
+ isInformationUpdated = true;
+ }
+ }
+
+ if (!isInformationUpdated) {
SingletonUsbOtg.getInstance().resetUsbOtgRoot();
drawer.refreshDrawer();
- goToMain(null);
- }
}
- };
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
+ // Registering intent filter for OTG
+ IntentFilter otgFilter = new IntentFilter();
+ otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ registerReceiver(mOtgReceiver, otgFilter);
+ }
+
+ /**
+ * Receiver to check if a USB device is connected at the runtime of application
+ */
+ BroadcastReceiver mOtgReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ List connectedDevices =
+ OTGUtil.getMassStorageDevicesConnected(MainActivity.this);
+ if (!connectedDevices.isEmpty()) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
+ drawer.refreshDrawer();
+ }
+ } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ drawer.refreshDrawer();
+ goToMain(null);
+ }
+ }
+ };
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
/*
ImageView ib = findViewById(R.id.action_overflow);
if (ib.getVisibility() == View.VISIBLE) {
ib.performClick();
}
*/
- // return 'true' to prevent further propagation of the key event
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- // TODO: 6/5/2017 Android may choose to not call this method before destruction
- // TODO: https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
- closeInteractiveShell();
- NetCopyClientConnectionPool.INSTANCE.shutdown();
- if (drawer != null && drawer.getBilling() != null) {
- drawer.getBilling().destroyBillingInstance();
- }
- }
-
- /** Closes the interactive shell and threads associated */
- private void closeInteractiveShell() {
- if (isRootExplorer()) {
- // close interactive shell
- try {
- Shell.getShell().close();
- } catch (IOException e) {
- LOG.error("Error closing Shell", e);
- }
+ // return 'true' to prevent further propagation of the key event
+ return true;
+ }
+
+ return super.onKeyDown(keyCode, event);
}
- }
-
- public void updatePaths(int pos) {
- TabFragment tabFragment = getTabFragment();
- if (tabFragment != null) tabFragment.updatePaths(pos);
- }
-
- public void openCompressed(String path) {
- appBarLayout.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
- fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_in_bottom);
- Fragment zipFragment = new CompressedExplorerFragment();
- Bundle bundle = new Bundle();
- bundle.putString(CompressedExplorerFragment.KEY_PATH, path);
- zipFragment.setArguments(bundle);
- fragmentTransaction.add(R.id.content_frame, zipFragment);
- fragmentTransaction.commitAllowingStateLoss();
- }
-
- public @Nullable MainFragment getCurrentMainFragment() {
- TabFragment tab = getTabFragment();
-
- if (tab != null && tab.getCurrentTabFragment() instanceof MainFragment) {
- return (MainFragment) tab.getCurrentTabFragment();
- } else return null;
- }
-
- public TabFragment getTabFragment() {
- Fragment fragment = getFragmentAtFrame();
-
- if (!(fragment instanceof TabFragment)) return null;
- else return (TabFragment) fragment;
- }
-
- public Fragment getFragmentAtFrame() {
- return getSupportFragmentManager().findFragmentById(R.id.content_frame);
- }
-
- public void setPagingEnabled(boolean b) {
- getTabFragment().setPagingEnabled(b);
- }
-
- public File getUsbDrive() {
- File parent = new File("/storage");
-
- try {
- for (File f : parent.listFiles())
- if (f.exists() && f.getName().toLowerCase().contains("usb") && f.canExecute()) return f;
- } catch (Exception e) {
- }
-
- parent = new File("/mnt/sdcard/usbStorage");
- if (parent.exists() && parent.canExecute()) return parent;
- parent = new File("/mnt/sdcard/usb_storage");
- if (parent.exists() && parent.canExecute()) return parent;
-
- return null;
- }
-
- public SpeedDialView getFAB() {
- return floatingActionButton;
- }
-
- public void showFab() {
- getFAB().setVisibility(View.VISIBLE);
- getFAB().show();
- CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
- params.setBehavior(new SpeedDialView.ScrollingViewSnackbarBehavior());
- getFAB().requestLayout();
- }
-
- public void hideFab() {
- getFAB().setVisibility(View.GONE);
- getFAB().hide();
- CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
- params.setBehavior(new SpeedDialView.NoBehavior());
- getFAB().requestLayout();
- }
-
- public AppBar getAppbar() {
- return appbar;
- }
-
- public Drawer getDrawer() {
- return drawer;
- }
-
- protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
- super.onActivityResult(requestCode, responseCode, intent);
- if (requestCode == Drawer.image_selector_request_code) {
- drawer.onActivityResult(requestCode, responseCode, intent);
- } else if (requestCode == 3) {
- Uri treeUri;
- if (responseCode == Activity.RESULT_OK) {
- // Get Uri from Storage Access Framework.
- treeUri = intent.getData();
- // Persist URI - this is required for verification of writability.
- if (treeUri != null)
- getPrefs()
- .edit()
- .putString(PreferencesConstants.PREFERENCE_URI, treeUri.toString())
- .apply();
- } else {
- // If not confirmed SAF, or if still not writable, then revert settings.
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // TODO: 6/5/2017 Android may choose to not call this method before destruction
+ // TODO: https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
+ closeInteractiveShell();
+ NetCopyClientConnectionPool.INSTANCE.shutdown();
+ if (drawer != null && drawer.getBilling() != null) {
+ drawer.getBilling().destroyBillingInstance();
+ }
+ }
+
+ /**
+ * Closes the interactive shell and threads associated
+ */
+ private void closeInteractiveShell() {
+ if (isRootExplorer()) {
+ // close interactive shell
+ try {
+ Shell.getShell().close();
+ } catch (IOException e) {
+ LOG.error("Error closing Shell", e);
+ }
+ }
+ }
+
+ public void updatePaths(int pos) {
+ TabFragment tabFragment = getTabFragment();
+ if (tabFragment != null) tabFragment.updatePaths(pos);
+ }
+
+ public void openCompressed(String path) {
+ appBarLayout.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_in_bottom);
+ Fragment zipFragment = new CompressedExplorerFragment();
+ Bundle bundle = new Bundle();
+ bundle.putString(CompressedExplorerFragment.KEY_PATH, path);
+ zipFragment.setArguments(bundle);
+ fragmentTransaction.add(R.id.content_frame, zipFragment);
+ fragmentTransaction.commitAllowingStateLoss();
+ }
+
+ public @Nullable
+ MainFragment getCurrentMainFragment() {
+ TabFragment tab = getTabFragment();
+
+ if (tab != null && tab.getCurrentTabFragment() instanceof MainFragment) {
+ return (MainFragment) tab.getCurrentTabFragment();
+ } else return null;
+ }
+
+ public TabFragment getTabFragment() {
+ Fragment fragment = getFragmentAtFrame();
+
+ if (!(fragment instanceof TabFragment)) return null;
+ else return (TabFragment) fragment;
+ }
+
+ public Fragment getFragmentAtFrame() {
+ return getSupportFragmentManager().findFragmentById(R.id.content_frame);
+ }
+
+ public void setPagingEnabled(boolean b) {
+ getTabFragment().setPagingEnabled(b);
+ }
+
+ public File getUsbDrive() {
+ File parent = new File("/storage");
+
+ try {
+ for (File f : parent.listFiles())
+ if (f.exists() && f.getName().toLowerCase().contains("usb") && f.canExecute())
+ return f;
+ } catch (Exception e) {
+ }
+
+ parent = new File("/mnt/sdcard/usbStorage");
+ if (parent.exists() && parent.canExecute()) return parent;
+ parent = new File("/mnt/sdcard/usb_storage");
+ if (parent.exists() && parent.canExecute()) return parent;
+
+ return null;
+ }
+
+ public SpeedDialView getFAB() {
+ return floatingActionButton;
+ }
+
+ public void showFab() {
+ getFAB().setVisibility(View.VISIBLE);
+ getFAB().show();
+ CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
+ params.setBehavior(new SpeedDialView.ScrollingViewSnackbarBehavior());
+ getFAB().requestLayout();
+ }
+
+ public void hideFab() {
+ getFAB().setVisibility(View.GONE);
+ getFAB().hide();
+ CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
+ params.setBehavior(new SpeedDialView.NoBehavior());
+ getFAB().requestLayout();
+ }
+
+ public AppBar getAppbar() {
+ return appbar;
+ }
+
+ public Drawer getDrawer() {
+ return drawer;
+ }
+
+ protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
+ super.onActivityResult(requestCode, responseCode, intent);
+ if (requestCode == Drawer.image_selector_request_code) {
+ drawer.onActivityResult(requestCode, responseCode, intent);
+ } else if (requestCode == 3) {
+ Uri treeUri;
+ if (responseCode == Activity.RESULT_OK) {
+ // Get Uri from Storage Access Framework.
+ treeUri = intent.getData();
+ // Persist URI - this is required for verification of writability.
+ if (treeUri != null)
+ getPrefs()
+ .edit()
+ .putString(PreferencesConstants.PREFERENCE_URI, treeUri.toString())
+ .apply();
+ } else {
+ // If not confirmed SAF, or if still not writable, then revert settings.
/* DialogUtil.displayError(getActivity(), R.string.message_dialog_cannot_write_to_folder_saf, false, currentFolder);
||!FileUtil.isWritableNormalOrSaf(currentFolder)*/
- return;
- }
+ return;
+ }
- // After confirmation, update stored value of folder.
- // Persist access permissions.
+ // After confirmation, update stored value of folder.
+ // Persist access permissions.
- if (SDK_INT >= KITKAT) {
- getContentResolver()
- .takePersistableUriPermission(
- treeUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
+ if (SDK_INT >= KITKAT) {
+ getContentResolver()
+ .takePersistableUriPermission(
+ treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
- executeWithMainFragment(
- mainFragment -> {
- switch (operation) {
- case DELETE: // deletion
- new DeleteTask(mainActivity).execute((oparrayList));
- break;
- case COPY: // copying
- // legacy compatibility
- if (oparrayList != null && oparrayList.size() != 0) {
- oparrayListList = new ArrayList<>();
- oparrayListList.add(oparrayList);
- oparrayList = null;
- oppatheList = new ArrayList<>();
- oppatheList.add(oppathe);
- oppathe = "";
- }
- for (int i = 0; i < oparrayListList.size(); i++) {
- ArrayList sourceList = oparrayListList.get(i);
- Intent intent1 = new Intent(this, CopyService.class);
- intent1.putExtra(CopyService.TAG_COPY_SOURCES, sourceList);
- intent1.putExtra(CopyService.TAG_COPY_TARGET, oppatheList.get(i));
- ServiceWatcherUtil.runService(this, intent1);
- }
- break;
- case MOVE: // moving
- // legacy compatibility
- if (oparrayList != null && oparrayList.size() != 0) {
- oparrayListList = new ArrayList<>();
- oparrayListList.add(oparrayList);
- oparrayList = null;
- oppatheList = new ArrayList<>();
- oppatheList.add(oppathe);
- oppathe = "";
- }
+ executeWithMainFragment(
+ mainFragment -> {
+ switch (operation) {
+ case DELETE: // deletion
+ new DeleteTask(mainActivity).execute((oparrayList));
+ break;
+ case COPY: // copying
+ // legacy compatibility
+ if (oparrayList != null && oparrayList.size() != 0) {
+ oparrayListList = new ArrayList<>();
+ oparrayListList.add(oparrayList);
+ oparrayList = null;
+ oppatheList = new ArrayList<>();
+ oppatheList.add(oppathe);
+ oppathe = "";
+ }
+ for (int i = 0; i < oparrayListList.size(); i++) {
+ ArrayList sourceList = oparrayListList.get(i);
+ Intent intent1 = new Intent(this, CopyService.class);
+ intent1.putExtra(CopyService.TAG_COPY_SOURCES, sourceList);
+ intent1.putExtra(CopyService.TAG_COPY_TARGET, oppatheList.get(i));
+ ServiceWatcherUtil.runService(this, intent1);
+ }
+ break;
+ case MOVE: // moving
+ // legacy compatibility
+ if (oparrayList != null && oparrayList.size() != 0) {
+ oparrayListList = new ArrayList<>();
+ oparrayListList.add(oparrayList);
+ oparrayList = null;
+ oppatheList = new ArrayList<>();
+ oppatheList.add(oppathe);
+ oppathe = "";
+ }
+
+ TaskKt.fromTask(
+ new MoveFilesTask(
+ oparrayListList,
+ isRootExplorer(),
+ mainFragment.getCurrentPath(),
+ this,
+ OpenMode.FILE,
+ oppatheList));
+ break;
+ case NEW_FOLDER: // mkdir
+ mainActivityHelper.mkDir(
+ new HybridFile(OpenMode.FILE, oppathe),
+ RootHelper.generateBaseFile(new File(oppathe), true),
+ mainFragment);
+ break;
+ case RENAME:
+ mainActivityHelper.rename(
+ mainFragment.getMainFragmentViewModel().getOpenMode(),
+ (oppathe),
+ (oppathe1),
+ null,
+ false,
+ mainActivity,
+ isRootExplorer());
+ mainFragment.updateList(false);
+ break;
+ case NEW_FILE:
+ mainActivityHelper.mkFile(
+ new HybridFile(OpenMode.FILE, oppathe),
+ new HybridFile(OpenMode.FILE, oppathe),
+ mainFragment);
+ break;
+ case EXTRACT:
+ mainActivityHelper.extractFile(new File(oppathe));
+ break;
+ case COMPRESS:
+ mainActivityHelper.compressFiles(new File(oppathe), oparrayList);
+ break;
+ case SAVE_FILE:
+ FileUtil.writeUriToStorage(
+ this, urisToBeSaved, getContentResolver(), mainFragment.getCurrentPath());
+ urisToBeSaved = null;
+ finish();
+ break;
+ default:
+ LogHelper.logOnProductionOrCrash("Incorrect value for switch");
+ }
+ return null;
+ },
+ true);
+ operation = UNDEFINED;
+ } else if (requestCode == REQUEST_CODE_SAF) {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (responseCode == Activity.RESULT_OK && intent.getData() != null) {
+ // otg access
+ Uri usbOtgRoot = intent.getData();
+ SingletonUsbOtg.getInstance().setUsbOtgRoot(usbOtgRoot);
+ mainFragment.loadlist(OTGUtil.PREFIX_OTG, false, OpenMode.OTG, true);
+ drawer.closeIfNotLocked();
+ if (drawer.isLocked()) drawer.onDrawerClosed();
+ } else if (requestCode == REQUEST_CODE_SAF_FTP) {
+ FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
+ ftpServerFragment.changeFTPServerPath(intent.getData().toString());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+
+ } else {
+ Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
+ // otg access not provided
+ drawer.resetPendingPath();
+ }
+ return null;
+ },
+ true);
+ }
+ }
+
+ void initialisePreferences() {
+ currentTab = getCurrentTab();
+ skinStatusBar = PreferenceUtils.getStatusColor(getPrimary());
+ }
+
+ void initialiseViews() {
- TaskKt.fromTask(
- new MoveFilesTask(
- oparrayListList,
- isRootExplorer(),
- mainFragment.getCurrentPath(),
+ appbar =
+ new AppBar(
this,
- OpenMode.FILE,
- oppatheList));
- break;
- case NEW_FOLDER: // mkdir
- mainActivityHelper.mkDir(
- new HybridFile(OpenMode.FILE, oppathe),
- RootHelper.generateBaseFile(new File(oppathe), true),
- mainFragment);
- break;
- case RENAME:
- mainActivityHelper.rename(
- mainFragment.getMainFragmentViewModel().getOpenMode(),
- (oppathe),
- (oppathe1),
- null,
- false,
- mainActivity,
- isRootExplorer());
- mainFragment.updateList(false);
+ getPrefs(),
+ queue -> {
+ if (!queue.isEmpty()) {
+ mainActivityHelper.search(getPrefs(), queue);
+ }
+ });
+ appBarLayout = getAppbar().getAppbarLayout();
+
+ setSupportActionBar(getAppbar().getToolbar());
+ drawer = new Drawer(this);
+
+ indicator_layout = findViewById(R.id.indicator_layout);
+
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ fabBgView = findViewById(R.id.fabs_overlay_layout);
+
+ switch (getAppTheme().getSimpleTheme(this)) {
+ case DARK:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
break;
- case NEW_FILE:
- mainActivityHelper.mkFile(
- new HybridFile(OpenMode.FILE, oppathe),
- new HybridFile(OpenMode.FILE, oppathe),
- mainFragment);
+ case BLACK:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
break;
- case EXTRACT:
- mainActivityHelper.extractFile(new File(oppathe));
+ }
+
+ fabBgView.setOnClickListener(
+ view -> {
+ if (getAppbar().getSearchView().isEnabled())
+ getAppbar().getSearchView().hideSearchView();
+ });
+
+ drawer.setDrawerHeaderBackground();
+ }
+
+ /**
+ * Call this method when you need to update the MainActivity view components' colors based on
+ * update in the {@link MainActivity#currentTab} Warning - All the variables should be initialised
+ * before calling this method!
+ */
+ public void updateViews(ColorDrawable colorDrawable) {
+ // appbar view color
+ appbar.getBottomBar().setBackgroundColor(colorDrawable.getColor());
+ // action bar color
+ mainActivity.getSupportActionBar().setBackgroundDrawable(colorDrawable);
+
+ drawer.setBackgroundColor(colorDrawable.getColor());
+
+ if (SDK_INT >= LOLLIPOP) {
+ // for lollipop devices, the status bar color
+ mainActivity.getWindow().setStatusBarColor(colorDrawable.getColor());
+ if (getBoolean(PREFERENCE_COLORED_NAVIGATION)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(PreferenceUtils.getStatusColor(colorDrawable.getColor()));
+ } else {
+ if (getAppTheme().equals(AppTheme.LIGHT)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, android.R.color.white));
+ } else if (getAppTheme().equals(AppTheme.BLACK)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, android.R.color.black));
+ } else {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, R.color.holo_dark_background));
+ }
+ }
+ } else if (SDK_INT == KITKAT_WATCH || SDK_INT == KITKAT) {
+
+ // for kitkat devices, the status bar color
+ SystemBarTintManager tintManager = new SystemBarTintManager(this);
+ tintManager.setStatusBarTintEnabled(true);
+ tintManager.setStatusBarTintColor(colorDrawable.getColor());
+ }
+ }
+
+ void initialiseFab() {
+ int colorAccent = getAccent();
+
+ floatingActionButton = findViewById(R.id.fabs_menu);
+ floatingActionButton.setMainFabClosedBackgroundColor(colorAccent);
+ floatingActionButton.setMainFabOpenedBackgroundColor(colorAccent);
+ initializeFabActionViews();
+ }
+
+ public void initializeFabActionViews() {
+ // NOTE: SpeedDial inverts insert index than FABsmenu
+ FabWithLabelView cloudFab =
+ initFabTitle(
+ R.id.menu_new_cloud, R.string.cloud_connection, R.drawable.ic_cloud_white_24dp);
+ FabWithLabelView newFileFab =
+ initFabTitle(R.id.menu_new_file, R.string.file, R.drawable.ic_insert_drive_file_white_48dp);
+ FabWithLabelView newFolderFab =
+ initFabTitle(R.id.menu_new_folder, R.string.folder, R.drawable.folder_fab);
+
+ floatingActionButton.setOnActionSelectedListener(new FabActionListener(this));
+ floatingActionButton.setOnClickListener(
+ view -> {
+ fabButtonClick(cloudFab);
+ });
+ floatingActionButton.setOnFocusChangeListener(new CustomZoomFocusChange());
+ floatingActionButton.getMainFab().setOnFocusChangeListener(new CustomZoomFocusChange());
+ floatingActionButton.setNextFocusUpId(cloudFab.getId());
+ floatingActionButton.getMainFab().setNextFocusUpId(cloudFab.getId());
+ floatingActionButton.setOnKeyListener(
+ (v, keyCode, event) -> {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ if (getCurrentTab() == 0 && getFAB().isFocused()) {
+ getTabFragment().setCurrentItem(1);
+ }
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
+ findViewById(R.id.content_frame).requestFocus();
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
+ if (pasteHelper != null
+ && pasteHelper.getSnackbar() != null
+ && pasteHelper.getSnackbar().isShown())
+ ((Snackbar.SnackbarLayout) pasteHelper.getSnackbar().getView())
+ .findViewById(R.id.snackBarActionButton)
+ .requestFocus();
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
+ fabButtonClick(cloudFab);
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ onBackPressed();
+ } else {
+ return false;
+ }
+ }
+ return true;
+ });
+ cloudFab.setNextFocusDownId(floatingActionButton.getMainFab().getId());
+ cloudFab.setNextFocusUpId(newFileFab.getId());
+ cloudFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ newFileFab.setNextFocusDownId(cloudFab.getId());
+ newFileFab.setNextFocusUpId(newFolderFab.getId());
+ newFileFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ newFolderFab.setNextFocusDownId(newFileFab.getId());
+ newFolderFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ }
+
+ private void fabButtonClick(FabWithLabelView cloudFab) {
+ if (floatingActionButton.isOpen()) {
+ floatingActionButton.close(true);
+ } else {
+ floatingActionButton.open(true);
+ cloudFab.requestFocus();
+ }
+ }
+
+ private FabWithLabelView initFabTitle(
+ @IdRes int id, @StringRes int fabTitle, @DrawableRes int icon) {
+ int iconSkin = getCurrentColorPreference().getIconSkin();
+
+ SpeedDialActionItem.Builder builder =
+ new SpeedDialActionItem.Builder(id, icon)
+ .setLabel(fabTitle)
+ .setFabBackgroundColor(iconSkin);
+
+ switch (getAppTheme().getSimpleTheme(this)) {
+ case LIGHT:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_light);
break;
- case COMPRESS:
- mainActivityHelper.compressFiles(new File(oppathe), oparrayList);
+ case DARK:
+ builder
+ .setLabelBackgroundColor(Utils.getColor(this, R.color.holo_dark_background))
+ .setLabelColor(Utils.getColor(this, R.color.text_dark));
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
break;
- case SAVE_FILE:
- FileUtil.writeUriToStorage(
- this, urisToBeSaved, getContentResolver(), mainFragment.getCurrentPath());
- urisToBeSaved = null;
- finish();
+ case BLACK:
+ builder
+ .setLabelBackgroundColor(Color.BLACK)
+ .setLabelColor(Utils.getColor(this, R.color.text_dark));
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
break;
- default:
- LogHelper.logOnProductionOrCrash("Incorrect value for switch");
+ }
+
+ return floatingActionButton.addActionItem(builder.create());
+ }
+
+ public boolean copyToClipboard(Context context, String text) {
+ try {
+ android.content.ClipboardManager clipboard =
+ (android.content.ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
+ android.content.ClipData clip =
+ android.content.ClipData.newPlainText("Path copied to clipboard", text);
+ clipboard.setPrimaryClip(clip);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public void renameBookmark(final String title, final String path) {
+ if (dataUtils.containsBooks(new String[]{title, path}) != -1) {
+ RenameBookmark renameBookmark = RenameBookmark.getInstance(title, path, getAccent());
+ if (renameBookmark != null) renameBookmark.show(getFragmentManager(), "renamedialog");
+ }
+ }
+
+ public PasteHelper getPaste() {
+ return pasteHelper;
+ }
+
+ public MainActivityActionMode getActionModeHelper() {
+ return this.mainActivityActionMode;
+ }
+
+ public void setPaste(PasteHelper p) {
+ pasteHelper = p;
+ }
+
+ @Override
+ public void onNewIntent(Intent i) {
+ super.onNewIntent(i);
+ intent = i;
+ path = i.getStringExtra("path");
+
+ if (path != null) {
+ if (new File(path).isDirectory()) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment != null) {
+ mainFragment.loadlist(path, false, OpenMode.FILE, true);
+ } else {
+ goToMain(path);
+ }
+ } else FileUtils.openFile(new File(path), mainActivity, getPrefs());
+ } else if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, this);
+ }
+ } else if (i.getCategories() != null
+ && i.getCategories().contains(CLOUD_AUTHENTICATOR_GDRIVE)) {
+ // we used an external authenticator instead of APIs. Probably for Google Drive
+ CloudRail.setAuthenticationResponse(intent);
+ if (intent.getAction() != null) {
+ checkForExternalIntent(intent);
+ invalidateFragmentAndBundle(null, false);
}
- return null;
- },
- true);
- operation = UNDEFINED;
- } else if (requestCode == REQUEST_CODE_SAF) {
- executeWithMainFragment(
- mainFragment -> {
- if (responseCode == Activity.RESULT_OK && intent.getData() != null) {
- // otg access
- Uri usbOtgRoot = intent.getData();
- SingletonUsbOtg.getInstance().setUsbOtgRoot(usbOtgRoot);
- mainFragment.loadlist(OTGUtil.PREFIX_OTG, false, OpenMode.OTG, true);
- drawer.closeIfNotLocked();
- if (drawer.isLocked()) drawer.onDrawerClosed();
- } else if (requestCode == REQUEST_CODE_SAF_FTP) {
- FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
- ftpServerFragment.changeFTPServerPath(intent.getData().toString());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+ } else if ((openProcesses = i.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false))) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(
+ R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
+ // transaction.addToBackStack(null);
+ openProcesses = false;
+ // title.setText(utils.getString(con, R.string.process_viewer));
+ // Commit the transaction
+ transaction.commitAllowingStateLoss();
+ supportInvalidateOptionsMenu();
+ } else if (intent.getAction() != null) {
+ checkForExternalIntent(intent);
+ invalidateFragmentAndBundle(null, false);
+
+ if (SDK_INT >= KITKAT) {
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ drawer.refreshDrawer();
+ }
+ }
+ }
+ }
+
+ private BroadcastReceiver receiver2 =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent i) {
+ if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, mainActivity);
+ }
+ }
+ }
+ };
+
+ public void showSMBDialog(String name, String path, boolean edit) {
+ if (path.length() > 0 && name.length() == 0) {
+ int i = dataUtils.containsServer(new String[]{name, path});
+ if (i != -1) name = dataUtils.getServers().get(i)[0];
+ }
+ SmbConnectDialog smbConnectDialog = new SmbConnectDialog();
+ Bundle bundle = new Bundle();
+ bundle.putString("name", name);
+ bundle.putString("path", path);
+ bundle.putBoolean("edit", edit);
+ smbConnectDialog.setArguments(bundle);
+ smbConnectDialog.show(getFragmentManager(), "smbdailog");
+ }
+
+ @SuppressLint("CheckResult")
+ public void showSftpDialog(String name, String path, boolean edit) {
+ if (path.length() > 0 && name.length() == 0) {
+ int i = dataUtils.containsServer(new String[]{name, path});
+ if (i != -1) name = dataUtils.getServers().get(i)[0];
+ }
+ SftpConnectDialog sftpConnectDialog = new SftpConnectDialog();
+ String finalName = name;
+ Flowable.fromCallable(() -> new NetCopyClientConnectionPool.ConnectionInfo(path))
+ .flatMap(
+ connectionInfo -> {
+ Bundle retval = new Bundle();
+ retval.putString(ARG_PROTOCOL, connectionInfo.getPrefix());
+ retval.putString(ARG_NAME, finalName);
+ retval.putString(ARG_ADDRESS, connectionInfo.getHost());
+ retval.putInt(ARG_PORT, connectionInfo.getPort());
+ if (!TextUtils.isEmpty(connectionInfo.getDefaultPath())) {
+ retval.putString(ARG_DEFAULT_PATH, connectionInfo.getDefaultPath());
+ }
+ retval.putString(ARG_USERNAME, connectionInfo.getUsername());
+
+ if (connectionInfo.getPassword() == null) {
+ retval.putBoolean(ARG_HAS_PASSWORD, false);
+ retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path));
+ } else {
+ retval.putBoolean(ARG_HAS_PASSWORD, true);
+ retval.putString(ARG_PASSWORD, connectionInfo.getPassword());
+ }
+ retval.putBoolean(ARG_EDIT, edit);
+ return Flowable.just(retval);
+ })
+ .subscribeOn(Schedulers.computation())
+ .subscribe(
+ bundle -> {
+ sftpConnectDialog.setArguments(bundle);
+ sftpConnectDialog.setCancelable(true);
+ sftpConnectDialog.show(getSupportFragmentManager(), "sftpdialog");
+ });
+ }
+ /**
+ * Shows a view that goes from white at it's lowest part to transparent a the top. It covers the
+ * fragment.
+ */
+ public void showSmokeScreen() {
+ fabBgView.show();
+ }
+
+ public void hideSmokeScreen() {
+ fabBgView.hide();
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void addConnection(
+ boolean edit,
+ @NonNull final String name,
+ @NonNull final String path,
+ @Nullable final String encryptedPath,
+ @Nullable final String oldname,
+ @Nullable final String oldPath) {
+ String[] s = new String[]{name, path};
+ if (!edit) {
+ if ((dataUtils.containsServer(path)) == -1) {
+ Completable.fromRunnable(
+ () -> {
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.SMB, name, encryptedPath));
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ () -> {
+ dataUtils.addServer(s);
+ drawer.refreshDrawer();
+ // grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.loadlist(path, false, OpenMode.UNKNOWN, true);
+ return null;
+ },
+ true);
+ });
} else {
- Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
- // otg access not provided
- drawer.resetPendingPath();
+ Snackbar.make(
+ findViewById(R.id.navigation),
+ getString(R.string.connection_exists),
+ Snackbar.LENGTH_SHORT)
+ .show();
}
- return null;
- },
- true);
- }
- }
-
- void initialisePreferences() {
- currentTab = getCurrentTab();
- skinStatusBar = PreferenceUtils.getStatusColor(getPrimary());
- }
-
- void initialiseViews() {
-
- appbar =
- new AppBar(
- this,
- getPrefs(),
- queue -> {
- if (!queue.isEmpty()) {
- mainActivityHelper.search(getPrefs(), queue);
- }
- });
- appBarLayout = getAppbar().getAppbarLayout();
-
- setSupportActionBar(getAppbar().getToolbar());
- drawer = new Drawer(this);
-
- indicator_layout = findViewById(R.id.indicator_layout);
-
- getSupportActionBar().setDisplayShowTitleEnabled(false);
- fabBgView = findViewById(R.id.fabs_overlay_layout);
-
- switch (getAppTheme().getSimpleTheme(this)) {
- case DARK:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
- break;
- case BLACK:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
- break;
- }
-
- fabBgView.setOnClickListener(
- view -> {
- if (getAppbar().getSearchView().isEnabled()) getAppbar().getSearchView().hideSearchView();
- });
-
- drawer.setDrawerHeaderBackground();
- }
-
- /**
- * Call this method when you need to update the MainActivity view components' colors based on
- * update in the {@link MainActivity#currentTab} Warning - All the variables should be initialised
- * before calling this method!
- */
- public void updateViews(ColorDrawable colorDrawable) {
- // appbar view color
- appbar.getBottomBar().setBackgroundColor(colorDrawable.getColor());
- // action bar color
- mainActivity.getSupportActionBar().setBackgroundDrawable(colorDrawable);
-
- drawer.setBackgroundColor(colorDrawable.getColor());
-
- if (SDK_INT >= LOLLIPOP) {
- // for lollipop devices, the status bar color
- mainActivity.getWindow().setStatusBarColor(colorDrawable.getColor());
- if (getBoolean(PREFERENCE_COLORED_NAVIGATION)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(PreferenceUtils.getStatusColor(colorDrawable.getColor()));
- } else {
- if (getAppTheme().equals(AppTheme.LIGHT)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, android.R.color.white));
- } else if (getAppTheme().equals(AppTheme.BLACK)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, android.R.color.black));
} else {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, R.color.holo_dark_background));
+ int i = dataUtils.containsServer(new String[]{oldname, oldPath});
+ if (i != -1) {
+ dataUtils.removeServer(i);
+
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.renameSMB(oldname, oldPath, name, path);
+ });
+ // mainActivity.grid.removePath(oldname, oldPath, DataUtils.SMB);
+ }
+ dataUtils.addServer(s);
+ Collections.sort(dataUtils.getServers(), new BookSorter());
+ drawer.refreshDrawer();
+ // mainActivity.grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
}
- }
- } else if (SDK_INT == KITKAT_WATCH || SDK_INT == KITKAT) {
-
- // for kitkat devices, the status bar color
- SystemBarTintManager tintManager = new SystemBarTintManager(this);
- tintManager.setStatusBarTintEnabled(true);
- tintManager.setStatusBarTintColor(colorDrawable.getColor());
- }
- }
-
- void initialiseFab() {
- int colorAccent = getAccent();
-
- floatingActionButton = findViewById(R.id.fabs_menu);
- floatingActionButton.setMainFabClosedBackgroundColor(colorAccent);
- floatingActionButton.setMainFabOpenedBackgroundColor(colorAccent);
- initializeFabActionViews();
- }
-
- public void initializeFabActionViews() {
- // NOTE: SpeedDial inverts insert index than FABsmenu
- FabWithLabelView cloudFab =
- initFabTitle(
- R.id.menu_new_cloud, R.string.cloud_connection, R.drawable.ic_cloud_white_24dp);
- FabWithLabelView newFileFab =
- initFabTitle(R.id.menu_new_file, R.string.file, R.drawable.ic_insert_drive_file_white_48dp);
- FabWithLabelView newFolderFab =
- initFabTitle(R.id.menu_new_folder, R.string.folder, R.drawable.folder_fab);
-
- floatingActionButton.setOnActionSelectedListener(new FabActionListener(this));
- floatingActionButton.setOnClickListener(
- view -> {
- fabButtonClick(cloudFab);
- });
- floatingActionButton.setOnFocusChangeListener(new CustomZoomFocusChange());
- floatingActionButton.getMainFab().setOnFocusChangeListener(new CustomZoomFocusChange());
- floatingActionButton.setNextFocusUpId(cloudFab.getId());
- floatingActionButton.getMainFab().setNextFocusUpId(cloudFab.getId());
- floatingActionButton.setOnKeyListener(
- (v, keyCode, event) -> {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
- if (getCurrentTab() == 0 && getFAB().isFocused()) {
- getTabFragment().setCurrentItem(1);
- }
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
- findViewById(R.id.content_frame).requestFocus();
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
- if (pasteHelper != null
- && pasteHelper.getSnackbar() != null
- && pasteHelper.getSnackbar().isShown())
- ((Snackbar.SnackbarLayout) pasteHelper.getSnackbar().getView())
- .findViewById(R.id.snackBarActionButton)
- .requestFocus();
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
- fabButtonClick(cloudFab);
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- onBackPressed();
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void deleteConnection(final String name, final String path) {
+ int i = dataUtils.containsServer(new String[]{name, path});
+ if (i != -1) {
+ dataUtils.removeServer(i);
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(UtilsHandler.Operation.SMB, name, path));
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void delete(String title, String path) {
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(UtilsHandler.Operation.BOOKMARKS, title, path));
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void modify(String oldpath, String oldname, String newPath, String newname) {
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.renameBookmark(oldname, oldpath, newname, newPath);
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+
+ @Override
+ public void onPreExecute(String query) {
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.mSwipeRefreshLayout.setRefreshing(true);
+ mainFragment.onSearchPreExecute(query);
+ return null;
+ });
+ }
+
+ @Override
+ public void onPostExecute(String query) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ // TODO cancel search
+ return;
+ }
+
+ mainFragment.onSearchCompleted(query);
+ mainFragment.mSwipeRefreshLayout.setRefreshing(false);
+ }
+
+ @Override
+ public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ // TODO cancel search
+ return;
+ }
+
+ mainFragment.addSearchResult(hybridFileParcelable, query);
+ }
+
+ @Override
+ public void onCancelled() {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ return;
+ }
+
+ mainFragment.reloadListElements(
+ false, false, !mainFragment.getMainFragmentViewModel().isList());
+ mainFragment.mSwipeRefreshLayout.setRefreshing(false);
+ }
+
+ @Override
+ public void addConnection(OpenMode service) {
+ try {
+ if (cloudHandler.findEntry(service) != null) {
+ // cloud entry already exists
+ Toast.makeText(
+ this, getResources().getString(R.string.connection_exists), Toast.LENGTH_LONG)
+ .show();
+ } else if (BuildConfig.IS_VERSION_FDROID) {
+ Toast.makeText(
+ this, getResources().getString(R.string.cloud_error_fdroid), Toast.LENGTH_LONG)
+ .show();
} else {
- return false;
+ Toast.makeText(
+ MainActivity.this,
+ getResources().getString(R.string.please_wait),
+ Toast.LENGTH_LONG)
+ .show();
+ Bundle args = new Bundle();
+ args.putInt(ARGS_KEY_LOADER, service.ordinal());
+
+ // check if we already had done some work on the loader
+ Loader loader = getSupportLoaderManager().getLoader(REQUEST_CODE_CLOUD_LIST_KEY);
+ if (loader != null && loader.isStarted()) {
+
+ // making sure that loader is not started
+ getSupportLoaderManager().destroyLoader(REQUEST_CODE_CLOUD_LIST_KEY);
+ }
+
+ getSupportLoaderManager().initLoader(REQUEST_CODE_CLOUD_LIST_KEY, args, this);
}
- }
- return true;
- });
- cloudFab.setNextFocusDownId(floatingActionButton.getMainFab().getId());
- cloudFab.setNextFocusUpId(newFileFab.getId());
- cloudFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- newFileFab.setNextFocusDownId(cloudFab.getId());
- newFileFab.setNextFocusUpId(newFolderFab.getId());
- newFileFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- newFolderFab.setNextFocusDownId(newFileFab.getId());
- newFolderFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- }
-
- private void fabButtonClick(FabWithLabelView cloudFab) {
- if (floatingActionButton.isOpen()) {
- floatingActionButton.close(true);
- } else {
- floatingActionButton.open(true);
- cloudFab.requestFocus();
- }
- }
-
- private FabWithLabelView initFabTitle(
- @IdRes int id, @StringRes int fabTitle, @DrawableRes int icon) {
- int iconSkin = getCurrentColorPreference().getIconSkin();
-
- SpeedDialActionItem.Builder builder =
- new SpeedDialActionItem.Builder(id, icon)
- .setLabel(fabTitle)
- .setFabBackgroundColor(iconSkin);
-
- switch (getAppTheme().getSimpleTheme(this)) {
- case LIGHT:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_light);
- break;
- case DARK:
- builder
- .setLabelBackgroundColor(Utils.getColor(this, R.color.holo_dark_background))
- .setLabelColor(Utils.getColor(this, R.color.text_dark));
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
- break;
- case BLACK:
- builder
- .setLabelBackgroundColor(Color.BLACK)
- .setLabelColor(Utils.getColor(this, R.color.text_dark));
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
- break;
- }
-
- return floatingActionButton.addActionItem(builder.create());
- }
-
- public boolean copyToClipboard(Context context, String text) {
- try {
- android.content.ClipboardManager clipboard =
- (android.content.ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
- android.content.ClipData clip =
- android.content.ClipData.newPlainText("Path copied to clipboard", text);
- clipboard.setPrimaryClip(clip);
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-
- public void renameBookmark(final String title, final String path) {
- if (dataUtils.containsBooks(new String[] {title, path}) != -1) {
- RenameBookmark renameBookmark = RenameBookmark.getInstance(title, path, getAccent());
- if (renameBookmark != null) renameBookmark.show(getFragmentManager(), "renamedialog");
- }
- }
-
- public PasteHelper getPaste() {
- return pasteHelper;
- }
-
- public MainActivityActionMode getActionModeHelper() {
- return this.mainActivityActionMode;
- }
-
- public void setPaste(PasteHelper p) {
- pasteHelper = p;
- }
-
- @Override
- public void onNewIntent(Intent i) {
- super.onNewIntent(i);
- intent = i;
- path = i.getStringExtra("path");
-
- if (path != null) {
- if (new File(path).isDirectory()) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment != null) {
- mainFragment.loadlist(path, false, OpenMode.FILE, true);
+ } catch (CloudPluginException e) {
+ LOG.warn("failure when adding cloud plugin connections", e);
+ Toast.makeText(this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+
+ @Override
+ public void deleteConnection(OpenMode service) {
+ cloudHandler.clear(service);
+ dataUtils.removeAccount(service);
+
+ runOnUiThread(drawer::refreshDrawer);
+ }
+
+ @NonNull
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ Uri uri =
+ Uri.withAppendedPath(
+ Uri.parse("content://" + CloudContract.PROVIDER_AUTHORITY), "/keys.db/secret_keys");
+
+ String[] projection =
+ new String[]{
+ CloudContract.COLUMN_ID,
+ CloudContract.COLUMN_CLIENT_ID,
+ CloudContract.COLUMN_CLIENT_SECRET_KEY
+ };
+
+ switch (id) {
+ case REQUEST_CODE_CLOUD_LIST_KEY:
+ Uri uriAppendedPath = uri;
+ switch (OpenMode.getOpenMode(args.getInt(ARGS_KEY_LOADER, 2))) {
+ case GDRIVE:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 2);
+ break;
+ case DROPBOX:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 3);
+ break;
+ case BOX:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 4);
+ break;
+ case ONEDRIVE:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 5);
+ break;
+ }
+ return new CursorLoader(this, uriAppendedPath, projection, null, null, null);
+ case REQUEST_CODE_CLOUD_LIST_KEYS:
+ // we need a list of all secret keys
+
+ try {
+ List cloudEntries = cloudHandler.getAllEntries();
+
+ // we want keys for services saved in database, and the cloudrail app key which
+ // is at index 1
+ String ids[] = new String[cloudEntries.size() + 1];
+
+ ids[0] = 1 + "";
+ for (int i = 1; i <= cloudEntries.size(); i++) {
+
+ // we need to get only those cloud details which user wants
+ switch (cloudEntries.get(i - 1).getServiceType()) {
+ case GDRIVE:
+ ids[i] = 2 + "";
+ break;
+ case DROPBOX:
+ ids[i] = 3 + "";
+ break;
+ case BOX:
+ ids[i] = 4 + "";
+ break;
+ case ONEDRIVE:
+ ids[i] = 5 + "";
+ break;
+ }
+ }
+ return new CursorLoader(this, uri, projection, CloudContract.COLUMN_ID, ids, null);
+ } catch (CloudPluginException e) {
+ LOG.warn("failure when fetching cloud connections", e);
+ Toast.makeText(
+ this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
+ .show();
+ }
+ default:
+ Uri undefinedUriAppendedPath = ContentUris.withAppendedId(uri, 7);
+ return new CursorLoader(this, undefinedUriAppendedPath, projection, null, null, null);
+ }
+ }
+
+ @Override
+ public void onLoadFinished(Loader loader, final Cursor data) {
+ if (data == null) {
+ Toast.makeText(
+ this,
+ getResources().getString(R.string.cloud_error_failed_restart),
+ Toast.LENGTH_LONG)
+ .show();
+ return;
+ }
+
+ /*
+ * This is hack for repeated calls to onLoadFinished(),
+ * we take the Cursor provided to check if the function
+ * has already been called on it.
+ *
+ * TODO: find a fix for repeated callbacks to onLoadFinished()
+ */
+ if (cloudCursorData != null && cloudCursorData == data) return;
+ cloudCursorData = data;
+
+ if (cloudLoaderAsyncTask != null
+ && cloudLoaderAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
+ return;
+ }
+ cloudLoaderAsyncTask = new CloudLoaderAsyncTask(this, cloudHandler, cloudCursorData);
+ cloudLoaderAsyncTask.execute();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ // For passing code check
+ }
+
+ public void initCornersDragListener(boolean destroy, boolean shouldInvokeLeftAndRight) {
+ initBottomDragListener(destroy);
+ initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
+ }
+
+ private void initBottomDragListener(boolean destroy) {
+ View bottomPlaceholder = findViewById(R.id.placeholder_drag_bottom);
+ if (destroy) {
+ bottomPlaceholder.setOnDragListener(null);
+ bottomPlaceholder.setVisibility(View.GONE);
} else {
- goToMain(path);
- }
- } else FileUtils.openFile(new File(path), mainActivity, getPrefs());
- } else if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, this);
- }
- } else if (i.getCategories() != null
- && i.getCategories().contains(CLOUD_AUTHENTICATOR_GDRIVE)) {
- // we used an external authenticator instead of APIs. Probably for Google Drive
- CloudRail.setAuthenticationResponse(intent);
- if (intent.getAction() != null) {
- checkForExternalIntent(intent);
- invalidateFragmentAndBundle(null, false);
- }
- } else if ((openProcesses = i.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false))) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(
- R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
- // transaction.addToBackStack(null);
- openProcesses = false;
- // title.setText(utils.getString(con, R.string.process_viewer));
- // Commit the transaction
- transaction.commitAllowingStateLoss();
- supportInvalidateOptionsMenu();
- } else if (intent.getAction() != null) {
- checkForExternalIntent(intent);
- invalidateFragmentAndBundle(null, false);
-
- if (SDK_INT >= KITKAT) {
- if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- drawer.refreshDrawer();
+ bottomPlaceholder.setVisibility(View.VISIBLE);
+ bottomPlaceholder.setOnDragListener(
+ new TabFragmentBottomDragListener(
+ () -> {
+ getCurrentMainFragment().smoothScrollListView(false);
+ return null;
+ },
+ () -> {
+ getCurrentMainFragment().stopSmoothScrollListView();
+ return null;
+ }));
}
- }
}
- }
- private BroadcastReceiver receiver2 =
- new BroadcastReceiver() {
+ private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInvokeLeftAndRight) {
+ TabFragment tabFragment = getTabFragment();
+ tabFragment.initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
+ }
+
+ private static final class FabActionListener implements SpeedDialView.OnActionSelectedListener {
+
+ MainActivity mainActivity;
+ SpeedDialView floatingActionButton;
+
+ FabActionListener(MainActivity mainActivity) {
+ this.mainActivity = mainActivity;
+ this.floatingActionButton = mainActivity.floatingActionButton;
+ }
+
@Override
- public void onReceive(Context context, Intent i) {
- if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, mainActivity);
+ public boolean onActionSelected(SpeedDialActionItem actionItem) {
+ final MainFragment ma =
+ (MainFragment)
+ ((TabFragment)
+ mainActivity.getSupportFragmentManager().findFragmentById(R.id.content_frame))
+ .getCurrentTabFragment();
+ final String path = ma.getCurrentPath();
+
+ switch (actionItem.getId()) {
+ case R.id.menu_new_folder:
+ mainActivity.mainActivityHelper.mkdir(
+ ma.getMainFragmentViewModel().getOpenMode(), path, ma);
+ break;
+ case R.id.menu_new_file:
+ mainActivity.mainActivityHelper.mkfile(
+ ma.getMainFragmentViewModel().getOpenMode(), path, ma);
+ break;
+ case R.id.menu_new_cloud:
+ BottomSheetDialogFragment fragment = new CloudSheetFragment();
+ fragment.show(
+ ma.getActivity().getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT);
+ break;
}
- }
- }
- };
-
- public void showSMBDialog(String name, String path, boolean edit) {
- if (path.length() > 0 && name.length() == 0) {
- int i = dataUtils.containsServer(new String[] {name, path});
- if (i != -1) name = dataUtils.getServers().get(i)[0];
- }
- SmbConnectDialog smbConnectDialog = new SmbConnectDialog();
- Bundle bundle = new Bundle();
- bundle.putString("name", name);
- bundle.putString("path", path);
- bundle.putBoolean("edit", edit);
- smbConnectDialog.setArguments(bundle);
- smbConnectDialog.show(getFragmentManager(), "smbdailog");
- }
-
- @SuppressLint("CheckResult")
- public void showSftpDialog(String name, String path, boolean edit) {
- if (path.length() > 0 && name.length() == 0) {
- int i = dataUtils.containsServer(new String[] {name, path});
- if (i != -1) name = dataUtils.getServers().get(i)[0];
- }
- SftpConnectDialog sftpConnectDialog = new SftpConnectDialog();
- String finalName = name;
- Flowable.fromCallable(() -> new NetCopyClientConnectionPool.ConnectionInfo(path))
- .flatMap(
- connectionInfo -> {
- Bundle retval = new Bundle();
- retval.putString(ARG_PROTOCOL, connectionInfo.getPrefix());
- retval.putString(ARG_NAME, finalName);
- retval.putString(ARG_ADDRESS, connectionInfo.getHost());
- retval.putInt(ARG_PORT, connectionInfo.getPort());
- if (!TextUtils.isEmpty(connectionInfo.getDefaultPath())) {
- retval.putString(ARG_DEFAULT_PATH, connectionInfo.getDefaultPath());
- }
- retval.putString(ARG_USERNAME, connectionInfo.getUsername());
-
- if (connectionInfo.getPassword() == null) {
- retval.putBoolean(ARG_HAS_PASSWORD, false);
- retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path));
- } else {
- retval.putBoolean(ARG_HAS_PASSWORD, true);
- retval.putString(ARG_PASSWORD, connectionInfo.getPassword());
- }
- retval.putBoolean(ARG_EDIT, edit);
- return Flowable.just(retval);
- })
- .subscribeOn(Schedulers.computation())
- .subscribe(
- bundle -> {
- sftpConnectDialog.setArguments(bundle);
- sftpConnectDialog.setCancelable(true);
- sftpConnectDialog.show(getSupportFragmentManager(), "sftpdialog");
- });
- }
-
- /**
- * Shows a view that goes from white at it's lowest part to transparent a the top. It covers the
- * fragment.
- */
- public void showSmokeScreen() {
- fabBgView.show();
- }
-
- public void hideSmokeScreen() {
- fabBgView.hide();
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void addConnection(
- boolean edit,
- @NonNull final String name,
- @NonNull final String path,
- @Nullable final String encryptedPath,
- @Nullable final String oldname,
- @Nullable final String oldPath) {
- String[] s = new String[] {name, path};
- if (!edit) {
- if ((dataUtils.containsServer(path)) == -1) {
- Completable.fromRunnable(
- () -> {
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.SMB, name, encryptedPath));
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- () -> {
- dataUtils.addServer(s);
- drawer.refreshDrawer();
- // grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.loadlist(path, false, OpenMode.UNKNOWN, true);
- return null;
- },
- true);
- });
- } else {
- Snackbar.make(
- findViewById(R.id.navigation),
- getString(R.string.connection_exists),
- Snackbar.LENGTH_SHORT)
- .show();
- }
- } else {
- int i = dataUtils.containsServer(new String[] {oldname, oldPath});
- if (i != -1) {
- dataUtils.removeServer(i);
-
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.renameSMB(oldname, oldPath, name, path);
- });
- // mainActivity.grid.removePath(oldname, oldPath, DataUtils.SMB);
- }
- dataUtils.addServer(s);
- Collections.sort(dataUtils.getServers(), new BookSorter());
- drawer.refreshDrawer();
- // mainActivity.grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
- }
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void deleteConnection(final String name, final String path) {
- int i = dataUtils.containsServer(new String[] {name, path});
- if (i != -1) {
- dataUtils.removeServer(i);
- Completable.fromCallable(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(UtilsHandler.Operation.SMB, name, path));
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void delete(String title, String path) {
- Completable.fromCallable(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(UtilsHandler.Operation.BOOKMARKS, title, path));
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void modify(String oldpath, String oldname, String newPath, String newname) {
- Completable.fromCallable(
- () -> {
- utilsHandler.renameBookmark(oldname, oldpath, newname, newPath);
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
-
- @Override
- public void onPreExecute(String query) {
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.mSwipeRefreshLayout.setRefreshing(true);
- mainFragment.onSearchPreExecute(query);
- return null;
- });
- }
-
- @Override
- public void onPostExecute(String query) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- // TODO cancel search
- return;
- }
-
- mainFragment.onSearchCompleted(query);
- mainFragment.mSwipeRefreshLayout.setRefreshing(false);
- }
-
- @Override
- public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- // TODO cancel search
- return;
- }
-
- mainFragment.addSearchResult(hybridFileParcelable, query);
- }
-
- @Override
- public void onCancelled() {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- return;
- }
-
- mainFragment.reloadListElements(
- false, false, !mainFragment.getMainFragmentViewModel().isList());
- mainFragment.mSwipeRefreshLayout.setRefreshing(false);
- }
-
- @Override
- public void addConnection(OpenMode service) {
- try {
- if (cloudHandler.findEntry(service) != null) {
- // cloud entry already exists
- Toast.makeText(
- this, getResources().getString(R.string.connection_exists), Toast.LENGTH_LONG)
- .show();
- } else if (BuildConfig.IS_VERSION_FDROID) {
- Toast.makeText(
- this, getResources().getString(R.string.cloud_error_fdroid), Toast.LENGTH_LONG)
- .show();
- } else {
- Toast.makeText(
- MainActivity.this,
- getResources().getString(R.string.please_wait),
- Toast.LENGTH_LONG)
- .show();
- Bundle args = new Bundle();
- args.putInt(ARGS_KEY_LOADER, service.ordinal());
-
- // check if we already had done some work on the loader
- Loader loader = getSupportLoaderManager().getLoader(REQUEST_CODE_CLOUD_LIST_KEY);
- if (loader != null && loader.isStarted()) {
-
- // making sure that loader is not started
- getSupportLoaderManager().destroyLoader(REQUEST_CODE_CLOUD_LIST_KEY);
- }
-
- getSupportLoaderManager().initLoader(REQUEST_CODE_CLOUD_LIST_KEY, args, this);
- }
- } catch (CloudPluginException e) {
- LOG.warn("failure when adding cloud plugin connections", e);
- Toast.makeText(this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
- .show();
- }
- }
-
- @Override
- public void deleteConnection(OpenMode service) {
- cloudHandler.clear(service);
- dataUtils.removeAccount(service);
-
- runOnUiThread(drawer::refreshDrawer);
- }
-
- @NonNull
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- Uri uri =
- Uri.withAppendedPath(
- Uri.parse("content://" + CloudContract.PROVIDER_AUTHORITY), "/keys.db/secret_keys");
-
- String[] projection =
- new String[] {
- CloudContract.COLUMN_ID,
- CloudContract.COLUMN_CLIENT_ID,
- CloudContract.COLUMN_CLIENT_SECRET_KEY
- };
-
- switch (id) {
- case REQUEST_CODE_CLOUD_LIST_KEY:
- Uri uriAppendedPath = uri;
- switch (OpenMode.getOpenMode(args.getInt(ARGS_KEY_LOADER, 2))) {
- case GDRIVE:
- uriAppendedPath = ContentUris.withAppendedId(uri, 2);
- break;
- case DROPBOX:
- uriAppendedPath = ContentUris.withAppendedId(uri, 3);
- break;
- case BOX:
- uriAppendedPath = ContentUris.withAppendedId(uri, 4);
- break;
- case ONEDRIVE:
- uriAppendedPath = ContentUris.withAppendedId(uri, 5);
- break;
- }
- return new CursorLoader(this, uriAppendedPath, projection, null, null, null);
- case REQUEST_CODE_CLOUD_LIST_KEYS:
- // we need a list of all secret keys
-
- try {
- List cloudEntries = cloudHandler.getAllEntries();
- // we want keys for services saved in database, and the cloudrail app key which
- // is at index 1
- String ids[] = new String[cloudEntries.size() + 1];
+ floatingActionButton.close(true);
+ return true;
+ }
- ids[0] = 1 + "";
- for (int i = 1; i <= cloudEntries.size(); i++) {
+ }
- // we need to get only those cloud details which user wants
- switch (cloudEntries.get(i - 1).getServiceType()) {
- case GDRIVE:
- ids[i] = 2 + "";
- break;
- case DROPBOX:
- ids[i] = 3 + "";
- break;
- case BOX:
- ids[i] = 4 + "";
+ /**
+ * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path.
+ *
+ * @param dialog
+ * @param folder selected folder
+ * @see FtpServerFragment#changeFTPServerPath(String)
+ * @see FolderChooserDialog
+ * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
+ */
+ @Override
+ public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
+ switch (dialog.getTag()) {
+ case FtpServerFragment.TAG:
+ FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
+ if (folder.exists() && folder.isDirectory()) {
+ if (FileUtils.isRunningAboveStorage(folder.getAbsolutePath())) {
+ if (!isRootExplorer()) {
+ AlertDialog.show(
+ this,
+ R.string.ftp_server_root_unavailable,
+ R.string.error,
+ android.R.string.ok,
+ null,
+ false);
+ } else {
+ MaterialDialog confirmDialog =
+ GeneralDialogCreation.showBasicDialog(
+ this,
+ R.string.ftp_server_root_filesystem_warning,
+ R.string.warning,
+ android.R.string.ok,
+ android.R.string.cancel);
+ confirmDialog
+ .getActionButton(DialogAction.POSITIVE)
+ .setOnClickListener(
+ v -> {
+ ftpServerFragment.changeFTPServerPath(folder.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT)
+ .show();
+ confirmDialog.dismiss();
+ });
+ confirmDialog
+ .getActionButton(DialogAction.NEGATIVE)
+ .setOnClickListener(v -> confirmDialog.dismiss());
+ confirmDialog.show();
+ }
+ } else {
+ ftpServerFragment.changeFTPServerPath(folder.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // try to get parent
+ String pathParentFilePath = folder.getParent();
+ if (pathParentFilePath == null) {
+ dialog.dismiss();
+ return;
+ }
+ File pathParentFile = new File(pathParentFilePath);
+ if (pathParentFile.exists() && pathParentFile.isDirectory()) {
+ ftpServerFragment.changeFTPServerPath(pathParentFile.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+ } else {
+ // don't have access, print error
+ Toast.makeText(this, R.string.ftp_path_change_error_invalid, Toast.LENGTH_SHORT).show();
+ }
+ }
+ dialog.dismiss();
break;
- case ONEDRIVE:
- ids[i] = 5 + "";
+ default:
+ dialog.dismiss();
break;
- }
- }
- return new CursorLoader(this, uri, projection, CloudContract.COLUMN_ID, ids, null);
- } catch (CloudPluginException e) {
- LOG.warn("failure when fetching cloud connections", e);
- Toast.makeText(
- this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
- .show();
}
- default:
- Uri undefinedUriAppendedPath = ContentUris.withAppendedId(uri, 7);
- return new CursorLoader(this, undefinedUriAppendedPath, projection, null, null, null);
}
- }
- @Override
- public void onLoadFinished(Loader loader, final Cursor data) {
- if (data == null) {
- Toast.makeText(
- this,
- getResources().getString(R.string.cloud_error_failed_restart),
- Toast.LENGTH_LONG)
- .show();
- return;
+ /**
+ * Get whether list item is selected for action mode or not
+ *
+ * @return value
+ */
+ public boolean getListItemSelected() {
+ return this.listItemSelected;
}
- /*
- * This is hack for repeated calls to onLoadFinished(),
- * we take the Cursor provided to check if the function
- * has already been called on it.
+ public String getScrollToFileName() {
+ return this.scrollToFileName;
+ }
+
+ /**
+ * Set list item selected value
*
- * TODO: find a fix for repeated callbacks to onLoadFinished()
+ * @param value value
*/
- if (cloudCursorData != null && cloudCursorData == data) return;
- cloudCursorData = data;
-
- if (cloudLoaderAsyncTask != null
- && cloudLoaderAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
- return;
- }
- cloudLoaderAsyncTask = new CloudLoaderAsyncTask(this, cloudHandler, cloudCursorData);
- cloudLoaderAsyncTask.execute();
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- // For passing code check
- }
-
- public void initCornersDragListener(boolean destroy, boolean shouldInvokeLeftAndRight) {
- initBottomDragListener(destroy);
- initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
- }
-
- private void initBottomDragListener(boolean destroy) {
- View bottomPlaceholder = findViewById(R.id.placeholder_drag_bottom);
- if (destroy) {
- bottomPlaceholder.setOnDragListener(null);
- bottomPlaceholder.setVisibility(View.GONE);
- } else {
- bottomPlaceholder.setVisibility(View.VISIBLE);
- bottomPlaceholder.setOnDragListener(
- new TabFragmentBottomDragListener(
- () -> {
- getCurrentMainFragment().smoothScrollListView(false);
- return null;
- },
- () -> {
- getCurrentMainFragment().stopSmoothScrollListView();
- return null;
- }));
- }
- }
-
- private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInvokeLeftAndRight) {
- TabFragment tabFragment = getTabFragment();
- tabFragment.initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
- }
-
- private static final class FabActionListener implements SpeedDialView.OnActionSelectedListener {
-
- MainActivity mainActivity;
- SpeedDialView floatingActionButton;
-
- FabActionListener(MainActivity mainActivity) {
- this.mainActivity = mainActivity;
- this.floatingActionButton = mainActivity.floatingActionButton;
+ public void setListItemSelected(boolean value) {
+ this.listItemSelected = value;
}
+ /**
+ * Do nothing other than dismissing the folder selection dialog.
+ *
+ * @param dialog
+ * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
+ */
@Override
- public boolean onActionSelected(SpeedDialActionItem actionItem) {
- final MainFragment ma =
- (MainFragment)
- ((TabFragment)
- mainActivity.getSupportFragmentManager().findFragmentById(R.id.content_frame))
- .getCurrentTabFragment();
- final String path = ma.getCurrentPath();
-
- switch (actionItem.getId()) {
- case R.id.menu_new_folder:
- mainActivity.mainActivityHelper.mkdir(
- ma.getMainFragmentViewModel().getOpenMode(), path, ma);
- break;
- case R.id.menu_new_file:
- mainActivity.mainActivityHelper.mkfile(
- ma.getMainFragmentViewModel().getOpenMode(), path, ma);
- break;
- case R.id.menu_new_cloud:
- BottomSheetDialogFragment fragment = new CloudSheetFragment();
- fragment.show(
- ma.getActivity().getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT);
- break;
- }
+ public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {
+ dialog.dismiss();
+ }
- floatingActionButton.close(true);
- return true;
- }
- }
- /**
- * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path.
- *
- * @see FtpServerFragment#changeFTPServerPath(String)
- * @see FolderChooserDialog
- * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
- * @param dialog
- * @param folder selected folder
- */
- @Override
- public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
- switch (dialog.getTag()) {
- case FtpServerFragment.TAG:
- FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
- if (folder.exists() && folder.isDirectory()) {
- if (FileUtils.isRunningAboveStorage(folder.getAbsolutePath())) {
- if (!isRootExplorer()) {
- AlertDialog.show(
- this,
- R.string.ftp_server_root_unavailable,
- R.string.error,
- android.R.string.ok,
- null,
- false);
- } else {
- MaterialDialog confirmDialog =
- GeneralDialogCreation.showBasicDialog(
- this,
- R.string.ftp_server_root_filesystem_warning,
- R.string.warning,
- android.R.string.ok,
- android.R.string.cancel);
- confirmDialog
- .getActionButton(DialogAction.POSITIVE)
- .setOnClickListener(
- v -> {
- ftpServerFragment.changeFTPServerPath(folder.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT)
- .show();
- confirmDialog.dismiss();
- });
- confirmDialog
- .getActionButton(DialogAction.NEGATIVE)
- .setOnClickListener(v -> confirmDialog.dismiss());
- confirmDialog.show();
- }
- } else {
- ftpServerFragment.changeFTPServerPath(folder.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
- }
+ private void executeWithMainFragment(@NonNull Function lambda) {
+ executeWithMainFragment(lambda, false);
+ }
+
+ @Nullable
+ private void executeWithMainFragment(
+ @NonNull Function lambda, boolean showToastIfMainFragmentIsNull) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment != null && mainFragment.getMainFragmentViewModel() != null) {
+ lambda.apply(mainFragment);
} else {
- // try to get parent
- String pathParentFilePath = folder.getParent();
- if (pathParentFilePath == null) {
- dialog.dismiss();
- return;
- }
- File pathParentFile = new File(pathParentFilePath);
- if (pathParentFile.exists() && pathParentFile.isDirectory()) {
- ftpServerFragment.changeFTPServerPath(pathParentFile.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
- } else {
- // don't have access, print error
- Toast.makeText(this, R.string.ftp_path_change_error_invalid, Toast.LENGTH_SHORT).show();
- }
+ LOG.warn("MainFragment is null");
+ if (showToastIfMainFragmentIsNull) {
+ AppConfig.toast(this, R.string.operation_unsuccesful);
+ }
}
- dialog.dismiss();
- break;
- default:
- dialog.dismiss();
- break;
- }
- }
-
- /**
- * Get whether list item is selected for action mode or not
- *
- * @return value
- */
- public boolean getListItemSelected() {
- return this.listItemSelected;
- }
-
- public String getScrollToFileName() {
- return this.scrollToFileName;
- }
-
- /**
- * Set list item selected value
- *
- * @param value value
- */
- public void setListItemSelected(boolean value) {
- this.listItemSelected = value;
- }
-
- /**
- * Do nothing other than dismissing the folder selection dialog.
- *
- * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
- * @param dialog
- */
- @Override
- public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {
- dialog.dismiss();
- }
-
- private void executeWithMainFragment(@NonNull Function lambda) {
- executeWithMainFragment(lambda, false);
- }
-
- @Nullable
- private void executeWithMainFragment(
- @NonNull Function lambda, boolean showToastIfMainFragmentIsNull) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment != null && mainFragment.getMainFragmentViewModel() != null) {
- lambda.apply(mainFragment);
- } else {
- LOG.warn("MainFragment is null");
- if (showToastIfMainFragmentIsNull) {
- AppConfig.toast(this, R.string.operation_unsuccesful);
- }
}
- }
}
diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
index f6b1345d13..c792d5d00b 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
@@ -236,4 +236,5 @@ private void initSearchViewColor(MainActivity a) {
public interface SearchListener {
void onSearch(String queue);
}
+
}
From 929ba5d78dc83635389abaf69d99d43308d455a7 Mon Sep 17 00:00:00 2001
From: peerzadaburhan
Date: Thu, 2 Feb 2023 14:45:47 +0530
Subject: [PATCH 043/384] Issue #3394
---
.../ui/activities/MainActivity.java | 4316 ++++++++---------
.../ui/views/appbar/SearchView.java | 1 -
2 files changed, 2140 insertions(+), 2177 deletions(-)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index e1f509521e..1ebb3a8b9f 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -162,7 +162,6 @@
import android.content.res.Configuration;
import android.database.Cursor;
import android.graphics.Color;
-import android.graphics.Rect;
import android.graphics.drawable.ColorDrawable;
import android.hardware.usb.UsbManager;
import android.media.RingtoneManager;
@@ -176,19 +175,12 @@
import android.os.storage.StorageVolume;
import android.service.quicksettings.TileService;
import android.text.TextUtils;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.util.TypedValue;
import android.view.KeyEvent;
-import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.WindowInsets;
import android.view.animation.DecelerateInterpolator;
-import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import androidx.annotation.DrawableRes;
@@ -199,9 +191,6 @@
import androidx.annotation.StringRes;
import androidx.arch.core.util.Function;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowInsetsCompat;
-import androidx.drawerlayout.widget.DrawerLayout;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.loader.app.LoaderManager;
@@ -216,7 +205,7 @@
import io.reactivex.schedulers.Schedulers;
public class MainActivity extends PermissionsActivity
- implements SmbConnectionListener,
+ implements SmbConnectionListener,
BookmarkCallback,
SearchWorkerFragment.HelperCallbacks,
CloudConnectionCallbacks,
@@ -224,803 +213,788 @@ public class MainActivity extends PermissionsActivity
FolderChooserDialog.FolderCallback,
PermissionsActivity.OnPermissionGranted {
- private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
-
- public static final Pattern DIR_SEPARATOR = Pattern.compile("/");
- public static final String TAG_ASYNC_HELPER = "async_helper";
-
- private DataUtils dataUtils;
-
- public String path = "";
- public boolean mReturnIntent = false;
- public boolean isCompressedOpen = false;
- public boolean mRingtonePickerIntent = false;
- public int skinStatusBar;
-
- private SpeedDialView floatingActionButton;
-
- public MainActivityHelper mainActivityHelper;
-
- public int operation = -1;
- public ArrayList oparrayList;
- public ArrayList> oparrayListList;
-
- // oppathe - the path at which certain operation needs to be performed
- // oppathe1 - the new path which user wants to create/modify
- // oppathList - the paths at which certain operation needs to be performed (pairs with
- // oparrayList)
- public String oppathe, oppathe1;
- public ArrayList oppatheList;
-
- // This holds the Uris to be written at initFabToSave()
- private ArrayList urisToBeSaved;
-
- public static final String PASTEHELPER_BUNDLE = "pasteHelper";
-
- private static final String KEY_DRAWER_SELECTED = "selectitem";
- private static final String KEY_OPERATION_PATH = "oppathe";
- private static final String KEY_OPERATED_ON_PATH = "oppathe1";
- private static final String KEY_OPERATIONS_PATH_LIST = "oparraylist";
- private static final String KEY_OPERATION = "operation";
- private static final String KEY_SELECTED_LIST_ITEM = "select_list_item";
-
- private AppBar appbar;
- private Drawer drawer;
- // private HistoryManager history, grid;
- private MainActivity mainActivity = this;
- private String pathInCompressedArchive;
- private boolean openProcesses = false;
- private MaterialDialog materialDialog;
- private boolean backPressedToExitOnce = false;
- private WeakReference toast = new WeakReference<>(null);
- private Intent intent;
- private View indicator_layout;
-
- private AppBarLayout appBarLayout;
-
- private SpeedDialOverlayLayout fabBgView;
- private UtilsHandler utilsHandler;
- private CloudHandler cloudHandler;
- private CloudLoaderAsyncTask cloudLoaderAsyncTask;
- /**
- * This is for a hack.
- *
- * @see MainActivity#onLoadFinished(Loader, Cursor)
- */
- private Cursor cloudCursorData = null;
-
- public static final int REQUEST_CODE_SAF = 223;
-
- public static final String KEY_INTENT_PROCESS_VIEWER = "openprocesses";
- public static final String TAG_INTENT_FILTER_FAILED_OPS = "failedOps";
- public static final String TAG_INTENT_FILTER_GENERAL = "general_communications";
- public static final String ARGS_KEY_LOADER = "loader_cloud_args_service";
-
- /**
- * Broadcast which will be fired after every file operation, will denote list loading Registered
- * by {@link MainFragment}
- */
- public static final String KEY_INTENT_LOAD_LIST = "loadlist";
-
- /**
- * Extras carried by the list loading intent Contains path of parent directory in which operation
- * took place, so that we can run media scanner on it
- */
- public static final String KEY_INTENT_LOAD_LIST_FILE = "loadlist_file";
-
- /**
- * Mime type in intent that apps need to pass when trying to open file manager from a specific
- * directory Should be clubbed with {@link Intent#ACTION_VIEW} and send in path to open in intent
- * data field
- */
- public static final String ARGS_INTENT_ACTION_VIEW_MIME_FOLDER = "resource/folder";
-
- public static final String ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL = "application/*";
-
- public static final String CLOUD_AUTHENTICATOR_GDRIVE = "android.intent.category.BROWSABLE";
- public static final String CLOUD_AUTHENTICATOR_REDIRECT_URI = "com.amaze.filemanager:/auth";
-
- // the current visible tab, either 0 or 1
- public static int currentTab;
- private boolean listItemSelected = false;
-
- private String scrollToFileName = null;
-
- public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463;
- public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472;
-
- private PasteHelper pasteHelper;
- private MainActivityActionMode mainActivityActionMode;
+ private static final Logger LOG = LoggerFactory.getLogger(MainActivity.class);
+
+ public static final Pattern DIR_SEPARATOR = Pattern.compile("/");
+ public static final String TAG_ASYNC_HELPER = "async_helper";
+
+ private DataUtils dataUtils;
+
+ public String path = "";
+ public boolean mReturnIntent = false;
+ public boolean isCompressedOpen = false;
+ public boolean mRingtonePickerIntent = false;
+ public int skinStatusBar;
+
+ private SpeedDialView floatingActionButton;
+
+ public MainActivityHelper mainActivityHelper;
+
+ public int operation = -1;
+ public ArrayList oparrayList;
+ public ArrayList> oparrayListList;
+
+ // oppathe - the path at which certain operation needs to be performed
+ // oppathe1 - the new path which user wants to create/modify
+ // oppathList - the paths at which certain operation needs to be performed (pairs with
+ // oparrayList)
+ public String oppathe, oppathe1;
+ public ArrayList oppatheList;
+
+ // This holds the Uris to be written at initFabToSave()
+ private ArrayList urisToBeSaved;
+
+ public static final String PASTEHELPER_BUNDLE = "pasteHelper";
+
+ private static final String KEY_DRAWER_SELECTED = "selectitem";
+ private static final String KEY_OPERATION_PATH = "oppathe";
+ private static final String KEY_OPERATED_ON_PATH = "oppathe1";
+ private static final String KEY_OPERATIONS_PATH_LIST = "oparraylist";
+ private static final String KEY_OPERATION = "operation";
+ private static final String KEY_SELECTED_LIST_ITEM = "select_list_item";
+
+ private AppBar appbar;
+ private Drawer drawer;
+ // private HistoryManager history, grid;
+ private MainActivity mainActivity = this;
+ private String pathInCompressedArchive;
+ private boolean openProcesses = false;
+ private MaterialDialog materialDialog;
+ private boolean backPressedToExitOnce = false;
+ private WeakReference toast = new WeakReference<>(null);
+ private Intent intent;
+ private View indicator_layout;
- private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0";
- private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage";
- private static final String INTENT_ACTION_OPEN_QUICK_ACCESS =
- "com.amaze.filemanager.openQuickAccess";
- private static final String INTENT_ACTION_OPEN_RECENT = "com.amaze.filemanager.openRecent";
- private static final String INTENT_ACTION_OPEN_FTP_SERVER = "com.amaze.filemanager.openFTPServer";
- private static final String INTENT_ACTION_OPEN_APP_MANAGER =
- "com.amaze.filemanager.openAppManager";
+ private AppBarLayout appBarLayout;
- /**
- * Called when the activity is first created.
- */
- @Override
- public void onCreate(final Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main_toolbar);
-
-
- intent = getIntent();
-
-
- dataUtils = DataUtils.getInstance();
- if (savedInstanceState != null) {
- listItemSelected = savedInstanceState.getBoolean(KEY_SELECTED_LIST_ITEM, false);
- }
-
- initialisePreferences();
- initializeInteractiveShell();
+ private SpeedDialOverlayLayout fabBgView;
+ private UtilsHandler utilsHandler;
+ private CloudHandler cloudHandler;
+ private CloudLoaderAsyncTask cloudLoaderAsyncTask;
+ /**
+ * This is for a hack.
+ *
+ * @see MainActivity#onLoadFinished(Loader, Cursor)
+ */
+ private Cursor cloudCursorData = null;
+ public static final int REQUEST_CODE_SAF = 223;
- dataUtils.registerOnDataChangedListener(new SaveOnDataUtilsChange(drawer));
+ public static final String KEY_INTENT_PROCESS_VIEWER = "openprocesses";
+ public static final String TAG_INTENT_FILTER_FAILED_OPS = "failedOps";
+ public static final String TAG_INTENT_FILTER_GENERAL = "general_communications";
+ public static final String ARGS_KEY_LOADER = "loader_cloud_args_service";
- AppConfig.getInstance().setMainActivityContext(this);
+ /**
+ * Broadcast which will be fired after every file operation, will denote list loading Registered
+ * by {@link MainFragment}
+ */
+ public static final String KEY_INTENT_LOAD_LIST = "loadlist";
- initialiseViews();
- utilsHandler = AppConfig.getInstance().getUtilsHandler();
- cloudHandler = new CloudHandler(this, AppConfig.getInstance().getExplorerDatabase());
+ /**
+ * Extras carried by the list loading intent Contains path of parent directory in which operation
+ * took place, so that we can run media scanner on it
+ */
+ public static final String KEY_INTENT_LOAD_LIST_FILE = "loadlist_file";
- initialiseFab(); // TODO: 7/12/2017 not init when actionIntent != null
- mainActivityHelper = new MainActivityHelper(this);
- mainActivityActionMode = new MainActivityActionMode(new WeakReference<>(MainActivity.this));
+ /**
+ * Mime type in intent that apps need to pass when trying to open file manager from a specific
+ * directory Should be clubbed with {@link Intent#ACTION_VIEW} and send in path to open in intent
+ * data field
+ */
+ public static final String ARGS_INTENT_ACTION_VIEW_MIME_FOLDER = "resource/folder";
- if (CloudSheetFragment.isCloudProviderAvailable(this)) {
+ public static final String ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL = "application/*";
- LoaderManager.getInstance(this).initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
- }
-
- path = intent.getStringExtra("path");
- openProcesses = intent.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false);
+ public static final String CLOUD_AUTHENTICATOR_GDRIVE = "android.intent.category.BROWSABLE";
+ public static final String CLOUD_AUTHENTICATOR_REDIRECT_URI = "com.amaze.filemanager:/auth";
- if (intent.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- intent.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, this);
- }
- }
+ // the current visible tab, either 0 or 1
+ public static int currentTab;
+ private boolean listItemSelected = false;
- checkForExternalIntent(intent);
+ private String scrollToFileName = null;
- drawer.setDrawerIndicatorEnabled();
+ public static final int REQUEST_CODE_CLOUD_LIST_KEYS = 5463;
+ public static final int REQUEST_CODE_CLOUD_LIST_KEY = 5472;
+ private PasteHelper pasteHelper;
+ private MainActivityActionMode mainActivityActionMode;
+
+ private static final String DEFAULT_FALLBACK_STORAGE_PATH = "/storage/sdcard0";
+ private static final String INTERNAL_SHARED_STORAGE = "Internal shared storage";
+ private static final String INTENT_ACTION_OPEN_QUICK_ACCESS =
+ "com.amaze.filemanager.openQuickAccess";
+ private static final String INTENT_ACTION_OPEN_RECENT = "com.amaze.filemanager.openRecent";
+ private static final String INTENT_ACTION_OPEN_FTP_SERVER = "com.amaze.filemanager.openFTPServer";
+ private static final String INTENT_ACTION_OPEN_APP_MANAGER =
+ "com.amaze.filemanager.openAppManager";
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_toolbar);
+
+ intent = getIntent();
+
+ dataUtils = DataUtils.getInstance();
+ if (savedInstanceState != null) {
+ listItemSelected = savedInstanceState.getBoolean(KEY_SELECTED_LIST_ITEM, false);
+ }
+
+ initialisePreferences();
+ initializeInteractiveShell();
+
+ dataUtils.registerOnDataChangedListener(new SaveOnDataUtilsChange(drawer));
+
+ AppConfig.getInstance().setMainActivityContext(this);
+
+ initialiseViews();
+ utilsHandler = AppConfig.getInstance().getUtilsHandler();
+ cloudHandler = new CloudHandler(this, AppConfig.getInstance().getExplorerDatabase());
+
+ initialiseFab(); // TODO: 7/12/2017 not init when actionIntent != null
+ mainActivityHelper = new MainActivityHelper(this);
+ mainActivityActionMode = new MainActivityActionMode(new WeakReference<>(MainActivity.this));
- if (!getBoolean(PREFERENCE_BOOKMARKS_ADDED)) {
- utilsHandler.addCommonBookmarks();
- getPrefs().edit().putBoolean(PREFERENCE_BOOKMARKS_ADDED, true).commit();
- }
+ if (CloudSheetFragment.isCloudProviderAvailable(this)) {
- checkForExternalPermission();
-
- Completable.fromRunnable(
- () -> {
- dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
- dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
- dataUtils.setGridfiles(utilsHandler.getGridViewList());
- dataUtils.setListfiles(utilsHandler.getListViewList());
- dataUtils.setBooks(utilsHandler.getBookmarksList());
- ArrayList servers = new ArrayList<>();
- servers.addAll(utilsHandler.getSmbList());
- servers.addAll(utilsHandler.getSftpList());
- dataUtils.setServers(servers);
-
- ExtensionsKt.updateAUAlias(
- this,
- !PackageUtils.Companion.appInstalledOrNot(
- AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager())
- && !getBoolean(
- PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS));
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- new CompletableObserver() {
- @Override
- public void onSubscribe(@NonNull Disposable d) {
- }
-
- @Override
- public void onComplete() {
- drawer.refreshDrawer();
- invalidateFragmentAndBundle(savedInstanceState, false);
- }
-
- @Override
- public void onError(@NonNull Throwable e) {
- LOG.error("Error setting up DataUtils", e);
- drawer.refreshDrawer();
- invalidateFragmentAndBundle(savedInstanceState, false);
- }
- });
- initStatusBarResources(findViewById(R.id.drawer_layout));
+ LoaderManager.getInstance(this).initLoader(REQUEST_CODE_CLOUD_LIST_KEYS, null, this);
}
- public void invalidateFragmentAndBundle(Bundle savedInstanceState, boolean isCloudRefresh) {
- if (savedInstanceState == null) {
- if (openProcesses) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(
- R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
- // transaction.addToBackStack(null);
- openProcesses = false;
- // title.setText(utils.getString(con, R.string.process_viewer));
- // Commit the transaction
- transaction.commit();
- supportInvalidateOptionsMenu();
- } else if (intent.getAction() != null
- && (intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)
- || INTENT_ACTION_OPEN_FTP_SERVER.equals(intent.getAction()))) {
- // tile preferences, open ftp fragment
-
- FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
- transaction2.replace(R.id.content_frame, new FtpServerFragment());
- appBarLayout
- .animate()
- .translationY(0)
- .setInterpolator(new DecelerateInterpolator(2))
- .start();
-
- drawer.deselectEverything();
- transaction2.commit();
- } else if (intent.getAction() != null
- && INTENT_ACTION_OPEN_APP_MANAGER.equals(intent.getAction())) {
- FragmentTransaction transaction3 = getSupportFragmentManager().beginTransaction();
- transaction3.replace(R.id.content_frame, new AppsListFragment());
- appBarLayout
- .animate()
- .translationY(0)
- .setInterpolator(new DecelerateInterpolator(2))
- .start();
-
- drawer.deselectEverything();
- transaction3.commit();
- } else {
- if (path != null && path.length() > 0) {
- HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
- file.generateMode(MainActivity.this);
- if (file.isCloudDriveFile() && dataUtils.getAccounts().size() == 0) {
- // not ready to serve cloud files
- goToMain(null);
- } else if (file.isDirectory(MainActivity.this) && !isCloudRefresh) {
- goToMain(path);
- } else {
- if (!isCloudRefresh) {
- goToMain(null);
- }
- if (file.isSmb() || file.isSftp()) {
- String authorisedPath =
- SshClientUtils.formatPlainServerPathToAuthorised(dataUtils.getServers(), path);
- file.setPath(authorisedPath);
- LOG.info(
- "Opening smb file from deeplink, modify plain path to authorised path {}",
- authorisedPath);
- }
- file.openFile(this, true);
- }
- } else if (!isCloudRefresh) {
- goToMain(null);
- }
- }
- } else {
- pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
- oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
- oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
- oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
- operation = savedInstanceState.getInt(KEY_OPERATION);
- int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
- getDrawer().selectCorrectDrawerItem(selectedStorage);
- }
- }
+ path = intent.getStringExtra("path");
+ openProcesses = intent.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false);
- @Override
- public void onPermissionGranted() {
- drawer.refreshDrawer();
- TabFragment tabFragment = getTabFragment();
- boolean b = getBoolean(PREFERENCE_NEED_TO_SET_HOME);
- // reset home and current paths according to new storages
- if (b) {
- TabHandler tabHandler = TabHandler.getInstance();
- tabHandler
- .clear()
- .subscribe(
- () -> {
- if (tabFragment != null) {
- tabFragment.refactorDrawerStorages(false);
- Fragment main = tabFragment.getFragmentAtIndex(0);
- if (main != null)
- ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1));
- Fragment main1 = tabFragment.getFragmentAtIndex(1);
- if (main1 != null)
- ((MainFragment) main1).updateTabWithDb(tabHandler.findTab(2));
- }
- getPrefs().edit().putBoolean(PREFERENCE_NEED_TO_SET_HOME, false).commit();
- });
- } else {
- // just refresh list
- if (tabFragment != null) {
- Fragment main = tabFragment.getFragmentAtIndex(0);
- if (main != null) ((MainFragment) main).updateList(false);
- Fragment main1 = tabFragment.getFragmentAtIndex(1);
- if (main1 != null) ((MainFragment) main1).updateList(false);
- }
- }
+ if (intent.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ intent.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, this);
+ }
}
- private void checkForExternalPermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (!checkStoragePermission()) {
- requestStoragePermission(this, true);
+ checkForExternalIntent(intent);
+
+ drawer.setDrawerIndicatorEnabled();
+
+ if (!getBoolean(PREFERENCE_BOOKMARKS_ADDED)) {
+ utilsHandler.addCommonBookmarks();
+ getPrefs().edit().putBoolean(PREFERENCE_BOOKMARKS_ADDED, true).commit();
+ }
+
+ checkForExternalPermission();
+
+ Completable.fromRunnable(
+ () -> {
+ dataUtils.setHiddenFiles(utilsHandler.getHiddenFilesConcurrentRadixTree());
+ dataUtils.setHistory(utilsHandler.getHistoryLinkedList());
+ dataUtils.setGridfiles(utilsHandler.getGridViewList());
+ dataUtils.setListfiles(utilsHandler.getListViewList());
+ dataUtils.setBooks(utilsHandler.getBookmarksList());
+ ArrayList servers = new ArrayList<>();
+ servers.addAll(utilsHandler.getSmbList());
+ servers.addAll(utilsHandler.getSftpList());
+ dataUtils.setServers(servers);
+
+ ExtensionsKt.updateAUAlias(
+ this,
+ !PackageUtils.Companion.appInstalledOrNot(
+ AboutActivity.PACKAGE_AMAZE_UTILS, mainActivity.getPackageManager())
+ && !getBoolean(
+ PreferencesConstants.PREFERENCE_DISABLE_PLAYER_INTENT_FILTERS));
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ new CompletableObserver() {
+ @Override
+ public void onSubscribe(@NonNull Disposable d) {}
+
+ @Override
+ public void onComplete() {
+ drawer.refreshDrawer();
+ invalidateFragmentAndBundle(savedInstanceState, false);
+ }
+
+ @Override
+ public void onError(@NonNull Throwable e) {
+ LOG.error("Error setting up DataUtils", e);
+ drawer.refreshDrawer();
+ invalidateFragmentAndBundle(savedInstanceState, false);
+ }
+ });
+ initStatusBarResources(findViewById(R.id.drawer_layout));
+ }
+
+ public void invalidateFragmentAndBundle(Bundle savedInstanceState, boolean isCloudRefresh) {
+ if (savedInstanceState == null) {
+ if (openProcesses) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(
+ R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
+ // transaction.addToBackStack(null);
+ openProcesses = false;
+ // title.setText(utils.getString(con, R.string.process_viewer));
+ // Commit the transaction
+ transaction.commit();
+ supportInvalidateOptionsMenu();
+ } else if (intent.getAction() != null
+ && (intent.getAction().equals(TileService.ACTION_QS_TILE_PREFERENCES)
+ || INTENT_ACTION_OPEN_FTP_SERVER.equals(intent.getAction()))) {
+ // tile preferences, open ftp fragment
+
+ FragmentTransaction transaction2 = getSupportFragmentManager().beginTransaction();
+ transaction2.replace(R.id.content_frame, new FtpServerFragment());
+ appBarLayout
+ .animate()
+ .translationY(0)
+ .setInterpolator(new DecelerateInterpolator(2))
+ .start();
+
+ drawer.deselectEverything();
+ transaction2.commit();
+ } else if (intent.getAction() != null
+ && INTENT_ACTION_OPEN_APP_MANAGER.equals(intent.getAction())) {
+ FragmentTransaction transaction3 = getSupportFragmentManager().beginTransaction();
+ transaction3.replace(R.id.content_frame, new AppsListFragment());
+ appBarLayout
+ .animate()
+ .translationY(0)
+ .setInterpolator(new DecelerateInterpolator(2))
+ .start();
+
+ drawer.deselectEverything();
+ transaction3.commit();
+ } else {
+ if (path != null && path.length() > 0) {
+ HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
+ file.generateMode(MainActivity.this);
+ if (file.isCloudDriveFile() && dataUtils.getAccounts().size() == 0) {
+ // not ready to serve cloud files
+ goToMain(null);
+ } else if (file.isDirectory(MainActivity.this) && !isCloudRefresh) {
+ goToMain(path);
+ } else {
+ if (!isCloudRefresh) {
+ goToMain(null);
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
- requestAllFilesAccess(this);
+ if (file.isSmb() || file.isSftp()) {
+ String authorisedPath =
+ SshClientUtils.formatPlainServerPathToAuthorised(dataUtils.getServers(), path);
+ file.setPath(authorisedPath);
+ LOG.info(
+ "Opening smb file from deeplink, modify plain path to authorised path {}",
+ authorisedPath);
}
+ file.openFile(this, true);
+ }
+ } else if (!isCloudRefresh) {
+ goToMain(null);
}
- }
-
- /**
- * Checks for the action to take when Amaze receives an intent from external source
- */
- private void checkForExternalIntent(Intent intent) {
- final String actionIntent = intent.getAction();
- if (actionIntent == null) {
- return;
- }
-
- final String type = intent.getType();
-
- if (actionIntent.equals(Intent.ACTION_GET_CONTENT)) {
- // file picker intent
- mReturnIntent = true;
- Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
-
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when picking file
- Utils.disableScreenRotation(this);
- } else if (actionIntent.equals(RingtoneManager.ACTION_RINGTONE_PICKER)) {
- // ringtone picker intent
- mReturnIntent = true;
- mRingtonePickerIntent = true;
- Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
-
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when picking file
- Utils.disableScreenRotation(this);
- } else if (actionIntent.equals(Intent.ACTION_VIEW)) {
- // zip viewer intent
- Uri uri = intent.getData();
-
- if (type != null
- && (type.equals(ARGS_INTENT_ACTION_VIEW_MIME_FOLDER)
- || type.equals(ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL))) {
- // support for syncting or intents from external apps that
- // need to start file manager from a specific path
-
- if (uri != null) {
-
- path = Utils.sanitizeInput(FileUtils.fromContentUri(uri).getAbsolutePath());
- scrollToFileName = intent.getStringExtra("com.amaze.fileutilities.AFM_LOCATE_FILE_NAME");
- } else {
- // no data field, open home for the tab in later processing
- path = null;
+ }
+ } else {
+ pasteHelper = savedInstanceState.getParcelable(PASTEHELPER_BUNDLE);
+ oppathe = savedInstanceState.getString(KEY_OPERATION_PATH);
+ oppathe1 = savedInstanceState.getString(KEY_OPERATED_ON_PATH);
+ oparrayList = savedInstanceState.getParcelableArrayList(KEY_OPERATIONS_PATH_LIST);
+ operation = savedInstanceState.getInt(KEY_OPERATION);
+ int selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
+ getDrawer().selectCorrectDrawerItem(selectedStorage);
+ }
+ }
+
+ @Override
+ public void onPermissionGranted() {
+ drawer.refreshDrawer();
+ TabFragment tabFragment = getTabFragment();
+ boolean b = getBoolean(PREFERENCE_NEED_TO_SET_HOME);
+ // reset home and current paths according to new storages
+ if (b) {
+ TabHandler tabHandler = TabHandler.getInstance();
+ tabHandler
+ .clear()
+ .subscribe(
+ () -> {
+ if (tabFragment != null) {
+ tabFragment.refactorDrawerStorages(false);
+ Fragment main = tabFragment.getFragmentAtIndex(0);
+ if (main != null) ((MainFragment) main).updateTabWithDb(tabHandler.findTab(1));
+ Fragment main1 = tabFragment.getFragmentAtIndex(1);
+ if (main1 != null) ((MainFragment) main1).updateTabWithDb(tabHandler.findTab(2));
}
- } else if (FileUtils.isCompressedFile(Utils.sanitizeInput(uri.toString()))) {
- // we don't have folder resource mime type set, supposed to be zip/rar
- isCompressedOpen = true;
- pathInCompressedArchive = Utils.sanitizeInput(uri.toString());
- openCompressed(pathInCompressedArchive);
- } else if (uri.getPath().startsWith("/open_file")) {
- /**
- * Deeplink to open files directly through amaze using following format:
- * http://teamamaze.xyz/open_file?path=path-to-file
- */
- path = Utils.sanitizeInput(uri.getQueryParameter("path"));
- } else {
- LOG.warn(getString(R.string.error_cannot_find_way_open));
- }
-
- } else if (actionIntent.equals(Intent.ACTION_SEND)) {
- if ("text/plain".equals(type)) {
- initFabToSave(null);
- } else {
- // save a single file to filesystem
- Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- ArrayList uris = new ArrayList<>();
- uris.add(uri);
- initFabToSave(uris);
- }
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when saving a file
- Utils.disableScreenRotation(this);
-
- } else if (actionIntent.equals(Intent.ACTION_SEND_MULTIPLE) && type != null) {
- // save multiple files to filesystem
-
- ArrayList arrayList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
- initFabToSave(arrayList);
-
- // disable screen rotation just for convenience purpose
- // TODO: Support screen rotation when saving a file
- Utils.disableScreenRotation(this);
- }
+ getPrefs().edit().putBoolean(PREFERENCE_NEED_TO_SET_HOME, false).commit();
+ });
+ } else {
+ // just refresh list
+ if (tabFragment != null) {
+ Fragment main = tabFragment.getFragmentAtIndex(0);
+ if (main != null) ((MainFragment) main).updateList(false);
+ Fragment main1 = tabFragment.getFragmentAtIndex(1);
+ if (main1 != null) ((MainFragment) main1).updateList(false);
+ }
}
+ }
- /**
- * Initializes the floating action button to act as to save data from an external intent
- */
- private void initFabToSave(final ArrayList uris) {
- Utils.showThemedSnackbar(
- this,
- getString(R.string.select_save_location),
- BaseTransientBottomBar.LENGTH_INDEFINITE,
- R.string.save,
- () -> saveExternalIntent(uris));
+ private void checkForExternalPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!checkStoragePermission()) {
+ requestStoragePermission(this, true);
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ requestAllFilesAccess(this);
+ }
}
+ }
- private void saveExternalIntent(final ArrayList uris) {
- executeWithMainFragment(
- mainFragment -> {
- if (uris != null && uris.size() > 0) {
- if (SDK_INT >= LOLLIPOP) {
- File folder = new File(mainFragment.getCurrentPath());
- int result = mainActivityHelper.checkFolder(folder, MainActivity.this);
- if (result == WRITABLE_OR_ON_SDCARD) {
- FileUtil.writeUriToStorage(
- MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
- finish();
- } else {
- // Trigger SAF intent, keep uri until finish
- operation = SAVE_FILE;
- urisToBeSaved = uris;
- mainActivityHelper.checkFolder(folder, MainActivity.this);
- }
- } else {
- FileUtil.writeUriToStorage(
- MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
- }
- } else {
- saveExternalIntentExtras();
- }
- Toast.makeText(
- MainActivity.this,
- getResources().getString(R.string.saving)
- + " to "
- + mainFragment.getCurrentPath(),
- Toast.LENGTH_LONG)
- .show();
- finish();
- return null;
- });
+ /** Checks for the action to take when Amaze receives an intent from external source */
+ private void checkForExternalIntent(Intent intent) {
+ final String actionIntent = intent.getAction();
+ if (actionIntent == null) {
+ return;
}
- private void saveExternalIntentExtras() {
- executeWithMainFragment(
- mainFragment -> {
- Bundle extras = intent.getExtras();
- StringBuilder data = new StringBuilder();
- if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_SUBJECT))) {
- data.append(extras.getString(Intent.EXTRA_SUBJECT));
- }
- if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_TEXT))) {
- data.append(AppConstants.NEW_LINE).append(extras.getString(Intent.EXTRA_TEXT));
- }
- String fileName = Long.toString(System.currentTimeMillis());
- AppConfig.getInstance()
- .runInBackground(
- () ->
- MakeFileOperation.mktextfile(
- data.toString(), mainFragment.getCurrentPath(), fileName));
- return null;
- });
- }
+ final String type = intent.getType();
- public void clearFabActionItems() {
- floatingActionButton.removeActionItemById(R.id.menu_new_folder);
- floatingActionButton.removeActionItemById(R.id.menu_new_file);
- floatingActionButton.removeActionItemById(R.id.menu_new_cloud);
- }
+ if (actionIntent.equals(Intent.ACTION_GET_CONTENT)) {
+ // file picker intent
+ mReturnIntent = true;
+ Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
- /**
- * Initializes an interactive shell, which will stay throughout the app lifecycle.
- */
- private void initializeInteractiveShell() {
- if (isRootExplorer()) {
- // Enable mount-master flag when invoking su command, to force su run in the global mount
- // namespace. See https://github.com/topjohnwu/libsu/issues/75
- Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
- Shell.getShell();
- }
- }
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when picking file
+ Utils.disableScreenRotation(this);
+ } else if (actionIntent.equals(RingtoneManager.ACTION_RINGTONE_PICKER)) {
+ // ringtone picker intent
+ mReturnIntent = true;
+ mRingtonePickerIntent = true;
+ Toast.makeText(this, getString(R.string.pick_a_file), Toast.LENGTH_LONG).show();
- /**
- * @return paths to all available volumes in the system (include emulated)
- */
- public synchronized ArrayList getStorageDirectories() {
- ArrayList volumes;
- if (SDK_INT >= N) {
- volumes = getStorageDirectoriesNew();
- } else {
- volumes = getStorageDirectoriesLegacy();
- }
- if (isRootExplorer()) {
- volumes.add(
- new StorageDirectoryParcelable(
- "/",
- getResources().getString(R.string.root_directory),
- R.drawable.ic_drawer_root_white));
- }
- return volumes;
- }
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when picking file
+ Utils.disableScreenRotation(this);
+ } else if (actionIntent.equals(Intent.ACTION_VIEW)) {
+ // zip viewer intent
+ Uri uri = intent.getData();
- /**
- * @return All available storage volumes (including internal storage, SD-Cards and USB devices)
- */
- @TargetApi(N)
- public synchronized ArrayList getStorageDirectoriesNew() {
- // Final set of paths
- ArrayList volumes = new ArrayList<>();
- StorageManager sm = getSystemService(StorageManager.class);
- for (StorageVolume volume : sm.getStorageVolumes()) {
- if (!volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)
- && !volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED_READ_ONLY)) {
- continue;
- }
- File path = Utils.getVolumeDirectory(volume);
- String name = volume.getDescription(this);
- if (INTERNAL_SHARED_STORAGE.equalsIgnoreCase(name)) {
- name = getString(R.string.storage_internal);
- }
- int icon;
- if (!volume.isRemovable()) {
- icon = R.drawable.ic_phone_android_white_24dp;
- } else {
- // HACK: There is no reliable way to distinguish USB and SD external storage
- // However it is often enough to check for "USB" String
- if (name.toUpperCase().contains("USB") || path.getPath().toUpperCase().contains("USB")) {
- icon = R.drawable.ic_usb_white_24dp;
- } else {
- icon = R.drawable.ic_sd_storage_white_24dp;
- }
- }
- volumes.add(new StorageDirectoryParcelable(path.getPath(), name, icon));
- }
- return volumes;
- }
+ if (type != null
+ && (type.equals(ARGS_INTENT_ACTION_VIEW_MIME_FOLDER)
+ || type.equals(ARGS_INTENT_ACTION_VIEW_APPLICATION_ALL))) {
+ // support for syncting or intents from external apps that
+ // need to start file manager from a specific path
- /**
- * Returns all available SD-Cards in the system (include emulated)
- *
- * Warning: Hack! Based on Android source code of version 4.3 (API 18) Because there was no
- * standard way to get it before android N
- *
- * @return All available SD-Cards in the system (include emulated)
- */
- public synchronized ArrayList getStorageDirectoriesLegacy() {
- List rv = new ArrayList<>();
-
- // Primary physical SD-CARD (not emulated)
- final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
- // All Secondary SD-CARDs (all exclude primary) separated by ":"
- final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
- // Primary emulated SD-CARD
- final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
- if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
- // Device has physical external storage; use plain paths.
- if (TextUtils.isEmpty(rawExternalStorage)) {
- // EXTERNAL_STORAGE undefined; falling back to default.
- // Check for actual existence of the directory before adding to list
- if (new File(DEFAULT_FALLBACK_STORAGE_PATH).exists()) {
- rv.add(DEFAULT_FALLBACK_STORAGE_PATH);
- } else {
- // We know nothing else, use Environment's fallback
- rv.add(Environment.getExternalStorageDirectory().getAbsolutePath());
- }
- } else {
- rv.add(rawExternalStorage);
- }
+ if (uri != null) {
+
+ path = Utils.sanitizeInput(FileUtils.fromContentUri(uri).getAbsolutePath());
+ scrollToFileName = intent.getStringExtra("com.amaze.fileutilities.AFM_LOCATE_FILE_NAME");
} else {
- // Device has emulated storage; external storage paths should have
- // userId burned into them.
- final String rawUserId;
- if (SDK_INT < JELLY_BEAN_MR1) {
- rawUserId = "";
- } else {
- final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
- final String[] folders = DIR_SEPARATOR.split(path);
- final String lastFolder = folders[folders.length - 1];
- boolean isDigit = false;
- try {
- Integer.valueOf(lastFolder);
- isDigit = true;
- } catch (NumberFormatException ignored) {
- }
- rawUserId = isDigit ? lastFolder : "";
- }
- // /storage/emulated/0[1,2,...]
- if (TextUtils.isEmpty(rawUserId)) {
- rv.add(rawEmulatedStorageTarget);
+ // no data field, open home for the tab in later processing
+ path = null;
+ }
+ } else if (FileUtils.isCompressedFile(Utils.sanitizeInput(uri.toString()))) {
+ // we don't have folder resource mime type set, supposed to be zip/rar
+ isCompressedOpen = true;
+ pathInCompressedArchive = Utils.sanitizeInput(uri.toString());
+ openCompressed(pathInCompressedArchive);
+ } else if (uri.getPath().startsWith("/open_file")) {
+ /**
+ * Deeplink to open files directly through amaze using following format:
+ * http://teamamaze.xyz/open_file?path=path-to-file
+ */
+ path = Utils.sanitizeInput(uri.getQueryParameter("path"));
+ } else {
+ LOG.warn(getString(R.string.error_cannot_find_way_open));
+ }
+
+ } else if (actionIntent.equals(Intent.ACTION_SEND)) {
+ if ("text/plain".equals(type)) {
+ initFabToSave(null);
+ } else {
+ // save a single file to filesystem
+ Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ ArrayList uris = new ArrayList<>();
+ uris.add(uri);
+ initFabToSave(uris);
+ }
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when saving a file
+ Utils.disableScreenRotation(this);
+
+ } else if (actionIntent.equals(Intent.ACTION_SEND_MULTIPLE) && type != null) {
+ // save multiple files to filesystem
+
+ ArrayList arrayList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ initFabToSave(arrayList);
+
+ // disable screen rotation just for convenience purpose
+ // TODO: Support screen rotation when saving a file
+ Utils.disableScreenRotation(this);
+ }
+ }
+
+ /** Initializes the floating action button to act as to save data from an external intent */
+ private void initFabToSave(final ArrayList uris) {
+ Utils.showThemedSnackbar(
+ this,
+ getString(R.string.select_save_location),
+ BaseTransientBottomBar.LENGTH_INDEFINITE,
+ R.string.save,
+ () -> saveExternalIntent(uris));
+ }
+
+ private void saveExternalIntent(final ArrayList uris) {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (uris != null && uris.size() > 0) {
+ if (SDK_INT >= LOLLIPOP) {
+ File folder = new File(mainFragment.getCurrentPath());
+ int result = mainActivityHelper.checkFolder(folder, MainActivity.this);
+ if (result == WRITABLE_OR_ON_SDCARD) {
+ FileUtil.writeUriToStorage(
+ MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
+ finish();
+ } else {
+ // Trigger SAF intent, keep uri until finish
+ operation = SAVE_FILE;
+ urisToBeSaved = uris;
+ mainActivityHelper.checkFolder(folder, MainActivity.this);
+ }
} else {
- rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
+ FileUtil.writeUriToStorage(
+ MainActivity.this, uris, getContentResolver(), mainFragment.getCurrentPath());
}
+ } else {
+ saveExternalIntentExtras();
+ }
+ Toast.makeText(
+ MainActivity.this,
+ getResources().getString(R.string.saving)
+ + " to "
+ + mainFragment.getCurrentPath(),
+ Toast.LENGTH_LONG)
+ .show();
+ finish();
+ return null;
+ });
+ }
+
+ private void saveExternalIntentExtras() {
+ executeWithMainFragment(
+ mainFragment -> {
+ Bundle extras = intent.getExtras();
+ StringBuilder data = new StringBuilder();
+ if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_SUBJECT))) {
+ data.append(extras.getString(Intent.EXTRA_SUBJECT));
+ }
+ if (!Utils.isNullOrEmpty(extras.getString(Intent.EXTRA_TEXT))) {
+ data.append(AppConstants.NEW_LINE).append(extras.getString(Intent.EXTRA_TEXT));
+ }
+ String fileName = Long.toString(System.currentTimeMillis());
+ AppConfig.getInstance()
+ .runInBackground(
+ () ->
+ MakeFileOperation.mktextfile(
+ data.toString(), mainFragment.getCurrentPath(), fileName));
+ return null;
+ });
+ }
+
+ public void clearFabActionItems() {
+ floatingActionButton.removeActionItemById(R.id.menu_new_folder);
+ floatingActionButton.removeActionItemById(R.id.menu_new_file);
+ floatingActionButton.removeActionItemById(R.id.menu_new_cloud);
+ }
+
+ /** Initializes an interactive shell, which will stay throughout the app lifecycle. */
+ private void initializeInteractiveShell() {
+ if (isRootExplorer()) {
+ // Enable mount-master flag when invoking su command, to force su run in the global mount
+ // namespace. See https://github.com/topjohnwu/libsu/issues/75
+ Shell.setDefaultBuilder(Shell.Builder.create().setFlags(Shell.FLAG_MOUNT_MASTER));
+ Shell.getShell();
+ }
+ }
+
+ /**
+ * @return paths to all available volumes in the system (include emulated)
+ */
+ public synchronized ArrayList getStorageDirectories() {
+ ArrayList volumes;
+ if (SDK_INT >= N) {
+ volumes = getStorageDirectoriesNew();
+ } else {
+ volumes = getStorageDirectoriesLegacy();
+ }
+ if (isRootExplorer()) {
+ volumes.add(
+ new StorageDirectoryParcelable(
+ "/",
+ getResources().getString(R.string.root_directory),
+ R.drawable.ic_drawer_root_white));
+ }
+ return volumes;
+ }
+
+ /**
+ * @return All available storage volumes (including internal storage, SD-Cards and USB devices)
+ */
+ @TargetApi(N)
+ public synchronized ArrayList getStorageDirectoriesNew() {
+ // Final set of paths
+ ArrayList volumes = new ArrayList<>();
+ StorageManager sm = getSystemService(StorageManager.class);
+ for (StorageVolume volume : sm.getStorageVolumes()) {
+ if (!volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED)
+ && !volume.getState().equalsIgnoreCase(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+ continue;
+ }
+ File path = Utils.getVolumeDirectory(volume);
+ String name = volume.getDescription(this);
+ if (INTERNAL_SHARED_STORAGE.equalsIgnoreCase(name)) {
+ name = getString(R.string.storage_internal);
+ }
+ int icon;
+ if (!volume.isRemovable()) {
+ icon = R.drawable.ic_phone_android_white_24dp;
+ } else {
+ // HACK: There is no reliable way to distinguish USB and SD external storage
+ // However it is often enough to check for "USB" String
+ if (name.toUpperCase().contains("USB") || path.getPath().toUpperCase().contains("USB")) {
+ icon = R.drawable.ic_usb_white_24dp;
+ } else {
+ icon = R.drawable.ic_sd_storage_white_24dp;
}
- // Add all secondary storages
- if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
- // All Secondary SD-CARDs splited into array
- final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
- Collections.addAll(rv, rawSecondaryStorages);
- }
- if (SDK_INT >= M && checkStoragePermission()) rv.clear();
- if (SDK_INT >= KITKAT) {
- String strings[] = ExternalSdCardOperation.getExtSdCardPathsForActivity(this);
- for (String s : strings) {
- File f = new File(s);
- if (!rv.contains(s) && FileUtils.canListFiles(f)) rv.add(s);
- }
+ }
+ volumes.add(new StorageDirectoryParcelable(path.getPath(), name, icon));
+ }
+ return volumes;
+ }
+
+ /**
+ * Returns all available SD-Cards in the system (include emulated)
+ *
+ * Warning: Hack! Based on Android source code of version 4.3 (API 18) Because there was no
+ * standard way to get it before android N
+ *
+ * @return All available SD-Cards in the system (include emulated)
+ */
+ public synchronized ArrayList getStorageDirectoriesLegacy() {
+ List rv = new ArrayList<>();
+
+ // Primary physical SD-CARD (not emulated)
+ final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE");
+ // All Secondary SD-CARDs (all exclude primary) separated by ":"
+ final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE");
+ // Primary emulated SD-CARD
+ final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET");
+ if (TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+ // Device has physical external storage; use plain paths.
+ if (TextUtils.isEmpty(rawExternalStorage)) {
+ // EXTERNAL_STORAGE undefined; falling back to default.
+ // Check for actual existence of the directory before adding to list
+ if (new File(DEFAULT_FALLBACK_STORAGE_PATH).exists()) {
+ rv.add(DEFAULT_FALLBACK_STORAGE_PATH);
+ } else {
+ // We know nothing else, use Environment's fallback
+ rv.add(Environment.getExternalStorageDirectory().getAbsolutePath());
}
- File usb = getUsbDrive();
- if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath());
-
- if (SDK_INT >= KITKAT) {
- if (SingletonUsbOtg.getInstance().isDeviceConnected()) {
- rv.add(OTGUtil.PREFIX_OTG + "/");
- }
+ } else {
+ rv.add(rawExternalStorage);
+ }
+ } else {
+ // Device has emulated storage; external storage paths should have
+ // userId burned into them.
+ final String rawUserId;
+ if (SDK_INT < JELLY_BEAN_MR1) {
+ rawUserId = "";
+ } else {
+ final String path = Environment.getExternalStorageDirectory().getAbsolutePath();
+ final String[] folders = DIR_SEPARATOR.split(path);
+ final String lastFolder = folders[folders.length - 1];
+ boolean isDigit = false;
+ try {
+ Integer.valueOf(lastFolder);
+ isDigit = true;
+ } catch (NumberFormatException ignored) {
}
+ rawUserId = isDigit ? lastFolder : "";
+ }
+ // /storage/emulated/0[1,2,...]
+ if (TextUtils.isEmpty(rawUserId)) {
+ rv.add(rawEmulatedStorageTarget);
+ } else {
+ rv.add(rawEmulatedStorageTarget + File.separator + rawUserId);
+ }
+ }
+ // Add all secondary storages
+ if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) {
+ // All Secondary SD-CARDs splited into array
+ final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator);
+ Collections.addAll(rv, rawSecondaryStorages);
+ }
+ if (SDK_INT >= M && checkStoragePermission()) rv.clear();
+ if (SDK_INT >= KITKAT) {
+ String strings[] = ExternalSdCardOperation.getExtSdCardPathsForActivity(this);
+ for (String s : strings) {
+ File f = new File(s);
+ if (!rv.contains(s) && FileUtils.canListFiles(f)) rv.add(s);
+ }
+ }
+ File usb = getUsbDrive();
+ if (usb != null && !rv.contains(usb.getPath())) rv.add(usb.getPath());
- // Assign a label and icon to each directory
- ArrayList volumes = new ArrayList<>();
- for (String file : rv) {
- File f = new File(file);
- @DrawableRes int icon;
-
- if ("/storage/emulated/legacy".equals(file)
- || "/storage/emulated/0".equals(file)
- || "/mnt/sdcard".equals(file)) {
- icon = R.drawable.ic_phone_android_white_24dp;
- } else if ("/storage/sdcard1".equals(file)) {
- icon = R.drawable.ic_sd_storage_white_24dp;
- } else if ("/".equals(file)) {
- icon = R.drawable.ic_drawer_root_white;
- } else {
- icon = R.drawable.ic_sd_storage_white_24dp;
- }
+ if (SDK_INT >= KITKAT) {
+ if (SingletonUsbOtg.getInstance().isDeviceConnected()) {
+ rv.add(OTGUtil.PREFIX_OTG + "/");
+ }
+ }
- @StorageNaming.DeviceDescription
- int deviceDescription = StorageNaming.getDeviceDescriptionLegacy(f);
- String name = StorageNamingHelper.getNameForDeviceDescription(this, f, deviceDescription);
+ // Assign a label and icon to each directory
+ ArrayList volumes = new ArrayList<>();
+ for (String file : rv) {
+ File f = new File(file);
+ @DrawableRes int icon;
+
+ if ("/storage/emulated/legacy".equals(file)
+ || "/storage/emulated/0".equals(file)
+ || "/mnt/sdcard".equals(file)) {
+ icon = R.drawable.ic_phone_android_white_24dp;
+ } else if ("/storage/sdcard1".equals(file)) {
+ icon = R.drawable.ic_sd_storage_white_24dp;
+ } else if ("/".equals(file)) {
+ icon = R.drawable.ic_drawer_root_white;
+ } else {
+ icon = R.drawable.ic_sd_storage_white_24dp;
+ }
- volumes.add(new StorageDirectoryParcelable(file, name, icon));
- }
+ @StorageNaming.DeviceDescription
+ int deviceDescription = StorageNaming.getDeviceDescriptionLegacy(f);
+ String name = StorageNamingHelper.getNameForDeviceDescription(this, f, deviceDescription);
- return volumes;
+ volumes.add(new StorageDirectoryParcelable(file, name, icon));
}
- @Override
- public void onBackPressed() {
- if (!drawer.isLocked() && drawer.isOpen()) {
- drawer.close();
- return;
- }
-
- Fragment fragment = getFragmentAtFrame();
- if (getAppbar().getSearchView().isShown()) {
- // hide search view if visible, with an animation
- getAppbar().getSearchView().hideSearchView();
- } else if (fragment instanceof TabFragment) {
- if (floatingActionButton.isOpen()) {
- floatingActionButton.close(true);
- } else {
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.goBack();
- return null;
- });
- }
- } else if (fragment instanceof CompressedExplorerFragment) {
- CompressedExplorerFragment compressedExplorerFragment =
- (CompressedExplorerFragment) getFragmentAtFrame();
- if (compressedExplorerFragment.mActionMode == null) {
- if (compressedExplorerFragment.canGoBack()) {
- compressedExplorerFragment.goBack();
- } else if (isCompressedOpen) {
- isCompressedOpen = false;
- finish();
- } else {
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
- fragmentTransaction.setCustomAnimations(R.anim.slide_out_bottom, R.anim.slide_out_bottom);
- fragmentTransaction.remove(compressedExplorerFragment);
- fragmentTransaction.commit();
- supportInvalidateOptionsMenu();
- floatingActionButton.show();
- }
- } else {
- compressedExplorerFragment.mActionMode.finish();
- }
- } else if (fragment instanceof FtpServerFragment) {
- // returning back from FTP server
- if (path != null && path.length() > 0) {
- HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
- file.generateMode(this);
- if (file.isDirectory(this)) goToMain(path);
- else {
- goToMain(null);
- FileUtils.openFile(new File(path), this, getPrefs());
- }
- } else {
- goToMain(null);
- }
- } else {
- goToMain(null);
- }
- }
+ return volumes;
+ }
- public void invalidatePasteSnackbar(boolean showSnackbar) {
- if (pasteHelper != null) {
- pasteHelper.invalidateSnackbar(this, showSnackbar);
- }
+ @Override
+ public void onBackPressed() {
+ if (!drawer.isLocked() && drawer.isOpen()) {
+ drawer.close();
+ return;
}
- public void exit() {
- if (backPressedToExitOnce) {
- NetCopyClientConnectionPool.INSTANCE.shutdown();
- finish();
- if (isRootExplorer()) {
- closeInteractiveShell();
- }
+ Fragment fragment = getFragmentAtFrame();
+ if (getAppbar().getSearchView().isShown()) {
+ // hide search view if visible, with an animation
+ getAppbar().getSearchView().hideSearchView();
+ } else if (fragment instanceof TabFragment) {
+ if (floatingActionButton.isOpen()) {
+ floatingActionButton.close(true);
+ } else {
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.goBack();
+ return null;
+ });
+ }
+ } else if (fragment instanceof CompressedExplorerFragment) {
+ CompressedExplorerFragment compressedExplorerFragment =
+ (CompressedExplorerFragment) getFragmentAtFrame();
+ if (compressedExplorerFragment.mActionMode == null) {
+ if (compressedExplorerFragment.canGoBack()) {
+ compressedExplorerFragment.goBack();
+ } else if (isCompressedOpen) {
+ isCompressedOpen = false;
+ finish();
} else {
- this.backPressedToExitOnce = true;
- final Toast toast = Toast.makeText(this, getString(R.string.press_again), Toast.LENGTH_SHORT);
- this.toast = new WeakReference<>(toast);
- toast.show();
- new Handler()
- .postDelayed(
- () -> {
- backPressedToExitOnce = false;
- },
- 2000);
- }
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.setCustomAnimations(R.anim.slide_out_bottom, R.anim.slide_out_bottom);
+ fragmentTransaction.remove(compressedExplorerFragment);
+ fragmentTransaction.commit();
+ supportInvalidateOptionsMenu();
+ floatingActionButton.show();
+ }
+ } else {
+ compressedExplorerFragment.mActionMode.finish();
+ }
+ } else if (fragment instanceof FtpServerFragment) {
+ // returning back from FTP server
+ if (path != null && path.length() > 0) {
+ HybridFile file = new HybridFile(OpenMode.UNKNOWN, path);
+ file.generateMode(this);
+ if (file.isDirectory(this)) goToMain(path);
+ else {
+ goToMain(null);
+ FileUtils.openFile(new File(path), this, getPrefs());
+ }
+ } else {
+ goToMain(null);
+ }
+ } else {
+ goToMain(null);
}
+ }
- public void goToMain(String path) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- // title.setText(R.string.app_name);
- TabFragment tabFragment = new TabFragment();
- if (intent != null && intent.getAction() != null) {
- if (INTENT_ACTION_OPEN_QUICK_ACCESS.equals(intent.getAction())) {
- path = "5";
- } else if (INTENT_ACTION_OPEN_RECENT.equals(intent.getAction())) {
- path = "6";
- }
- }
- if (path != null && path.length() > 0) {
- Bundle b = new Bundle();
- b.putString("path", path);
- tabFragment.setArguments(b);
- }
- transaction.replace(R.id.content_frame, tabFragment);
- // Commit the transaction
- transaction.addToBackStack("tabt" + 1);
- transaction.commitAllowingStateLoss();
- appbar.setTitle(null);
- floatingActionButton.show();
- if (isCompressedOpen && pathInCompressedArchive != null) {
- openCompressed(pathInCompressedArchive);
- pathInCompressedArchive = null;
- }
+ public void invalidatePasteSnackbar(boolean showSnackbar) {
+ if (pasteHelper != null) {
+ pasteHelper.invalidateSnackbar(this, showSnackbar);
}
+ }
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- MenuInflater menuInflater = getMenuInflater();
- menuInflater.inflate(R.menu.activity_extra, menu);
+ public void exit() {
+ if (backPressedToExitOnce) {
+ NetCopyClientConnectionPool.INSTANCE.shutdown();
+ finish();
+ if (isRootExplorer()) {
+ closeInteractiveShell();
+ }
+ } else {
+ this.backPressedToExitOnce = true;
+ final Toast toast = Toast.makeText(this, getString(R.string.press_again), Toast.LENGTH_SHORT);
+ this.toast = new WeakReference<>(toast);
+ toast.show();
+ new Handler()
+ .postDelayed(
+ () -> {
+ backPressedToExitOnce = false;
+ },
+ 2000);
+ }
+ }
+
+ public void goToMain(String path) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ // title.setText(R.string.app_name);
+ TabFragment tabFragment = new TabFragment();
+ if (intent != null && intent.getAction() != null) {
+ if (INTENT_ACTION_OPEN_QUICK_ACCESS.equals(intent.getAction())) {
+ path = "5";
+ } else if (INTENT_ACTION_OPEN_RECENT.equals(intent.getAction())) {
+ path = "6";
+ }
+ }
+ if (path != null && path.length() > 0) {
+ Bundle b = new Bundle();
+ b.putString("path", path);
+ tabFragment.setArguments(b);
+ }
+ transaction.replace(R.id.content_frame, tabFragment);
+ // Commit the transaction
+ transaction.addToBackStack("tabt" + 1);
+ transaction.commitAllowingStateLoss();
+ appbar.setTitle(null);
+ floatingActionButton.show();
+ if (isCompressedOpen && pathInCompressedArchive != null) {
+ openCompressed(pathInCompressedArchive);
+ pathInCompressedArchive = null;
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater menuInflater = getMenuInflater();
+ menuInflater.inflate(R.menu.activity_extra, menu);
/*
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
@@ -1044,236 +1018,236 @@ public boolean onMenuItemActionCollapse(MenuItem item) {
}
});
*/
- return super.onCreateOptionsMenu(menu);
- }
+ return super.onCreateOptionsMenu(menu);
+ }
- @Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- MenuItem s = menu.findItem(R.id.view);
- MenuItem search = menu.findItem(R.id.search);
- Fragment fragment = getFragmentAtFrame();
- if (fragment instanceof TabFragment) {
- appbar.setTitle(R.string.appbar_name);
- if (getBoolean(PREFERENCE_VIEW)) {
- s.setTitle(getResources().getString(R.string.gridview));
- } else {
- s.setTitle(getResources().getString(R.string.listview));
- }
- try {
- executeWithMainFragment(
- mainFragment -> {
- if (mainFragment.getMainFragmentViewModel().isList()) {
- s.setTitle(R.string.gridview);
- } else {
- s.setTitle(R.string.listview);
- }
- appbar
- .getBottomBar()
- .updatePath(
- mainFragment.getCurrentPath(),
- mainFragment.getMainFragmentViewModel().getResults(),
- MainActivityHelper.SEARCH_TEXT,
- mainFragment.getMainFragmentViewModel().getOpenMode(),
- mainFragment.getMainFragmentViewModel().getFolderCount(),
- mainFragment.getMainFragmentViewModel().getFileCount(),
- mainFragment);
- return null;
- });
- } catch (Exception e) {
- LOG.warn("failure while preparing options menu", e);
- }
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem s = menu.findItem(R.id.view);
+ MenuItem search = menu.findItem(R.id.search);
+ Fragment fragment = getFragmentAtFrame();
+ if (fragment instanceof TabFragment) {
+ appbar.setTitle(R.string.appbar_name);
+ if (getBoolean(PREFERENCE_VIEW)) {
+ s.setTitle(getResources().getString(R.string.gridview));
+ } else {
+ s.setTitle(getResources().getString(R.string.listview));
+ }
+ try {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (mainFragment.getMainFragmentViewModel().isList()) {
+ s.setTitle(R.string.gridview);
+ } else {
+ s.setTitle(R.string.listview);
+ }
+ appbar
+ .getBottomBar()
+ .updatePath(
+ mainFragment.getCurrentPath(),
+ mainFragment.getMainFragmentViewModel().getResults(),
+ MainActivityHelper.SEARCH_TEXT,
+ mainFragment.getMainFragmentViewModel().getOpenMode(),
+ mainFragment.getMainFragmentViewModel().getFolderCount(),
+ mainFragment.getMainFragmentViewModel().getFileCount(),
+ mainFragment);
+ return null;
+ });
+ } catch (Exception e) {
+ LOG.warn("failure while preparing options menu", e);
+ }
- appbar.getBottomBar().setClickListener();
-
- search.setVisible(true);
- if (indicator_layout != null) indicator_layout.setVisibility(View.VISIBLE);
- menu.findItem(R.id.search).setVisible(true);
- menu.findItem(R.id.home).setVisible(true);
- menu.findItem(R.id.history).setVisible(true);
- menu.findItem(R.id.sethome).setVisible(true);
- menu.findItem(R.id.sort).setVisible(true);
- menu.findItem(R.id.hiddenitems).setVisible(true);
- menu.findItem(R.id.view).setVisible(true);
- menu.findItem(R.id.extract).setVisible(false);
- invalidatePasteSnackbar(true);
- findViewById(R.id.buttonbarframe).setVisibility(View.VISIBLE);
- } else if (fragment instanceof AppsListFragment
- || fragment instanceof ProcessViewerFragment
- || fragment instanceof FtpServerFragment) {
- appBarLayout.setExpanded(true);
- menu.findItem(R.id.sethome).setVisible(false);
- if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
- findViewById(R.id.buttonbarframe).setVisibility(View.GONE);
- menu.findItem(R.id.search).setVisible(false);
- menu.findItem(R.id.home).setVisible(false);
- menu.findItem(R.id.history).setVisible(false);
- menu.findItem(R.id.extract).setVisible(false);
- if (fragment instanceof ProcessViewerFragment) {
- menu.findItem(R.id.sort).setVisible(false);
- } else if (fragment instanceof FtpServerFragment) {
- menu.findItem(R.id.sort).setVisible(false);
- } else {
- menu.findItem(R.id.dsort).setVisible(false);
- menu.findItem(R.id.sortby).setVisible(false);
- }
- menu.findItem(R.id.hiddenitems).setVisible(false);
- menu.findItem(R.id.view).setVisible(false);
- invalidatePasteSnackbar(false);
- } else if (fragment instanceof CompressedExplorerFragment) {
- appbar.setTitle(R.string.appbar_name);
- menu.findItem(R.id.sethome).setVisible(false);
- if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
- getAppbar().getBottomBar().resetClickListener();
- menu.findItem(R.id.search).setVisible(false);
- menu.findItem(R.id.home).setVisible(false);
- menu.findItem(R.id.history).setVisible(false);
- menu.findItem(R.id.sort).setVisible(false);
- menu.findItem(R.id.hiddenitems).setVisible(false);
- menu.findItem(R.id.view).setVisible(false);
- menu.findItem(R.id.extract).setVisible(true);
- invalidatePasteSnackbar(false);
- }
- return super.onPrepareOptionsMenu(menu);
- }
+ appbar.getBottomBar().setClickListener();
+
+ search.setVisible(true);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.VISIBLE);
+ menu.findItem(R.id.search).setVisible(true);
+ menu.findItem(R.id.home).setVisible(true);
+ menu.findItem(R.id.history).setVisible(true);
+ menu.findItem(R.id.sethome).setVisible(true);
+ menu.findItem(R.id.sort).setVisible(true);
+ menu.findItem(R.id.hiddenitems).setVisible(true);
+ menu.findItem(R.id.view).setVisible(true);
+ menu.findItem(R.id.extract).setVisible(false);
+ invalidatePasteSnackbar(true);
+ findViewById(R.id.buttonbarframe).setVisibility(View.VISIBLE);
+ } else if (fragment instanceof AppsListFragment
+ || fragment instanceof ProcessViewerFragment
+ || fragment instanceof FtpServerFragment) {
+ appBarLayout.setExpanded(true);
+ menu.findItem(R.id.sethome).setVisible(false);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
+ findViewById(R.id.buttonbarframe).setVisibility(View.GONE);
+ menu.findItem(R.id.search).setVisible(false);
+ menu.findItem(R.id.home).setVisible(false);
+ menu.findItem(R.id.history).setVisible(false);
+ menu.findItem(R.id.extract).setVisible(false);
+ if (fragment instanceof ProcessViewerFragment) {
+ menu.findItem(R.id.sort).setVisible(false);
+ } else if (fragment instanceof FtpServerFragment) {
+ menu.findItem(R.id.sort).setVisible(false);
+ } else {
+ menu.findItem(R.id.dsort).setVisible(false);
+ menu.findItem(R.id.sortby).setVisible(false);
+ }
+ menu.findItem(R.id.hiddenitems).setVisible(false);
+ menu.findItem(R.id.view).setVisible(false);
+ invalidatePasteSnackbar(false);
+ } else if (fragment instanceof CompressedExplorerFragment) {
+ appbar.setTitle(R.string.appbar_name);
+ menu.findItem(R.id.sethome).setVisible(false);
+ if (indicator_layout != null) indicator_layout.setVisibility(View.GONE);
+ getAppbar().getBottomBar().resetClickListener();
+ menu.findItem(R.id.search).setVisible(false);
+ menu.findItem(R.id.home).setVisible(false);
+ menu.findItem(R.id.history).setVisible(false);
+ menu.findItem(R.id.sort).setVisible(false);
+ menu.findItem(R.id.hiddenitems).setVisible(false);
+ menu.findItem(R.id.view).setVisible(false);
+ menu.findItem(R.id.extract).setVisible(true);
+ invalidatePasteSnackbar(false);
+ }
+ return super.onPrepareOptionsMenu(menu);
+ }
+
+ // called when the user exits the action mode
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ // The action bar home/up action should open or close the drawer.
+ // ActionBarDrawerToggle will take care of this.
+ if (drawer.onOptionsItemSelected(item)) return true;
+ // Same thing goes to other Fragments loaded.
+ // If they have handled the options, we don't need to.
+ if (getFragmentAtFrame().onOptionsItemSelected(item)) return true;
+
+ // Handle action buttons
+ executeWithMainFragment(
+ mainFragment -> {
+ switch (item.getItemId()) {
+ case R.id.home:
+ mainFragment.home();
+ break;
+ case R.id.history:
+ HistoryDialog.showHistoryDialog(this, mainFragment);
+ break;
+ case R.id.sethome:
+ if (mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.FILE
+ && mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.ROOT) {
+ Toast.makeText(mainActivity, R.string.not_allowed, Toast.LENGTH_SHORT).show();
+ break;
+ }
+ final MaterialDialog dialog =
+ GeneralDialogCreation.showBasicDialog(
+ mainActivity,
+ R.string.question_set_path_as_home,
+ R.string.set_as_home,
+ R.string.yes,
+ R.string.no);
+ dialog
+ .getActionButton(DialogAction.POSITIVE)
+ .setOnClickListener(
+ (v) -> {
+ mainFragment
+ .getMainFragmentViewModel()
+ .setHome(mainFragment.getCurrentPath());
+ updatePaths(mainFragment.getMainFragmentViewModel().getNo());
+ dialog.dismiss();
+ });
+ dialog.show();
+ break;
+ case R.id.exit:
+ finish();
+ break;
+ case R.id.sortby:
+ GeneralDialogCreation.showSortDialog(mainFragment, getAppTheme(), getPrefs());
+ break;
+ case R.id.dsort:
+ String[] sort = getResources().getStringArray(R.array.directorysortmode);
+ MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity);
+ builder.theme(getAppTheme().getMaterialDialogTheme(this));
+ builder.title(R.string.directorysort);
+ int current =
+ Integer.parseInt(
+ getPrefs()
+ .getString(PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "0"));
+
+ builder
+ .items(sort)
+ .itemsCallbackSingleChoice(
+ current,
+ (dialog1, view, which, text) -> {
+ getPrefs()
+ .edit()
+ .putString(
+ PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "" + which)
+ .commit();
+ mainFragment
+ .getMainFragmentViewModel()
+ .initSortModes(
+ SortHandler.getSortType(
+ this, mainFragment.getMainFragmentViewModel().getCurrentPath()),
+ getPrefs());
+ mainFragment.updateList(false);
+ dialog1.dismiss();
+ return true;
+ });
+ builder.build().show();
+ break;
+ case R.id.hiddenitems:
+ HiddenFilesDialog.showHiddenDialog(this, mainFragment);
+ break;
+ case R.id.view:
+ int pathLayout =
+ dataUtils.getListOrGridForPath(mainFragment.getCurrentPath(), DataUtils.LIST);
+ if (mainFragment.getMainFragmentViewModel().isList()) {
+ if (pathLayout == DataUtils.LIST) {
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(
+ UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
+ });
+ }
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
+
+ dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.GRID);
+ } else {
+ if (pathLayout == DataUtils.GRID) {
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(
+ UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
+ });
+ }
- // called when the user exits the action mode
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- // The action bar home/up action should open or close the drawer.
- // ActionBarDrawerToggle will take care of this.
- if (drawer.onOptionsItemSelected(item)) return true;
- // Same thing goes to other Fragments loaded.
- // If they have handled the options, we don't need to.
- if (getFragmentAtFrame().onOptionsItemSelected(item)) return true;
-
- // Handle action buttons
- executeWithMainFragment(
- mainFragment -> {
- switch (item.getItemId()) {
- case R.id.home:
- mainFragment.home();
- break;
- case R.id.history:
- HistoryDialog.showHistoryDialog(this, mainFragment);
- break;
- case R.id.sethome:
- if (mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.FILE
- && mainFragment.getMainFragmentViewModel().getOpenMode() != OpenMode.ROOT) {
- Toast.makeText(mainActivity, R.string.not_allowed, Toast.LENGTH_SHORT).show();
- break;
- }
- final MaterialDialog dialog =
- GeneralDialogCreation.showBasicDialog(
- mainActivity,
- R.string.question_set_path_as_home,
- R.string.set_as_home,
- R.string.yes,
- R.string.no);
- dialog
- .getActionButton(DialogAction.POSITIVE)
- .setOnClickListener(
- (v) -> {
- mainFragment
- .getMainFragmentViewModel()
- .setHome(mainFragment.getCurrentPath());
- updatePaths(mainFragment.getMainFragmentViewModel().getNo());
- dialog.dismiss();
- });
- dialog.show();
- break;
- case R.id.exit:
- finish();
- break;
- case R.id.sortby:
- GeneralDialogCreation.showSortDialog(mainFragment, getAppTheme(), getPrefs());
- break;
- case R.id.dsort:
- String[] sort = getResources().getStringArray(R.array.directorysortmode);
- MaterialDialog.Builder builder = new MaterialDialog.Builder(mainActivity);
- builder.theme(getAppTheme().getMaterialDialogTheme(this));
- builder.title(R.string.directorysort);
- int current =
- Integer.parseInt(
- getPrefs()
- .getString(PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "0"));
-
- builder
- .items(sort)
- .itemsCallbackSingleChoice(
- current,
- (dialog1, view, which, text) -> {
- getPrefs()
- .edit()
- .putString(
- PreferencesConstants.PREFERENCE_DIRECTORY_SORT_MODE, "" + which)
- .commit();
- mainFragment
- .getMainFragmentViewModel()
- .initSortModes(
- SortHandler.getSortType(
- this, mainFragment.getMainFragmentViewModel().getCurrentPath()),
- getPrefs());
- mainFragment.updateList(false);
- dialog1.dismiss();
- return true;
- });
- builder.build().show();
- break;
- case R.id.hiddenitems:
- HiddenFilesDialog.showHiddenDialog(this, mainFragment);
- break;
- case R.id.view:
- int pathLayout =
- dataUtils.getListOrGridForPath(mainFragment.getCurrentPath(), DataUtils.LIST);
- if (mainFragment.getMainFragmentViewModel().isList()) {
- if (pathLayout == DataUtils.LIST) {
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(
- UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
- });
- }
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
-
- dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.GRID);
- } else {
- if (pathLayout == DataUtils.GRID) {
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(
- UtilsHandler.Operation.GRID, mainFragment.getCurrentPath()));
- });
- }
-
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
-
- dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.LIST);
- }
- mainFragment.switchView();
- break;
- case R.id.extract:
- Fragment fragment1 = getFragmentAtFrame();
- if (fragment1 instanceof CompressedExplorerFragment) {
- mainActivityHelper.extractFile(
- ((CompressedExplorerFragment) fragment1).compressedFile);
- }
- break;
- case R.id.search:
- getAppbar().getSearchView().revealSearchView();
- break;
- }
- return null;
- },
- false);
-
- return super.onOptionsItemSelected(item);
- }
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.LIST, mainFragment.getCurrentPath()));
+
+ dataUtils.setPathAsGridOrList(mainFragment.getCurrentPath(), DataUtils.LIST);
+ }
+ mainFragment.switchView();
+ break;
+ case R.id.extract:
+ Fragment fragment1 = getFragmentAtFrame();
+ if (fragment1 instanceof CompressedExplorerFragment) {
+ mainActivityHelper.extractFile(
+ ((CompressedExplorerFragment) fragment1).compressedFile);
+ }
+ break;
+ case R.id.search:
+ getAppbar().getSearchView().revealSearchView();
+ break;
+ }
+ return null;
+ },
+ false);
+
+ return super.onOptionsItemSelected(item);
+ }
/*@Override
public void onRestoreInstanceState(Bundle savedInstanceState){
@@ -1287,1265 +1261,1255 @@ public void onRestoreInstanceState(Bundle savedInstanceState){
selectedStorage = savedInstanceState.getInt(KEY_DRAWER_SELECTED, 0);
}*/
- @Override
- protected void onPostCreate(Bundle savedInstanceState) {
- super.onPostCreate(savedInstanceState);
- // Sync the toggle state after onRestoreInstanceState has occurred.
- drawer.syncState();
- }
-
- @Override
- public void onConfigurationChanged(Configuration newConfig) {
- super.onConfigurationChanged(newConfig);
- drawer.onConfigurationChanged(newConfig);
- // Pass any configuration change to the drawer toggls
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putInt(KEY_DRAWER_SELECTED, getDrawer().getDrawerSelectedItem());
- outState.putBoolean(KEY_SELECTED_LIST_ITEM, listItemSelected);
- if (pasteHelper != null) {
- outState.putParcelable(PASTEHELPER_BUNDLE, pasteHelper);
- }
-
- if (oppathe != null) {
- outState.putString(KEY_OPERATION_PATH, oppathe);
- outState.putString(KEY_OPERATED_ON_PATH, oppathe1);
- outState.putParcelableArrayList(KEY_OPERATIONS_PATH_LIST, (oparrayList));
- outState.putInt(KEY_OPERATION, operation);
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ drawer.syncState();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ drawer.onConfigurationChanged(newConfig);
+ // Pass any configuration change to the drawer toggls
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_DRAWER_SELECTED, getDrawer().getDrawerSelectedItem());
+ outState.putBoolean(KEY_SELECTED_LIST_ITEM, listItemSelected);
+ if (pasteHelper != null) {
+ outState.putParcelable(PASTEHELPER_BUNDLE, pasteHelper);
+ }
+
+ if (oppathe != null) {
+ outState.putString(KEY_OPERATION_PATH, oppathe);
+ outState.putString(KEY_OPERATED_ON_PATH, oppathe1);
+ outState.putParcelableArrayList(KEY_OPERATIONS_PATH_LIST, (oparrayList));
+ outState.putInt(KEY_OPERATION, operation);
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ unregisterReceiver(mainActivityHelper.mNotificationReceiver);
+ unregisterReceiver(receiver2);
+
+ if (SDK_INT >= KITKAT) {
+ unregisterReceiver(mOtgReceiver);
+ }
+
+ final Toast toast = this.toast.get();
+ if (toast != null) {
+ toast.cancel();
+ }
+ this.toast = new WeakReference<>(null);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (materialDialog != null && !materialDialog.isShowing()) {
+ materialDialog.show();
+ materialDialog = null;
+ }
+
+ drawer.refreshDrawer();
+ drawer.refactorDrawerLockMode();
+
+ IntentFilter newFilter = new IntentFilter();
+ newFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
+ newFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
+ newFilter.addDataScheme(ContentResolver.SCHEME_FILE);
+ registerReceiver(mainActivityHelper.mNotificationReceiver, newFilter);
+ registerReceiver(receiver2, new IntentFilter(TAG_INTENT_FILTER_GENERAL));
+
+ if (SDK_INT >= Build.VERSION_CODES.KITKAT) {
+ updateUsbInformation();
+ }
+ }
+
+ /** Updates everything related to USB devices MUST ALWAYS be called after onResume() */
+ @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+ private void updateUsbInformation() {
+ boolean isInformationUpdated = false;
+ List connectedDevices = OTGUtil.getMassStorageDevicesConnected(this);
+
+ if (!connectedDevices.isEmpty()) {
+ if (SingletonUsbOtg.getInstance().getUsbOtgRoot() != null
+ && OTGUtil.isUsbUriAccessible(this)) {
+ for (UsbOtgRepresentation device : connectedDevices) {
+ if (SingletonUsbOtg.getInstance().checkIfRootIsFromDevice(device)) {
+ isInformationUpdated = true;
+ break;
+ }
}
- }
- @Override
- protected void onPause() {
- super.onPause();
- unregisterReceiver(mainActivityHelper.mNotificationReceiver);
- unregisterReceiver(receiver2);
-
- if (SDK_INT >= KITKAT) {
- unregisterReceiver(mOtgReceiver);
+ if (!isInformationUpdated) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
}
+ }
- final Toast toast = this.toast.get();
- if (toast != null) {
- toast.cancel();
- }
- this.toast = new WeakReference<>(null);
+ if (!isInformationUpdated) {
+ SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
+ isInformationUpdated = true;
+ }
}
- @Override
- public void onResume() {
- super.onResume();
- if (materialDialog != null && !materialDialog.isShowing()) {
- materialDialog.show();
- materialDialog = null;
- }
-
- drawer.refreshDrawer();
- drawer.refactorDrawerLockMode();
-
- IntentFilter newFilter = new IntentFilter();
- newFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
- newFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
- newFilter.addDataScheme(ContentResolver.SCHEME_FILE);
- registerReceiver(mainActivityHelper.mNotificationReceiver, newFilter);
- registerReceiver(receiver2, new IntentFilter(TAG_INTENT_FILTER_GENERAL));
-
- if (SDK_INT >= Build.VERSION_CODES.KITKAT) {
- updateUsbInformation();
- }
+ if (!isInformationUpdated) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ drawer.refreshDrawer();
}
- /**
- * Updates everything related to USB devices MUST ALWAYS be called after onResume()
- */
- @RequiresApi(api = Build.VERSION_CODES.KITKAT)
- private void updateUsbInformation() {
- boolean isInformationUpdated = false;
- List connectedDevices = OTGUtil.getMassStorageDevicesConnected(this);
-
- if (!connectedDevices.isEmpty()) {
- if (SingletonUsbOtg.getInstance().getUsbOtgRoot() != null
- && OTGUtil.isUsbUriAccessible(this)) {
- for (UsbOtgRepresentation device : connectedDevices) {
- if (SingletonUsbOtg.getInstance().checkIfRootIsFromDevice(device)) {
- isInformationUpdated = true;
- break;
- }
- }
+ // Registering intent filter for OTG
+ IntentFilter otgFilter = new IntentFilter();
+ otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
+ otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
+ registerReceiver(mOtgReceiver, otgFilter);
+ }
- if (!isInformationUpdated) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- }
- }
-
- if (!isInformationUpdated) {
- SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
- isInformationUpdated = true;
+ /** Receiver to check if a USB device is connected at the runtime of application */
+ BroadcastReceiver mOtgReceiver =
+ new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
+ List connectedDevices =
+ OTGUtil.getMassStorageDevicesConnected(MainActivity.this);
+ if (!connectedDevices.isEmpty()) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
+ drawer.refreshDrawer();
}
- }
-
- if (!isInformationUpdated) {
+ } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
SingletonUsbOtg.getInstance().resetUsbOtgRoot();
drawer.refreshDrawer();
+ goToMain(null);
+ }
}
+ };
- // Registering intent filter for OTG
- IntentFilter otgFilter = new IntentFilter();
- otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
- otgFilter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
- registerReceiver(mOtgReceiver, otgFilter);
- }
-
- /**
- * Receiver to check if a USB device is connected at the runtime of application
- */
- BroadcastReceiver mOtgReceiver =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_ATTACHED)) {
- List connectedDevices =
- OTGUtil.getMassStorageDevicesConnected(MainActivity.this);
- if (!connectedDevices.isEmpty()) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- SingletonUsbOtg.getInstance().setConnectedDevice(connectedDevices.get(0));
- drawer.refreshDrawer();
- }
- } else if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- drawer.refreshDrawer();
- goToMain(null);
- }
- }
- };
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_MENU) {
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_MENU) {
/*
ImageView ib = findViewById(R.id.action_overflow);
if (ib.getVisibility() == View.VISIBLE) {
ib.performClick();
}
*/
- // return 'true' to prevent further propagation of the key event
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- // TODO: 6/5/2017 Android may choose to not call this method before destruction
- // TODO: https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
- closeInteractiveShell();
- NetCopyClientConnectionPool.INSTANCE.shutdown();
- if (drawer != null && drawer.getBilling() != null) {
- drawer.getBilling().destroyBillingInstance();
- }
- }
-
- /**
- * Closes the interactive shell and threads associated
- */
- private void closeInteractiveShell() {
- if (isRootExplorer()) {
- // close interactive shell
- try {
- Shell.getShell().close();
- } catch (IOException e) {
- LOG.error("Error closing Shell", e);
- }
- }
- }
-
- public void updatePaths(int pos) {
- TabFragment tabFragment = getTabFragment();
- if (tabFragment != null) tabFragment.updatePaths(pos);
- }
-
- public void openCompressed(String path) {
- appBarLayout.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
- FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
- fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_in_bottom);
- Fragment zipFragment = new CompressedExplorerFragment();
- Bundle bundle = new Bundle();
- bundle.putString(CompressedExplorerFragment.KEY_PATH, path);
- zipFragment.setArguments(bundle);
- fragmentTransaction.add(R.id.content_frame, zipFragment);
- fragmentTransaction.commitAllowingStateLoss();
- }
-
- public @Nullable
- MainFragment getCurrentMainFragment() {
- TabFragment tab = getTabFragment();
-
- if (tab != null && tab.getCurrentTabFragment() instanceof MainFragment) {
- return (MainFragment) tab.getCurrentTabFragment();
- } else return null;
- }
-
- public TabFragment getTabFragment() {
- Fragment fragment = getFragmentAtFrame();
-
- if (!(fragment instanceof TabFragment)) return null;
- else return (TabFragment) fragment;
- }
-
- public Fragment getFragmentAtFrame() {
- return getSupportFragmentManager().findFragmentById(R.id.content_frame);
- }
-
- public void setPagingEnabled(boolean b) {
- getTabFragment().setPagingEnabled(b);
- }
-
- public File getUsbDrive() {
- File parent = new File("/storage");
-
- try {
- for (File f : parent.listFiles())
- if (f.exists() && f.getName().toLowerCase().contains("usb") && f.canExecute())
- return f;
- } catch (Exception e) {
- }
-
- parent = new File("/mnt/sdcard/usbStorage");
- if (parent.exists() && parent.canExecute()) return parent;
- parent = new File("/mnt/sdcard/usb_storage");
- if (parent.exists() && parent.canExecute()) return parent;
-
- return null;
- }
-
- public SpeedDialView getFAB() {
- return floatingActionButton;
- }
-
- public void showFab() {
- getFAB().setVisibility(View.VISIBLE);
- getFAB().show();
- CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
- params.setBehavior(new SpeedDialView.ScrollingViewSnackbarBehavior());
- getFAB().requestLayout();
- }
-
- public void hideFab() {
- getFAB().setVisibility(View.GONE);
- getFAB().hide();
- CoordinatorLayout.LayoutParams params =
- (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
- params.setBehavior(new SpeedDialView.NoBehavior());
- getFAB().requestLayout();
- }
-
- public AppBar getAppbar() {
- return appbar;
- }
-
- public Drawer getDrawer() {
- return drawer;
+ // return 'true' to prevent further propagation of the key event
+ return true;
+ }
+
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ // TODO: 6/5/2017 Android may choose to not call this method before destruction
+ // TODO: https://developer.android.com/reference/android/app/Activity.html#onDestroy%28%29
+ closeInteractiveShell();
+ NetCopyClientConnectionPool.INSTANCE.shutdown();
+ if (drawer != null && drawer.getBilling() != null) {
+ drawer.getBilling().destroyBillingInstance();
+ }
+ }
+
+ /** Closes the interactive shell and threads associated */
+ private void closeInteractiveShell() {
+ if (isRootExplorer()) {
+ // close interactive shell
+ try {
+ Shell.getShell().close();
+ } catch (IOException e) {
+ LOG.error("Error closing Shell", e);
+ }
}
-
- protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
- super.onActivityResult(requestCode, responseCode, intent);
- if (requestCode == Drawer.image_selector_request_code) {
- drawer.onActivityResult(requestCode, responseCode, intent);
- } else if (requestCode == 3) {
- Uri treeUri;
- if (responseCode == Activity.RESULT_OK) {
- // Get Uri from Storage Access Framework.
- treeUri = intent.getData();
- // Persist URI - this is required for verification of writability.
- if (treeUri != null)
- getPrefs()
- .edit()
- .putString(PreferencesConstants.PREFERENCE_URI, treeUri.toString())
- .apply();
- } else {
- // If not confirmed SAF, or if still not writable, then revert settings.
+ }
+
+ public void updatePaths(int pos) {
+ TabFragment tabFragment = getTabFragment();
+ if (tabFragment != null) tabFragment.updatePaths(pos);
+ }
+
+ public void openCompressed(String path) {
+ appBarLayout.animate().translationY(0).setInterpolator(new DecelerateInterpolator(2)).start();
+ FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
+ fragmentTransaction.setCustomAnimations(R.anim.slide_in_top, R.anim.slide_in_bottom);
+ Fragment zipFragment = new CompressedExplorerFragment();
+ Bundle bundle = new Bundle();
+ bundle.putString(CompressedExplorerFragment.KEY_PATH, path);
+ zipFragment.setArguments(bundle);
+ fragmentTransaction.add(R.id.content_frame, zipFragment);
+ fragmentTransaction.commitAllowingStateLoss();
+ }
+
+ public @Nullable MainFragment getCurrentMainFragment() {
+ TabFragment tab = getTabFragment();
+
+ if (tab != null && tab.getCurrentTabFragment() instanceof MainFragment) {
+ return (MainFragment) tab.getCurrentTabFragment();
+ } else return null;
+ }
+
+ public TabFragment getTabFragment() {
+ Fragment fragment = getFragmentAtFrame();
+
+ if (!(fragment instanceof TabFragment)) return null;
+ else return (TabFragment) fragment;
+ }
+
+ public Fragment getFragmentAtFrame() {
+ return getSupportFragmentManager().findFragmentById(R.id.content_frame);
+ }
+
+ public void setPagingEnabled(boolean b) {
+ getTabFragment().setPagingEnabled(b);
+ }
+
+ public File getUsbDrive() {
+ File parent = new File("/storage");
+
+ try {
+ for (File f : parent.listFiles())
+ if (f.exists() && f.getName().toLowerCase().contains("usb") && f.canExecute()) return f;
+ } catch (Exception e) {
+ }
+
+ parent = new File("/mnt/sdcard/usbStorage");
+ if (parent.exists() && parent.canExecute()) return parent;
+ parent = new File("/mnt/sdcard/usb_storage");
+ if (parent.exists() && parent.canExecute()) return parent;
+
+ return null;
+ }
+
+ public SpeedDialView getFAB() {
+ return floatingActionButton;
+ }
+
+ public void showFab() {
+ getFAB().setVisibility(View.VISIBLE);
+ getFAB().show();
+ CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
+ params.setBehavior(new SpeedDialView.ScrollingViewSnackbarBehavior());
+ getFAB().requestLayout();
+ }
+
+ public void hideFab() {
+ getFAB().setVisibility(View.GONE);
+ getFAB().hide();
+ CoordinatorLayout.LayoutParams params =
+ (CoordinatorLayout.LayoutParams) getFAB().getLayoutParams();
+ params.setBehavior(new SpeedDialView.NoBehavior());
+ getFAB().requestLayout();
+ }
+
+ public AppBar getAppbar() {
+ return appbar;
+ }
+
+ public Drawer getDrawer() {
+ return drawer;
+ }
+
+ protected void onActivityResult(int requestCode, int responseCode, Intent intent) {
+ super.onActivityResult(requestCode, responseCode, intent);
+ if (requestCode == Drawer.image_selector_request_code) {
+ drawer.onActivityResult(requestCode, responseCode, intent);
+ } else if (requestCode == 3) {
+ Uri treeUri;
+ if (responseCode == Activity.RESULT_OK) {
+ // Get Uri from Storage Access Framework.
+ treeUri = intent.getData();
+ // Persist URI - this is required for verification of writability.
+ if (treeUri != null)
+ getPrefs()
+ .edit()
+ .putString(PreferencesConstants.PREFERENCE_URI, treeUri.toString())
+ .apply();
+ } else {
+ // If not confirmed SAF, or if still not writable, then revert settings.
/* DialogUtil.displayError(getActivity(), R.string.message_dialog_cannot_write_to_folder_saf, false, currentFolder);
||!FileUtil.isWritableNormalOrSaf(currentFolder)*/
- return;
- }
-
- // After confirmation, update stored value of folder.
- // Persist access permissions.
-
- if (SDK_INT >= KITKAT) {
- getContentResolver()
- .takePersistableUriPermission(
- treeUri,
- Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
- }
-
- executeWithMainFragment(
- mainFragment -> {
- switch (operation) {
- case DELETE: // deletion
- new DeleteTask(mainActivity).execute((oparrayList));
- break;
- case COPY: // copying
- // legacy compatibility
- if (oparrayList != null && oparrayList.size() != 0) {
- oparrayListList = new ArrayList<>();
- oparrayListList.add(oparrayList);
- oparrayList = null;
- oppatheList = new ArrayList<>();
- oppatheList.add(oppathe);
- oppathe = "";
- }
- for (int i = 0; i < oparrayListList.size(); i++) {
- ArrayList sourceList = oparrayListList.get(i);
- Intent intent1 = new Intent(this, CopyService.class);
- intent1.putExtra(CopyService.TAG_COPY_SOURCES, sourceList);
- intent1.putExtra(CopyService.TAG_COPY_TARGET, oppatheList.get(i));
- ServiceWatcherUtil.runService(this, intent1);
- }
- break;
- case MOVE: // moving
- // legacy compatibility
- if (oparrayList != null && oparrayList.size() != 0) {
- oparrayListList = new ArrayList<>();
- oparrayListList.add(oparrayList);
- oparrayList = null;
- oppatheList = new ArrayList<>();
- oppatheList.add(oppathe);
- oppathe = "";
- }
-
- TaskKt.fromTask(
- new MoveFilesTask(
- oparrayListList,
- isRootExplorer(),
- mainFragment.getCurrentPath(),
- this,
- OpenMode.FILE,
- oppatheList));
- break;
- case NEW_FOLDER: // mkdir
- mainActivityHelper.mkDir(
- new HybridFile(OpenMode.FILE, oppathe),
- RootHelper.generateBaseFile(new File(oppathe), true),
- mainFragment);
- break;
- case RENAME:
- mainActivityHelper.rename(
- mainFragment.getMainFragmentViewModel().getOpenMode(),
- (oppathe),
- (oppathe1),
- null,
- false,
- mainActivity,
- isRootExplorer());
- mainFragment.updateList(false);
- break;
- case NEW_FILE:
- mainActivityHelper.mkFile(
- new HybridFile(OpenMode.FILE, oppathe),
- new HybridFile(OpenMode.FILE, oppathe),
- mainFragment);
- break;
- case EXTRACT:
- mainActivityHelper.extractFile(new File(oppathe));
- break;
- case COMPRESS:
- mainActivityHelper.compressFiles(new File(oppathe), oparrayList);
- break;
- case SAVE_FILE:
- FileUtil.writeUriToStorage(
- this, urisToBeSaved, getContentResolver(), mainFragment.getCurrentPath());
- urisToBeSaved = null;
- finish();
- break;
- default:
- LogHelper.logOnProductionOrCrash("Incorrect value for switch");
- }
- return null;
- },
- true);
- operation = UNDEFINED;
- } else if (requestCode == REQUEST_CODE_SAF) {
- executeWithMainFragment(
- mainFragment -> {
- if (responseCode == Activity.RESULT_OK && intent.getData() != null) {
- // otg access
- Uri usbOtgRoot = intent.getData();
- SingletonUsbOtg.getInstance().setUsbOtgRoot(usbOtgRoot);
- mainFragment.loadlist(OTGUtil.PREFIX_OTG, false, OpenMode.OTG, true);
- drawer.closeIfNotLocked();
- if (drawer.isLocked()) drawer.onDrawerClosed();
- } else if (requestCode == REQUEST_CODE_SAF_FTP) {
- FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
- ftpServerFragment.changeFTPServerPath(intent.getData().toString());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
-
- } else {
- Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
- // otg access not provided
- drawer.resetPendingPath();
- }
- return null;
- },
- true);
- }
- }
-
- void initialisePreferences() {
- currentTab = getCurrentTab();
- skinStatusBar = PreferenceUtils.getStatusColor(getPrimary());
- }
-
- void initialiseViews() {
-
- appbar =
- new AppBar(
- this,
- getPrefs(),
- queue -> {
- if (!queue.isEmpty()) {
- mainActivityHelper.search(getPrefs(), queue);
- }
- });
- appBarLayout = getAppbar().getAppbarLayout();
-
- setSupportActionBar(getAppbar().getToolbar());
- drawer = new Drawer(this);
+ return;
+ }
- indicator_layout = findViewById(R.id.indicator_layout);
+ // After confirmation, update stored value of folder.
+ // Persist access permissions.
- getSupportActionBar().setDisplayShowTitleEnabled(false);
- fabBgView = findViewById(R.id.fabs_overlay_layout);
+ if (SDK_INT >= KITKAT) {
+ getContentResolver()
+ .takePersistableUriPermission(
+ treeUri,
+ Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ }
- switch (getAppTheme().getSimpleTheme(this)) {
- case DARK:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
+ executeWithMainFragment(
+ mainFragment -> {
+ switch (operation) {
+ case DELETE: // deletion
+ new DeleteTask(mainActivity).execute((oparrayList));
break;
- case BLACK:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
+ case COPY: // copying
+ // legacy compatibility
+ if (oparrayList != null && oparrayList.size() != 0) {
+ oparrayListList = new ArrayList<>();
+ oparrayListList.add(oparrayList);
+ oparrayList = null;
+ oppatheList = new ArrayList<>();
+ oppatheList.add(oppathe);
+ oppathe = "";
+ }
+ for (int i = 0; i < oparrayListList.size(); i++) {
+ ArrayList sourceList = oparrayListList.get(i);
+ Intent intent1 = new Intent(this, CopyService.class);
+ intent1.putExtra(CopyService.TAG_COPY_SOURCES, sourceList);
+ intent1.putExtra(CopyService.TAG_COPY_TARGET, oppatheList.get(i));
+ ServiceWatcherUtil.runService(this, intent1);
+ }
break;
- }
-
- fabBgView.setOnClickListener(
- view -> {
- if (getAppbar().getSearchView().isEnabled())
- getAppbar().getSearchView().hideSearchView();
- });
-
- drawer.setDrawerHeaderBackground();
- }
-
- /**
- * Call this method when you need to update the MainActivity view components' colors based on
- * update in the {@link MainActivity#currentTab} Warning - All the variables should be initialised
- * before calling this method!
- */
- public void updateViews(ColorDrawable colorDrawable) {
- // appbar view color
- appbar.getBottomBar().setBackgroundColor(colorDrawable.getColor());
- // action bar color
- mainActivity.getSupportActionBar().setBackgroundDrawable(colorDrawable);
-
- drawer.setBackgroundColor(colorDrawable.getColor());
-
- if (SDK_INT >= LOLLIPOP) {
- // for lollipop devices, the status bar color
- mainActivity.getWindow().setStatusBarColor(colorDrawable.getColor());
- if (getBoolean(PREFERENCE_COLORED_NAVIGATION)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(PreferenceUtils.getStatusColor(colorDrawable.getColor()));
- } else {
- if (getAppTheme().equals(AppTheme.LIGHT)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, android.R.color.white));
- } else if (getAppTheme().equals(AppTheme.BLACK)) {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, android.R.color.black));
- } else {
- mainActivity
- .getWindow()
- .setNavigationBarColor(Utils.getColor(this, R.color.holo_dark_background));
+ case MOVE: // moving
+ // legacy compatibility
+ if (oparrayList != null && oparrayList.size() != 0) {
+ oparrayListList = new ArrayList<>();
+ oparrayListList.add(oparrayList);
+ oparrayList = null;
+ oppatheList = new ArrayList<>();
+ oppatheList.add(oppathe);
+ oppathe = "";
}
- }
- } else if (SDK_INT == KITKAT_WATCH || SDK_INT == KITKAT) {
- // for kitkat devices, the status bar color
- SystemBarTintManager tintManager = new SystemBarTintManager(this);
- tintManager.setStatusBarTintEnabled(true);
- tintManager.setStatusBarTintColor(colorDrawable.getColor());
- }
- }
-
- void initialiseFab() {
- int colorAccent = getAccent();
-
- floatingActionButton = findViewById(R.id.fabs_menu);
- floatingActionButton.setMainFabClosedBackgroundColor(colorAccent);
- floatingActionButton.setMainFabOpenedBackgroundColor(colorAccent);
- initializeFabActionViews();
- }
-
- public void initializeFabActionViews() {
- // NOTE: SpeedDial inverts insert index than FABsmenu
- FabWithLabelView cloudFab =
- initFabTitle(
- R.id.menu_new_cloud, R.string.cloud_connection, R.drawable.ic_cloud_white_24dp);
- FabWithLabelView newFileFab =
- initFabTitle(R.id.menu_new_file, R.string.file, R.drawable.ic_insert_drive_file_white_48dp);
- FabWithLabelView newFolderFab =
- initFabTitle(R.id.menu_new_folder, R.string.folder, R.drawable.folder_fab);
-
- floatingActionButton.setOnActionSelectedListener(new FabActionListener(this));
- floatingActionButton.setOnClickListener(
- view -> {
- fabButtonClick(cloudFab);
- });
- floatingActionButton.setOnFocusChangeListener(new CustomZoomFocusChange());
- floatingActionButton.getMainFab().setOnFocusChangeListener(new CustomZoomFocusChange());
- floatingActionButton.setNextFocusUpId(cloudFab.getId());
- floatingActionButton.getMainFab().setNextFocusUpId(cloudFab.getId());
- floatingActionButton.setOnKeyListener(
- (v, keyCode, event) -> {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
- if (getCurrentTab() == 0 && getFAB().isFocused()) {
- getTabFragment().setCurrentItem(1);
- }
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
- findViewById(R.id.content_frame).requestFocus();
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
- if (pasteHelper != null
- && pasteHelper.getSnackbar() != null
- && pasteHelper.getSnackbar().isShown())
- ((Snackbar.SnackbarLayout) pasteHelper.getSnackbar().getView())
- .findViewById(R.id.snackBarActionButton)
- .requestFocus();
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
- fabButtonClick(cloudFab);
- } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
- onBackPressed();
- } else {
- return false;
- }
- }
- return true;
- });
- cloudFab.setNextFocusDownId(floatingActionButton.getMainFab().getId());
- cloudFab.setNextFocusUpId(newFileFab.getId());
- cloudFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- newFileFab.setNextFocusDownId(cloudFab.getId());
- newFileFab.setNextFocusUpId(newFolderFab.getId());
- newFileFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- newFolderFab.setNextFocusDownId(newFileFab.getId());
- newFolderFab.setOnFocusChangeListener(new CustomZoomFocusChange());
- }
-
- private void fabButtonClick(FabWithLabelView cloudFab) {
- if (floatingActionButton.isOpen()) {
- floatingActionButton.close(true);
- } else {
- floatingActionButton.open(true);
- cloudFab.requestFocus();
- }
- }
-
- private FabWithLabelView initFabTitle(
- @IdRes int id, @StringRes int fabTitle, @DrawableRes int icon) {
- int iconSkin = getCurrentColorPreference().getIconSkin();
-
- SpeedDialActionItem.Builder builder =
- new SpeedDialActionItem.Builder(id, icon)
- .setLabel(fabTitle)
- .setFabBackgroundColor(iconSkin);
-
- switch (getAppTheme().getSimpleTheme(this)) {
- case LIGHT:
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_light);
+ TaskKt.fromTask(
+ new MoveFilesTask(
+ oparrayListList,
+ isRootExplorer(),
+ mainFragment.getCurrentPath(),
+ this,
+ OpenMode.FILE,
+ oppatheList));
break;
- case DARK:
- builder
- .setLabelBackgroundColor(Utils.getColor(this, R.color.holo_dark_background))
- .setLabelColor(Utils.getColor(this, R.color.text_dark));
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
+ case NEW_FOLDER: // mkdir
+ mainActivityHelper.mkDir(
+ new HybridFile(OpenMode.FILE, oppathe),
+ RootHelper.generateBaseFile(new File(oppathe), true),
+ mainFragment);
break;
- case BLACK:
- builder
- .setLabelBackgroundColor(Color.BLACK)
- .setLabelColor(Utils.getColor(this, R.color.text_dark));
- fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
+ case RENAME:
+ mainActivityHelper.rename(
+ mainFragment.getMainFragmentViewModel().getOpenMode(),
+ (oppathe),
+ (oppathe1),
+ null,
+ false,
+ mainActivity,
+ isRootExplorer());
+ mainFragment.updateList(false);
break;
- }
-
- return floatingActionButton.addActionItem(builder.create());
- }
-
- public boolean copyToClipboard(Context context, String text) {
- try {
- android.content.ClipboardManager clipboard =
- (android.content.ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
- android.content.ClipData clip =
- android.content.ClipData.newPlainText("Path copied to clipboard", text);
- clipboard.setPrimaryClip(clip);
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-
- public void renameBookmark(final String title, final String path) {
- if (dataUtils.containsBooks(new String[]{title, path}) != -1) {
- RenameBookmark renameBookmark = RenameBookmark.getInstance(title, path, getAccent());
- if (renameBookmark != null) renameBookmark.show(getFragmentManager(), "renamedialog");
- }
- }
-
- public PasteHelper getPaste() {
- return pasteHelper;
- }
-
- public MainActivityActionMode getActionModeHelper() {
- return this.mainActivityActionMode;
- }
-
- public void setPaste(PasteHelper p) {
- pasteHelper = p;
- }
-
- @Override
- public void onNewIntent(Intent i) {
- super.onNewIntent(i);
- intent = i;
- path = i.getStringExtra("path");
-
- if (path != null) {
- if (new File(path).isDirectory()) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment != null) {
- mainFragment.loadlist(path, false, OpenMode.FILE, true);
- } else {
- goToMain(path);
- }
- } else FileUtils.openFile(new File(path), mainActivity, getPrefs());
- } else if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, this);
- }
- } else if (i.getCategories() != null
- && i.getCategories().contains(CLOUD_AUTHENTICATOR_GDRIVE)) {
- // we used an external authenticator instead of APIs. Probably for Google Drive
- CloudRail.setAuthenticationResponse(intent);
- if (intent.getAction() != null) {
- checkForExternalIntent(intent);
- invalidateFragmentAndBundle(null, false);
- }
- } else if ((openProcesses = i.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false))) {
- FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
- transaction.replace(
- R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
- // transaction.addToBackStack(null);
- openProcesses = false;
- // title.setText(utils.getString(con, R.string.process_viewer));
- // Commit the transaction
- transaction.commitAllowingStateLoss();
- supportInvalidateOptionsMenu();
- } else if (intent.getAction() != null) {
- checkForExternalIntent(intent);
- invalidateFragmentAndBundle(null, false);
-
- if (SDK_INT >= KITKAT) {
- if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
- SingletonUsbOtg.getInstance().resetUsbOtgRoot();
- drawer.refreshDrawer();
- }
+ case NEW_FILE:
+ mainActivityHelper.mkFile(
+ new HybridFile(OpenMode.FILE, oppathe),
+ new HybridFile(OpenMode.FILE, oppathe),
+ mainFragment);
+ break;
+ case EXTRACT:
+ mainActivityHelper.extractFile(new File(oppathe));
+ break;
+ case COMPRESS:
+ mainActivityHelper.compressFiles(new File(oppathe), oparrayList);
+ break;
+ case SAVE_FILE:
+ FileUtil.writeUriToStorage(
+ this, urisToBeSaved, getContentResolver(), mainFragment.getCurrentPath());
+ urisToBeSaved = null;
+ finish();
+ break;
+ default:
+ LogHelper.logOnProductionOrCrash("Incorrect value for switch");
}
- }
- }
-
- private BroadcastReceiver receiver2 =
- new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent i) {
- if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
- ArrayList failedOps =
- i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
- if (failedOps != null) {
- mainActivityHelper.showFailedOperationDialog(failedOps, mainActivity);
- }
- }
- }
- };
-
- public void showSMBDialog(String name, String path, boolean edit) {
- if (path.length() > 0 && name.length() == 0) {
- int i = dataUtils.containsServer(new String[]{name, path});
- if (i != -1) name = dataUtils.getServers().get(i)[0];
- }
- SmbConnectDialog smbConnectDialog = new SmbConnectDialog();
- Bundle bundle = new Bundle();
- bundle.putString("name", name);
- bundle.putString("path", path);
- bundle.putBoolean("edit", edit);
- smbConnectDialog.setArguments(bundle);
- smbConnectDialog.show(getFragmentManager(), "smbdailog");
- }
-
- @SuppressLint("CheckResult")
- public void showSftpDialog(String name, String path, boolean edit) {
- if (path.length() > 0 && name.length() == 0) {
- int i = dataUtils.containsServer(new String[]{name, path});
- if (i != -1) name = dataUtils.getServers().get(i)[0];
- }
- SftpConnectDialog sftpConnectDialog = new SftpConnectDialog();
- String finalName = name;
- Flowable.fromCallable(() -> new NetCopyClientConnectionPool.ConnectionInfo(path))
- .flatMap(
- connectionInfo -> {
- Bundle retval = new Bundle();
- retval.putString(ARG_PROTOCOL, connectionInfo.getPrefix());
- retval.putString(ARG_NAME, finalName);
- retval.putString(ARG_ADDRESS, connectionInfo.getHost());
- retval.putInt(ARG_PORT, connectionInfo.getPort());
- if (!TextUtils.isEmpty(connectionInfo.getDefaultPath())) {
- retval.putString(ARG_DEFAULT_PATH, connectionInfo.getDefaultPath());
- }
- retval.putString(ARG_USERNAME, connectionInfo.getUsername());
-
- if (connectionInfo.getPassword() == null) {
- retval.putBoolean(ARG_HAS_PASSWORD, false);
- retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path));
- } else {
- retval.putBoolean(ARG_HAS_PASSWORD, true);
- retval.putString(ARG_PASSWORD, connectionInfo.getPassword());
- }
- retval.putBoolean(ARG_EDIT, edit);
- return Flowable.just(retval);
- })
- .subscribeOn(Schedulers.computation())
- .subscribe(
- bundle -> {
- sftpConnectDialog.setArguments(bundle);
- sftpConnectDialog.setCancelable(true);
- sftpConnectDialog.show(getSupportFragmentManager(), "sftpdialog");
- });
- }
+ return null;
+ },
+ true);
+ operation = UNDEFINED;
+ } else if (requestCode == REQUEST_CODE_SAF) {
+ executeWithMainFragment(
+ mainFragment -> {
+ if (responseCode == Activity.RESULT_OK && intent.getData() != null) {
+ // otg access
+ Uri usbOtgRoot = intent.getData();
+ SingletonUsbOtg.getInstance().setUsbOtgRoot(usbOtgRoot);
+ mainFragment.loadlist(OTGUtil.PREFIX_OTG, false, OpenMode.OTG, true);
+ drawer.closeIfNotLocked();
+ if (drawer.isLocked()) drawer.onDrawerClosed();
+ } else if (requestCode == REQUEST_CODE_SAF_FTP) {
+ FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
+ ftpServerFragment.changeFTPServerPath(intent.getData().toString());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
- /**
- * Shows a view that goes from white at it's lowest part to transparent a the top. It covers the
- * fragment.
- */
- public void showSmokeScreen() {
- fabBgView.show();
- }
-
- public void hideSmokeScreen() {
- fabBgView.hide();
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void addConnection(
- boolean edit,
- @NonNull final String name,
- @NonNull final String path,
- @Nullable final String encryptedPath,
- @Nullable final String oldname,
- @Nullable final String oldPath) {
- String[] s = new String[]{name, path};
- if (!edit) {
- if ((dataUtils.containsServer(path)) == -1) {
- Completable.fromRunnable(
- () -> {
- utilsHandler.saveToDatabase(
- new OperationData(UtilsHandler.Operation.SMB, name, encryptedPath));
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(
- () -> {
- dataUtils.addServer(s);
- drawer.refreshDrawer();
- // grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.loadlist(path, false, OpenMode.UNKNOWN, true);
- return null;
- },
- true);
- });
} else {
- Snackbar.make(
- findViewById(R.id.navigation),
- getString(R.string.connection_exists),
- Snackbar.LENGTH_SHORT)
- .show();
+ Toast.makeText(this, R.string.error, Toast.LENGTH_SHORT).show();
+ // otg access not provided
+ drawer.resetPendingPath();
}
+ return null;
+ },
+ true);
+ }
+ }
+
+ void initialisePreferences() {
+ currentTab = getCurrentTab();
+ skinStatusBar = PreferenceUtils.getStatusColor(getPrimary());
+ }
+
+ void initialiseViews() {
+
+ appbar =
+ new AppBar(
+ this,
+ getPrefs(),
+ queue -> {
+ if (!queue.isEmpty()) {
+ mainActivityHelper.search(getPrefs(), queue);
+ }
+ });
+ appBarLayout = getAppbar().getAppbarLayout();
+
+ setSupportActionBar(getAppbar().getToolbar());
+ drawer = new Drawer(this);
+
+ indicator_layout = findViewById(R.id.indicator_layout);
+
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ fabBgView = findViewById(R.id.fabs_overlay_layout);
+
+ switch (getAppTheme().getSimpleTheme(this)) {
+ case DARK:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
+ break;
+ case BLACK:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
+ break;
+ }
+
+ fabBgView.setOnClickListener(
+ view -> {
+ if (getAppbar().getSearchView().isEnabled()) getAppbar().getSearchView().hideSearchView();
+ });
+
+ drawer.setDrawerHeaderBackground();
+ }
+
+ /**
+ * Call this method when you need to update the MainActivity view components' colors based on
+ * update in the {@link MainActivity#currentTab} Warning - All the variables should be initialised
+ * before calling this method!
+ */
+ public void updateViews(ColorDrawable colorDrawable) {
+ // appbar view color
+ appbar.getBottomBar().setBackgroundColor(colorDrawable.getColor());
+ // action bar color
+ mainActivity.getSupportActionBar().setBackgroundDrawable(colorDrawable);
+
+ drawer.setBackgroundColor(colorDrawable.getColor());
+
+ if (SDK_INT >= LOLLIPOP) {
+ // for lollipop devices, the status bar color
+ mainActivity.getWindow().setStatusBarColor(colorDrawable.getColor());
+ if (getBoolean(PREFERENCE_COLORED_NAVIGATION)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(PreferenceUtils.getStatusColor(colorDrawable.getColor()));
+ } else {
+ if (getAppTheme().equals(AppTheme.LIGHT)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, android.R.color.white));
+ } else if (getAppTheme().equals(AppTheme.BLACK)) {
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, android.R.color.black));
} else {
- int i = dataUtils.containsServer(new String[]{oldname, oldPath});
- if (i != -1) {
- dataUtils.removeServer(i);
-
- AppConfig.getInstance()
- .runInBackground(
- () -> {
- utilsHandler.renameSMB(oldname, oldPath, name, path);
- });
- // mainActivity.grid.removePath(oldname, oldPath, DataUtils.SMB);
- }
- dataUtils.addServer(s);
- Collections.sort(dataUtils.getServers(), new BookSorter());
- drawer.refreshDrawer();
- // mainActivity.grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
+ mainActivity
+ .getWindow()
+ .setNavigationBarColor(Utils.getColor(this, R.color.holo_dark_background));
}
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void deleteConnection(final String name, final String path) {
- int i = dataUtils.containsServer(new String[]{name, path});
- if (i != -1) {
- dataUtils.removeServer(i);
- Completable.fromCallable(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(UtilsHandler.Operation.SMB, name, path));
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void delete(String title, String path) {
- Completable.fromCallable(
- () -> {
- utilsHandler.removeFromDatabase(
- new OperationData(UtilsHandler.Operation.BOOKMARKS, title, path));
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
-
- @Override
- @SuppressLint("CheckResult")
- public void modify(String oldpath, String oldname, String newPath, String newname) {
- Completable.fromCallable(
- () -> {
- utilsHandler.renameBookmark(oldname, oldpath, newname, newPath);
- return true;
- })
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe(() -> drawer.refreshDrawer());
- }
-
- @Override
- public void onPreExecute(String query) {
- executeWithMainFragment(
- mainFragment -> {
- mainFragment.mSwipeRefreshLayout.setRefreshing(true);
- mainFragment.onSearchPreExecute(query);
- return null;
- });
- }
-
- @Override
- public void onPostExecute(String query) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- // TODO cancel search
- return;
- }
-
- mainFragment.onSearchCompleted(query);
- mainFragment.mSwipeRefreshLayout.setRefreshing(false);
- }
-
- @Override
- public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- // TODO cancel search
- return;
- }
-
- mainFragment.addSearchResult(hybridFileParcelable, query);
- }
-
- @Override
- public void onCancelled() {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment == null) {
- return;
- }
-
- mainFragment.reloadListElements(
- false, false, !mainFragment.getMainFragmentViewModel().isList());
- mainFragment.mSwipeRefreshLayout.setRefreshing(false);
- }
-
- @Override
- public void addConnection(OpenMode service) {
- try {
- if (cloudHandler.findEntry(service) != null) {
- // cloud entry already exists
- Toast.makeText(
- this, getResources().getString(R.string.connection_exists), Toast.LENGTH_LONG)
- .show();
- } else if (BuildConfig.IS_VERSION_FDROID) {
- Toast.makeText(
- this, getResources().getString(R.string.cloud_error_fdroid), Toast.LENGTH_LONG)
- .show();
+ }
+ } else if (SDK_INT == KITKAT_WATCH || SDK_INT == KITKAT) {
+
+ // for kitkat devices, the status bar color
+ SystemBarTintManager tintManager = new SystemBarTintManager(this);
+ tintManager.setStatusBarTintEnabled(true);
+ tintManager.setStatusBarTintColor(colorDrawable.getColor());
+ }
+ }
+
+ void initialiseFab() {
+ int colorAccent = getAccent();
+
+ floatingActionButton = findViewById(R.id.fabs_menu);
+ floatingActionButton.setMainFabClosedBackgroundColor(colorAccent);
+ floatingActionButton.setMainFabOpenedBackgroundColor(colorAccent);
+ initializeFabActionViews();
+ }
+
+ public void initializeFabActionViews() {
+ // NOTE: SpeedDial inverts insert index than FABsmenu
+ FabWithLabelView cloudFab =
+ initFabTitle(
+ R.id.menu_new_cloud, R.string.cloud_connection, R.drawable.ic_cloud_white_24dp);
+ FabWithLabelView newFileFab =
+ initFabTitle(R.id.menu_new_file, R.string.file, R.drawable.ic_insert_drive_file_white_48dp);
+ FabWithLabelView newFolderFab =
+ initFabTitle(R.id.menu_new_folder, R.string.folder, R.drawable.folder_fab);
+
+ floatingActionButton.setOnActionSelectedListener(new FabActionListener(this));
+ floatingActionButton.setOnClickListener(
+ view -> {
+ fabButtonClick(cloudFab);
+ });
+ floatingActionButton.setOnFocusChangeListener(new CustomZoomFocusChange());
+ floatingActionButton.getMainFab().setOnFocusChangeListener(new CustomZoomFocusChange());
+ floatingActionButton.setNextFocusUpId(cloudFab.getId());
+ floatingActionButton.getMainFab().setNextFocusUpId(cloudFab.getId());
+ floatingActionButton.setOnKeyListener(
+ (v, keyCode, event) -> {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ if (getCurrentTab() == 0 && getFAB().isFocused()) {
+ getTabFragment().setCurrentItem(1);
+ }
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_LEFT) {
+ findViewById(R.id.content_frame).requestFocus();
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_DOWN) {
+ if (pasteHelper != null
+ && pasteHelper.getSnackbar() != null
+ && pasteHelper.getSnackbar().isShown())
+ ((Snackbar.SnackbarLayout) pasteHelper.getSnackbar().getView())
+ .findViewById(R.id.snackBarActionButton)
+ .requestFocus();
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_DPAD_CENTER) {
+ fabButtonClick(cloudFab);
+ } else if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
+ onBackPressed();
} else {
- Toast.makeText(
- MainActivity.this,
- getResources().getString(R.string.please_wait),
- Toast.LENGTH_LONG)
- .show();
- Bundle args = new Bundle();
- args.putInt(ARGS_KEY_LOADER, service.ordinal());
-
- // check if we already had done some work on the loader
- Loader loader = getSupportLoaderManager().getLoader(REQUEST_CODE_CLOUD_LIST_KEY);
- if (loader != null && loader.isStarted()) {
-
- // making sure that loader is not started
- getSupportLoaderManager().destroyLoader(REQUEST_CODE_CLOUD_LIST_KEY);
- }
-
- getSupportLoaderManager().initLoader(REQUEST_CODE_CLOUD_LIST_KEY, args, this);
+ return false;
}
- } catch (CloudPluginException e) {
- LOG.warn("failure when adding cloud plugin connections", e);
- Toast.makeText(this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
- .show();
- }
- }
-
- @Override
- public void deleteConnection(OpenMode service) {
- cloudHandler.clear(service);
- dataUtils.removeAccount(service);
-
- runOnUiThread(drawer::refreshDrawer);
- }
-
- @NonNull
- @Override
- public Loader onCreateLoader(int id, Bundle args) {
- Uri uri =
- Uri.withAppendedPath(
- Uri.parse("content://" + CloudContract.PROVIDER_AUTHORITY), "/keys.db/secret_keys");
-
- String[] projection =
- new String[]{
- CloudContract.COLUMN_ID,
- CloudContract.COLUMN_CLIENT_ID,
- CloudContract.COLUMN_CLIENT_SECRET_KEY
- };
-
- switch (id) {
- case REQUEST_CODE_CLOUD_LIST_KEY:
- Uri uriAppendedPath = uri;
- switch (OpenMode.getOpenMode(args.getInt(ARGS_KEY_LOADER, 2))) {
- case GDRIVE:
- uriAppendedPath = ContentUris.withAppendedId(uri, 2);
- break;
- case DROPBOX:
- uriAppendedPath = ContentUris.withAppendedId(uri, 3);
- break;
- case BOX:
- uriAppendedPath = ContentUris.withAppendedId(uri, 4);
- break;
- case ONEDRIVE:
- uriAppendedPath = ContentUris.withAppendedId(uri, 5);
- break;
- }
- return new CursorLoader(this, uriAppendedPath, projection, null, null, null);
- case REQUEST_CODE_CLOUD_LIST_KEYS:
- // we need a list of all secret keys
-
- try {
- List cloudEntries = cloudHandler.getAllEntries();
-
- // we want keys for services saved in database, and the cloudrail app key which
- // is at index 1
- String ids[] = new String[cloudEntries.size() + 1];
-
- ids[0] = 1 + "";
- for (int i = 1; i <= cloudEntries.size(); i++) {
-
- // we need to get only those cloud details which user wants
- switch (cloudEntries.get(i - 1).getServiceType()) {
- case GDRIVE:
- ids[i] = 2 + "";
- break;
- case DROPBOX:
- ids[i] = 3 + "";
- break;
- case BOX:
- ids[i] = 4 + "";
- break;
- case ONEDRIVE:
- ids[i] = 5 + "";
- break;
- }
- }
- return new CursorLoader(this, uri, projection, CloudContract.COLUMN_ID, ids, null);
- } catch (CloudPluginException e) {
- LOG.warn("failure when fetching cloud connections", e);
- Toast.makeText(
- this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
- .show();
- }
- default:
- Uri undefinedUriAppendedPath = ContentUris.withAppendedId(uri, 7);
- return new CursorLoader(this, undefinedUriAppendedPath, projection, null, null, null);
- }
- }
-
- @Override
- public void onLoadFinished(Loader loader, final Cursor data) {
- if (data == null) {
- Toast.makeText(
- this,
- getResources().getString(R.string.cloud_error_failed_restart),
- Toast.LENGTH_LONG)
- .show();
- return;
- }
-
- /*
- * This is hack for repeated calls to onLoadFinished(),
- * we take the Cursor provided to check if the function
- * has already been called on it.
- *
- * TODO: find a fix for repeated callbacks to onLoadFinished()
- */
- if (cloudCursorData != null && cloudCursorData == data) return;
- cloudCursorData = data;
-
- if (cloudLoaderAsyncTask != null
- && cloudLoaderAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
- return;
- }
- cloudLoaderAsyncTask = new CloudLoaderAsyncTask(this, cloudHandler, cloudCursorData);
- cloudLoaderAsyncTask.execute();
- }
-
- @Override
- public void onLoaderReset(Loader loader) {
- // For passing code check
- }
-
- public void initCornersDragListener(boolean destroy, boolean shouldInvokeLeftAndRight) {
- initBottomDragListener(destroy);
- initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
- }
-
- private void initBottomDragListener(boolean destroy) {
- View bottomPlaceholder = findViewById(R.id.placeholder_drag_bottom);
- if (destroy) {
- bottomPlaceholder.setOnDragListener(null);
- bottomPlaceholder.setVisibility(View.GONE);
+ }
+ return true;
+ });
+ cloudFab.setNextFocusDownId(floatingActionButton.getMainFab().getId());
+ cloudFab.setNextFocusUpId(newFileFab.getId());
+ cloudFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ newFileFab.setNextFocusDownId(cloudFab.getId());
+ newFileFab.setNextFocusUpId(newFolderFab.getId());
+ newFileFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ newFolderFab.setNextFocusDownId(newFileFab.getId());
+ newFolderFab.setOnFocusChangeListener(new CustomZoomFocusChange());
+ }
+
+ private void fabButtonClick(FabWithLabelView cloudFab) {
+ if (floatingActionButton.isOpen()) {
+ floatingActionButton.close(true);
+ } else {
+ floatingActionButton.open(true);
+ cloudFab.requestFocus();
+ }
+ }
+
+ private FabWithLabelView initFabTitle(
+ @IdRes int id, @StringRes int fabTitle, @DrawableRes int icon) {
+ int iconSkin = getCurrentColorPreference().getIconSkin();
+
+ SpeedDialActionItem.Builder builder =
+ new SpeedDialActionItem.Builder(id, icon)
+ .setLabel(fabTitle)
+ .setFabBackgroundColor(iconSkin);
+
+ switch (getAppTheme().getSimpleTheme(this)) {
+ case LIGHT:
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_light);
+ break;
+ case DARK:
+ builder
+ .setLabelBackgroundColor(Utils.getColor(this, R.color.holo_dark_background))
+ .setLabelColor(Utils.getColor(this, R.color.text_dark));
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_dark);
+ break;
+ case BLACK:
+ builder
+ .setLabelBackgroundColor(Color.BLACK)
+ .setLabelColor(Utils.getColor(this, R.color.text_dark));
+ fabBgView.setBackgroundResource(R.drawable.fab_shadow_black);
+ break;
+ }
+
+ return floatingActionButton.addActionItem(builder.create());
+ }
+
+ public boolean copyToClipboard(Context context, String text) {
+ try {
+ android.content.ClipboardManager clipboard =
+ (android.content.ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
+ android.content.ClipData clip =
+ android.content.ClipData.newPlainText("Path copied to clipboard", text);
+ clipboard.setPrimaryClip(clip);
+ return true;
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ public void renameBookmark(final String title, final String path) {
+ if (dataUtils.containsBooks(new String[] {title, path}) != -1) {
+ RenameBookmark renameBookmark = RenameBookmark.getInstance(title, path, getAccent());
+ if (renameBookmark != null) renameBookmark.show(getFragmentManager(), "renamedialog");
+ }
+ }
+
+ public PasteHelper getPaste() {
+ return pasteHelper;
+ }
+
+ public MainActivityActionMode getActionModeHelper() {
+ return this.mainActivityActionMode;
+ }
+
+ public void setPaste(PasteHelper p) {
+ pasteHelper = p;
+ }
+
+ @Override
+ public void onNewIntent(Intent i) {
+ super.onNewIntent(i);
+ intent = i;
+ path = i.getStringExtra("path");
+
+ if (path != null) {
+ if (new File(path).isDirectory()) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment != null) {
+ mainFragment.loadlist(path, false, OpenMode.FILE, true);
} else {
- bottomPlaceholder.setVisibility(View.VISIBLE);
- bottomPlaceholder.setOnDragListener(
- new TabFragmentBottomDragListener(
- () -> {
- getCurrentMainFragment().smoothScrollListView(false);
- return null;
- },
- () -> {
- getCurrentMainFragment().stopSmoothScrollListView();
- return null;
- }));
+ goToMain(path);
+ }
+ } else FileUtils.openFile(new File(path), mainActivity, getPrefs());
+ } else if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, this);
+ }
+ } else if (i.getCategories() != null
+ && i.getCategories().contains(CLOUD_AUTHENTICATOR_GDRIVE)) {
+ // we used an external authenticator instead of APIs. Probably for Google Drive
+ CloudRail.setAuthenticationResponse(intent);
+ if (intent.getAction() != null) {
+ checkForExternalIntent(intent);
+ invalidateFragmentAndBundle(null, false);
+ }
+ } else if ((openProcesses = i.getBooleanExtra(KEY_INTENT_PROCESS_VIEWER, false))) {
+ FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
+ transaction.replace(
+ R.id.content_frame, new ProcessViewerFragment(), KEY_INTENT_PROCESS_VIEWER);
+ // transaction.addToBackStack(null);
+ openProcesses = false;
+ // title.setText(utils.getString(con, R.string.process_viewer));
+ // Commit the transaction
+ transaction.commitAllowingStateLoss();
+ supportInvalidateOptionsMenu();
+ } else if (intent.getAction() != null) {
+ checkForExternalIntent(intent);
+ invalidateFragmentAndBundle(null, false);
+
+ if (SDK_INT >= KITKAT) {
+ if (intent.getAction().equals(UsbManager.ACTION_USB_DEVICE_DETACHED)) {
+ SingletonUsbOtg.getInstance().resetUsbOtgRoot();
+ drawer.refreshDrawer();
}
+ }
}
+ }
- private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInvokeLeftAndRight) {
- TabFragment tabFragment = getTabFragment();
- tabFragment.initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
- }
-
- private static final class FabActionListener implements SpeedDialView.OnActionSelectedListener {
-
- MainActivity mainActivity;
- SpeedDialView floatingActionButton;
-
- FabActionListener(MainActivity mainActivity) {
- this.mainActivity = mainActivity;
- this.floatingActionButton = mainActivity.floatingActionButton;
- }
-
+ private BroadcastReceiver receiver2 =
+ new BroadcastReceiver() {
@Override
- public boolean onActionSelected(SpeedDialActionItem actionItem) {
- final MainFragment ma =
- (MainFragment)
- ((TabFragment)
- mainActivity.getSupportFragmentManager().findFragmentById(R.id.content_frame))
- .getCurrentTabFragment();
- final String path = ma.getCurrentPath();
-
- switch (actionItem.getId()) {
- case R.id.menu_new_folder:
- mainActivity.mainActivityHelper.mkdir(
- ma.getMainFragmentViewModel().getOpenMode(), path, ma);
- break;
- case R.id.menu_new_file:
- mainActivity.mainActivityHelper.mkfile(
- ma.getMainFragmentViewModel().getOpenMode(), path, ma);
- break;
- case R.id.menu_new_cloud:
- BottomSheetDialogFragment fragment = new CloudSheetFragment();
- fragment.show(
- ma.getActivity().getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT);
- break;
+ public void onReceive(Context context, Intent i) {
+ if (i.getStringArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS) != null) {
+ ArrayList failedOps =
+ i.getParcelableArrayListExtra(TAG_INTENT_FILTER_FAILED_OPS);
+ if (failedOps != null) {
+ mainActivityHelper.showFailedOperationDialog(failedOps, mainActivity);
}
+ }
+ }
+ };
+
+ public void showSMBDialog(String name, String path, boolean edit) {
+ if (path.length() > 0 && name.length() == 0) {
+ int i = dataUtils.containsServer(new String[] {name, path});
+ if (i != -1) name = dataUtils.getServers().get(i)[0];
+ }
+ SmbConnectDialog smbConnectDialog = new SmbConnectDialog();
+ Bundle bundle = new Bundle();
+ bundle.putString("name", name);
+ bundle.putString("path", path);
+ bundle.putBoolean("edit", edit);
+ smbConnectDialog.setArguments(bundle);
+ smbConnectDialog.show(getFragmentManager(), "smbdailog");
+ }
+
+ @SuppressLint("CheckResult")
+ public void showSftpDialog(String name, String path, boolean edit) {
+ if (path.length() > 0 && name.length() == 0) {
+ int i = dataUtils.containsServer(new String[] {name, path});
+ if (i != -1) name = dataUtils.getServers().get(i)[0];
+ }
+ SftpConnectDialog sftpConnectDialog = new SftpConnectDialog();
+ String finalName = name;
+ Flowable.fromCallable(() -> new NetCopyClientConnectionPool.ConnectionInfo(path))
+ .flatMap(
+ connectionInfo -> {
+ Bundle retval = new Bundle();
+ retval.putString(ARG_PROTOCOL, connectionInfo.getPrefix());
+ retval.putString(ARG_NAME, finalName);
+ retval.putString(ARG_ADDRESS, connectionInfo.getHost());
+ retval.putInt(ARG_PORT, connectionInfo.getPort());
+ if (!TextUtils.isEmpty(connectionInfo.getDefaultPath())) {
+ retval.putString(ARG_DEFAULT_PATH, connectionInfo.getDefaultPath());
+ }
+ retval.putString(ARG_USERNAME, connectionInfo.getUsername());
+
+ if (connectionInfo.getPassword() == null) {
+ retval.putBoolean(ARG_HAS_PASSWORD, false);
+ retval.putString(ARG_KEYPAIR_NAME, utilsHandler.getSshAuthPrivateKeyName(path));
+ } else {
+ retval.putBoolean(ARG_HAS_PASSWORD, true);
+ retval.putString(ARG_PASSWORD, connectionInfo.getPassword());
+ }
+ retval.putBoolean(ARG_EDIT, edit);
+ return Flowable.just(retval);
+ })
+ .subscribeOn(Schedulers.computation())
+ .subscribe(
+ bundle -> {
+ sftpConnectDialog.setArguments(bundle);
+ sftpConnectDialog.setCancelable(true);
+ sftpConnectDialog.show(getSupportFragmentManager(), "sftpdialog");
+ });
+ }
+
+ /**
+ * Shows a view that goes from white at it's lowest part to transparent a the top. It covers the
+ * fragment.
+ */
+ public void showSmokeScreen() {
+ fabBgView.show();
+ }
+
+ public void hideSmokeScreen() {
+ fabBgView.hide();
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void addConnection(
+ boolean edit,
+ @NonNull final String name,
+ @NonNull final String path,
+ @Nullable final String encryptedPath,
+ @Nullable final String oldname,
+ @Nullable final String oldPath) {
+ String[] s = new String[] {name, path};
+ if (!edit) {
+ if ((dataUtils.containsServer(path)) == -1) {
+ Completable.fromRunnable(
+ () -> {
+ utilsHandler.saveToDatabase(
+ new OperationData(UtilsHandler.Operation.SMB, name, encryptedPath));
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(
+ () -> {
+ dataUtils.addServer(s);
+ drawer.refreshDrawer();
+ // grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.loadlist(path, false, OpenMode.UNKNOWN, true);
+ return null;
+ },
+ true);
+ });
+ } else {
+ Snackbar.make(
+ findViewById(R.id.navigation),
+ getString(R.string.connection_exists),
+ Snackbar.LENGTH_SHORT)
+ .show();
+ }
+ } else {
+ int i = dataUtils.containsServer(new String[] {oldname, oldPath});
+ if (i != -1) {
+ dataUtils.removeServer(i);
+
+ AppConfig.getInstance()
+ .runInBackground(
+ () -> {
+ utilsHandler.renameSMB(oldname, oldPath, name, path);
+ });
+ // mainActivity.grid.removePath(oldname, oldPath, DataUtils.SMB);
+ }
+ dataUtils.addServer(s);
+ Collections.sort(dataUtils.getServers(), new BookSorter());
+ drawer.refreshDrawer();
+ // mainActivity.grid.addPath(name, encryptedPath, DataUtils.SMB, 1);
+ }
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void deleteConnection(final String name, final String path) {
+ int i = dataUtils.containsServer(new String[] {name, path});
+ if (i != -1) {
+ dataUtils.removeServer(i);
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(UtilsHandler.Operation.SMB, name, path));
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void delete(String title, String path) {
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.removeFromDatabase(
+ new OperationData(UtilsHandler.Operation.BOOKMARKS, title, path));
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+
+ @Override
+ @SuppressLint("CheckResult")
+ public void modify(String oldpath, String oldname, String newPath, String newname) {
+ Completable.fromCallable(
+ () -> {
+ utilsHandler.renameBookmark(oldname, oldpath, newname, newPath);
+ return true;
+ })
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(() -> drawer.refreshDrawer());
+ }
+
+ @Override
+ public void onPreExecute(String query) {
+ executeWithMainFragment(
+ mainFragment -> {
+ mainFragment.mSwipeRefreshLayout.setRefreshing(true);
+ mainFragment.onSearchPreExecute(query);
+ return null;
+ });
+ }
+
+ @Override
+ public void onPostExecute(String query) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ // TODO cancel search
+ return;
+ }
+
+ mainFragment.onSearchCompleted(query);
+ mainFragment.mSwipeRefreshLayout.setRefreshing(false);
+ }
+
+ @Override
+ public void onProgressUpdate(@NonNull HybridFileParcelable hybridFileParcelable, String query) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ // TODO cancel search
+ return;
+ }
+
+ mainFragment.addSearchResult(hybridFileParcelable, query);
+ }
+
+ @Override
+ public void onCancelled() {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment == null) {
+ return;
+ }
+
+ mainFragment.reloadListElements(
+ false, false, !mainFragment.getMainFragmentViewModel().isList());
+ mainFragment.mSwipeRefreshLayout.setRefreshing(false);
+ }
+
+ @Override
+ public void addConnection(OpenMode service) {
+ try {
+ if (cloudHandler.findEntry(service) != null) {
+ // cloud entry already exists
+ Toast.makeText(
+ this, getResources().getString(R.string.connection_exists), Toast.LENGTH_LONG)
+ .show();
+ } else if (BuildConfig.IS_VERSION_FDROID) {
+ Toast.makeText(
+ this, getResources().getString(R.string.cloud_error_fdroid), Toast.LENGTH_LONG)
+ .show();
+ } else {
+ Toast.makeText(
+ MainActivity.this,
+ getResources().getString(R.string.please_wait),
+ Toast.LENGTH_LONG)
+ .show();
+ Bundle args = new Bundle();
+ args.putInt(ARGS_KEY_LOADER, service.ordinal());
+
+ // check if we already had done some work on the loader
+ Loader loader = getSupportLoaderManager().getLoader(REQUEST_CODE_CLOUD_LIST_KEY);
+ if (loader != null && loader.isStarted()) {
+
+ // making sure that loader is not started
+ getSupportLoaderManager().destroyLoader(REQUEST_CODE_CLOUD_LIST_KEY);
+ }
+
+ getSupportLoaderManager().initLoader(REQUEST_CODE_CLOUD_LIST_KEY, args, this);
+ }
+ } catch (CloudPluginException e) {
+ LOG.warn("failure when adding cloud plugin connections", e);
+ Toast.makeText(this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
+ .show();
+ }
+ }
+
+ @Override
+ public void deleteConnection(OpenMode service) {
+ cloudHandler.clear(service);
+ dataUtils.removeAccount(service);
+
+ runOnUiThread(drawer::refreshDrawer);
+ }
+
+ @NonNull
+ @Override
+ public Loader onCreateLoader(int id, Bundle args) {
+ Uri uri =
+ Uri.withAppendedPath(
+ Uri.parse("content://" + CloudContract.PROVIDER_AUTHORITY), "/keys.db/secret_keys");
+
+ String[] projection =
+ new String[] {
+ CloudContract.COLUMN_ID,
+ CloudContract.COLUMN_CLIENT_ID,
+ CloudContract.COLUMN_CLIENT_SECRET_KEY
+ };
+
+ switch (id) {
+ case REQUEST_CODE_CLOUD_LIST_KEY:
+ Uri uriAppendedPath = uri;
+ switch (OpenMode.getOpenMode(args.getInt(ARGS_KEY_LOADER, 2))) {
+ case GDRIVE:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 2);
+ break;
+ case DROPBOX:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 3);
+ break;
+ case BOX:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 4);
+ break;
+ case ONEDRIVE:
+ uriAppendedPath = ContentUris.withAppendedId(uri, 5);
+ break;
+ }
+ return new CursorLoader(this, uriAppendedPath, projection, null, null, null);
+ case REQUEST_CODE_CLOUD_LIST_KEYS:
+ // we need a list of all secret keys
- floatingActionButton.close(true);
- return true;
- }
+ try {
+ List cloudEntries = cloudHandler.getAllEntries();
- }
+ // we want keys for services saved in database, and the cloudrail app key which
+ // is at index 1
+ String ids[] = new String[cloudEntries.size() + 1];
- /**
- * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path.
- *
- * @param dialog
- * @param folder selected folder
- * @see FtpServerFragment#changeFTPServerPath(String)
- * @see FolderChooserDialog
- * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
- */
- @Override
- public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
- switch (dialog.getTag()) {
- case FtpServerFragment.TAG:
- FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
- if (folder.exists() && folder.isDirectory()) {
- if (FileUtils.isRunningAboveStorage(folder.getAbsolutePath())) {
- if (!isRootExplorer()) {
- AlertDialog.show(
- this,
- R.string.ftp_server_root_unavailable,
- R.string.error,
- android.R.string.ok,
- null,
- false);
- } else {
- MaterialDialog confirmDialog =
- GeneralDialogCreation.showBasicDialog(
- this,
- R.string.ftp_server_root_filesystem_warning,
- R.string.warning,
- android.R.string.ok,
- android.R.string.cancel);
- confirmDialog
- .getActionButton(DialogAction.POSITIVE)
- .setOnClickListener(
- v -> {
- ftpServerFragment.changeFTPServerPath(folder.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT)
- .show();
- confirmDialog.dismiss();
- });
- confirmDialog
- .getActionButton(DialogAction.NEGATIVE)
- .setOnClickListener(v -> confirmDialog.dismiss());
- confirmDialog.show();
- }
- } else {
- ftpServerFragment.changeFTPServerPath(folder.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
- }
- } else {
- // try to get parent
- String pathParentFilePath = folder.getParent();
- if (pathParentFilePath == null) {
- dialog.dismiss();
- return;
- }
- File pathParentFile = new File(pathParentFilePath);
- if (pathParentFile.exists() && pathParentFile.isDirectory()) {
- ftpServerFragment.changeFTPServerPath(pathParentFile.getPath());
- Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
- } else {
- // don't have access, print error
- Toast.makeText(this, R.string.ftp_path_change_error_invalid, Toast.LENGTH_SHORT).show();
- }
- }
- dialog.dismiss();
+ ids[0] = 1 + "";
+ for (int i = 1; i <= cloudEntries.size(); i++) {
+
+ // we need to get only those cloud details which user wants
+ switch (cloudEntries.get(i - 1).getServiceType()) {
+ case GDRIVE:
+ ids[i] = 2 + "";
+ break;
+ case DROPBOX:
+ ids[i] = 3 + "";
break;
- default:
- dialog.dismiss();
+ case BOX:
+ ids[i] = 4 + "";
break;
+ case ONEDRIVE:
+ ids[i] = 5 + "";
+ break;
+ }
+ }
+ return new CursorLoader(this, uri, projection, CloudContract.COLUMN_ID, ids, null);
+ } catch (CloudPluginException e) {
+ LOG.warn("failure when fetching cloud connections", e);
+ Toast.makeText(
+ this, getResources().getString(R.string.cloud_error_plugin), Toast.LENGTH_LONG)
+ .show();
}
+ default:
+ Uri undefinedUriAppendedPath = ContentUris.withAppendedId(uri, 7);
+ return new CursorLoader(this, undefinedUriAppendedPath, projection, null, null, null);
}
+ }
- /**
- * Get whether list item is selected for action mode or not
- *
- * @return value
- */
- public boolean getListItemSelected() {
- return this.listItemSelected;
- }
-
- public String getScrollToFileName() {
- return this.scrollToFileName;
+ @Override
+ public void onLoadFinished(Loader loader, final Cursor data) {
+ if (data == null) {
+ Toast.makeText(
+ this,
+ getResources().getString(R.string.cloud_error_failed_restart),
+ Toast.LENGTH_LONG)
+ .show();
+ return;
}
- /**
- * Set list item selected value
+ /*
+ * This is hack for repeated calls to onLoadFinished(),
+ * we take the Cursor provided to check if the function
+ * has already been called on it.
*
- * @param value value
+ * TODO: find a fix for repeated callbacks to onLoadFinished()
*/
- public void setListItemSelected(boolean value) {
- this.listItemSelected = value;
+ if (cloudCursorData != null && cloudCursorData == data) return;
+ cloudCursorData = data;
+
+ if (cloudLoaderAsyncTask != null
+ && cloudLoaderAsyncTask.getStatus() == AsyncTask.Status.RUNNING) {
+ return;
+ }
+ cloudLoaderAsyncTask = new CloudLoaderAsyncTask(this, cloudHandler, cloudCursorData);
+ cloudLoaderAsyncTask.execute();
+ }
+
+ @Override
+ public void onLoaderReset(Loader loader) {
+ // For passing code check
+ }
+
+ public void initCornersDragListener(boolean destroy, boolean shouldInvokeLeftAndRight) {
+ initBottomDragListener(destroy);
+ initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
+ }
+
+ private void initBottomDragListener(boolean destroy) {
+ View bottomPlaceholder = findViewById(R.id.placeholder_drag_bottom);
+ if (destroy) {
+ bottomPlaceholder.setOnDragListener(null);
+ bottomPlaceholder.setVisibility(View.GONE);
+ } else {
+ bottomPlaceholder.setVisibility(View.VISIBLE);
+ bottomPlaceholder.setOnDragListener(
+ new TabFragmentBottomDragListener(
+ () -> {
+ getCurrentMainFragment().smoothScrollListView(false);
+ return null;
+ },
+ () -> {
+ getCurrentMainFragment().stopSmoothScrollListView();
+ return null;
+ }));
+ }
+ }
+
+ private void initLeftRightAndTopDragListeners(boolean destroy, boolean shouldInvokeLeftAndRight) {
+ TabFragment tabFragment = getTabFragment();
+ tabFragment.initLeftRightAndTopDragListeners(destroy, shouldInvokeLeftAndRight);
+ }
+
+ private static final class FabActionListener implements SpeedDialView.OnActionSelectedListener {
+
+ MainActivity mainActivity;
+ SpeedDialView floatingActionButton;
+
+ FabActionListener(MainActivity mainActivity) {
+ this.mainActivity = mainActivity;
+ this.floatingActionButton = mainActivity.floatingActionButton;
}
- /**
- * Do nothing other than dismissing the folder selection dialog.
- *
- * @param dialog
- * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
- */
@Override
- public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {
- dialog.dismiss();
- }
-
- private void executeWithMainFragment(@NonNull Function lambda) {
- executeWithMainFragment(lambda, false);
- }
+ public boolean onActionSelected(SpeedDialActionItem actionItem) {
+ final MainFragment ma =
+ (MainFragment)
+ ((TabFragment)
+ mainActivity.getSupportFragmentManager().findFragmentById(R.id.content_frame))
+ .getCurrentTabFragment();
+ final String path = ma.getCurrentPath();
+
+ switch (actionItem.getId()) {
+ case R.id.menu_new_folder:
+ mainActivity.mainActivityHelper.mkdir(
+ ma.getMainFragmentViewModel().getOpenMode(), path, ma);
+ break;
+ case R.id.menu_new_file:
+ mainActivity.mainActivityHelper.mkfile(
+ ma.getMainFragmentViewModel().getOpenMode(), path, ma);
+ break;
+ case R.id.menu_new_cloud:
+ BottomSheetDialogFragment fragment = new CloudSheetFragment();
+ fragment.show(
+ ma.getActivity().getSupportFragmentManager(), CloudSheetFragment.TAG_FRAGMENT);
+ break;
+ }
- @Nullable
- private void executeWithMainFragment(
- @NonNull Function lambda, boolean showToastIfMainFragmentIsNull) {
- final MainFragment mainFragment = getCurrentMainFragment();
- if (mainFragment != null && mainFragment.getMainFragmentViewModel() != null) {
- lambda.apply(mainFragment);
- } else {
- LOG.warn("MainFragment is null");
- if (showToastIfMainFragmentIsNull) {
- AppConfig.toast(this, R.string.operation_unsuccesful);
+ floatingActionButton.close(true);
+ return true;
+ }
+ }
+
+ /**
+ * Invoke {@link FtpServerFragment#changeFTPServerPath(String)} to change FTP server share path.
+ *
+ * @param dialog
+ * @param folder selected folder
+ * @see FtpServerFragment#changeFTPServerPath(String)
+ * @see FolderChooserDialog
+ * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
+ */
+ @Override
+ public void onFolderSelection(@NonNull FolderChooserDialog dialog, @NonNull File folder) {
+ switch (dialog.getTag()) {
+ case FtpServerFragment.TAG:
+ FtpServerFragment ftpServerFragment = (FtpServerFragment) getFragmentAtFrame();
+ if (folder.exists() && folder.isDirectory()) {
+ if (FileUtils.isRunningAboveStorage(folder.getAbsolutePath())) {
+ if (!isRootExplorer()) {
+ AlertDialog.show(
+ this,
+ R.string.ftp_server_root_unavailable,
+ R.string.error,
+ android.R.string.ok,
+ null,
+ false);
+ } else {
+ MaterialDialog confirmDialog =
+ GeneralDialogCreation.showBasicDialog(
+ this,
+ R.string.ftp_server_root_filesystem_warning,
+ R.string.warning,
+ android.R.string.ok,
+ android.R.string.cancel);
+ confirmDialog
+ .getActionButton(DialogAction.POSITIVE)
+ .setOnClickListener(
+ v -> {
+ ftpServerFragment.changeFTPServerPath(folder.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT)
+ .show();
+ confirmDialog.dismiss();
+ });
+ confirmDialog
+ .getActionButton(DialogAction.NEGATIVE)
+ .setOnClickListener(v -> confirmDialog.dismiss());
+ confirmDialog.show();
}
+ } else {
+ ftpServerFragment.changeFTPServerPath(folder.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ // try to get parent
+ String pathParentFilePath = folder.getParent();
+ if (pathParentFilePath == null) {
+ dialog.dismiss();
+ return;
+ }
+ File pathParentFile = new File(pathParentFilePath);
+ if (pathParentFile.exists() && pathParentFile.isDirectory()) {
+ ftpServerFragment.changeFTPServerPath(pathParentFile.getPath());
+ Toast.makeText(this, R.string.ftp_path_change_success, Toast.LENGTH_SHORT).show();
+ } else {
+ // don't have access, print error
+ Toast.makeText(this, R.string.ftp_path_change_error_invalid, Toast.LENGTH_SHORT).show();
+ }
}
+ dialog.dismiss();
+ break;
+ default:
+ dialog.dismiss();
+ break;
+ }
+ }
+
+ /**
+ * Get whether list item is selected for action mode or not
+ *
+ * @return value
+ */
+ public boolean getListItemSelected() {
+ return this.listItemSelected;
+ }
+
+ public String getScrollToFileName() {
+ return this.scrollToFileName;
+ }
+
+ /**
+ * Set list item selected value
+ *
+ * @param value value
+ */
+ public void setListItemSelected(boolean value) {
+ this.listItemSelected = value;
+ }
+
+ /**
+ * Do nothing other than dismissing the folder selection dialog.
+ *
+ * @param dialog
+ * @see com.afollestad.materialdialogs.folderselector.FolderChooserDialog.FolderCallback
+ */
+ @Override
+ public void onFolderChooserDismissed(@NonNull FolderChooserDialog dialog) {
+ dialog.dismiss();
+ }
+
+ private void executeWithMainFragment(@NonNull Function lambda) {
+ executeWithMainFragment(lambda, false);
+ }
+
+ @Nullable
+ private void executeWithMainFragment(
+ @NonNull Function lambda, boolean showToastIfMainFragmentIsNull) {
+ final MainFragment mainFragment = getCurrentMainFragment();
+ if (mainFragment != null && mainFragment.getMainFragmentViewModel() != null) {
+ lambda.apply(mainFragment);
+ } else {
+ LOG.warn("MainFragment is null");
+ if (showToastIfMainFragmentIsNull) {
+ AppConfig.toast(this, R.string.operation_unsuccesful);
+ }
}
+ }
}
diff --git a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
index c792d5d00b..f6b1345d13 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/views/appbar/SearchView.java
@@ -236,5 +236,4 @@ private void initSearchViewColor(MainActivity a) {
public interface SearchListener {
void onSearch(String queue);
}
-
}
From 7773e1171ed24c99f3d30612953445774e73c8ca Mon Sep 17 00:00:00 2001
From: Raymond Lai
Date: Thu, 2 Feb 2023 22:55:19 +0800
Subject: [PATCH 044/384] Fix source tree test failures
Follow-up fix for #3692.
- Migrate tests that are still testing on JELLY_BEAN
- Update submodules Android version requirement to 4.4 too
---
.../asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt | 4 ++--
.../asynctasks/ssh/PemToKeyPairObservableRsaTest.kt | 4 ++--
commons_compress_7z/build.gradle | 2 +-
file_operations/build.gradle | 2 +-
4 files changed, 6 insertions(+), 6 deletions(-)
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
index f58cfdcc01..748e4e3732 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableEd25519Test.kt
@@ -20,7 +20,7 @@
package com.amaze.filemanager.asynchronous.asynctasks.ssh
-import android.os.Build.VERSION_CODES.JELLY_BEAN
+import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.P
import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -44,7 +44,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowTabHandler::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P, VERSION_CODES.R]
)
class PemToKeyPairObservableEd25519Test {
diff --git a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
index 1192f5e6ad..cf041dae5e 100644
--- a/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
+++ b/app/src/test/java/com/amaze/filemanager/asynchronous/asynctasks/ssh/PemToKeyPairObservableRsaTest.kt
@@ -21,7 +21,7 @@
package com.amaze.filemanager.asynchronous.asynctasks.ssh
import android.os.Build.VERSION.SDK_INT
-import android.os.Build.VERSION_CODES.JELLY_BEAN
+import android.os.Build.VERSION_CODES
import android.os.Build.VERSION_CODES.KITKAT
import android.os.Build.VERSION_CODES.N
import android.os.Build.VERSION_CODES.P
@@ -64,7 +64,7 @@ import java.util.concurrent.TimeUnit
@RunWith(AndroidJUnit4::class)
@Config(
shadows = [ShadowMultiDex::class, ShadowTabHandler::class],
- sdk = [JELLY_BEAN, KITKAT, P]
+ sdk = [KITKAT, P, VERSION_CODES.R]
)
class PemToKeyPairObservableRsaTest {
diff --git a/commons_compress_7z/build.gradle b/commons_compress_7z/build.gradle
index a550f9a908..0b7e3a9e9a 100644
--- a/commons_compress_7z/build.gradle
+++ b/commons_compress_7z/build.gradle
@@ -5,7 +5,7 @@ android {
compileSdkVersion 28
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 19
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/file_operations/build.gradle b/file_operations/build.gradle
index 3ead8fcc15..eadddd661e 100644
--- a/file_operations/build.gradle
+++ b/file_operations/build.gradle
@@ -5,7 +5,7 @@ android {
compileSdkVersion 30
defaultConfig {
- minSdkVersion 14
+ minSdkVersion 19
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
From 982c6c95926b47a3b125d28a704a711952fb3f78 Mon Sep 17 00:00:00 2001
From: peerzadaburhan
Date: Fri, 3 Feb 2023 11:13:08 +0530
Subject: [PATCH 045/384] Issue #3394
---
.../java/com/amaze/filemanager/ui/activities/MainActivity.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index 1ebb3a8b9f..ad69cd4b3f 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -336,6 +336,7 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_toolbar);
+
intent = getIntent();
dataUtils = DataUtils.getInstance();
From 5cd4a1b923acf77dd79b1226a19c088ff17861ad Mon Sep 17 00:00:00 2001
From: peerzadaburhan
Date: Fri, 3 Feb 2023 12:10:37 +0530
Subject: [PATCH 046/384] Issue #3394
---
.../java/com/amaze/filemanager/ui/activities/MainActivity.java | 1 -
1 file changed, 1 deletion(-)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index ad69cd4b3f..1ebb3a8b9f 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -336,7 +336,6 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_toolbar);
-
intent = getIntent();
dataUtils = DataUtils.getInstance();
From ec30526fb336bd72735087a889e77ece952d9070 Mon Sep 17 00:00:00 2001
From: VishnuSanal
Date: Fri, 3 Feb 2023 15:29:56 +0530
Subject: [PATCH 047/384] fixes #3720
---
.../java/com/amaze/filemanager/ui/activities/MainActivity.java | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index 7bc4dd3221..4fee1ec4a5 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -2314,7 +2314,8 @@ public void onLoadFinished(Loader loader, final Cursor data) {
*
* TODO: find a fix for repeated callbacks to onLoadFinished()
*/
- if ((cloudCursorData != null && cloudCursorData == data)
+ if (cloudCursorData == null
+ || cloudCursorData == data
|| data.isClosed()
|| cloudCursorData.isClosed()) return;
cloudCursorData = data;
From 937e2f36d3b3902dd3ae8e041dbb137d5379c603 Mon Sep 17 00:00:00 2001
From: peerzadaburhan
Date: Fri, 3 Feb 2023 15:34:42 +0530
Subject: [PATCH 048/384] Issue #3394
---
.../java/com/amaze/filemanager/ui/activities/MainActivity.java | 1 +
1 file changed, 1 insertion(+)
diff --git a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
index 1ebb3a8b9f..ad69cd4b3f 100644
--- a/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
+++ b/app/src/main/java/com/amaze/filemanager/ui/activities/MainActivity.java
@@ -336,6 +336,7 @@ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_toolbar);
+
intent = getIntent();
dataUtils = DataUtils.getInstance();
From 8949382be5440e0da81bd9cb15a1408b947fc594 Mon Sep 17 00:00:00 2001
From: peerzadaburhan