fix taking photos/recording videos from camera for Android 11 [Christian Schneppe]

This commit is contained in:
12aw 2022-03-19 18:38:39 +01:00
parent a8857bec68
commit 46792bdfb8
13 changed files with 1046 additions and 12 deletions

View file

@ -89,6 +89,7 @@ dependencies {
implementation 'com.github.AppIntro:AppIntro:6.1.0'
implementation 'androidx.browser:browser:1.3.0' // 1.4.0 needs minCompileSdk 31
implementation 'com.otaliastudios:transcoder:0.9.1' // 0.10.4 seems to be buggy
implementation project(':libs:AXML')
implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs')
}

1
libs/AXML/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/build

31
libs/AXML/build.gradle Normal file
View file

@ -0,0 +1,31 @@
apply plugin: 'java'
group = 'fr.xgouchet'
dependencies {
// TODO UNIT TESTS
}
// Additional tasks for jitpack.io
// build a jar with source files
task sourcesJar(type: Jar) {
from sourceSets.main.java.srcDirs
archiveClassifier.set("sources")
}
// build a jar with javadoc
task javadocJar(type: Jar, dependsOn: javadoc) {
archiveClassifier.set("javadoc")
from javadoc.destinationDir
}
artifacts {
archives sourcesJar
archives javadocJar
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}

17
libs/AXML/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /opt/android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View file

@ -0,0 +1,66 @@
package fr.xgouchet.axml;
public class Attribute {
/**
* @return the name
*/
public String getName() {
return mName;
}
/**
* @return the prefix
*/
public String getPrefix() {
return mPrefix;
}
/**
* @return the namespace
*/
public String getNamespace() {
return mNamespace;
}
/**
* @return the value
*/
public String getValue() {
return mValue;
}
/**
* @param name
* the name to set
*/
public void setName(final String name) {
mName = name;
}
/**
* @param prefix
* the prefix to set
*/
public void setPrefix(final String prefix) {
mPrefix = prefix;
}
/**
* @param namespace
* the namespace to set
*/
public void setNamespace(final String namespace) {
mNamespace = namespace;
}
/**
* @param value
* the value to set
*/
public void setValue(final String value) {
mValue = value;
}
private String mName, mPrefix, mNamespace, mValue;
}

View file

@ -0,0 +1,92 @@
package fr.xgouchet.axml;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.Stack;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
public class CompressedXmlDomListener implements CompressedXmlParserListener {
/**
* @throws ParserConfigurationException if a DocumentBuilder can't be created
*/
public CompressedXmlDomListener() throws ParserConfigurationException {
mBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
mStack = new Stack<>();
}
public void startDocument() {
mDocument = mBuilder.newDocument();
mStack.push(mDocument);
}
public void endDocument() {
}
public void startPrefixMapping(String prefix, String uri) {
}
public void endPrefixMapping(String prefix, String uri) {
}
public void startElement(final String uri, final String localName,
final String qName, final Attribute[] attrs) {
Element elt;
// create elt
if (isEmpty(uri)) {
elt = mDocument.createElement(localName);
} else {
elt = mDocument.createElementNS(uri, qName);
}
// add attrs
for (Attribute attr : attrs) {
if (isEmpty(attr.getNamespace())) {
elt.setAttribute(attr.getName(), attr.getValue());
} else {
elt.setAttributeNS(attr.getNamespace(), attr.getPrefix() + ':'
+ attr.getName(), attr.getValue());
}
}
// handle stack
mStack.peek().appendChild(elt);
mStack.push(elt);
}
public void endElement(String uri, String localName, String qName) {
mStack.pop();
}
public void characterData(String data) {
mStack.peek().appendChild(mDocument.createCDATASection(data));
}
public void text(String data) {
mStack.peek().appendChild(mDocument.createTextNode(data));
}
public void processingInstruction(String target, String data) {
}
/**
* @return the parsed document
*/
public Document getDocument() {
return mDocument;
}
private static boolean isEmpty(String text) {
return (text == null) || "".equals(text);
}
private Stack<Node> mStack;
private Document mDocument;
private final DocumentBuilder mBuilder;
}

View file

@ -0,0 +1,515 @@
package fr.xgouchet.axml;
import org.w3c.dom.Document;
import java.io.IOException;
import java.io.InputStream;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.ParserConfigurationException;
public class CompressedXmlParser {
public static final String TAG = "CXP";
public static final int WORD_START_DOCUMENT = 0x00080003;
public static final int WORD_STRING_TABLE = 0x001C0001;
public static final int WORD_RES_TABLE = 0x00080180;
public static final int WORD_START_NS = 0x00100100;
public static final int WORD_END_NS = 0x00100101;
public static final int WORD_START_TAG = 0x00100102;
public static final int WORD_END_TAG = 0x00100103;
public static final int WORD_TEXT = 0x00100104;
public static final int WORD_EOS = 0xFFFFFFFF;
public static final int WORD_SIZE = 4;
private static final int TYPE_ID_REF = 0x01000008;
private static final int TYPE_ATTR_REF = 0x02000008;
private static final int TYPE_STRING = 0x03000008;
private static final int TYPE_DIMEN = 0x05000008;
private static final int TYPE_FRACTION = 0x06000008;
private static final int TYPE_INT = 0x10000008;
private static final int TYPE_FLOAT = 0x04000008;
private static final int TYPE_FLAGS = 0x11000008;
private static final int TYPE_BOOL = 0x12000008;
private static final int TYPE_COLOR = 0x1C000008;
private static final int TYPE_COLOR2 = 0x1D000008;
private static final String[] DIMEN = new String[]{"px", "dp", "sp",
"pt", "in", "mm"};
public CompressedXmlParser() {
mNamespaces = new HashMap<>();
}
/**
* Parses the xml data in the given file,
*
* @param input the source input to parse
* @param listener the listener for XML events (must not be null)
* @throws IOException if the input can't be read
*/
public void parse(final InputStream input,
final CompressedXmlParserListener listener) throws IOException {
if (listener == null) {
throw new IllegalArgumentException(
"CompressedXmlParser Listener can' be null");
}
mListener = listener;
// TODO is.available may not be accurate !!!
mData = new byte[input.available()];
input.read(mData);
input.close();
// parseCompressedHeader();
parseCompressedXml();
}
/**
* Parses the xml data in the given file,
*
* @param input the source file to parse
* @return the DOM document object
* @throws IOException if the input can't be read
* @throws ParserConfigurationException if a DocumentBuilder can't be created
*/
public Document parseDOM(final InputStream input) throws IOException,
ParserConfigurationException {
CompressedXmlDomListener dom = new CompressedXmlDomListener();
parse(input, dom);
return dom.getDocument();
}
/**
* Each tag starts with a 32 bits word (different for start tag, end tag and
* end doc)
*/
private void parseCompressedXml() {
int word0;
while (mParserOffset < mData.length) {
word0 = getLEWord(mParserOffset);
switch (word0) {
case WORD_START_DOCUMENT:
parseStartDocument();
break;
case WORD_STRING_TABLE:
parseStringTable();
break;
case WORD_RES_TABLE:
parseResourceTable();
break;
case WORD_START_NS:
parseNamespace(true);
break;
case WORD_END_NS:
parseNamespace(false);
break;
case WORD_START_TAG:
parseStartTag();
break;
case WORD_END_TAG:
parseEndTag();
break;
case WORD_TEXT:
parseText();
break;
case WORD_EOS:
mListener.endDocument();
break;
default:
mParserOffset += WORD_SIZE;
// Log.w(TAG, "Unknown word 0x" + Integer.toHexString(word0)
// + " @" + mParserOffset);
break;
}
}
mListener.endDocument();
}
/**
* A doc starts with the following 4bytes words :
* <ul>
* <li>0th word : 0x00080003</li>
* <li>1st word : chunk size</li>
* </ul>
*/
private void parseStartDocument() {
mListener.startDocument();
mParserOffset += (2 * WORD_SIZE);
}
/**
* the string table starts with the following 4bytes words :
* <ul>
* <li>0th word : 0x1c0001</li>
* <li>1st word : chunk size</li>
* <li>2nd word : number of string in the string table</li>
* <li>3rd word : number of styles in the string table</li>
* <li>4th word : flags - sorted/utf8 flag (0)</li>
* <li>5th word : Offset to String data</li>
* <li>6th word : Offset to style data</li>
* </ul>
*/
private void parseStringTable() {
int chunk = getLEWord(mParserOffset + (1 * WORD_SIZE));
mStringsCount = getLEWord(mParserOffset + (2 * WORD_SIZE));
mStylesCount = getLEWord(mParserOffset + (3 * WORD_SIZE));
int strOffset = mParserOffset
+ getLEWord(mParserOffset + (5 * WORD_SIZE));
int styleOffset = getLEWord(mParserOffset + (6 * WORD_SIZE));
mStringsTable = new String[mStringsCount];
int offset;
for (int i = 0; i < mStringsCount; ++i) {
offset = strOffset
+ getLEWord(mParserOffset + ((i + 7) * WORD_SIZE));
mStringsTable[i] = getStringFromStringTable(offset);
}
if (styleOffset > 0) {
// Log.w(TAG, "Unread styles");
for (int i = 0; i < mStylesCount; ++i) {
// TODO read the styles ???
}
}
mParserOffset += chunk;
}
/**
* the resource ids table starts with the following 4bytes words :
* <ul>
* <li>0th word : 0x00080180</li>
* <li>1st word : chunk size</li>
* </ul>
*/
private void parseResourceTable() {
int chunk = getLEWord(mParserOffset + (1 * WORD_SIZE));
mResCount = (chunk / 4) - 2;
mResourcesIds = new int[mResCount];
for (int i = 0; i < mResCount; ++i) {
mResourcesIds[i] = getLEWord(mParserOffset + ((i + 2) * WORD_SIZE));
}
mParserOffset += chunk;
}
/**
* A namespace tag contains the following 4bytes words :
* <ul>
* <li>0th word : 0x00100100 = Start NS / 0x00100101 = end NS</li>
* <li>1st word : chunk size</li>
* <li>2nd word : line this tag appeared</li>
* <li>3rd word : optional xml comment for element (usually 0xFFFFFF)</li>
* <li>4th word : index of namespace prefix in StringIndexTable</li>
* <li>5th word : index of namespace uri in StringIndexTable</li>
* </ul>
*/
private void parseNamespace(boolean start) {
final int prefixIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
final int uriIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
final String uri = getString(uriIdx);
final String prefix = getString(prefixIdx);
if (start) {
mListener.startPrefixMapping(prefix, uri);
mNamespaces.put(uri, prefix);
} else {
mListener.endPrefixMapping(prefix, uri);
mNamespaces.remove(uri);
}
// Offset to first tag
mParserOffset += (6 * WORD_SIZE);
}
/**
* A start tag will start with the following 4bytes words :
* <ul>
* <li>0th word : 0x00100102 = Start_Tag</li>
* <li>1st word : chunk size</li>
* <li>2nd word : line this tag appeared in the original file</li>
* <li>3rd word : optional xml comment for element (usually 0xFFFFFF)</li>
* <li>4th word : index of namespace uri in StringIndexTable, or 0xFFFFFFFF
* for default NS</li>
* <li>5th word : index of element name in StringIndexTable</li>
* <li>6th word : size of attribute structures to follow</li>
* <li>7th word : number of attributes following the start tag</li>
* <li>8th word : index of id attribute (0 if none)</li>
* </ul>
*/
private void parseStartTag() {
// get tag info
final int uriIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
final int nameIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
final int attrCount = getLEShort(mParserOffset + (7 * WORD_SIZE));
final String name = getString(nameIdx);
String uri, qname;
if (uriIdx == 0xFFFFFFFF) {
uri = "";
qname = name;
} else {
uri = getString(uriIdx);
if (mNamespaces.containsKey(uri)) {
qname = mNamespaces.get(uri) + ':' + name;
} else {
qname = name;
}
}
// offset to start of attributes
mParserOffset += (9 * WORD_SIZE);
final Attribute[] attrs = new Attribute[attrCount]; // NOPMD
for (int a = 0; a < attrCount; a++) {
attrs[a] = parseAttribute(); // NOPMD
// offset to next attribute or tag
mParserOffset += (5 * 4);
}
mListener.startElement(uri, name, qname, attrs);
}
/**
* An attribute will have the following 4bytes words :
* <ul>
* <li>0th word : index of namespace uri in StringIndexTable, or 0xFFFFFFFF
* for default NS</li>
* <li>1st word : index of attribute name in StringIndexTable</li>
* <li>2nd word : index of attribute value, or 0xFFFFFFFF if value is a
* typed value</li>
* <li>3rd word : value type</li>
* <li>4th word : resource id value</li>
* </ul>
*/
private Attribute parseAttribute() {
final int attrNSIdx = getLEWord(mParserOffset);
final int attrNameIdx = getLEWord(mParserOffset + (1 * WORD_SIZE));
final int attrValueIdx = getLEWord(mParserOffset + (2 * WORD_SIZE));
final int attrType = getLEWord(mParserOffset + (3 * WORD_SIZE));
final int attrData = getLEWord(mParserOffset + (4 * WORD_SIZE));
final Attribute attr = new Attribute();
attr.setName(getString(attrNameIdx));
if (attrNSIdx == 0xFFFFFFFF) {
attr.setNamespace(null);
attr.setPrefix(null);
} else {
String uri = getString(attrNSIdx);
if (mNamespaces.containsKey(uri)) {
attr.setNamespace(uri);
attr.setPrefix(mNamespaces.get(uri));
}
}
if (attrValueIdx == 0xFFFFFFFF) {
attr.setValue(getAttributeValue(attrType, attrData));
} else {
attr.setValue(getString(attrValueIdx));
}
return attr;
}
/**
* A text will start with the following 4bytes word :
* <ul>
* <li>0th word : 0x00100104 = Text</li>
* <li>1st word : chunk size</li>
* <li>2nd word : line this element appeared in the original document</li>
* <li>3rd word : optional xml comment for element (usually 0xFFFFFF)</li>
* <li>4rd word : string index in string table</li>
* <li>5rd word : ??? (always 8)</li>
* <li>6rd word : ??? (always 0)</li>
* </ul>
*/
private void parseText() {
// get tag infos
final int strIndex = getLEWord(mParserOffset + (4 * WORD_SIZE));
String data = getString(strIndex);
mListener.characterData(data);
// offset to next node
mParserOffset += (7 * WORD_SIZE);
}
/**
* EndTag contains the following 4bytes words :
* <ul>
* <li>0th word : 0x00100103 = End_Tag</li>
* <li>1st word : chunk size</li>
* <li>2nd word : line this tag appeared in the original file</li>
* <li>3rd word : optional xml comment for element (usually 0xFFFFFF)</li>
* <li>4th word : index of namespace name in StringIndexTable, or 0xFFFFFFFF
* for default NS</li>
* <li>5th word : index of element name in StringIndexTable</li>
* </ul>
*/
private void parseEndTag() {
// get tag info
final int uriIdx = getLEWord(mParserOffset + (4 * WORD_SIZE));
final int nameIdx = getLEWord(mParserOffset + (5 * WORD_SIZE));
final String name = getString(nameIdx);
String uri;
if (uriIdx == 0xFFFFFFFF) {
uri = "";
} else {
uri = getString(uriIdx);
}
mListener.endElement(uri, name, null);
// offset to start of next tag
mParserOffset += (6 * WORD_SIZE);
}
/**
* @param index the index of the string in the StringIndexTable
* @return the string
*/
private String getString(final int index) {
String res;
if ((index >= 0) && (index < mStringsCount)) {
res = mStringsTable[index];
} else {
res = null; // NOPMD
}
return res;
}
/**
* @param offset offset of the beginning of the string inside the StringTable
* (and not the whole data array)
* @return the String
*/
private String getStringFromStringTable(final int offset) {
int strLength;
byte chars[];
if (mData[offset + 1] == mData[offset]) {
strLength = mData[offset];
chars = new byte[strLength];// NOPMD
for (int i = 0; i < strLength; i++) {
chars[i] = mData[offset + 2 + i]; // NOPMD
}
} else {
strLength = ((mData[offset + 1] << 8) & 0xFF00)
| (mData[offset] & 0xFF);
chars = new byte[strLength]; // NOPMD
for (int i = 0; i < strLength; i++) {
chars[i] = mData[offset + 2 + (i * 2)]; // NOPMD
}
}
return new String(chars);
}
/**
* @param off the offset of the word to read
* @return value of a Little Endian 32 bit word from the byte arrayat offset
* off.
*/
private int getLEWord(final int off) {
return ((mData[off + 3] << 24) & 0xff000000)
| ((mData[off + 2] << 16) & 0x00ff0000)
| ((mData[off + 1] << 8) & 0x0000ff00)
| ((mData[off + 0] << 0) & 0x000000ff);
}
/**
* @param off the offset of the word to read
* @return value of a Little Endian 16 bit word from the byte array at offset
* off.
*/
private int getLEShort(final int off) {
return ((mData[off + 1] << 8) & 0xff00)
| ((mData[off + 0] << 0) & 0x00ff);
}
/**
* @param type the attribute type
* @param data the data value
* @return the typed value
*/
private String getAttributeValue(final int type, final int data) {
String res;
switch (type) {
case TYPE_STRING:
res = getString(data);
break;
case TYPE_DIMEN:
res = Integer.toString(data >> 8) + DIMEN[data & 0xFF];
break;
case TYPE_FRACTION:
double fracValue = (((double) data) / ((double) 0x7FFFFFFF));
// res = String.format("%.2f%%", fracValue);
res = new DecimalFormat("#.##%").format(fracValue);
break;
case TYPE_FLOAT:
res = Float.toString(Float.intBitsToFloat(data));
break;
case TYPE_INT:
case TYPE_FLAGS:
res = Integer.toString(data);
break;
case TYPE_BOOL:
res = Boolean.toString(data != 0);
break;
case TYPE_COLOR:
case TYPE_COLOR2:
res = String.format("#%08X", data);
break;
case TYPE_ID_REF:
res = String.format("@id/0x%08X", data);
break;
case TYPE_ATTR_REF:
res = String.format("?id/0x%08X", data);
break;
default:
// Log.w(TAG, "(type=" + Integer.toHexString(type) + ") : " + data
// + " (0x" + Integer.toHexString(data) + ") @"
// + mParserOffset);
res = String.format("%08X/0x%08X", type, data);
break;
}
return res;
}
// Data
private CompressedXmlParserListener mListener;
// Internal
private Map<String, String> mNamespaces;
private byte[] mData;
private String[] mStringsTable;
private int[] mResourcesIds;
private int mStringsCount, mStylesCount, mResCount;
private int mParserOffset;
}

View file

@ -0,0 +1,101 @@
package fr.xgouchet.axml;
public interface CompressedXmlParserListener {
/**
* Receive notification of the beginning of a document.
*/
void startDocument();
/**
* Receive notification of the end of a document.
*/
void endDocument();
/**
* Begin the scope of a prefix-URI Namespace mapping.
*
* @param prefix
* the Namespace prefix being declared. An empty string is used
* for the default element namespace, which has no prefix.
* @param uri
* the Namespace URI the prefix is mapped to
*/
void startPrefixMapping(String prefix, String uri);
/**
* End the scope of a prefix-URI mapping.
*
* @param prefix
* the prefix that was being mapped. This is the empty string
* when a default mapping scope ends.
* @param uri
* the Namespace URI the prefix is mapped to
*/
void endPrefixMapping(String prefix, String uri);
/**
* Receive notification of the beginning of an element.
*
* @param uri
* the Namespace URI, or the empty string if the element has no
* Namespace URI or if Namespace processing is not being
* performed
* @param localName
* the local name (without prefix), or the empty string if
* Namespace processing is not being performed
* @param qName
* the qualified name (with prefix), or the empty string if
* qualified names are not available
* @param atts
* the attributes attached to the element. If there are no
* attributes, it shall be an empty Attributes object. The value
* of this object after startElement returns is undefined
*/
void startElement(String uri, String localName, String qName,
Attribute[] atts);
/**
* Receive notification of the end of an element.
*
* @param uri
* the Namespace URI, or the empty string if the element has no
* Namespace URI or if Namespace processing is not being
* performed
* @param localName
* the local name (without prefix), or the empty string if
* Namespace processing is not being performed
* @param qName
* the qualified XML name (with prefix), or the empty string if
* qualified names are not available
*/
void endElement(String uri, String localName, String qName);
/**
* Receive notification of text.
*
* @param data
* the text data
*/
void text(String data);
/**
* Receive notification of character data (in a &lt;![CDATA[ ]]&gt; block).
*
* @param data
* the text data
*/
void characterData(String data);
/**
* Receive notification of a processing instruction.
*
* @param target
* the processing instruction target
* @param data
* the processing instruction data, or null if none was supplied.
* The data does not include any whitespace separating it from
* the target
*/
void processingInstruction(String target, String data);
}

View file

@ -0,0 +1,60 @@
package fr.xgouchet.axml;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
public final class CompressedXmlUtils {
/**
* @param input an input stream
* @return if the given input stream looks like an AXML document
*/
public static boolean isCompressedXml(final InputStream input) {
if (input == null) return false;
boolean result;
try {
final byte[] header = new byte[4];
int read = input.read(header, 0, 4);
if (read < 4) return false;
result = (header[0] == 0x03)
&& (header[1] == 0x00)
&& (header[2] == 0x08)
&& (header[3] == 0x00);
} catch (Exception e) {
result = false;
} finally {
try {
input.close();
} catch (Exception e) {
// ignore this exception
}
}
return result;
}
/**
* @param source a source file
* @return if the given file looks like an AXML file
*/
public static boolean isCompressedXml(final File source) {
boolean result;
try {
final InputStream input = new FileInputStream(source.getPath());
result = isCompressedXml(input);
} catch (Exception e) {
result = false;
}
return result;
}
private CompressedXmlUtils() {
}
}

View file

@ -1,3 +1,3 @@
include ':libs:android-transcoder'
include ':libs:AXML'
include ':libs:xmpp-addr'
rootProject.name = 'monocles chat'

View file

@ -122,19 +122,19 @@ public class AbstractConnectionManager {
final long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile);
final long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming);
long config = 0;
String config = "0";
if (mXmppConnectionService.isWIFI()) {
config = this.mXmppConnectionService.getPreferences().getLong(
"auto_accept_file_size_wifi", defaultValue_wifi);
config = this.mXmppConnectionService.getPreferences().getString(
"auto_accept_file_size_wifi", String.valueOf(defaultValue_wifi));
} else if (mXmppConnectionService.isMobile()) {
config = this.mXmppConnectionService.getPreferences().getLong(
"auto_accept_file_size_mobile", defaultValue_mobile);
config = this.mXmppConnectionService.getPreferences().getString(
"auto_accept_file_size_mobile", String.valueOf(defaultValue_mobile));
} else if (mXmppConnectionService.isMobileRoaming()) {
config = this.mXmppConnectionService.getPreferences().getLong(
"auto_accept_file_size_roaming", defaultValue_roaming);
config = this.mXmppConnectionService.getPreferences().getString(
"auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming));
}
try {
return config <= 0 ? -1 : config;
return Long.parseLong(config) <= 0 ? -1 : Long.parseLong(config);
} catch (NumberFormatException e) {
return defaultValue_mobile;
}

View file

@ -22,6 +22,7 @@ import android.app.Fragment;
import android.app.FragmentManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -143,6 +144,7 @@ import eu.siacs.conversations.ui.util.ShareUtil;
import eu.siacs.conversations.ui.util.StyledAttributes;
import eu.siacs.conversations.ui.util.ViewUtil;
import eu.siacs.conversations.ui.widget.EditMessage;
import eu.siacs.conversations.utils.CameraUtils;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MenuDoubleTabUtil;
@ -2077,12 +2079,30 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
intent.setAction(Intent.ACTION_GET_CONTENT);
break;
case ATTACHMENT_CHOICE_RECORD_VIDEO:
if (Compatibility.runsThirty()) {
final List<CameraUtils> cameraApps = CameraUtils.getCameraApps(activity);
if (cameraApps.size() == 0) {
ToastCompat.makeText(activity, R.string.no_application_found, ToastCompat.LENGTH_LONG).show();
} else {
final ComponentName correctComponent = cameraApps.get(0).componentNames.get(0);
intent.setComponent(correctComponent);
}
}
intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE);
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
final Uri uri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri();
pendingTakePhotoUri.push(uri);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
final Uri photoUri = activity.xmppConnectionService.getFileBackend().getTakePhotoUri();
pendingTakePhotoUri.push(photoUri);
if (Compatibility.runsThirty()) {
final List<CameraUtils> cameraApps = CameraUtils.getCameraApps(activity);
if (cameraApps.size() == 0) {
ToastCompat.makeText(activity, R.string.no_application_found, ToastCompat.LENGTH_LONG).show();
} else {
final ComponentName correctComponent = cameraApps.get(0).componentNames.get(0);
intent.setComponent(correctComponent);
}
}
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
intent.setFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION & Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
break;

View file

@ -0,0 +1,130 @@
package eu.siacs.conversations.utils;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.provider.MediaStore;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import fr.xgouchet.axml.CompressedXmlParser;
public class CameraUtils {
public PackageInfo packageInfo;
public List<ComponentName> componentNames;
public CameraUtils(PackageInfo packageInfo, List<ComponentName> componentNames) {
this.packageInfo = packageInfo;
this.componentNames = componentNames;
}
public static List<CameraUtils> getCameraApps(Context context) {
//Step 1 - Get apps with Camera permission
List<PackageInfo> cameraPermissionPackages = getPackageInfosWithCameraPermission(context);
//Step 2 - Filter out apps with the correct intent-filter(s)
List<CameraUtils> cameraApps = new ArrayList<CameraUtils>();
for (PackageInfo somePackage : cameraPermissionPackages) {
try {
//Step 2a - Get the AndroidManifest.xml
Document doc = readAndroidManifestFromPackageInfo(somePackage);
//Step 2b - Get Camera ComponentNames from Manifest
List<ComponentName> componentNames = getCameraComponentNamesFromDocument(doc);
if (componentNames.size() == 0) {
continue; //This is not a Camera app
}
//Step 2c - Create CameraAppModel
CameraUtils cameraApp = new CameraUtils(somePackage, componentNames);
cameraApps.add(cameraApp);
} catch (Exception e) {
//ignore
}
}
return cameraApps;
}
public static List<PackageInfo> getPackageInfosWithCameraPermission(Context context) {
//Get a list of compatible apps
PackageManager pm = context.getPackageManager();
List<PackageInfo> installedPackages = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
ArrayList<PackageInfo> cameraPermissionPackages = new ArrayList<PackageInfo>();
//filter out only camera apps
for (PackageInfo somePackage : installedPackages) {
//- A camera app should have the Camera permission
boolean hasCameraPermission = false;
if (somePackage.requestedPermissions == null || somePackage.requestedPermissions.length == 0) {
continue;
}
for (String requestPermission : somePackage.requestedPermissions) {
if (requestPermission.equals(Manifest.permission.CAMERA)) {
//Ask for Camera permission, now see if it's granted.
if (pm.checkPermission(Manifest.permission.CAMERA, somePackage.packageName) == PackageManager.PERMISSION_GRANTED) {
hasCameraPermission = true;
break;
}
}
}
if (hasCameraPermission) {
cameraPermissionPackages.add(somePackage);
}
}
return cameraPermissionPackages;
}
public static Document readAndroidManifestFromPackageInfo(PackageInfo packageInfo) throws IOException {
File apkFile = new File(packageInfo.applicationInfo.publicSourceDir);
//Get AndroidManifest.xml from APK
ZipFile apkZipFile = new ZipFile(apkFile, ZipFile.OPEN_READ);
ZipEntry manifestEntry = apkZipFile.getEntry("AndroidManifest.xml");
InputStream manifestInputStream = apkZipFile.getInputStream(manifestEntry);
try {
return new CompressedXmlParser().parseDOM(manifestInputStream);
} catch (Exception e) {
throw new IOException("Error reading AndroidManifest", e);
}
}
public static List<ComponentName> getCameraComponentNamesFromDocument(Document doc) {
@SuppressLint("InlinedApi")
String[] correctActions = {MediaStore.ACTION_IMAGE_CAPTURE, MediaStore.ACTION_IMAGE_CAPTURE_SECURE, MediaStore.ACTION_VIDEO_CAPTURE};
ArrayList<ComponentName> componentNames = new ArrayList<ComponentName>();
Element manifestElement = (Element) doc.getElementsByTagName("manifest").item(0);
String packageName = manifestElement.getAttribute("package");
Element applicationElement = (Element) manifestElement.getElementsByTagName("application").item(0);
NodeList activities = applicationElement.getElementsByTagName("activity");
for (int i = 0; i < activities.getLength(); i++) {
Element activityElement = (Element) activities.item(i);
String activityName = activityElement.getAttribute("android:name");
NodeList intentFiltersList = activityElement.getElementsByTagName("intent-filter");
for (int j = 0; j < intentFiltersList.getLength(); j++) {
Element intentFilterElement = (Element) intentFiltersList.item(j);
NodeList actionsList = intentFilterElement.getElementsByTagName("action");
for (int k = 0; k < actionsList.getLength(); k++) {
Element actionElement = (Element) actionsList.item(k);
String actionName = actionElement.getAttribute("android:name");
for (String correctAction : correctActions) {
if (actionName.equals(correctAction)) {
//this activity has an intent filter with a correct action, add this to the list.
componentNames.add(new ComponentName(packageName, activityName));
}
}
}
}
}
return componentNames;
}
}