mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 11:19:04 +01:00
Compare commits
7 Commits
68-test-ou
...
variable-l
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
56c1fef4db | ||
|
|
c2f96580b8 | ||
|
|
a5c9e7c74d | ||
|
|
8a4a343147 | ||
|
|
7b22d3b84d | ||
|
|
c8fa23273f | ||
|
|
fbe5df8938 |
@@ -1,5 +1,7 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
|
|
||||||
public class ByteUtils {
|
public class ByteUtils {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -12,15 +14,26 @@ public class ByteUtils {
|
|||||||
*/
|
*/
|
||||||
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
|
public static final long MAX_32_BIT_UNSIGNED = 4294967295L; // 2^32 - 1
|
||||||
|
|
||||||
/** The number of bytes needed to encode a 16-bit integer. */
|
/**
|
||||||
|
* The number of bytes needed to encode a 16-bit integer.
|
||||||
|
*/
|
||||||
public static final int INT_16_BYTES = 2;
|
public static final int INT_16_BYTES = 2;
|
||||||
|
|
||||||
/** The number of bytes needed to encode a 32-bit integer. */
|
/**
|
||||||
|
* The number of bytes needed to encode a 32-bit integer.
|
||||||
|
*/
|
||||||
public static final int INT_32_BYTES = 4;
|
public static final int INT_32_BYTES = 4;
|
||||||
|
|
||||||
/** The number of bytes needed to encode a 64-bit integer. */
|
/**
|
||||||
|
* The number of bytes needed to encode a 64-bit integer.
|
||||||
|
*/
|
||||||
public static final int INT_64_BYTES = 8;
|
public static final int INT_64_BYTES = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The maximum number of bytes needed to encode a variable-length integer.
|
||||||
|
*/
|
||||||
|
public static final int MAX_VARINT_BYTES = 9;
|
||||||
|
|
||||||
public static void writeUint16(int src, byte[] dest, int offset) {
|
public static void writeUint16(int src, byte[] dest, int offset) {
|
||||||
if (src < 0) throw new IllegalArgumentException();
|
if (src < 0) throw new IllegalArgumentException();
|
||||||
if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
|
if (src > MAX_16_BIT_UNSIGNED) throw new IllegalArgumentException();
|
||||||
@@ -55,6 +68,42 @@ public class ByteUtils {
|
|||||||
dest[offset + 7] = (byte) (src & 0xFF);
|
dest[offset + 7] = (byte) (src & 0xFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of bytes needed to represent 'src' as a
|
||||||
|
* variable-length integer.
|
||||||
|
* <p>
|
||||||
|
* 'src' must not be negative.
|
||||||
|
*/
|
||||||
|
public static int getVarIntBytes(long src) {
|
||||||
|
if (src < 0) throw new IllegalArgumentException();
|
||||||
|
int len = 1;
|
||||||
|
while ((src >>= 7) > 0) len++;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes 'src' to 'dest' as a variable-length integer, starting at
|
||||||
|
* 'offset', and returns the number of bytes written.
|
||||||
|
* <p>
|
||||||
|
* `src` must not be negative.
|
||||||
|
*/
|
||||||
|
public static int writeVarInt(long src, byte[] dest, int offset) {
|
||||||
|
if (src < 0) throw new IllegalArgumentException();
|
||||||
|
int len = getVarIntBytes(src);
|
||||||
|
if (dest.length < offset + len) throw new IllegalArgumentException();
|
||||||
|
// Work backwards from the end
|
||||||
|
int end = offset + len - 1;
|
||||||
|
for (int i = end; i >= offset; i--) {
|
||||||
|
// Encode 7 bits
|
||||||
|
dest[i] = (byte) (src & 0x7F);
|
||||||
|
// Raise the continuation flag, except for the last byte
|
||||||
|
if (i < end) dest[i] |= (byte) 0x80;
|
||||||
|
// Shift out the bits that were encoded
|
||||||
|
src >>= 7;
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
public static int readUint16(byte[] src, int offset) {
|
public static int readUint16(byte[] src, int offset) {
|
||||||
if (src.length < offset + INT_16_BYTES)
|
if (src.length < offset + INT_16_BYTES)
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
@@ -83,14 +132,46 @@ public class ByteUtils {
|
|||||||
| (src[offset + 7] & 0xFFL);
|
| (src[offset + 7] & 0xFFL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int readUint(byte[] src, int bits) {
|
/**
|
||||||
if (src.length << 3 < bits) throw new IllegalArgumentException();
|
* Returns the length in bytes of a variable-length integer encoded in
|
||||||
int dest = 0;
|
* 'src' starting at 'offset'.
|
||||||
for (int i = 0; i < bits; i++) {
|
*
|
||||||
if ((src[i >> 3] & 128 >> (i & 7)) != 0) dest |= 1 << bits - i - 1;
|
* @throws FormatException if there is not a valid variable-length integer
|
||||||
|
* at the specified position.
|
||||||
|
*/
|
||||||
|
public static int getVarIntBytes(byte[] src, int offset)
|
||||||
|
throws FormatException {
|
||||||
|
if (src.length < offset) throw new IllegalArgumentException();
|
||||||
|
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
|
||||||
|
// If the continuation flag is lowered, this is the last byte
|
||||||
|
if ((src[offset + i] & 0x80) == 0) return i + 1;
|
||||||
}
|
}
|
||||||
if (dest < 0) throw new AssertionError();
|
// We've read 9 bytes or reached the end of the input without finding
|
||||||
if (dest >= 1 << bits) throw new AssertionError();
|
// the last byte
|
||||||
return dest;
|
throw new FormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a variable-length integer from 'src' starting at 'offset' and
|
||||||
|
* returns it.
|
||||||
|
*
|
||||||
|
* @throws FormatException if there is not a valid variable-length integer
|
||||||
|
* at the specified position.
|
||||||
|
*/
|
||||||
|
public static long readVarInt(byte[] src, int offset)
|
||||||
|
throws FormatException {
|
||||||
|
if (src.length < offset) throw new IllegalArgumentException();
|
||||||
|
long dest = 0;
|
||||||
|
for (int i = 0; i < MAX_VARINT_BYTES && offset + i < src.length; i++) {
|
||||||
|
// Decode 7 bits
|
||||||
|
dest |= src[offset + i] & 0x7F;
|
||||||
|
// If the continuation flag is lowered, this is the last byte
|
||||||
|
if ((src[offset + i] & 0x80) == 0) return dest;
|
||||||
|
// Make room for the next 7 bits
|
||||||
|
dest <<= 7;
|
||||||
|
}
|
||||||
|
// We've read 9 bytes or reached the end of the input without finding
|
||||||
|
// the last byte
|
||||||
|
throw new FormatException();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,118 +1,135 @@
|
|||||||
package org.briarproject.bramble.util;
|
package org.briarproject.bramble.util;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.FormatException;
|
||||||
import org.briarproject.bramble.test.BrambleTestCase;
|
import org.briarproject.bramble.test.BrambleTestCase;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import static java.util.Arrays.fill;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_16_BIT_UNSIGNED;
|
||||||
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
import static org.briarproject.bramble.util.ByteUtils.MAX_32_BIT_UNSIGNED;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.MAX_VARINT_BYTES;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.getVarIntBytes;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.readUint16;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.readUint32;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.readUint64;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.readVarInt;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.writeUint16;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.writeUint32;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.writeUint64;
|
||||||
|
import static org.briarproject.bramble.util.ByteUtils.writeVarInt;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.fromHexString;
|
||||||
|
import static org.briarproject.bramble.util.StringUtils.toHexString;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
@SuppressWarnings("ResultOfMethodCallIgnored")
|
||||||
public class ByteUtilsTest extends BrambleTestCase {
|
public class ByteUtilsTest extends BrambleTestCase {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint16() {
|
public void testReadUint16() {
|
||||||
byte[] b = StringUtils.fromHexString("00000000");
|
byte[] b = fromHexString("00000000");
|
||||||
assertEquals(0, ByteUtils.readUint16(b, 1));
|
assertEquals(0, readUint16(b, 1));
|
||||||
b = StringUtils.fromHexString("00000100");
|
b = fromHexString("00000100");
|
||||||
assertEquals(1, ByteUtils.readUint16(b, 1));
|
assertEquals(1, readUint16(b, 1));
|
||||||
b = StringUtils.fromHexString("007FFF00");
|
b = fromHexString("007FFF00");
|
||||||
assertEquals(Short.MAX_VALUE, ByteUtils.readUint16(b, 1));
|
assertEquals(Short.MAX_VALUE, readUint16(b, 1));
|
||||||
b = StringUtils.fromHexString("00FFFF00");
|
b = fromHexString("00FFFF00");
|
||||||
assertEquals(65535, ByteUtils.readUint16(b, 1));
|
assertEquals(65535, readUint16(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint16ValidatesArguments1() {
|
public void testReadUint16ValidatesArguments1() {
|
||||||
ByteUtils.readUint16(new byte[1], 0);
|
readUint16(new byte[1], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint16ValidatesArguments2() {
|
public void testReadUint16ValidatesArguments2() {
|
||||||
ByteUtils.readUint16(new byte[2], 1);
|
readUint16(new byte[2], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint32() {
|
public void testReadUint32() {
|
||||||
byte[] b = StringUtils.fromHexString("000000000000");
|
byte[] b = fromHexString("000000000000");
|
||||||
assertEquals(0, ByteUtils.readUint32(b, 1));
|
assertEquals(0, readUint32(b, 1));
|
||||||
b = StringUtils.fromHexString("000000000100");
|
b = fromHexString("000000000100");
|
||||||
assertEquals(1, ByteUtils.readUint32(b, 1));
|
assertEquals(1, readUint32(b, 1));
|
||||||
b = StringUtils.fromHexString("007FFFFFFF00");
|
b = fromHexString("007FFFFFFF00");
|
||||||
assertEquals(Integer.MAX_VALUE, ByteUtils.readUint32(b, 1));
|
assertEquals(Integer.MAX_VALUE, readUint32(b, 1));
|
||||||
b = StringUtils.fromHexString("00FFFFFFFF00");
|
b = fromHexString("00FFFFFFFF00");
|
||||||
assertEquals(4294967295L, ByteUtils.readUint32(b, 1));
|
assertEquals(4294967295L, readUint32(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint32ValidatesArguments1() {
|
public void testReadUint32ValidatesArguments1() {
|
||||||
ByteUtils.readUint32(new byte[3], 0);
|
readUint32(new byte[3], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint32ValidatesArguments2() {
|
public void testReadUint32ValidatesArguments2() {
|
||||||
ByteUtils.readUint32(new byte[4], 1);
|
readUint32(new byte[4], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint64() {
|
public void testReadUint64() {
|
||||||
byte[] b = StringUtils.fromHexString("00000000000000000000");
|
byte[] b = fromHexString("00000000000000000000");
|
||||||
assertEquals(0L, ByteUtils.readUint64(b, 1));
|
assertEquals(0L, readUint64(b, 1));
|
||||||
b = StringUtils.fromHexString("00000000000000000100");
|
b = fromHexString("00000000000000000100");
|
||||||
assertEquals(1L, ByteUtils.readUint64(b, 1));
|
assertEquals(1L, readUint64(b, 1));
|
||||||
b = StringUtils.fromHexString("007FFFFFFFFFFFFFFF00");
|
b = fromHexString("007FFFFFFFFFFFFFFF00");
|
||||||
assertEquals(Long.MAX_VALUE, ByteUtils.readUint64(b, 1));
|
assertEquals(Long.MAX_VALUE, readUint64(b, 1));
|
||||||
b = StringUtils.fromHexString("00800000000000000000");
|
b = fromHexString("00800000000000000000");
|
||||||
assertEquals(Long.MIN_VALUE, ByteUtils.readUint64(b, 1));
|
assertEquals(Long.MIN_VALUE, readUint64(b, 1));
|
||||||
b = StringUtils.fromHexString("00FFFFFFFFFFFFFFFF00");
|
b = fromHexString("00FFFFFFFFFFFFFFFF00");
|
||||||
assertEquals(-1L, ByteUtils.readUint64(b, 1));
|
assertEquals(-1L, readUint64(b, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint64ValidatesArguments1() {
|
public void testReadUint64ValidatesArguments1() {
|
||||||
ByteUtils.readUint64(new byte[7], 0);
|
readUint64(new byte[7], 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test(expected = IllegalArgumentException.class)
|
||||||
public void testReadUint64ValidatesArguments2() {
|
public void testReadUint64ValidatesArguments2() {
|
||||||
ByteUtils.readUint64(new byte[8], 1);
|
readUint64(new byte[8], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint16() {
|
public void testWriteUint16() {
|
||||||
byte[] b = new byte[4];
|
byte[] b = new byte[4];
|
||||||
ByteUtils.writeUint16(0, b, 1);
|
writeUint16(0, b, 1);
|
||||||
assertEquals("00000000", StringUtils.toHexString(b));
|
assertEquals("00000000", toHexString(b));
|
||||||
ByteUtils.writeUint16(1, b, 1);
|
writeUint16(1, b, 1);
|
||||||
assertEquals("00000100", StringUtils.toHexString(b));
|
assertEquals("00000100", toHexString(b));
|
||||||
ByteUtils.writeUint16(Short.MAX_VALUE, b, 1);
|
writeUint16(Short.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFF00", StringUtils.toHexString(b));
|
assertEquals("007FFF00", toHexString(b));
|
||||||
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
|
writeUint16(MAX_16_BIT_UNSIGNED, b, 1);
|
||||||
assertEquals("00FFFF00", StringUtils.toHexString(b));
|
assertEquals("00FFFF00", toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint16ValidatesArguments() {
|
public void testWriteUint16ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint16(0, new byte[1], 0);
|
writeUint16(0, new byte[1], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint16(0, new byte[2], 1);
|
writeUint16(0, new byte[2], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint16(-1, new byte[2], 0);
|
writeUint16(-1, new byte[2], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
|
writeUint16(MAX_16_BIT_UNSIGNED + 1, new byte[2], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -122,38 +139,38 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteUint32() {
|
public void testWriteUint32() {
|
||||||
byte[] b = new byte[6];
|
byte[] b = new byte[6];
|
||||||
ByteUtils.writeUint32(0, b, 1);
|
writeUint32(0, b, 1);
|
||||||
assertEquals("000000000000", StringUtils.toHexString(b));
|
assertEquals("000000000000", toHexString(b));
|
||||||
ByteUtils.writeUint32(1, b, 1);
|
writeUint32(1, b, 1);
|
||||||
assertEquals("000000000100", StringUtils.toHexString(b));
|
assertEquals("000000000100", toHexString(b));
|
||||||
ByteUtils.writeUint32(Integer.MAX_VALUE, b, 1);
|
writeUint32(Integer.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFFFFFF00", StringUtils.toHexString(b));
|
assertEquals("007FFFFFFF00", toHexString(b));
|
||||||
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
|
writeUint32(MAX_32_BIT_UNSIGNED, b, 1);
|
||||||
assertEquals("00FFFFFFFF00", StringUtils.toHexString(b));
|
assertEquals("00FFFFFFFF00", toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint32ValidatesArguments() {
|
public void testWriteUint32ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint32(0, new byte[3], 0);
|
writeUint32(0, new byte[3], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint32(0, new byte[4], 1);
|
writeUint32(0, new byte[4], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint32(-1, new byte[4], 0);
|
writeUint32(-1, new byte[4], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
|
writeUint32(MAX_32_BIT_UNSIGNED + 1, new byte[4], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -163,30 +180,30 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
@Test
|
@Test
|
||||||
public void testWriteUint64() {
|
public void testWriteUint64() {
|
||||||
byte[] b = new byte[10];
|
byte[] b = new byte[10];
|
||||||
ByteUtils.writeUint64(0, b, 1);
|
writeUint64(0, b, 1);
|
||||||
assertEquals("00000000000000000000", StringUtils.toHexString(b));
|
assertEquals("00000000000000000000", toHexString(b));
|
||||||
ByteUtils.writeUint64(1, b, 1);
|
writeUint64(1, b, 1);
|
||||||
assertEquals("00000000000000000100", StringUtils.toHexString(b));
|
assertEquals("00000000000000000100", toHexString(b));
|
||||||
ByteUtils.writeUint64(Long.MAX_VALUE, b, 1);
|
writeUint64(Long.MAX_VALUE, b, 1);
|
||||||
assertEquals("007FFFFFFFFFFFFFFF00", StringUtils.toHexString(b));
|
assertEquals("007FFFFFFFFFFFFFFF00", toHexString(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testWriteUint64ValidatesArguments() {
|
public void testWriteUint64ValidatesArguments() {
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint64(0, new byte[7], 0);
|
writeUint64(0, new byte[7], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint64(0, new byte[8], 1);
|
writeUint64(0, new byte[8], 1);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteUtils.writeUint64(-1, new byte[8], 0);
|
writeUint64(-1, new byte[8], 0);
|
||||||
fail();
|
fail();
|
||||||
} catch (IllegalArgumentException expected) {
|
} catch (IllegalArgumentException expected) {
|
||||||
// Expected
|
// Expected
|
||||||
@@ -194,17 +211,170 @@ public class ByteUtilsTest extends BrambleTestCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReadUint() {
|
public void testGetVarIntBytesToWrite() {
|
||||||
byte[] b = new byte[1];
|
assertEquals(1, getVarIntBytes(0));
|
||||||
b[0] = (byte) 128;
|
assertEquals(1, getVarIntBytes(0x7F)); // Max 7-bit int
|
||||||
for (int i = 0; i < 8; i++) {
|
assertEquals(2, getVarIntBytes(0x7F + 1));
|
||||||
assertEquals(1 << i, ByteUtils.readUint(b, i + 1));
|
assertEquals(2, getVarIntBytes(0x3FFF)); // Max 14-bit int
|
||||||
}
|
assertEquals(3, getVarIntBytes(0x3FFF + 1));
|
||||||
b = new byte[2];
|
assertEquals(3, getVarIntBytes(0x1FFFFF)); // Max 21-bit int
|
||||||
for (int i = 0; i < 65535; i++) {
|
assertEquals(4, getVarIntBytes(0x1FFFFF + 1));
|
||||||
ByteUtils.writeUint16(i, b, 0);
|
assertEquals(4, getVarIntBytes(0xFFFFFFF)); // Max 28-bit int
|
||||||
assertEquals(i, ByteUtils.readUint(b, 16));
|
assertEquals(5, getVarIntBytes(0xFFFFFFF + 1));
|
||||||
assertEquals(i >> 1, ByteUtils.readUint(b, 15));
|
assertEquals(5, getVarIntBytes(0x7FFFFFFFFL)); // Max 35-bit int
|
||||||
|
assertEquals(6, getVarIntBytes(0x7FFFFFFFFL + 1));
|
||||||
|
assertEquals(6, getVarIntBytes(0x3FFFFFFFFFFL)); // Max 42-bit int
|
||||||
|
assertEquals(7, getVarIntBytes(0x3FFFFFFFFFFL + 1));
|
||||||
|
assertEquals(7, getVarIntBytes(0x1FFFFFFFFFFFFL)); // Max 49-bit int
|
||||||
|
assertEquals(8, getVarIntBytes(0x1FFFFFFFFFFFFL + 1));
|
||||||
|
assertEquals(8, getVarIntBytes(0xFFFFFFFFFFFFFFL)); // Max 56-bit int
|
||||||
|
assertEquals(9, getVarIntBytes(0xFFFFFFFFFFFFFFL + 1));
|
||||||
|
assertEquals(9, getVarIntBytes(0x7FFFFFFFFFFFFFFFL)); // Max 63-bit int
|
||||||
|
assertEquals(MAX_VARINT_BYTES, getVarIntBytes(Long.MAX_VALUE)); // Same
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteVarInt() {
|
||||||
|
testWriteVarInt(0, 1, "00");
|
||||||
|
testWriteVarInt(1, 1, "01");
|
||||||
|
testWriteVarInt(0x7F, 1, "7F"); // Max 7-bit int
|
||||||
|
testWriteVarInt(0x7F + 1, 2, "8100");
|
||||||
|
testWriteVarInt(0x3FFF, 2, "FF7F"); // Max 14-bit int
|
||||||
|
testWriteVarInt(0x3FFF + 1, 3, "818000");
|
||||||
|
testWriteVarInt(0x1FFFFF, 3, "FFFF7F"); // Max 21-bit int
|
||||||
|
testWriteVarInt(0x1FFFFF + 1, 4, "81808000");
|
||||||
|
testWriteVarInt(0xFFFFFFF, 4, "FFFFFF7F"); // Max 28-bit int
|
||||||
|
testWriteVarInt(0xFFFFFFF + 1, 5, "8180808000");
|
||||||
|
testWriteVarInt(0x7FFFFFFFFL, 5, "FFFFFFFF7F"); // Max 35-bit int
|
||||||
|
testWriteVarInt(0x7FFFFFFFFL + 1, 6, "818080808000");
|
||||||
|
testWriteVarInt(0x3FFFFFFFFFFL, 6, "FFFFFFFFFF7F"); // Max 42-bit int
|
||||||
|
testWriteVarInt(0x3FFFFFFFFFFL + 1, 7, "81808080808000");
|
||||||
|
testWriteVarInt(0x1FFFFFFFFFFFFL, 7, "FFFFFFFFFFFF7F"); // Max 49
|
||||||
|
testWriteVarInt(0x1FFFFFFFFFFFFL + 1, 8, "8180808080808000");
|
||||||
|
testWriteVarInt(0xFFFFFFFFFFFFFFL, 8, "FFFFFFFFFFFFFF7F"); // Max 56
|
||||||
|
testWriteVarInt(0xFFFFFFFFFFFFFFL + 1, 9, "818080808080808000");
|
||||||
|
testWriteVarInt(0x7FFFFFFFFFFFFFFFL, 9, "FFFFFFFFFFFFFFFF7F"); // Max 63
|
||||||
|
testWriteVarInt(Long.MAX_VALUE, MAX_VARINT_BYTES, "FFFFFFFFFFFFFFFF7F");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testWriteVarInt(long src, int len, String destHex) {
|
||||||
|
byte[] dest = new byte[9];
|
||||||
|
assertEquals(len, writeVarInt(src, dest, 0));
|
||||||
|
assertEquals(destHex, toHexString(dest).substring(0, len * 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testGetVarIntBytesToRead() throws FormatException {
|
||||||
|
testGetVarIntBytesToRead(1, "00", 0);
|
||||||
|
testGetVarIntBytesToRead(1, "01", 0);
|
||||||
|
testGetVarIntBytesToRead(1, "7F", 0); // Max 7-bit int
|
||||||
|
testGetVarIntBytesToRead(2, "8100", 0);
|
||||||
|
testGetVarIntBytesToRead(2, "FF7F", 0); // Max 14-bit int
|
||||||
|
testGetVarIntBytesToRead(3, "818000", 0);
|
||||||
|
testGetVarIntBytesToRead(3, "FFFF7F", 0); // Max 21-bit int
|
||||||
|
testGetVarIntBytesToRead(4, "81808000", 0);
|
||||||
|
testGetVarIntBytesToRead(4, "FFFFFF7F", 0); // Max 28-bit int
|
||||||
|
testGetVarIntBytesToRead(5, "8180808000", 0);
|
||||||
|
testGetVarIntBytesToRead(5, "FFFFFFFF7F", 0); // Max 35-bit int
|
||||||
|
testGetVarIntBytesToRead(6, "818080808000", 0);
|
||||||
|
testGetVarIntBytesToRead(6, "FFFFFFFFFF7F", 0); // Max 42-bit int
|
||||||
|
testGetVarIntBytesToRead(7, "81808080808000", 0);
|
||||||
|
testGetVarIntBytesToRead(7, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
|
||||||
|
testGetVarIntBytesToRead(8, "8180808080808000", 0);
|
||||||
|
testGetVarIntBytesToRead(8, "FFFFFFFFFFFFFF7F", 0); // Max 56-bit int
|
||||||
|
testGetVarIntBytesToRead(9, "818080808080808000", 0);
|
||||||
|
testGetVarIntBytesToRead(9, "FFFFFFFFFFFFFFFF7F", 0); // Max 63-bit int
|
||||||
|
// Start at offset, ignore trailing data
|
||||||
|
testGetVarIntBytesToRead(1, "FF0000", 1);
|
||||||
|
testGetVarIntBytesToRead(9, "00FFFFFFFFFFFFFFFF7F00", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testGetVarIntBytesToRead(int len, String srcHex, int offset)
|
||||||
|
throws FormatException {
|
||||||
|
assertEquals(len, getVarIntBytes(fromHexString(srcHex), offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testGetVarIntBytesToReadThrowsExceptionAtEndOfInput()
|
||||||
|
throws FormatException {
|
||||||
|
byte[] src = new byte[MAX_VARINT_BYTES - 1];
|
||||||
|
fill(src, (byte) 0xFF);
|
||||||
|
// Reaches end of input without finding lowered continuation flag
|
||||||
|
getVarIntBytes(src, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testGetVarIntBytesToReadThrowsExceptionAfterNineBytes()
|
||||||
|
throws FormatException {
|
||||||
|
byte[] src = new byte[MAX_VARINT_BYTES];
|
||||||
|
fill(src, (byte) 0xFF);
|
||||||
|
// Reaches max length without finding lowered continuation flag
|
||||||
|
getVarIntBytes(src, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testReadVarInt() throws FormatException {
|
||||||
|
testReadVarInt(0, "00", 0);
|
||||||
|
testReadVarInt(1, "01", 0);
|
||||||
|
testReadVarInt(0x7F, "7F", 0); // Max 7-bit int
|
||||||
|
testReadVarInt(0x7F + 1, "8100", 0);
|
||||||
|
testReadVarInt(0x3FFF, "FF7F", 0); // Max 14-bit int
|
||||||
|
testReadVarInt(0x3FFF + 1, "818000", 0);
|
||||||
|
testReadVarInt(0x1FFFFF, "FFFF7F", 0); // Max 21-bit int
|
||||||
|
testReadVarInt(0x1FFFFF + 1, "81808000", 0);
|
||||||
|
testReadVarInt(0xFFFFFFF, "FFFFFF7F", 0); // Max 28-bit int
|
||||||
|
testReadVarInt(0xFFFFFFF + 1, "8180808000", 0);
|
||||||
|
testReadVarInt(0x7FFFFFFFFL, "FFFFFFFF7F", 0); // Max 35-bit int
|
||||||
|
testReadVarInt(0x7FFFFFFFFL + 1, "818080808000", 0);
|
||||||
|
testReadVarInt(0x3FFFFFFFFFFL, "FFFFFFFFFF7F", 0); // Max 42-bit int
|
||||||
|
testReadVarInt(0x3FFFFFFFFFFL + 1, "81808080808000", 0);
|
||||||
|
testReadVarInt(0x1FFFFFFFFFFFFL, "FFFFFFFFFFFF7F", 0); // Max 49-bit int
|
||||||
|
testReadVarInt(0x1FFFFFFFFFFFFL + 1, "8180808080808000", 0);
|
||||||
|
testReadVarInt(0xFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFF7F", 0); // Max 56
|
||||||
|
testReadVarInt(0xFFFFFFFFFFFFFFL + 1, "818080808080808000", 0);
|
||||||
|
testReadVarInt(0x7FFFFFFFFFFFFFFFL, "FFFFFFFFFFFFFFFF7F", 0); // Max 63
|
||||||
|
testReadVarInt(Long.MAX_VALUE, "FFFFFFFFFFFFFFFF7F", 0);
|
||||||
|
// Start at offset, ignore trailing data
|
||||||
|
testReadVarInt(0, "FF0000", 1);
|
||||||
|
testReadVarInt(Long.MAX_VALUE, "00FFFFFFFFFFFFFFFF7F00", 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void testReadVarInt(long dest, String srcHex, int offset)
|
||||||
|
throws FormatException {
|
||||||
|
assertEquals(dest, readVarInt(fromHexString(srcHex), offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testReadVarIntThrowsExceptionAtEndOfInput()
|
||||||
|
throws FormatException {
|
||||||
|
byte[] src = new byte[MAX_VARINT_BYTES - 1];
|
||||||
|
fill(src, (byte) 0xFF);
|
||||||
|
// Reaches end of input without finding lowered continuation flag
|
||||||
|
readVarInt(src, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = FormatException.class)
|
||||||
|
public void testReadVarIntThrowsExceptionAfterNineBytes()
|
||||||
|
throws FormatException {
|
||||||
|
byte[] src = new byte[MAX_VARINT_BYTES];
|
||||||
|
fill(src, (byte) 0xFF);
|
||||||
|
// Reaches max length without finding lowered continuation flag
|
||||||
|
readVarInt(src, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWriteAndReadVarInt() throws FormatException {
|
||||||
|
Random random = new Random();
|
||||||
|
int padding = 10;
|
||||||
|
byte[] buf = new byte[MAX_VARINT_BYTES + padding];
|
||||||
|
for (int i = 0; i < 1000; i++) {
|
||||||
|
long src = random.nextLong() & 0x7FFFFFFFFFFFFFFFL; // Non-negative
|
||||||
|
int offset = random.nextInt(padding);
|
||||||
|
int len = getVarIntBytes(src);
|
||||||
|
assertEquals(len, writeVarInt(src, buf, offset));
|
||||||
|
assertEquals(len, getVarIntBytes(buf, offset));
|
||||||
|
assertEquals(src, readVarInt(buf, offset));
|
||||||
|
fill(buf, (byte) 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ dependencies {
|
|||||||
exclude group: 'com.android.support'
|
exclude group: 'com.android.support'
|
||||||
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
exclude module: 'disklrucache' // when there's no disk cache, we can't accidentally use it
|
||||||
}
|
}
|
||||||
|
implementation 'com.github.chrisbanes:PhotoView:2.1.4' // later versions already use androidx
|
||||||
|
|
||||||
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
annotationProcessor 'com.google.dagger:dagger-compiler:2.19'
|
||||||
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
annotationProcessor "com.github.bumptech.glide:compiler:$glideVersion"
|
||||||
|
|||||||
@@ -113,6 +113,15 @@
|
|||||||
/>
|
/>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".android.conversation.ImageActivity"
|
||||||
|
android:parentActivityName="org.briarproject.briar.android.conversation.ConversationActivity"
|
||||||
|
android:theme="@style/BriarTheme.Transparent.NoActionBar">
|
||||||
|
<meta-data
|
||||||
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
|
android:value="org.briarproject.briar.android.conversation.ConversationActivity"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
android:name="org.briarproject.briar.android.privategroup.creation.CreateGroupActivity"
|
||||||
android:label="@string/groups_create_group_title"
|
android:label="@string/groups_create_group_title"
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ import org.briarproject.briar.android.blog.ReblogFragment;
|
|||||||
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
import org.briarproject.briar.android.blog.RssFeedImportActivity;
|
||||||
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
import org.briarproject.briar.android.blog.RssFeedManageActivity;
|
||||||
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
import org.briarproject.briar.android.blog.WriteBlogPostActivity;
|
||||||
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
|
||||||
import org.briarproject.briar.android.contact.ContactListFragment;
|
import org.briarproject.briar.android.contact.ContactListFragment;
|
||||||
import org.briarproject.briar.android.contact.ContactModule;
|
import org.briarproject.briar.android.contact.ContactModule;
|
||||||
|
import org.briarproject.briar.android.conversation.AliasDialogFragment;
|
||||||
import org.briarproject.briar.android.conversation.ConversationActivity;
|
import org.briarproject.briar.android.conversation.ConversationActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.ImageActivity;
|
||||||
import org.briarproject.briar.android.forum.CreateForumActivity;
|
import org.briarproject.briar.android.forum.CreateForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumActivity;
|
import org.briarproject.briar.android.forum.ForumActivity;
|
||||||
import org.briarproject.briar.android.forum.ForumListFragment;
|
import org.briarproject.briar.android.forum.ForumListFragment;
|
||||||
@@ -110,6 +111,8 @@ public interface ActivityComponent {
|
|||||||
|
|
||||||
void inject(ConversationActivity activity);
|
void inject(ConversationActivity activity);
|
||||||
|
|
||||||
|
void inject(ImageActivity activity);
|
||||||
|
|
||||||
void inject(ForumInvitationActivity activity);
|
void inject(ForumInvitationActivity activity);
|
||||||
|
|
||||||
void inject(BlogInvitationActivity activity);
|
void inject(BlogInvitationActivity activity);
|
||||||
|
|||||||
@@ -2,12 +2,11 @@ package org.briarproject.briar.android.activity;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
import android.support.v7.app.ActionBar;
|
import android.support.v7.app.ActionBar;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
import android.transition.Slide;
|
|
||||||
import android.transition.Transition;
|
import android.transition.Transition;
|
||||||
import android.view.Gravity;
|
|
||||||
import android.view.Window;
|
import android.view.Window;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
|
||||||
@@ -33,6 +32,7 @@ import static android.os.Build.VERSION.SDK_INT;
|
|||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_DOZE_WHITELISTING;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_UNLOCK;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.excludeSystemUi;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
import static org.briarproject.briar.android.util.UiUtils.getDozeWhitelistingIntent;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
import static org.briarproject.briar.android.util.UiUtils.isSamsung7;
|
||||||
|
|
||||||
@@ -111,21 +111,28 @@ public abstract class BriarActivity extends BaseActivity {
|
|||||||
lockManager.onActivityStop();
|
lockManager.onActivityStop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSceneTransitionAnimation() {
|
/**
|
||||||
if (SDK_INT < 21) return;
|
* Sets the transition animations.
|
||||||
|
* @param enterTransition used to move views into initial positions
|
||||||
|
* @param exitTransition used to move views out when starting a <b>new</b> activity.
|
||||||
|
* @param returnTransition used when window is closing, because the activity is finishing.
|
||||||
|
*/
|
||||||
|
@RequiresApi(api = 21)
|
||||||
|
public void setSceneTransitionAnimation(
|
||||||
|
@Nullable Transition enterTransition,
|
||||||
|
@Nullable Transition exitTransition,
|
||||||
|
@Nullable Transition returnTransition) {
|
||||||
// workaround for #1007
|
// workaround for #1007
|
||||||
if (isSamsung7()) {
|
if (isSamsung7()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Transition slide = new Slide(Gravity.RIGHT);
|
if (enterTransition != null) excludeSystemUi(enterTransition);
|
||||||
slide.excludeTarget(android.R.id.statusBarBackground, true);
|
if (exitTransition != null) excludeSystemUi(exitTransition);
|
||||||
slide.excludeTarget(android.R.id.navigationBarBackground, true);
|
if (returnTransition != null) excludeSystemUi(returnTransition);
|
||||||
Window window = getWindow();
|
Window window = getWindow();
|
||||||
window.requestFeature(Window.FEATURE_CONTENT_TRANSITIONS);
|
window.setEnterTransition(enterTransition);
|
||||||
window.setExitTransition(slide);
|
window.setExitTransition(exitTransition);
|
||||||
window.setEnterTransition(slide);
|
window.setReturnTransition(returnTransition);
|
||||||
window.setTransitionBackgroundFadeDuration(getResources()
|
|
||||||
.getInteger(android.R.integer.config_longAnimTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ public class ReblogActivity extends BriarActivity implements
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
setSceneTransitionAnimation();
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
import org.briarproject.bramble.api.sync.MessageId;
|
import org.briarproject.bramble.api.sync.MessageId;
|
||||||
|
|
||||||
@@ -7,13 +10,26 @@ import javax.annotation.concurrent.Immutable;
|
|||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
@NotNullByDefault
|
@NotNullByDefault
|
||||||
public class AttachmentItem {
|
public class AttachmentItem implements Parcelable {
|
||||||
|
|
||||||
private final MessageId messageId;
|
private final MessageId messageId;
|
||||||
private final int width, height;
|
private final int width, height;
|
||||||
private final int thumbnailWidth, thumbnailHeight;
|
private final int thumbnailWidth, thumbnailHeight;
|
||||||
private final boolean hasError;
|
private final boolean hasError;
|
||||||
|
|
||||||
|
public static final Creator<AttachmentItem> CREATOR =
|
||||||
|
new Creator<AttachmentItem>() {
|
||||||
|
@Override
|
||||||
|
public AttachmentItem createFromParcel(Parcel in) {
|
||||||
|
return new AttachmentItem(in);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AttachmentItem[] newArray(int size) {
|
||||||
|
return new AttachmentItem[size];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
AttachmentItem(MessageId messageId, int width, int height,
|
AttachmentItem(MessageId messageId, int width, int height,
|
||||||
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
|
int thumbnailWidth, int thumbnailHeight, boolean hasError) {
|
||||||
this.messageId = messageId;
|
this.messageId = messageId;
|
||||||
@@ -24,6 +40,17 @@ public class AttachmentItem {
|
|||||||
this.hasError = hasError;
|
this.hasError = hasError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected AttachmentItem(Parcel in) {
|
||||||
|
byte[] messageIdByte = new byte[MessageId.LENGTH];
|
||||||
|
in.readByteArray(messageIdByte);
|
||||||
|
messageId = new MessageId(messageIdByte);
|
||||||
|
width = in.readInt();
|
||||||
|
height = in.readInt();
|
||||||
|
thumbnailWidth = in.readInt();
|
||||||
|
thumbnailHeight = in.readInt();
|
||||||
|
hasError = in.readByte() != 0;
|
||||||
|
}
|
||||||
|
|
||||||
public MessageId getMessageId() {
|
public MessageId getMessageId() {
|
||||||
return messageId;
|
return messageId;
|
||||||
}
|
}
|
||||||
@@ -48,4 +75,24 @@ public class AttachmentItem {
|
|||||||
return hasError;
|
return hasError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO use counter instead, because in theory one attachment can appear in more than one messages
|
||||||
|
String getTransitionName() {
|
||||||
|
return String.valueOf(messageId.hashCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int describeContents() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void writeToParcel(Parcel dest, int flags) {
|
||||||
|
dest.writeByteArray(messageId.getBytes());
|
||||||
|
dest.writeInt(width);
|
||||||
|
dest.writeInt(height);
|
||||||
|
dest.writeInt(thumbnailWidth);
|
||||||
|
dest.writeInt(thumbnailHeight);
|
||||||
|
dest.writeByte((byte) (hasError ? 1 : 0));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,15 @@ import android.os.Bundle;
|
|||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
import android.support.design.widget.Snackbar;
|
import android.support.design.widget.Snackbar;
|
||||||
|
import android.support.v4.app.ActivityCompat;
|
||||||
|
import android.support.v4.app.ActivityOptionsCompat;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
import android.support.v7.app.AlertDialog;
|
import android.support.v7.app.AlertDialog;
|
||||||
import android.support.v7.widget.ActionMenuView;
|
import android.support.v7.widget.ActionMenuView;
|
||||||
import android.support.v7.widget.LinearLayoutManager;
|
import android.support.v7.widget.LinearLayoutManager;
|
||||||
import android.support.v7.widget.Toolbar;
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.transition.Slide;
|
||||||
|
import android.transition.Transition;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@@ -96,8 +100,11 @@ import im.delight.android.identicons.IdenticonDrawable;
|
|||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt;
|
||||||
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
import uk.co.samuelwall.materialtaptargetprompt.MaterialTapTargetPrompt.PromptStateChangeListener;
|
||||||
|
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.support.v4.app.ActivityOptionsCompat.makeSceneTransitionAnimation;
|
||||||
import static android.support.v4.view.ViewCompat.setTransitionName;
|
import static android.support.v4.view.ViewCompat.setTransitionName;
|
||||||
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||||
|
import static android.view.Gravity.END;
|
||||||
import static android.widget.Toast.LENGTH_SHORT;
|
import static android.widget.Toast.LENGTH_SHORT;
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Collections.sort;
|
import static java.util.Collections.sort;
|
||||||
@@ -108,6 +115,9 @@ import static org.briarproject.bramble.util.LogUtils.logDuration;
|
|||||||
import static org.briarproject.bramble.util.LogUtils.logException;
|
import static org.briarproject.bramble.util.LogUtils.logException;
|
||||||
import static org.briarproject.bramble.util.LogUtils.now;
|
import static org.briarproject.bramble.util.LogUtils.now;
|
||||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHMENT;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
|
||||||
|
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
|
||||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||||
@@ -186,7 +196,10 @@ public class ConversationActivity extends BriarActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle state) {
|
public void onCreate(@Nullable Bundle state) {
|
||||||
setSceneTransitionAnimation();
|
if (SDK_INT >= 21) {
|
||||||
|
Transition slide = new Slide(END);
|
||||||
|
setSceneTransitionAnimation(slide, null, slide);
|
||||||
|
}
|
||||||
super.onCreate(state);
|
super.onCreate(state);
|
||||||
|
|
||||||
Intent i = getIntent();
|
Intent i = getIntent();
|
||||||
@@ -802,6 +815,31 @@ public class ConversationActivity extends BriarActivity
|
|||||||
startActivity(i);
|
startActivity(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttachmentClicked(View view,
|
||||||
|
ConversationMessageItem messageItem, AttachmentItem item) {
|
||||||
|
String name;
|
||||||
|
if (messageItem.isIncoming()) {
|
||||||
|
// must be available when items are being displayed
|
||||||
|
name = viewModel.getContactDisplayName().getValue();
|
||||||
|
} else {
|
||||||
|
name = getString(R.string.you);
|
||||||
|
}
|
||||||
|
Intent i = new Intent(this, ImageActivity.class);
|
||||||
|
i.putExtra(ATTACHMENT, item);
|
||||||
|
i.putExtra(NAME, name);
|
||||||
|
i.putExtra(DATE, messageItem.getTime());
|
||||||
|
if (SDK_INT >= 23) {
|
||||||
|
String transitionName = item.getTransitionName();
|
||||||
|
ActivityOptionsCompat options =
|
||||||
|
makeSceneTransitionAnimation(this, view, transitionName);
|
||||||
|
ActivityCompat.startActivity(this, i, options.toBundle());
|
||||||
|
} else {
|
||||||
|
// work-around for android bug #224270
|
||||||
|
startActivity(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@DatabaseExecutor
|
@DatabaseExecutor
|
||||||
private void respondToIntroductionRequest(SessionId sessionId,
|
private void respondToIntroductionRequest(SessionId sessionId,
|
||||||
boolean accept, long time) throws DbException {
|
boolean accept, long time) throws DbException {
|
||||||
@@ -845,4 +883,5 @@ public class ConversationActivity extends BriarActivity
|
|||||||
}
|
}
|
||||||
return attachments;
|
return attachments;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package org.briarproject.briar.android.conversation;
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
import android.support.annotation.UiThread;
|
import android.support.annotation.UiThread;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
@@ -14,4 +15,7 @@ interface ConversationListener {
|
|||||||
|
|
||||||
void openRequestedShareable(ConversationRequestItem item);
|
void openRequestedShareable(ConversationRequestItem item);
|
||||||
|
|
||||||
|
void onAttachmentClicked(View view, ConversationMessageItem messageItem,
|
||||||
|
AttachmentItem attachmentItem);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
if (item.getAttachments().isEmpty()) {
|
if (item.getAttachments().isEmpty()) {
|
||||||
bindTextItem();
|
bindTextItem();
|
||||||
} else {
|
} else {
|
||||||
bindImageItem(item);
|
bindImageItem(item, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,7 +98,8 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
textConstraints.applyTo(layout);
|
textConstraints.applyTo(layout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindImageItem(ConversationMessageItem item) {
|
private void bindImageItem(ConversationMessageItem item,
|
||||||
|
ConversationListener listener) {
|
||||||
// TODO show more than just the first image
|
// TODO show more than just the first image
|
||||||
AttachmentItem attachment = item.getAttachments().get(0);
|
AttachmentItem attachment = item.getAttachments().get(0);
|
||||||
|
|
||||||
@@ -127,17 +128,18 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
clearImage();
|
clearImage();
|
||||||
imageView.setImageResource(ERROR_RES);
|
imageView.setImageResource(ERROR_RES);
|
||||||
} else {
|
} else {
|
||||||
loadImage(item, attachment);
|
loadImage(item, attachment, listener);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void clearImage() {
|
private void clearImage() {
|
||||||
GlideApp.with(imageView)
|
GlideApp.with(imageView)
|
||||||
.clear(imageView);
|
.clear(imageView);
|
||||||
|
imageView.setOnClickListener(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadImage(ConversationMessageItem item,
|
private void loadImage(ConversationMessageItem item,
|
||||||
AttachmentItem attachment) {
|
AttachmentItem attachment, ConversationListener listener) {
|
||||||
boolean leftCornerSmall =
|
boolean leftCornerSmall =
|
||||||
(isIncoming() && !isRtl) || (!isIncoming() && isRtl);
|
(isIncoming() && !isRtl) || (!isIncoming() && isRtl);
|
||||||
boolean bottomRound = item.getText() == null;
|
boolean bottomRound = item.getText() == null;
|
||||||
@@ -152,6 +154,8 @@ class ConversationMessageViewHolder extends ConversationItemViewHolder {
|
|||||||
.transition(withCrossFade())
|
.transition(withCrossFade())
|
||||||
.into(imageView)
|
.into(imageView)
|
||||||
.waitForLayout();
|
.waitForLayout();
|
||||||
|
imageView.setOnClickListener(
|
||||||
|
view -> listener.onAttachmentClicked(view, item, attachment));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
package org.briarproject.briar.android.conversation;
|
||||||
|
|
||||||
|
import android.graphics.drawable.Animatable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
|
import android.support.design.widget.AppBarLayout;
|
||||||
|
import android.support.v7.widget.Toolbar;
|
||||||
|
import android.transition.Fade;
|
||||||
|
import android.transition.Transition;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.Window;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import com.bumptech.glide.load.DataSource;
|
||||||
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
import com.bumptech.glide.request.RequestListener;
|
||||||
|
import com.bumptech.glide.request.target.Target;
|
||||||
|
import com.github.chrisbanes.photoview.PhotoView;
|
||||||
|
|
||||||
|
import org.briarproject.briar.R;
|
||||||
|
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||||
|
import org.briarproject.briar.android.activity.BriarActivity;
|
||||||
|
import org.briarproject.briar.android.conversation.glide.GlideApp;
|
||||||
|
import org.briarproject.briar.android.view.PullDownLayout;
|
||||||
|
|
||||||
|
import static android.graphics.Color.TRANSPARENT;
|
||||||
|
import static android.os.Build.VERSION.SDK_INT;
|
||||||
|
import static android.view.View.GONE;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
|
||||||
|
import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||||
|
import static android.view.View.VISIBLE;
|
||||||
|
import static android.view.WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS;
|
||||||
|
import static android.widget.ImageView.ScaleType.FIT_START;
|
||||||
|
import static com.bumptech.glide.load.engine.DiskCacheStrategy.NONE;
|
||||||
|
import static java.util.Objects.requireNonNull;
|
||||||
|
import static org.briarproject.briar.android.util.UiUtils.formatDateAbsolute;
|
||||||
|
|
||||||
|
public class ImageActivity extends BriarActivity
|
||||||
|
implements PullDownLayout.Callback {
|
||||||
|
|
||||||
|
final static String ATTACHMENT = "attachment";
|
||||||
|
final static String NAME = "name";
|
||||||
|
final static String DATE = "date";
|
||||||
|
|
||||||
|
private PullDownLayout layout;
|
||||||
|
private AppBarLayout appBarLayout;
|
||||||
|
private PhotoView photoView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void injectActivity(ActivityComponent component) {
|
||||||
|
component.inject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle state) {
|
||||||
|
super.onCreate(state);
|
||||||
|
|
||||||
|
// Transitions
|
||||||
|
supportPostponeEnterTransition();
|
||||||
|
Window window = getWindow();
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
Transition transition = new Fade();
|
||||||
|
setSceneTransitionAnimation(transition, null, transition);
|
||||||
|
}
|
||||||
|
|
||||||
|
// inflate layout
|
||||||
|
setContentView(R.layout.activity_image);
|
||||||
|
layout = findViewById(R.id.layout);
|
||||||
|
layout.getBackground().setAlpha(255);
|
||||||
|
layout.setCallback(this);
|
||||||
|
|
||||||
|
// Status Bar
|
||||||
|
if (SDK_INT >= 21) {
|
||||||
|
window.setStatusBarColor(TRANSPARENT);
|
||||||
|
} else if (SDK_INT >= 19) {
|
||||||
|
// we can't make the status bar transparent, but translucent
|
||||||
|
window.addFlags(FLAG_TRANSLUCENT_STATUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Toolbar
|
||||||
|
appBarLayout = findViewById(R.id.appBarLayout);
|
||||||
|
Toolbar toolbar = requireNonNull(setUpCustomToolbar(true));
|
||||||
|
TextView contactName = toolbar.findViewById(R.id.contactName);
|
||||||
|
TextView dateView = toolbar.findViewById(R.id.dateView);
|
||||||
|
|
||||||
|
// Intent Extras
|
||||||
|
AttachmentItem attachment = getIntent().getParcelableExtra(ATTACHMENT);
|
||||||
|
String name = getIntent().getStringExtra(NAME);
|
||||||
|
long time = getIntent().getLongExtra(DATE, 0);
|
||||||
|
String date = formatDateAbsolute(this, time);
|
||||||
|
contactName.setText(name);
|
||||||
|
dateView.setText(date);
|
||||||
|
|
||||||
|
// Image View
|
||||||
|
photoView = findViewById(R.id.photoView);
|
||||||
|
if (SDK_INT >= 16) {
|
||||||
|
photoView.setOnClickListener(view -> toggleSystemUi());
|
||||||
|
window.getDecorView().setSystemUiVisibility(
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request Listener
|
||||||
|
RequestListener<Drawable> listener = new RequestListener<Drawable>() {
|
||||||
|
@Override
|
||||||
|
public boolean onLoadFailed(@Nullable GlideException e,
|
||||||
|
Object model, Target<Drawable> target,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
supportStartPostponedEnterTransition();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onResourceReady(Drawable resource, Object model,
|
||||||
|
Target<Drawable> target, DataSource dataSource,
|
||||||
|
boolean isFirstResource) {
|
||||||
|
if (SDK_INT >= 21 && !(resource instanceof Animatable)) {
|
||||||
|
// set transition name only when not animatable,
|
||||||
|
// because the animation won't start otherwise
|
||||||
|
photoView.setTransitionName(
|
||||||
|
attachment.getTransitionName());
|
||||||
|
}
|
||||||
|
// Move image to the top if overlapping toolbar
|
||||||
|
if (isOverlappingToolbar(resource)) {
|
||||||
|
photoView.setScaleType(FIT_START);
|
||||||
|
}
|
||||||
|
supportStartPostponedEnterTransition();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load Image
|
||||||
|
GlideApp.with(this)
|
||||||
|
.load(attachment)
|
||||||
|
.diskCacheStrategy(NONE)
|
||||||
|
.error(R.drawable.ic_image_broken)
|
||||||
|
.dontTransform()
|
||||||
|
.addListener(listener)
|
||||||
|
.into(photoView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
onBackPressed();
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullStart() {
|
||||||
|
appBarLayout.animate()
|
||||||
|
.alpha(0f)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPull(float progress) {
|
||||||
|
layout.getBackground().setAlpha(Math.round((1 - progress) * 255));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullCancel() {
|
||||||
|
appBarLayout.animate()
|
||||||
|
.alpha(1f)
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPullComplete() {
|
||||||
|
supportFinishAfterTransition();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void toggleSystemUi() {
|
||||||
|
View decorView = getWindow().getDecorView();
|
||||||
|
if (appBarLayout.getVisibility() == VISIBLE) {
|
||||||
|
hideSystemUi(decorView);
|
||||||
|
} else {
|
||||||
|
showSystemUi(decorView);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void hideSystemUi(View decorView) {
|
||||||
|
decorView.setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
| SYSTEM_UI_FLAG_FULLSCREEN
|
||||||
|
);
|
||||||
|
appBarLayout.animate()
|
||||||
|
.translationYBy(-1 * appBarLayout.getHeight())
|
||||||
|
.alpha(0f)
|
||||||
|
.withEndAction(() -> appBarLayout.setVisibility(GONE))
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 16)
|
||||||
|
private void showSystemUi(View decorView) {
|
||||||
|
decorView.setSystemUiVisibility(
|
||||||
|
SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||||
|
| SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||||
|
);
|
||||||
|
appBarLayout.animate()
|
||||||
|
.translationYBy(appBarLayout.getHeight())
|
||||||
|
.alpha(1f)
|
||||||
|
.withStartAction(() -> appBarLayout.setVisibility(VISIBLE))
|
||||||
|
.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isOverlappingToolbar(Drawable drawable) {
|
||||||
|
int width = drawable.getIntrinsicWidth();
|
||||||
|
int height = drawable.getIntrinsicHeight();
|
||||||
|
float widthPercentage = photoView.getWidth() / (float) width;
|
||||||
|
float heightPercentage = photoView.getHeight() / (float) height;
|
||||||
|
float scaleFactor = Math.min(widthPercentage, heightPercentage);
|
||||||
|
int realWidth = (int) (width * scaleFactor);
|
||||||
|
int realHeight = (int) (height * scaleFactor);
|
||||||
|
// return if photo doesn't use the full width,
|
||||||
|
// because it will be moved to the right otherwise
|
||||||
|
if (realWidth < photoView.getWidth()) return false;
|
||||||
|
int drawableTop = (photoView.getHeight() - realHeight) / 2;
|
||||||
|
return drawableTop < appBarLayout.getBottom() &&
|
||||||
|
drawableTop != appBarLayout.getTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -16,6 +16,7 @@ import android.support.annotation.ColorInt;
|
|||||||
import android.support.annotation.ColorRes;
|
import android.support.annotation.ColorRes;
|
||||||
import android.support.annotation.MainThread;
|
import android.support.annotation.MainThread;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.annotation.RequiresApi;
|
||||||
import android.support.design.widget.TextInputLayout;
|
import android.support.design.widget.TextInputLayout;
|
||||||
import android.support.v4.app.FragmentManager;
|
import android.support.v4.app.FragmentManager;
|
||||||
import android.support.v4.content.ContextCompat;
|
import android.support.v4.content.ContextCompat;
|
||||||
@@ -31,6 +32,7 @@ import android.text.method.LinkMovementMethod;
|
|||||||
import android.text.style.ClickableSpan;
|
import android.text.style.ClickableSpan;
|
||||||
import android.text.style.ForegroundColorSpan;
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.text.style.URLSpan;
|
import android.text.style.URLSpan;
|
||||||
|
import android.transition.Transition;
|
||||||
import android.util.TypedValue;
|
import android.util.TypedValue;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@@ -60,12 +62,16 @@ import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_NO;
|
|||||||
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES;
|
import static android.support.v7.app.AppCompatDelegate.MODE_NIGHT_YES;
|
||||||
import static android.support.v7.app.AppCompatDelegate.setDefaultNightMode;
|
import static android.support.v7.app.AppCompatDelegate.setDefaultNightMode;
|
||||||
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
import static android.text.format.DateUtils.DAY_IN_MILLIS;
|
||||||
|
import static android.text.format.DateUtils.FORMAT_ABBREV_ALL;
|
||||||
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
|
import static android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
|
||||||
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
|
import static android.text.format.DateUtils.FORMAT_ABBREV_RELATIVE;
|
||||||
import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
|
import static android.text.format.DateUtils.FORMAT_ABBREV_TIME;
|
||||||
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
|
import static android.text.format.DateUtils.FORMAT_SHOW_DATE;
|
||||||
|
import static android.text.format.DateUtils.FORMAT_SHOW_TIME;
|
||||||
|
import static android.text.format.DateUtils.FORMAT_SHOW_YEAR;
|
||||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||||
|
import static android.text.format.DateUtils.YEAR_IN_MILLIS;
|
||||||
import static android.view.KeyEvent.ACTION_DOWN;
|
import static android.view.KeyEvent.ACTION_DOWN;
|
||||||
import static android.view.KeyEvent.KEYCODE_ENTER;
|
import static android.view.KeyEvent.KEYCODE_ENTER;
|
||||||
import static android.view.inputmethod.EditorInfo.IME_NULL;
|
import static android.view.inputmethod.EditorInfo.IME_NULL;
|
||||||
@@ -117,6 +123,13 @@ public class UiUtils {
|
|||||||
MIN_DATE_RESOLUTION, flags).toString();
|
MIN_DATE_RESOLUTION, flags).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String formatDateAbsolute(Context ctx, long time) {
|
||||||
|
int flags = FORMAT_SHOW_TIME | FORMAT_SHOW_DATE | FORMAT_ABBREV_ALL;
|
||||||
|
long diff = System.currentTimeMillis() - time;
|
||||||
|
if (diff >= YEAR_IN_MILLIS) flags |= FORMAT_SHOW_YEAR;
|
||||||
|
return DateUtils.formatDateTime(ctx, time, flags);
|
||||||
|
}
|
||||||
|
|
||||||
public static int getDaysUntilExpiry() {
|
public static int getDaysUntilExpiry() {
|
||||||
long now = System.currentTimeMillis();
|
long now = System.currentTimeMillis();
|
||||||
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
|
long daysBeforeExpiry = (EXPIRY_DATE - now) / 1000 / 60 / 60 / 24;
|
||||||
@@ -318,6 +331,12 @@ public class UiUtils {
|
|||||||
keyEvent.getKeyCode() == KEYCODE_ENTER;
|
keyEvent.getKeyCode() == KEYCODE_ENTER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(api = 21)
|
||||||
|
public static void excludeSystemUi(Transition transition) {
|
||||||
|
transition.excludeTarget(android.R.id.statusBarBackground, true);
|
||||||
|
transition.excludeTarget(android.R.id.navigationBarBackground, true);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Observes the given {@link LiveData} until the first change.
|
* Observes the given {@link LiveData} until the first change.
|
||||||
* If the LiveData's value is available, the {@link Observer} will be
|
* If the LiveData's value is available, the {@link Observer} will be
|
||||||
|
|||||||
@@ -0,0 +1,161 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License (MIT)
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 XiNGRZ <xxx@oxo.ooo>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.briarproject.briar.android.view;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.support.annotation.Nullable;
|
||||||
|
import android.support.v4.view.ViewCompat;
|
||||||
|
import android.support.v4.widget.ViewDragHelper;
|
||||||
|
import android.util.AttributeSet;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewConfiguration;
|
||||||
|
import android.widget.FrameLayout;
|
||||||
|
|
||||||
|
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||||
|
|
||||||
|
@NotNullByDefault
|
||||||
|
public class PullDownLayout extends FrameLayout {
|
||||||
|
|
||||||
|
private final ViewDragHelper dragger;
|
||||||
|
|
||||||
|
private final int minimumFlingVelocity;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Callback callback;
|
||||||
|
|
||||||
|
public PullDownLayout(Context context) {
|
||||||
|
this(context, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PullDownLayout(Context context, @Nullable AttributeSet attrs) {
|
||||||
|
this(context, attrs, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PullDownLayout(Context context, @Nullable AttributeSet attrs,
|
||||||
|
int defStyleAttr) {
|
||||||
|
super(context, attrs, defStyleAttr);
|
||||||
|
dragger = ViewDragHelper.create(this, 1f / 8f, new ViewDragCallback());
|
||||||
|
minimumFlingVelocity =
|
||||||
|
ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCallback(@Nullable Callback callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onInterceptTouchEvent(MotionEvent ev) {
|
||||||
|
return dragger.shouldInterceptTouchEvent(ev);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onTouchEvent(MotionEvent event) {
|
||||||
|
dragger.processTouchEvent(event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void computeScroll() {
|
||||||
|
if (dragger.continueSettling(true)) {
|
||||||
|
ViewCompat.postInvalidateOnAnimation(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface Callback {
|
||||||
|
|
||||||
|
void onPullStart();
|
||||||
|
|
||||||
|
void onPull(float progress);
|
||||||
|
|
||||||
|
void onPullCancel();
|
||||||
|
|
||||||
|
void onPullComplete();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ViewDragCallback extends ViewDragHelper.Callback {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean tryCaptureView(View child, int pointerId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int clampViewPositionHorizontal(View child, int left, int dx) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int clampViewPositionVertical(View child, int top, int dy) {
|
||||||
|
return Math.max(0, top);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewHorizontalDragRange(View child) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getViewVerticalDragRange(View child) {
|
||||||
|
return getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCaptured(View capturedChild, int activePointerId) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onPullStart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewPositionChanged(View changedView, int left, int top,
|
||||||
|
int dx, int dy) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onPull((float) top / (float) getHeight());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewReleased(View releasedChild, float xvel, float yvel) {
|
||||||
|
int slop = yvel > minimumFlingVelocity ? getHeight() / 6 :
|
||||||
|
getHeight() / 3;
|
||||||
|
if (releasedChild.getTop() > slop) {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onPullComplete();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (callback != null) {
|
||||||
|
callback.onPullCancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
dragger.settleCapturedViewAt(0, 0);
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
60
briar-android/src/main/res/layout/activity_image.xml
Normal file
60
briar-android/src/main/res/layout/activity_image.xml
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<org.briarproject.briar.android.view.PullDownLayout
|
||||||
|
android:id="@+id/layout"
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@color/briar_black"
|
||||||
|
tools:context=".android.conversation.ImageActivity">
|
||||||
|
|
||||||
|
<com.github.chrisbanes.photoview.PhotoView
|
||||||
|
android:id="@+id/photoView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:ignore="ContentDescription"
|
||||||
|
tools:srcCompat="@tools:sample/backgrounds/scenic"/>
|
||||||
|
|
||||||
|
<android.support.design.widget.AppBarLayout
|
||||||
|
android:id="@+id/appBarLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/msg_status_bubble_background">
|
||||||
|
|
||||||
|
<android.support.v7.widget.Toolbar
|
||||||
|
android:id="@+id/toolbar"
|
||||||
|
style="@style/BriarToolbar"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:background="@color/msg_status_bubble_background"
|
||||||
|
android:fitsSystemWindows="true">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<com.vanniktech.emoji.EmojiTextView
|
||||||
|
android:id="@+id/contactName"
|
||||||
|
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:textColor="@color/action_bar_text"
|
||||||
|
tools:text="Contact Name of someone who chose a long name"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dateView"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/action_bar_text"
|
||||||
|
tools:text="date"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</android.support.v7.widget.Toolbar>
|
||||||
|
|
||||||
|
</android.support.design.widget.AppBarLayout>
|
||||||
|
|
||||||
|
</org.briarproject.briar.android.view.PullDownLayout>
|
||||||
@@ -134,6 +134,8 @@
|
|||||||
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
<string name="dialog_title_delete_contact">Confirm Contact Deletion</string>
|
||||||
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
|
<string name="dialog_message_delete_contact">Are you sure that you want to remove this contact and all messages exchanged with this contact?</string>
|
||||||
<string name="contact_deleted_toast">Contact deleted</string>
|
<string name="contact_deleted_toast">Contact deleted</string>
|
||||||
|
<!-- This is shown in the action bar when opening an image in fullscreen that the user sent -->
|
||||||
|
<string name="you">You</string>
|
||||||
|
|
||||||
<!-- Adding Contacts -->
|
<!-- Adding Contacts -->
|
||||||
<string name="add_contact_title">Add a Contact</string>
|
<string name="add_contact_title">Add a Contact</string>
|
||||||
|
|||||||
@@ -18,6 +18,12 @@
|
|||||||
<item name="toolbarStyle">@style/BriarToolbar</item>
|
<item name="toolbarStyle">@style/BriarToolbar</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
<style name="BriarTheme.Transparent.NoActionBar" parent="BriarTheme.NoActionBar">
|
||||||
|
<item name="android:windowBackground">@android:color/transparent</item>
|
||||||
|
<item name="android:windowIsTranslucent">true</item>
|
||||||
|
<item name="android:windowActionBarOverlay">true</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
<style name="ActivityAnimation" parent="@android:style/Animation.Activity">
|
<style name="ActivityAnimation" parent="@android:style/Animation.Activity">
|
||||||
<item name="android:activityOpenEnterAnimation">@anim/screen_new_in</item>
|
<item name="android:activityOpenEnterAnimation">@anim/screen_new_in</item>
|
||||||
<item name="android:activityOpenExitAnimation">@anim/screen_old_out</item>
|
<item name="android:activityOpenExitAnimation">@anim/screen_old_out</item>
|
||||||
|
|||||||
@@ -89,6 +89,7 @@ dependencyVerification {
|
|||||||
'com.github.bumptech.glide:compiler:4.8.0:compiler-4.8.0.jar:1fa93dd0cf7ef0b8b98a59a67a1ee84915416c2d677d83a771ea3e32ad15e6bf',
|
'com.github.bumptech.glide:compiler:4.8.0:compiler-4.8.0.jar:1fa93dd0cf7ef0b8b98a59a67a1ee84915416c2d677d83a771ea3e32ad15e6bf',
|
||||||
'com.github.bumptech.glide:gifdecoder:4.8.0:gifdecoder-4.8.0.aar:b00c5454a023a9488ea49603930d9c25e09192e5ceaadf64977aa52946b3c1b4',
|
'com.github.bumptech.glide:gifdecoder:4.8.0:gifdecoder-4.8.0.aar:b00c5454a023a9488ea49603930d9c25e09192e5ceaadf64977aa52946b3c1b4',
|
||||||
'com.github.bumptech.glide:glide:4.8.0:glide-4.8.0.aar:5ddf08b12cc43332e812988f16c2c39e7fce49d1c4d94b7948dcde7f00bf49d6',
|
'com.github.bumptech.glide:glide:4.8.0:glide-4.8.0.aar:5ddf08b12cc43332e812988f16c2c39e7fce49d1c4d94b7948dcde7f00bf49d6',
|
||||||
|
'com.github.chrisbanes:PhotoView:2.1.4:PhotoView-2.1.4.aar:04cb397fcb3df0757c8aed6927ebdd247930b5c78ee9acc59cd07dccdaaf3460',
|
||||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
|
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.0:accessibility-test-framework-2.0.jar:cdf16ef8f5b8023d003ce3cc1b0d51bda737762e2dab2fedf43d1c4292353f7f',
|
||||||
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
|
'com.google.android.apps.common.testing.accessibility.framework:accessibility-test-framework:2.1:accessibility-test-framework-2.1.jar:7b0aa6ed7553597ce0610684a9f7eca8021eee218f2e2f427c04a7fbf5f920bd',
|
||||||
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
|
'com.google.code.findbugs:jsr305:1.3.9:jsr305-1.3.9.jar:905721a0eea90a81534abb7ee6ef4ea2e5e645fa1def0a5cd88402df1b46c9ed',
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ allprojects {
|
|||||||
jcenter()
|
jcenter()
|
||||||
mavenLocal()
|
mavenLocal()
|
||||||
google()
|
google()
|
||||||
|
maven { url "https://jitpack.io" }
|
||||||
}
|
}
|
||||||
afterEvaluate {
|
afterEvaluate {
|
||||||
tasks.withType(Test) {
|
tasks.withType(Test) {
|
||||||
|
|||||||
Reference in New Issue
Block a user