removed OTR (for now..), cherry picked blabber.im updates (Christan Schneppe, Daniel Gultsch)
This commit is contained in:
parent
792e58c570
commit
eca64c9362
315 changed files with 31885 additions and 4561 deletions
|
@ -8,7 +8,7 @@ jobs:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: gradle-{{ checksum "build.gradle" }}-{{ checksum ".circleci/config.yml" }}
|
key: gradle-{{ checksum "build.gradle" }}-{{ checksum ".circleci/config.yml" }}
|
||||||
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- run: wget -O libs/libwebrtc-m90.aar https://github.com/robjperez/libwebrtc_maven/raw/master/libwebrtc.aar
|
- run: wget -O libs/libwebrtc-m85.aar https://www.pix-art.de/files/libwebrtc-m85.aar
|
||||||
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
||||||
- run: ./gradlew lintGitDebug
|
- run: ./gradlew lintGitDebug
|
||||||
- save_cache:
|
- save_cache:
|
||||||
|
@ -27,7 +27,7 @@ jobs:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: android
|
key: android
|
||||||
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- run: wget -O libs/libwebrtc-m90.aar https://github.com/robjperez/libwebrtc_maven/raw/master/libwebrtc.aar
|
- run: wget -O libs/libwebrtc-m85.aar https://www.pix-art.de/files/libwebrtc-m85.aar
|
||||||
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
||||||
# build
|
# build
|
||||||
- run: ./gradlew assembleGit
|
- run: ./gradlew assembleGit
|
||||||
|
@ -46,10 +46,10 @@ jobs:
|
||||||
- restore_cache:
|
- restore_cache:
|
||||||
key: android
|
key: android
|
||||||
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
- run: export GRADLE_USER_HOME=$PWD/.gradle
|
||||||
- run: wget -O libs/libwebrtc-m90.aar https://github.com/robjperez/libwebrtc_maven/raw/master/libwebrtc.aar
|
- run: wget -O libs/libwebrtc-m85.aar https://www.pix-art.de/files/libwebrtc-m85.aar
|
||||||
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
|
||||||
# workaround for fdroid nightly circleci bug
|
# workaround for fdroid nightly circleci bug
|
||||||
- run: sed -i "s/os.getenv('CIRCLE_REPOSITORY_URL')/\"https:\/\/github.com\/kriztan\/Monocles-Messenger\"/" /usr/lib/python3/dist-packages/fdroidserver/nightly.py
|
- run: sed -i "s/os.getenv('CIRCLE_REPOSITORY_URL')/\"https:\/\/github.com\/kriztan\/Pix-Art-Messenger\"/" /usr/lib/python3/dist-packages/fdroidserver/nightly.py
|
||||||
# generate version number
|
# generate version number
|
||||||
- run: sed -i "s/^\(\s*versionCode\s*\).*$/\1$(git rev-list --first-parent --count HEAD)/" build.gradle
|
- run: sed -i "s/^\(\s*versionCode\s*\).*$/\1$(git rev-list --first-parent --count HEAD)/" build.gradle
|
||||||
- run: sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$(printf '%s-%05d' $(git describe --tag --abbrev=0) $(git rev-list --first-parent --count HEAD))\"/" build.gradle
|
- run: sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$(printf '%s-%05d' $(git describe --tag --abbrev=0) $(git rev-list --first-parent --count HEAD))\"/" build.gradle
|
||||||
|
|
34
.github/workflows/android.yml
vendored
Normal file
34
.github/workflows/android.yml
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
name: Android CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: set up JDK 11
|
||||||
|
uses: actions/setup-java@v2
|
||||||
|
with:
|
||||||
|
java-version: '11'
|
||||||
|
distribution: 'adopt'
|
||||||
|
- name: Download WebRTC
|
||||||
|
run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar
|
||||||
|
- name: Grant execute permission for gradlew
|
||||||
|
run: chmod +x gradlew
|
||||||
|
- name: Build Quicksy (Compat)
|
||||||
|
run: ./gradlew assembleQuicksyFreeCompatDebug
|
||||||
|
- name: Build Quicksy (System)
|
||||||
|
run: ./gradlew assembleQuicksyFreeSystemDebug
|
||||||
|
- name: Build Conversations (Compat)
|
||||||
|
run: ./gradlew assembleConversationsFreeCompatDebug
|
||||||
|
- name: Build Conversations (System)
|
||||||
|
run: ./gradlew assembleConversationsFreeSystemDebug
|
||||||
|
|
||||||
|
|
59
build.gradle
59
build.gradle
|
@ -15,19 +15,18 @@ apply plugin: 'com.android.application'
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url "https://github.com/robjperez/libwebrtc_maven/raw/master/libwebrtc.aar" }
|
maven { url "https://raw.github.com/abdularis/libwebrtc-android/repo/" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
jcenter()
|
|
||||||
maven { url "https://jitpack.io" }
|
maven { url "https://jitpack.io" }
|
||||||
maven { url "https://github.com/robjperez/libwebrtc_maven/raw/master/libwebrtc.aar" }
|
maven { url "https://raw.github.com/abdularis/libwebrtc-android/repo/" }
|
||||||
|
jcenter()
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
|
@ -37,9 +36,9 @@ configurations {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'org.webrtc:google-webrtc:1.+'
|
implementation 'com.github.webrtc-sdk:android:93.4577.01'
|
||||||
implementation project(':libs:android-transcoder')
|
implementation project(':libs:android-transcoder')
|
||||||
playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') { ///higher versions are causing crashes due to missing project IDs
|
playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-core'
|
exclude group: 'com.google.firebase', module: 'firebase-core'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
|
@ -51,7 +50,6 @@ dependencies {
|
||||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||||
exclude group: 'com.android.support', module: 'exifinterface'
|
exclude group: 'com.android.support', module: 'exifinterface'
|
||||||
}
|
}
|
||||||
implementation 'org.jitsi:org.otr4j:0.23'
|
|
||||||
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
|
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
|
||||||
implementation 'org.gnu.inet:libidn:1.15'
|
implementation 'org.gnu.inet:libidn:1.15'
|
||||||
implementation 'com.google.zxing:core:3.3.3' // > 3.3.x not working below SDK 24
|
implementation 'com.google.zxing:core:3.3.3' // > 3.3.x not working below SDK 24
|
||||||
|
@ -69,28 +67,30 @@ dependencies {
|
||||||
implementation 'androidx.emoji:emoji:1.1.0'
|
implementation 'androidx.emoji:emoji:1.1.0'
|
||||||
gitImplementation 'androidx.emoji:emoji-appcompat:1.1.0'
|
gitImplementation 'androidx.emoji:emoji-appcompat:1.1.0'
|
||||||
gitImplementation 'androidx.emoji:emoji-bundled:1.1.0'
|
gitImplementation 'androidx.emoji:emoji-bundled:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.0.0' // higher versions than 1.0.0 cause strange fab design
|
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||||
|
implementation 'com.google.android.material:material:1.4.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0' // for compatibility
|
implementation 'androidx.cardview:cardview:1.0.0' // for compatibility
|
||||||
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
|
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-core:2.15.0'
|
||||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.0'
|
implementation 'com.google.android.exoplayer:exoplayer-ui:2.15.0'
|
||||||
implementation 'com.wefika:flowlayout:0.4.1'
|
implementation 'com.wefika:flowlayout:0.4.1'
|
||||||
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5'
|
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5'
|
||||||
implementation 'org.jxmpp:jxmpp-jid:1.0.1'
|
implementation 'org.jxmpp:jxmpp-jid:1.0.2'
|
||||||
implementation 'org.hsluv:hsluv:0.2'
|
implementation 'org.hsluv:hsluv:0.2'
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||||
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' // 1.2.15 is last working version for minSDK 16
|
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.23'
|
||||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||||
implementation 'org.osmdroid:osmdroid-android:6.1.5'
|
implementation 'org.osmdroid:osmdroid-android:6.1.11'
|
||||||
implementation 'com.leinardi.android:speed-dial:3.1.1'
|
implementation 'com.leinardi.android:speed-dial:3.2.0'
|
||||||
implementation 'com.squareup.picasso:picasso:2.71828'
|
implementation 'com.squareup.picasso:picasso:2.71828'
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2' // versions > 3.12.x don't support API level < 21 anymore
|
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //retrofit needs to stick with 2.6.x for SDK < 21 (https://github.com/square/retrofit/blob/master/CHANGELOG.md)
|
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||||
implementation 'com.google.guava:guava:30.1.1-android'
|
implementation 'com.google.guava:guava:31.0.1-android'
|
||||||
implementation 'com.github.AppIntro:AppIntro:6.1.0'
|
implementation 'com.github.AppIntro:AppIntro:6.1.0'
|
||||||
implementation "androidx.browser:browser:1.3.0"
|
implementation 'androidx.browser:browser:1.3.0'
|
||||||
implementation fileTree(include: ['libwebrtc-m90.aar'], dir: 'libs')
|
implementation 'com.otaliastudios:transcoder:0.9.1' // 0.10.4 seems to be buggy
|
||||||
|
implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs')
|
||||||
}
|
}
|
||||||
|
|
||||||
ext {
|
ext {
|
||||||
|
@ -100,36 +100,29 @@ ext {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
signingConfigs {
|
//noinspection GradleCompatible
|
||||||
'monocles chat' {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
|
|
||||||
versionNameSuffix " beta_(2021-12-26)" // " beta_(XXXX-XX-XX)" // activate for beta versions
|
versionNameSuffix " beta_(2021-12-19)" // " beta_(XXXX-XX-XX)" // activate for beta versions
|
||||||
versionCode 110
|
versionCode 110
|
||||||
versionName "1.4.4"
|
versionName "1.4.4"
|
||||||
//resConfigs "en"
|
//resConfigs "en"
|
||||||
|
|
||||||
archivesBaseName += "-$versionName"
|
archivesBaseName += "-$versionName"
|
||||||
//archivesBaseName += "$versionNameSuffix" // activate for beta versions
|
archivesBaseName += "$versionNameSuffix" // activate for beta versions
|
||||||
applicationId "de.monocles.chat"
|
applicationId "de.monocles.chat"
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
|
|
||||||
|
buildConfigField("String", "LOGTAG", '"monocles chat"')
|
||||||
buildConfigField("String", "DOMAIN_LOCK", 'null')
|
buildConfigField("String", "DOMAIN_LOCK", 'null')
|
||||||
buildConfigField("String", "MAGIC_CREATE_DOMAIN", '"monocles.de"')
|
|
||||||
buildConfigField("boolean", "SHOW_INTRO", 'true')
|
buildConfigField("boolean", "SHOW_INTRO", 'true')
|
||||||
//buildConfigField("String", "UPDATE_URL", '"https://monocles.de/chat/update/"')
|
buildConfigField("String", "UPDATE_URL", '"https://monocles.de/chat/update/"')
|
||||||
resValue "string", "applicationId", applicationId
|
resValue "string", "applicationId", applicationId
|
||||||
signingConfig signingConfigs.'monocles chat'
|
resValue "string", "app_name", "monocles chat"
|
||||||
def appName = "monocles chat"
|
|
||||||
resValue "string", "app_name", appName
|
|
||||||
buildConfigField "String", "APP_NAME", "\"$appName\"";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dataBinding {
|
dataBinding {
|
||||||
|
@ -159,13 +152,10 @@ android {
|
||||||
applicationId "de.monocles.chat"
|
applicationId "de.monocles.chat"
|
||||||
buildConfigField("boolean", "SHOW_MIGRATION_INFO", 'false')
|
buildConfigField("boolean", "SHOW_MIGRATION_INFO", 'false')
|
||||||
resValue "string", "applicationId", applicationId
|
resValue "string", "applicationId", applicationId
|
||||||
signingConfig signingConfigs.'monocles chat'
|
|
||||||
}
|
}
|
||||||
git {
|
git {
|
||||||
dimension "distribution"
|
dimension "distribution"
|
||||||
buildConfigField("boolean", "SHOW_MIGRATION_INFO", 'true')
|
buildConfigField("boolean", "SHOW_MIGRATION_INFO", 'true')
|
||||||
applicationId 'de.monocles.chat'
|
|
||||||
signingConfig signingConfigs.'monocles chat'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (project.hasProperty('mStoreFile') &&
|
if (project.hasProperty('mStoreFile') &&
|
||||||
|
@ -205,7 +195,8 @@ android {
|
||||||
lintOptions {
|
lintOptions {
|
||||||
error 'StringFormatInvalid' , 'StringFormatMatches'
|
error 'StringFormatInvalid' , 'StringFormatMatches'
|
||||||
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource', 'RestrictedApi'
|
disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource', 'RestrictedApi'
|
||||||
abortOnError true
|
checkReleaseBuilds true
|
||||||
|
abortOnError false
|
||||||
}
|
}
|
||||||
|
|
||||||
subprojects {
|
subprojects {
|
||||||
|
|
2
proguard-rules.pro
vendored
2
proguard-rules.pro
vendored
|
@ -1,6 +1,7 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-keep class de.monocles.chat.**
|
-keep class de.monocles.chat.**
|
||||||
|
-keep class de.pixart.messenger.**
|
||||||
-keep class eu.siacs.conversations.**
|
-keep class eu.siacs.conversations.**
|
||||||
-keep class org.whispersystems.**
|
-keep class org.whispersystems.**
|
||||||
-keep class com.kyleduo.switchbutton.Configuration
|
-keep class com.kyleduo.switchbutton.Configuration
|
||||||
|
@ -38,7 +39,6 @@
|
||||||
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
|
||||||
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
-dontwarn org.openjsse.net.ssl.OpenJSSE
|
||||||
|
|
||||||
|
|
||||||
-keepclassmembers class eu.siacs.conversations.http.services.** {
|
-keepclassmembers class eu.siacs.conversations.http.services.** {
|
||||||
!transient <fields>;
|
!transient <fields>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||||
<uses-permission android:name="android.permission.READ_PROFILE" />
|
<uses-permission android:name="android.permission.READ_PROFILE" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="28" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" android:maxSdkVersion="22" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
@ -81,9 +81,7 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
<action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||||
<data
|
|
||||||
android:path="${applicationId}"
|
|
||||||
android:scheme="package" />
|
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
|
<action android:name="android.intent.action.PACKAGE_RESTARTED" />
|
||||||
|
@ -92,7 +90,7 @@
|
||||||
android:scheme="package" />
|
android:scheme="package" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
<receiver android:name=".services.AlarmReceiver"></receiver>
|
<receiver android:name=".services.AlarmReceiver" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="de.monocles.chat.ui.StartUI"
|
android:name="de.monocles.chat.ui.StartUI"
|
||||||
|
@ -110,8 +108,8 @@
|
||||||
android:name=".ui.ConversationsActivity"
|
android:name=".ui.ConversationsActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:minWidth="300dp"
|
android:minWidth="336dp"
|
||||||
android:minHeight="300dp"
|
android:minHeight="480dp"
|
||||||
android:windowSoftInputMode="stateHidden" />
|
android:windowSoftInputMode="stateHidden" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ScanActivity"
|
android:name=".ui.ScanActivity"
|
||||||
|
@ -148,6 +146,14 @@
|
||||||
<data android:scheme="imto" />
|
<data android:scheme="imto" />
|
||||||
<data android:host="jabber" />
|
<data android:host="jabber" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.SENDTO" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
|
||||||
|
<data android:scheme="imto" />
|
||||||
|
<data android:host="xmpp" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.StartConversationActivity"
|
android:name=".ui.StartConversationActivity"
|
||||||
|
@ -278,10 +284,6 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.PublishGroupChatProfilePictureActivity"
|
android:name=".ui.PublishGroupChatProfilePictureActivity"
|
||||||
android:label="@string/group_chat_avatar" />
|
android:label="@string/group_chat_avatar" />
|
||||||
<activity
|
|
||||||
android:name=".ui.VerifyOTRActivity"
|
|
||||||
android:label="@string/verify_otr"
|
|
||||||
android:windowSoftInputMode="stateHidden" />
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ShareWithActivity"
|
android:name=".ui.ShareWithActivity"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -359,7 +361,12 @@
|
||||||
android:name="android.support.PARENT_ACTIVITY"
|
android:name="android.support.PARENT_ACTIVITY"
|
||||||
android:value=".ui.SettingsActivity" />
|
android:value=".ui.SettingsActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
<activity
|
||||||
|
android:name=".ui.UpdaterActivity"
|
||||||
|
android:configChanges="orientation|screenSize"
|
||||||
|
android:label="@string/title_activity_updater"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:theme="@style/ConversationsTheme" />
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ShortcutActivity"
|
android:name=".ui.ShortcutActivity"
|
||||||
android:label="@string/contact"
|
android:label="@string/contact"
|
||||||
|
|
|
@ -84,9 +84,11 @@ public class PermissionsActivity extends AppCompatActivity
|
||||||
dialog.setCancelable(false);
|
dialog.setCancelable(false);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
} else {
|
} else {
|
||||||
|
if (Compatibility.hasStoragePermission(PermissionsActivity.this)) {
|
||||||
StartUI.next(this);
|
StartUI.next(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public interface OnPermissionGranted {
|
public interface OnPermissionGranted {
|
||||||
void onPermissionGranted();
|
void onPermissionGranted();
|
||||||
|
|
|
@ -6,15 +6,13 @@ import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||||
import eu.siacs.conversations.ui.util.IntroHelper;
|
import eu.siacs.conversations.ui.util.IntroHelper;
|
||||||
|
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
import eu.siacs.conversations.utils.Compatibility;
|
||||||
import eu.siacs.conversations.utils.ThemeHelper;
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
|
|
||||||
public class StartUI extends PermissionsActivity
|
public class StartUI extends PermissionsActivity
|
||||||
implements PermissionsActivity.OnPermissionGranted {
|
implements PermissionsActivity.OnPermissionGranted {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -25,7 +23,6 @@ import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
IntroHelper.showIntro(this, false);
|
IntroHelper.showIntro(this, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
@ -40,6 +37,11 @@ import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
if (Compatibility.runsAndTargetsThirty(this)) {
|
if (Compatibility.runsAndTargetsThirty(this)) {
|
||||||
requestAllFilesAccess(this);
|
requestAllFilesAccess(this);
|
||||||
}
|
}
|
||||||
|
if (checkStoragePermission() && !Compatibility.runsAndTargetsThirty(this)) {
|
||||||
|
next(this);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
next(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,13 +2,16 @@ package eu.siacs.conversations;
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
import eu.siacs.conversations.crypto.XmppDomainVerifier;
|
import eu.siacs.conversations.crypto.XmppDomainVerifier;
|
||||||
|
import eu.siacs.conversations.services.ProviderService;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||||
|
|
||||||
|
@ -44,32 +47,36 @@ public final class Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String monocles() {
|
public static String monocles() {
|
||||||
if (Locale.getDefault().getLanguage().equalsIgnoreCase("de")) {
|
//if (Locale.getDefault().getLanguage().equalsIgnoreCase("de")) {
|
||||||
return "monocles chat";
|
return "https://ocean.monocles.de/apps/registration/";
|
||||||
} else {
|
/*} else {
|
||||||
return "monocles chat/en";
|
return "blabber.im/en.html";
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final String LOGTAG = BuildConfig.APP_NAME.toLowerCase(Locale.US);
|
public static final String LOGTAG = BuildConfig.LOGTAG;
|
||||||
|
|
||||||
public static final Jid BUG_REPORTS = Jid.of("support@monocles.de");
|
public static final Jid BUG_REPORTS = Jid.of("support@monocles.de");
|
||||||
public static final Uri HELP = Uri.parse("https://help.conversations.im");
|
public static final Uri HELP = Uri.parse("https://monocles.de/howto");
|
||||||
|
|
||||||
public static final String inviteUserURL = "https://ocean.monocles.de/apps/registration/";
|
public static final String inviteUserURL = "https://" + monocles() + "/i/";
|
||||||
public static final String inviteMUCURL = "https://ocean.monocles.de/apps/registration/";
|
public static final String inviteMUCURL = "https://" + monocles() + "/j/";
|
||||||
public static final String inviteHostURL = "monocles.de"; // without http(s)
|
public static final String inviteHostURL = monocles(); // without http(s)
|
||||||
public static final String termsOfUseURL = "https://" + "monocles.de" + "/impressum/";
|
public static final String INVITE_DOMAIN = monocles();
|
||||||
public static final String privacyURL = "https://" + "monocles.de" + "/impressum/";
|
public static final String termsOfUseURL = "https://monocles.de/impressum/";
|
||||||
public static final String migrationURL = "https://" + "codeberg.org/Arne/monocles_chat/";
|
public static final String privacyURL = "https://monocles.de/impressum/";
|
||||||
|
public static final String migrationURL = Locale.getDefault().getLanguage().equalsIgnoreCase("de") ? "https://codeberg.org/Arne/monocles_chat" : "https://codeberg.org/Arne/monocles_chat";
|
||||||
|
|
||||||
public static final String CHANGELOG_URL = "https://codeberg.org/Arne/monocles_chat/src/branch/master/CHANGELOG.md";
|
public static final String CHANGELOG_URL = "https://codeberg.org/Arne/monocles_chat/src/branch/master/CHANGELOG.md";
|
||||||
public static final String GIT_URL = "https://codeberg.org/Arne/monocles_chat/";
|
public static final String GIT_URL = "https://codeberg.org/Arne/monocles_chat";
|
||||||
|
|
||||||
|
public static final String PROVIDER_URL = "https://invent.kde.org/melvo/xmpp-providers/-/raw/master/providers.json"; // https://invent.kde.org/melvo/xmpp-providers
|
||||||
|
|
||||||
public static final String XMPP_IP = null; //BuildConfig.XMPP_IP; // set to null means disable
|
public static final String XMPP_IP = null; //BuildConfig.XMPP_IP; // set to null means disable
|
||||||
public static final Integer[] XMPP_Ports = null; //BuildConfig.XMPP_Ports; // set to null means disable
|
public static final Integer[] XMPP_Ports = null; //BuildConfig.XMPP_Ports; // set to null means disable
|
||||||
public static final String DOMAIN_LOCK = BuildConfig.DOMAIN_LOCK; //only allow account creation for this domain
|
public static final String DOMAIN_LOCK = BuildConfig.DOMAIN_LOCK; //only allow account creation for this domain
|
||||||
public static final String MAGIC_CREATE_DOMAIN = BuildConfig.MAGIC_CREATE_DOMAIN; //"monocles.de";
|
public static final String MAGIC_CREATE_DOMAIN = DOMAIN.getRandomServer();
|
||||||
|
|
||||||
public static final String QUICKSY_DOMAIN = "quicksy.im";
|
public static final String QUICKSY_DOMAIN = "quicksy.im";
|
||||||
public static final String CHANNEL_DISCOVERY = "https://search.jabber.network";
|
public static final String CHANNEL_DISCOVERY = "https://search.jabber.network";
|
||||||
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
|
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
|
||||||
|
@ -102,7 +109,7 @@ public final class Config {
|
||||||
|
|
||||||
public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0
|
public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0
|
||||||
|
|
||||||
public static final int FILE_SIZE = 1048576; // 1 MiB
|
public static final int VIDEO_FAST_UPLOAD_SIZE = 5 * 1024 * 1024;
|
||||||
|
|
||||||
public static final int AVATAR_SIZE = 480;
|
public static final int AVATAR_SIZE = 480;
|
||||||
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
|
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
|
||||||
|
@ -111,11 +118,6 @@ public final class Config {
|
||||||
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
|
public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
|
||||||
public static final int IMAGE_QUALITY = 65;
|
public static final int IMAGE_QUALITY = 65;
|
||||||
|
|
||||||
public static final int DEFAULT_ZOOM = 15; //for locations
|
|
||||||
public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms
|
|
||||||
public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
|
|
||||||
public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms
|
|
||||||
|
|
||||||
public static final int MESSAGE_MERGE_WINDOW = 20;
|
public static final int MESSAGE_MERGE_WINDOW = 20;
|
||||||
|
|
||||||
public static final int PAGE_SIZE = 50;
|
public static final int PAGE_SIZE = 50;
|
||||||
|
@ -150,8 +152,9 @@ public final class Config {
|
||||||
public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false;
|
public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false;
|
||||||
|
|
||||||
public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys
|
public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys
|
||||||
|
public static final boolean REQUIRE_RTP_VERIFICATION = false; //require a/v calls to be verified with OMEMO
|
||||||
|
|
||||||
public static final boolean ONLY_INTERNAL_STORAGE = true; //use internal storage instead of sdcard to save attachments
|
public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments
|
||||||
|
|
||||||
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
|
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
|
||||||
public static final boolean MUC_LEAVE_BEFORE_JOIN = false;
|
public static final boolean MUC_LEAVE_BEFORE_JOIN = false;
|
||||||
|
@ -166,8 +169,8 @@ public final class Config {
|
||||||
|
|
||||||
public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes
|
||||||
|
|
||||||
//public static final String UPDATE_URL = BuildConfig.UPDATE_URL;
|
public static final String UPDATE_URL = BuildConfig.UPDATE_URL;
|
||||||
//public static final long UPDATE_CHECK_TIMER = 24 * 60 * 60; // 24 h in seconds
|
public static final long UPDATE_CHECK_TIMER = 24 * 60 * 60; // 24 h in seconds
|
||||||
|
|
||||||
public static final String ISSUE_URL = "xmpp://support@conference.monocles.de?join";
|
public static final String ISSUE_URL = "xmpp://support@conference.monocles.de?join";
|
||||||
|
|
||||||
|
@ -207,6 +210,45 @@ public final class Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class DOMAIN {
|
||||||
|
public static final List<String> DOMAINS = Arrays.asList(
|
||||||
|
"conversations.im",
|
||||||
|
"zp1.net"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final List<String> BLACKLISTED_DOMAINS = Arrays.asList(
|
||||||
|
"blabber.im"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static String getRandomServer() {
|
||||||
|
try {
|
||||||
|
new ProviderService().execute();
|
||||||
|
final String domain = ProviderService.getProviders().get(new Random().nextInt(ProviderService.getProviders().size()));
|
||||||
|
Log.d(LOGTAG, "MagicCreate account on domain: " + domain);
|
||||||
|
return domain;
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(LOGTAG, "Error getting random server ", e);
|
||||||
|
}
|
||||||
|
return "zp1.net";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private Config() {
|
private Config() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static final class Map {
|
||||||
|
public final static double INITIAL_ZOOM_LEVEL = 4;
|
||||||
|
public final static double FINAL_ZOOM_LEVEL = 15;
|
||||||
|
public final static int MY_LOCATION_INDICATOR_SIZE = 15;
|
||||||
|
public final static int MY_LOCATION_INDICATOR_OUTLINE_SIZE = 5;
|
||||||
|
public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms
|
||||||
|
public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
|
||||||
|
public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms
|
||||||
|
}
|
||||||
|
|
||||||
|
// How deep nested quotes should be displayed. '2' means one quote nested in another.
|
||||||
|
public static final int QUOTE_MAX_DEPTH = 7;
|
||||||
|
// How deep nested quotes should be created on quoting a message.
|
||||||
|
public static final int QUOTING_MAX_DEPTH = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,21 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
public class JabberIdContact extends AbstractPhoneContact {
|
public class JabberIdContact extends AbstractPhoneContact {
|
||||||
|
|
||||||
|
private static final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
|
||||||
|
ContactsContract.Data.DISPLAY_NAME,
|
||||||
|
ContactsContract.Data.PHOTO_URI,
|
||||||
|
ContactsContract.Data.LOOKUP_KEY,
|
||||||
|
ContactsContract.CommonDataKinds.Im.DATA
|
||||||
|
};
|
||||||
|
private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))";
|
||||||
|
|
||||||
|
private static final String[] SELECTION_ARGS = {
|
||||||
|
ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,
|
||||||
|
String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER),
|
||||||
|
String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM),
|
||||||
|
"xmpp"
|
||||||
|
};
|
||||||
|
|
||||||
private final Jid jid;
|
private final Jid jid;
|
||||||
|
|
||||||
private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
|
private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
|
||||||
|
@ -38,38 +53,26 @@ public class JabberIdContact extends AbstractPhoneContact {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
|
try (final Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, null)) {
|
||||||
ContactsContract.Data.DISPLAY_NAME,
|
if (cursor == null) {
|
||||||
ContactsContract.Data.PHOTO_URI,
|
|
||||||
ContactsContract.Data.LOOKUP_KEY,
|
|
||||||
ContactsContract.CommonDataKinds.Im.DATA};
|
|
||||||
|
|
||||||
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
|
|
||||||
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
|
|
||||||
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
|
|
||||||
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
|
|
||||||
+ "\")";
|
|
||||||
final Cursor cursor;
|
|
||||||
try {
|
|
||||||
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
final HashMap<Jid, JabberIdContact> contacts = new HashMap<>();
|
final HashMap<Jid, JabberIdContact> contacts = new HashMap<>();
|
||||||
while (cursor != null && cursor.moveToNext()) {
|
while (cursor.moveToNext()) {
|
||||||
try {
|
try {
|
||||||
final JabberIdContact contact = new JabberIdContact(cursor);
|
final JabberIdContact contact = new JabberIdContact(cursor);
|
||||||
final JabberIdContact preexisting = contacts.put(contact.getJid(), contact);
|
final JabberIdContact preexisting = contacts.put(contact.getJid(), contact);
|
||||||
if (preexisting == null || preexisting.rating() < contact.rating()) {
|
if (preexisting == null || preexisting.rating() < contact.rating()) {
|
||||||
contacts.put(contact.getJid(), contact);
|
contacts.put(contact.getJid(), contact);
|
||||||
}
|
}
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (final IllegalArgumentException e) {
|
||||||
Log.d(Config.LOGTAG,"unable to create jabber id contact");
|
Log.d(Config.LOGTAG, "unable to create jabber id contact");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cursor != null) {
|
|
||||||
cursor.close();
|
|
||||||
}
|
|
||||||
return contacts;
|
return contacts;
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to query", e);
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
package eu.siacs.conversations.crypto;
|
package eu.siacs.conversations.crypto;
|
||||||
|
|
||||||
import javax.net.ssl.HostnameVerifier;
|
import javax.net.ssl.HostnameVerifier;
|
||||||
import javax.net.ssl.SSLSession;
|
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||||
|
import javax.net.ssl.SSLSession;
|
||||||
|
|
||||||
public interface DomainHostnameVerifier extends HostnameVerifier {
|
public interface DomainHostnameVerifier extends HostnameVerifier {
|
||||||
|
|
||||||
boolean verify(String domain, String hostname, SSLSession sslSession) throws SSLPeerUnverifiedException;
|
boolean verify(String domain, String hostname, SSLSession sslSession) throws SSLPeerUnverifiedException;
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,312 +0,0 @@
|
||||||
package eu.siacs.conversations.crypto;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import net.java.otr4j.OtrEngineHost;
|
|
||||||
import net.java.otr4j.OtrException;
|
|
||||||
import net.java.otr4j.OtrPolicy;
|
|
||||||
import net.java.otr4j.OtrPolicyImpl;
|
|
||||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
|
||||||
import net.java.otr4j.crypto.OtrCryptoException;
|
|
||||||
import net.java.otr4j.session.FragmenterInstructions;
|
|
||||||
import net.java.otr4j.session.InstanceTag;
|
|
||||||
import net.java.otr4j.session.SessionID;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.security.KeyFactory;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.spec.DSAPrivateKeySpec;
|
|
||||||
import java.security.spec.DSAPublicKeySpec;
|
|
||||||
import java.security.spec.InvalidKeySpecException;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
|
||||||
import eu.siacs.conversations.generator.MessageGenerator;
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
|
||||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
|
||||||
import eu.siacs.conversations.xmpp.jid.OtrJidHelper;
|
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
|
||||||
|
|
||||||
public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
|
|
||||||
|
|
||||||
private Account account;
|
|
||||||
private OtrPolicy otrPolicy;
|
|
||||||
private KeyPair keyPair;
|
|
||||||
private XmppConnectionService mXmppConnectionService;
|
|
||||||
|
|
||||||
public OtrService(XmppConnectionService service, Account account) {
|
|
||||||
this.account = account;
|
|
||||||
this.otrPolicy = new OtrPolicyImpl();
|
|
||||||
this.otrPolicy.setAllowV1(false);
|
|
||||||
this.otrPolicy.setAllowV2(true);
|
|
||||||
this.otrPolicy.setAllowV3(true);
|
|
||||||
this.keyPair = loadKey(account.getKeys());
|
|
||||||
this.mXmppConnectionService = service;
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyPair loadKey(final JSONObject keys) {
|
|
||||||
if (keys == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
synchronized (keys) {
|
|
||||||
try {
|
|
||||||
BigInteger x = new BigInteger(keys.getString("otr_x"), 16);
|
|
||||||
BigInteger y = new BigInteger(keys.getString("otr_y"), 16);
|
|
||||||
BigInteger p = new BigInteger(keys.getString("otr_p"), 16);
|
|
||||||
BigInteger q = new BigInteger(keys.getString("otr_q"), 16);
|
|
||||||
BigInteger g = new BigInteger(keys.getString("otr_g"), 16);
|
|
||||||
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
|
|
||||||
DSAPublicKeySpec pubKeySpec = new DSAPublicKeySpec(y, p, q, g);
|
|
||||||
DSAPrivateKeySpec privateKeySpec = new DSAPrivateKeySpec(x, p, q, g);
|
|
||||||
PublicKey publicKey = keyFactory.generatePublic(pubKeySpec);
|
|
||||||
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);
|
|
||||||
return new KeyPair(publicKey, privateKey);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return null;
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
return null;
|
|
||||||
} catch (InvalidKeySpecException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveKey() {
|
|
||||||
PublicKey publicKey = keyPair.getPublic();
|
|
||||||
PrivateKey privateKey = keyPair.getPrivate();
|
|
||||||
KeyFactory keyFactory;
|
|
||||||
try {
|
|
||||||
keyFactory = KeyFactory.getInstance("DSA");
|
|
||||||
DSAPrivateKeySpec privateKeySpec = keyFactory.getKeySpec(
|
|
||||||
privateKey, DSAPrivateKeySpec.class);
|
|
||||||
DSAPublicKeySpec publicKeySpec = keyFactory.getKeySpec(publicKey,
|
|
||||||
DSAPublicKeySpec.class);
|
|
||||||
this.account.setKey("otr_x", privateKeySpec.getX().toString(16));
|
|
||||||
this.account.setKey("otr_g", privateKeySpec.getG().toString(16));
|
|
||||||
this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
|
|
||||||
this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
|
|
||||||
this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
|
|
||||||
} catch (final NoSuchAlgorithmException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
} catch (final InvalidKeySpecException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void askForSecret(SessionID id, InstanceTag instanceTag, String question) {
|
|
||||||
try {
|
|
||||||
final Jid jid = OtrJidHelper.fromSessionID(id);
|
|
||||||
Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
|
|
||||||
if (conversation != null) {
|
|
||||||
conversation.smp().hint = question;
|
|
||||||
conversation.smp().status = Conversation.Smp.STATUS_CONTACT_REQUESTED;
|
|
||||||
mXmppConnectionService.updateConversationUi();
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": smp in invalid session " + id.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void finishedSessionMessage(SessionID arg0, String arg1)
|
|
||||||
throws OtrException {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getFallbackMessage(SessionID arg0) {
|
|
||||||
return MessageGenerator.OTR_FALLBACK_MESSAGE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getLocalFingerprintRaw(SessionID arg0) {
|
|
||||||
try {
|
|
||||||
return getFingerprintRaw(getPublicKey());
|
|
||||||
} catch (OtrCryptoException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public PublicKey getPublicKey() {
|
|
||||||
if (this.keyPair == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return this.keyPair.getPublic();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public KeyPair getLocalKeyPair(SessionID arg0) throws OtrException {
|
|
||||||
if (this.keyPair == null) {
|
|
||||||
KeyPairGenerator kg;
|
|
||||||
try {
|
|
||||||
kg = KeyPairGenerator.getInstance("DSA");
|
|
||||||
this.keyPair = kg.genKeyPair();
|
|
||||||
this.saveKey();
|
|
||||||
mXmppConnectionService.databaseBackend.updateAccount(account);
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
"error generating key pair " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.keyPair;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getReplyForUnreadableMessage(SessionID arg0) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public OtrPolicy getSessionPolicy(SessionID arg0) {
|
|
||||||
return otrPolicy;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void injectMessage(SessionID session, String body)
|
|
||||||
throws OtrException {
|
|
||||||
MessagePacket packet = new MessagePacket();
|
|
||||||
packet.setFrom(account.getJid());
|
|
||||||
if (session.getUserID().isEmpty()) {
|
|
||||||
packet.setAttribute("to", session.getAccountID());
|
|
||||||
} else {
|
|
||||||
packet.setAttribute("to", session.getAccountID() + "/" + session.getUserID());
|
|
||||||
}
|
|
||||||
packet.setBody(body);
|
|
||||||
MessageGenerator.addMessageHints(packet);
|
|
||||||
try {
|
|
||||||
Jid jid = OtrJidHelper.fromSessionID(session);
|
|
||||||
Conversation conversation = mXmppConnectionService.find(account, jid);
|
|
||||||
if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
|
|
||||||
if (mXmppConnectionService.sendChatStates()) {
|
|
||||||
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (final IllegalArgumentException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
|
||||||
packet.addChild("encryption", "urn:xmpp:eme:0").setAttribute("namespace", "urn:xmpp:otr:0");
|
|
||||||
account.getXmppConnection().sendMessagePacket(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void messageFromAnotherInstanceReceived(SessionID session) {
|
|
||||||
sendOtrErrorMessage(session, "Message from another OTR-instance received");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void multipleInstancesDetected(SessionID arg0) {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void requireEncryptedMessage(SessionID arg0, String arg1)
|
|
||||||
throws OtrException {
|
|
||||||
// TODO Auto-generated method stub
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showError(SessionID arg0, String arg1) throws OtrException {
|
|
||||||
Log.d(Config.LOGTAG, "show error");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void smpAborted(SessionID id) throws OtrException {
|
|
||||||
setSmpStatus(id, Conversation.Smp.STATUS_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setSmpStatus(SessionID id, int status) {
|
|
||||||
try {
|
|
||||||
final Jid jid = OtrJidHelper.fromSessionID(id);
|
|
||||||
Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
|
|
||||||
if (conversation != null) {
|
|
||||||
conversation.smp().status = status;
|
|
||||||
mXmppConnectionService.updateConversationUi();
|
|
||||||
}
|
|
||||||
} catch (final IllegalArgumentException ignored) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void smpError(SessionID id, int arg1, boolean arg2)
|
|
||||||
throws OtrException {
|
|
||||||
setSmpStatus(id, Conversation.Smp.STATUS_NONE);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unencryptedMessageReceived(SessionID arg0, String arg1)
|
|
||||||
throws OtrException {
|
|
||||||
throw new OtrException(new Exception("unencrypted message received"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unreadableMessageReceived(SessionID session) throws OtrException {
|
|
||||||
Log.d(Config.LOGTAG, "unreadable message received");
|
|
||||||
sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sendOtrErrorMessage(SessionID session, String errorText) {
|
|
||||||
try {
|
|
||||||
Jid jid = OtrJidHelper.fromSessionID(session);
|
|
||||||
Conversation conversation = mXmppConnectionService.find(account, jid);
|
|
||||||
String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
|
|
||||||
if (id != null) {
|
|
||||||
MessagePacket packet = mXmppConnectionService.getMessageGenerator()
|
|
||||||
.generateOtrError(jid, id, errorText);
|
|
||||||
packet.setFrom(account.getJid());
|
|
||||||
mXmppConnectionService.sendMessagePacket(account, packet);
|
|
||||||
Log.d(Config.LOGTAG, packet.toString());
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString()
|
|
||||||
+ ": unreadable OTR message in " + conversation.getName());
|
|
||||||
}
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void unverify(SessionID id, String arg1) {
|
|
||||||
setSmpStatus(id, Conversation.Smp.STATUS_FAILED);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void verify(SessionID id, String fingerprint, boolean approved) {
|
|
||||||
Log.d(Config.LOGTAG, "OtrService.verify(" + id.toString() + "," + fingerprint + "," + String.valueOf(approved) + ")");
|
|
||||||
try {
|
|
||||||
final Jid jid = OtrJidHelper.fromSessionID(id);
|
|
||||||
Conversation conversation = this.mXmppConnectionService.find(this.account, jid);
|
|
||||||
if (conversation != null) {
|
|
||||||
if (approved) {
|
|
||||||
conversation.getContact().addOtrFingerprint(fingerprint);
|
|
||||||
}
|
|
||||||
conversation.smp().hint = null;
|
|
||||||
conversation.smp().status = Conversation.Smp.STATUS_VERIFIED;
|
|
||||||
mXmppConnectionService.updateConversationUi();
|
|
||||||
mXmppConnectionService.syncRosterToDisk(conversation.getAccount());
|
|
||||||
}
|
|
||||||
} catch (final IllegalArgumentException ignored) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public FragmenterInstructions getFragmenterInstructions(SessionID sessionID) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -8,7 +8,12 @@ import android.util.Pair;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import com.google.common.util.concurrent.SettableFuture;
|
||||||
|
|
||||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
@ -733,16 +738,22 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
axolotlStore.setFingerprintStatus(fingerprint, status);
|
axolotlStore.setFingerprintStatus(fingerprint, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void verifySessionWithPEP(final XmppAxolotlSession session) {
|
private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
|
||||||
Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
|
Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
|
||||||
final SignalProtocolAddress address = session.getRemoteAddress();
|
final SignalProtocolAddress address = session.getRemoteAddress();
|
||||||
final IdentityKey identityKey = session.getIdentityKey();
|
final IdentityKey identityKey = session.getIdentityKey();
|
||||||
|
final Jid jid;
|
||||||
try {
|
try {
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.of(address.getName()), address.getDeviceId());
|
jid = Jid.of(address.getName());
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
} catch (final IllegalArgumentException e) {
|
||||||
@Override
|
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
finishBuildingSessionsFromPEP(address);
|
||||||
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(packet);
|
return Futures.immediateFuture(session);
|
||||||
|
}
|
||||||
|
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
|
||||||
|
final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
|
||||||
|
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
|
||||||
|
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
|
||||||
if (verification != null) {
|
if (verification != null) {
|
||||||
try {
|
try {
|
||||||
Signature verifier = Signature.getInstance("sha256WithRSA");
|
Signature verifier = Signature.getInstance("sha256WithRSA");
|
||||||
|
@ -759,13 +770,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
|
Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
|
||||||
try {
|
try {
|
||||||
final String cn = information.getString("subject_cn");
|
final String cn = information.getString("subject_cn");
|
||||||
final Jid jid = Jid.of(address.getName());
|
final Jid jid1 = Jid.of(address.getName());
|
||||||
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
|
Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
|
||||||
account.getRoster().getContact(jid).setCommonName(cn);
|
account.getRoster().getContact(jid1).setCommonName(cn);
|
||||||
} catch (final IllegalArgumentException ignored) {
|
} catch (final IllegalArgumentException ignored) {
|
||||||
//ignored
|
//ignored
|
||||||
}
|
}
|
||||||
finishBuildingSessionsFromPEP(address);
|
finishBuildingSessionsFromPEP(address);
|
||||||
|
future.set(session);
|
||||||
return;
|
return;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG, "could not verify certificate");
|
Log.d(Config.LOGTAG, "could not verify certificate");
|
||||||
|
@ -779,12 +791,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
||||||
finishBuildingSessionsFromPEP(address);
|
finishBuildingSessionsFromPEP(address);
|
||||||
}
|
future.set(session);
|
||||||
});
|
});
|
||||||
} catch (IllegalArgumentException e) {
|
return future;
|
||||||
fetchStatusMap.put(address, FetchStatus.SUCCESS);
|
|
||||||
finishBuildingSessionsFromPEP(address);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
|
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
|
||||||
|
@ -900,22 +909,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildSessionFromPEP(final SignalProtocolAddress address) {
|
private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address) {
|
||||||
buildSessionFromPEP(address, null);
|
return buildSessionFromPEP(address, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
|
private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
|
||||||
|
final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
|
||||||
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
|
||||||
if (address.equals(getOwnAxolotlAddress())) {
|
if (address.equals(getOwnAxolotlAddress())) {
|
||||||
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
|
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
|
||||||
}
|
}
|
||||||
|
|
||||||
final Jid jid = Jid.of(address.getName());
|
final Jid jid = Jid.of(address.getName());
|
||||||
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
|
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
|
||||||
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
||||||
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
|
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||||
fetchStatusMap.put(address, FetchStatus.TIMEOUT);
|
fetchStatusMap.put(address, FetchStatus.TIMEOUT);
|
||||||
|
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
|
||||||
} else if (packet.getType() == IqPacket.TYPE.RESULT) {
|
} else if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
|
||||||
final IqParser parser = mXmppConnectionService.getIqParser();
|
final IqParser parser = mXmppConnectionService.getIqParser();
|
||||||
|
@ -928,6 +938,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onSessionBuildFailed();
|
callback.onSessionBuildFailed();
|
||||||
}
|
}
|
||||||
|
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Random random = new Random();
|
Random random = new Random();
|
||||||
|
@ -939,6 +950,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onSessionBuildFailed();
|
callback.onSessionBuildFailed();
|
||||||
}
|
}
|
||||||
|
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -953,7 +965,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
|
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
|
||||||
sessions.put(address, session);
|
sessions.put(address, session);
|
||||||
if (Config.X509_VERIFICATION) {
|
if (Config.X509_VERIFICATION) {
|
||||||
verifySessionWithPEP(session); //TODO; maybe inject callback in here too
|
sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too
|
||||||
} else {
|
} else {
|
||||||
FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
|
FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
|
||||||
FetchStatus fetchStatus;
|
FetchStatus fetchStatus;
|
||||||
|
@ -969,6 +981,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onSessionBuildSuccessful();
|
callback.onSessionBuildSuccessful();
|
||||||
}
|
}
|
||||||
|
sessionSettableFuture.set(session);
|
||||||
}
|
}
|
||||||
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
} catch (UntrustedIdentityException | InvalidKeyException e) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
|
||||||
|
@ -981,6 +994,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onSessionBuildFailed();
|
callback.onSessionBuildFailed();
|
||||||
}
|
}
|
||||||
|
sessionSettableFuture.setException(new CryptoFailedException(e));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
|
@ -994,8 +1008,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.onSessionBuildFailed();
|
callback.onSessionBuildFailed();
|
||||||
}
|
}
|
||||||
|
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
return sessionSettableFuture;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeFromDeviceAnnouncement(Integer id) {
|
private void removeFromDeviceAnnouncement(Integer id) {
|
||||||
|
@ -1228,36 +1244,63 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public OmemoVerifiedPayload<OmemoVerifiedRtpContentMap> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) throws CryptoFailedException {
|
public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
|
||||||
final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
|
return Futures.transformAsync(
|
||||||
final XmppAxolotlSession session = sessions.get(address);
|
getSession(jid, deviceId),
|
||||||
if (session == null) {
|
session -> encrypt(rtpContentMap, session),
|
||||||
throw new CryptoFailedException(String.format("No session found for %d", deviceId));
|
MoreExecutors.directExecutor()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
|
||||||
|
if (Config.REQUIRE_RTP_VERIFICATION) {
|
||||||
|
requireVerification(session);
|
||||||
}
|
}
|
||||||
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
|
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
|
||||||
final OmemoVerification omemoVerification = new OmemoVerification();
|
final OmemoVerification omemoVerification = new OmemoVerification();
|
||||||
omemoVerification.setDeviceId(deviceId);
|
omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
|
||||||
omemoVerification.setSessionFingerprint(session.getFingerprint());
|
omemoVerification.setSessionFingerprint(session.getFingerprint());
|
||||||
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
|
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
|
||||||
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
|
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
|
||||||
final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
|
final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
|
||||||
|
try {
|
||||||
|
encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
|
||||||
|
} catch (final CryptoFailedException e) {
|
||||||
|
return Futures.immediateFailedFuture(e);
|
||||||
|
}
|
||||||
descriptionTransportBuilder.put(
|
descriptionTransportBuilder.put(
|
||||||
content.getKey(),
|
content.getKey(),
|
||||||
new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo)
|
new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new OmemoVerifiedPayload<>(
|
return Futures.immediateFuture(
|
||||||
|
new OmemoVerifiedPayload<>(
|
||||||
omemoVerification,
|
omemoVerification,
|
||||||
new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
|
new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
public OmemoVerifiedPayload<RtpContentMap> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) throws CryptoFailedException {
|
private ListenableFuture<XmppAxolotlSession> getSession(final Jid jid, final int deviceId) {
|
||||||
|
final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
|
||||||
|
final XmppAxolotlSession session = sessions.get(address);
|
||||||
|
if (session == null) {
|
||||||
|
return buildSessionFromPEP(address);
|
||||||
|
}
|
||||||
|
return Futures.immediateFuture(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
|
||||||
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
|
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
|
||||||
final OmemoVerification omemoVerification = new OmemoVerification();
|
final OmemoVerification omemoVerification = new OmemoVerification();
|
||||||
|
final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
|
||||||
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
|
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
|
||||||
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
|
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
|
||||||
final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from);
|
final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
|
||||||
|
try {
|
||||||
|
decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
|
||||||
|
} catch (CryptoFailedException e) {
|
||||||
|
return Futures.immediateFailedFuture(e);
|
||||||
|
}
|
||||||
omemoVerification.setOrEnsureEqual(decryptedTransport);
|
omemoVerification.setOrEnsureEqual(decryptedTransport);
|
||||||
descriptionTransportBuilder.put(
|
descriptionTransportBuilder.put(
|
||||||
content.getKey(),
|
content.getKey(),
|
||||||
|
@ -1265,13 +1308,26 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
processPostponed();
|
processPostponed();
|
||||||
|
final ImmutableList<ListenableFuture<XmppAxolotlSession>> sessionFutures = pepVerificationFutures.build();
|
||||||
|
return Futures.transform(
|
||||||
|
Futures.allAsList(sessionFutures),
|
||||||
|
sessions -> {
|
||||||
|
if (Config.REQUIRE_RTP_VERIFICATION) {
|
||||||
|
for (XmppAxolotlSession session : sessions) {
|
||||||
|
requireVerification(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
return new OmemoVerifiedPayload<>(
|
return new OmemoVerifiedPayload<>(
|
||||||
omemoVerification,
|
omemoVerification,
|
||||||
new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
|
new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
|
||||||
);
|
);
|
||||||
|
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from) throws CryptoFailedException {
|
private OmemoVerifiedPayload<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
|
||||||
final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
|
final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
|
||||||
transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
|
transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
|
||||||
final OmemoVerification omemoVerification = new OmemoVerification();
|
final OmemoVerification omemoVerification = new OmemoVerification();
|
||||||
|
@ -1288,6 +1344,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (preKeyId != null) {
|
if (preKeyId != null) {
|
||||||
postponedSessions.add(session);
|
postponedSessions.add(session);
|
||||||
}
|
}
|
||||||
|
if (session.isFresh()) {
|
||||||
|
pepVerificationFutures.add(putFreshSession(session));
|
||||||
|
} else if (Config.REQUIRE_RTP_VERIFICATION) {
|
||||||
|
pepVerificationFutures.add(Futures.immediateFuture(session));
|
||||||
|
}
|
||||||
fingerprint.setContent(plaintext.getPlaintext());
|
fingerprint.setContent(plaintext.getPlaintext());
|
||||||
omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
|
omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
|
||||||
omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
|
omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
|
||||||
|
@ -1299,6 +1360,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
|
return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void requireVerification(final XmppAxolotlSession session) {
|
||||||
|
if (session.getTrust().isVerified()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new NotVerifiedException(String.format(
|
||||||
|
"session with %s was not verified",
|
||||||
|
session.getFingerprint()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
|
public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
|
||||||
executor.execute(new Runnable() {
|
executor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -1496,15 +1567,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return keyTransportMessage;
|
return keyTransportMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putFreshSession(XmppAxolotlSession session) {
|
private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
|
||||||
sessions.put(session);
|
sessions.put(session);
|
||||||
if (Config.X509_VERIFICATION) {
|
if (Config.X509_VERIFICATION) {
|
||||||
if (session.getIdentityKey() != null) {
|
if (session.getIdentityKey() != null) {
|
||||||
verifySessionWithPEP(session);
|
return verifySessionWithPEP(session);
|
||||||
} else {
|
} else {
|
||||||
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
|
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return Futures.immediateFuture(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum FetchStatus {
|
public enum FetchStatus {
|
||||||
|
@ -1690,4 +1762,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class NotVerifiedException extends SecurityException {
|
||||||
|
|
||||||
|
public NotVerifiedException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,16 +5,10 @@ import android.database.Cursor;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import net.java.otr4j.crypto.OtrCryptoEngineImpl;
|
|
||||||
import net.java.otr4j.crypto.OtrCryptoException;
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.interfaces.DSAPublicKey;
|
|
||||||
import java.util.Locale;
|
|
||||||
import eu.siacs.conversations.crypto.OtrService;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -88,7 +82,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
|
||||||
protected String hostname = null;
|
protected String hostname = null;
|
||||||
protected int port = 5222;
|
protected int port = 5222;
|
||||||
protected boolean online = false;
|
protected boolean online = false;
|
||||||
private OtrService mOtrService = null;
|
|
||||||
private String rosterVersion;
|
private String rosterVersion;
|
||||||
private String displayName = null;
|
private String displayName = null;
|
||||||
private AxolotlService axolotlService = null;
|
private AxolotlService axolotlService = null;
|
||||||
|
@ -403,16 +396,12 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
|
||||||
}
|
}
|
||||||
|
|
||||||
public void initAccountServices(final XmppConnectionService context) {
|
public void initAccountServices(final XmppConnectionService context) {
|
||||||
this.mOtrService = new OtrService(context, this);
|
|
||||||
this.axolotlService = new AxolotlService(this, context);
|
this.axolotlService = new AxolotlService(this, context);
|
||||||
this.pgpDecryptionService = new PgpDecryptionService(context);
|
this.pgpDecryptionService = new PgpDecryptionService(context);
|
||||||
if (xmppConnection != null) {
|
if (xmppConnection != null) {
|
||||||
xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
|
xmppConnection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public OtrService getOtrService() {
|
|
||||||
return this.mOtrService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PgpDecryptionService getPgpDecryptionService() {
|
public PgpDecryptionService getPgpDecryptionService() {
|
||||||
return this.pgpDecryptionService;
|
return this.pgpDecryptionService;
|
||||||
|
@ -425,25 +414,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
|
||||||
public void setXmppConnection(final XmppConnection connection) {
|
public void setXmppConnection(final XmppConnection connection) {
|
||||||
this.xmppConnection = connection;
|
this.xmppConnection = connection;
|
||||||
}
|
}
|
||||||
public String getOtrFingerprint() {
|
|
||||||
if (this.otrFingerprint == null) {
|
|
||||||
try {
|
|
||||||
if (this.mOtrService == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
final PublicKey publicKey = this.mOtrService.getPublicKey();
|
|
||||||
if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey).toLowerCase(Locale.US);
|
|
||||||
return this.otrFingerprint;
|
|
||||||
} catch (final OtrCryptoException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.otrFingerprint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getRosterVersion() {
|
public String getRosterVersion() {
|
||||||
if (this.rosterVersion == null) {
|
if (this.rosterVersion == null) {
|
||||||
|
@ -608,10 +578,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
|
||||||
|
|
||||||
private List<XmppUri.Fingerprint> getFingerprints() {
|
private List<XmppUri.Fingerprint> getFingerprints() {
|
||||||
ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
|
ArrayList<XmppUri.Fingerprint> fingerprints = new ArrayList<>();
|
||||||
final String otr = this.getOtrFingerprint();
|
|
||||||
if (otr != null) {
|
|
||||||
fingerprints.add(new XmppUri.Fingerprint(XmppUri.FingerprintType.OTR, otr));
|
|
||||||
}
|
|
||||||
if (axolotlService == null) {
|
if (axolotlService == null) {
|
||||||
return fingerprints;
|
return fingerprints;
|
||||||
}
|
}
|
||||||
|
|
|
@ -516,32 +516,6 @@ public class Contact implements ListItem, Blockable {
|
||||||
return avatar;
|
return avatar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public boolean deleteOtrFingerprint(String fingerprint) {
|
|
||||||
synchronized (this.keys) {
|
|
||||||
boolean success = false;
|
|
||||||
try {
|
|
||||||
if (this.keys.has("otr_fingerprints")) {
|
|
||||||
JSONArray newPrints = new JSONArray();
|
|
||||||
JSONArray oldPrints = this.keys
|
|
||||||
.getJSONArray("otr_fingerprints");
|
|
||||||
for (int i = 0; i < oldPrints.length(); ++i) {
|
|
||||||
if (!oldPrints.getString(i).equals(fingerprint)) {
|
|
||||||
newPrints.put(oldPrints.getString(i));
|
|
||||||
} else {
|
|
||||||
success = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.keys.put("otr_fingerprints", newPrints);
|
|
||||||
}
|
|
||||||
return success;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean mutualPresenceSubscription() {
|
public boolean mutualPresenceSubscription() {
|
||||||
return getOption(Options.FROM) && getOption(Options.TO);
|
return getOption(Options.FROM) && getOption(Options.TO);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,18 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.entities.Bookmark.printableValue;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import net.java.otr4j.OtrException;
|
|
||||||
import net.java.otr4j.crypto.OtrCryptoException;
|
|
||||||
import net.java.otr4j.session.SessionID;
|
|
||||||
import net.java.otr4j.session.SessionImpl;
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
|
||||||
import com.google.common.collect.ComparisonChain;
|
import com.google.common.collect.ComparisonChain;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import java.security.interfaces.DSAPublicKey;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
|
@ -22,6 +20,7 @@ import org.json.JSONObject;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
|
@ -34,13 +33,12 @@ import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||||
import eu.siacs.conversations.services.AvatarService;
|
import eu.siacs.conversations.services.AvatarService;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
import eu.siacs.conversations.utils.JidHelper;
|
import eu.siacs.conversations.utils.JidHelper;
|
||||||
|
import eu.siacs.conversations.utils.MessageUtils;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Bookmark.printableValue;
|
|
||||||
|
|
||||||
|
|
||||||
public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
|
public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
|
||||||
public static final String TABLENAME = "conversations";
|
public static final String TABLENAME = "conversations";
|
||||||
|
@ -84,15 +82,10 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
private int mode;
|
private int mode;
|
||||||
private JSONObject attributes;
|
private JSONObject attributes;
|
||||||
private Jid nextCounterpart;
|
private Jid nextCounterpart;
|
||||||
private transient SessionImpl otrSession;
|
|
||||||
private transient String otrFingerprint = null;
|
|
||||||
private Smp mSmp = new Smp();
|
|
||||||
private transient MucOptions mucOptions = null;
|
private transient MucOptions mucOptions = null;
|
||||||
private byte[] symmetricKey;
|
|
||||||
private boolean messagesLeftOnServer = true;
|
private boolean messagesLeftOnServer = true;
|
||||||
private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE;
|
private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE;
|
||||||
private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE;
|
private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE;
|
||||||
private String mLastReceivedOtrMessageId = null;
|
|
||||||
private String mFirstMamReference = null;
|
private String mFirstMamReference = null;
|
||||||
|
|
||||||
public Conversation(final String name, final Account account, final Jid contactJid,
|
public Conversation(final String name, final Account account, final Jid contactJid,
|
||||||
|
@ -305,9 +298,22 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
public Message findMessageWithFileAndUuid(final String uuid) {
|
public Message findMessageWithFileAndUuid(final String uuid) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (final Message message : this.messages) {
|
for (final Message message : this.messages) {
|
||||||
|
final Transferable transferable = message.getTransferable();
|
||||||
|
final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message);
|
||||||
if (message.getUuid().equals(uuid)
|
if (message.getUuid().equals(uuid)
|
||||||
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
&& message.getEncryption() != Message.ENCRYPTION_PGP
|
||||||
&& (message.isFileOrImage() || message.treatAsDownloadable())) {
|
&& (message.isFileOrImage() || message.treatAsDownloadable() || unInitiatedButKnownSize || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING))) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message findMessageWithUuid(final String uuid) {
|
||||||
|
synchronized (this.messages) {
|
||||||
|
for (final Message message : this.messages) {
|
||||||
|
if (message.getUuid().equals(uuid)) {
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -386,7 +392,13 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
public void trim() {
|
public void trim() {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
final int size = messages.size();
|
final int size = messages.size();
|
||||||
final int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES;
|
int maxsize = Config.PAGE_SIZE * Config.MAX_NUM_PAGES;
|
||||||
|
if (getAccount() != null && getAccount().getXmppConnection() != null && getAccount().getXmppConnection().getXmppConnectionService() != null) {
|
||||||
|
SharedPreferences pref = PreferenceManager.getDefaultSharedPreferences(getAccount().getXmppConnection().getXmppConnectionService());
|
||||||
|
int pagesize = Integer.parseInt(pref.getString("pagesize", String.valueOf(Config.PAGE_SIZE)));
|
||||||
|
int maxnumpages = Integer.parseInt(pref.getString("max_num_pages", String.valueOf(Config.MAX_NUM_PAGES)));
|
||||||
|
maxsize = pagesize * maxnumpages;
|
||||||
|
}
|
||||||
if (size > maxsize) {
|
if (size > maxsize) {
|
||||||
List<Message> discards = this.messages.subList(0, size - maxsize);
|
List<Message> discards = this.messages.subList(0, size - maxsize);
|
||||||
final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService();
|
final PgpDecryptionService pgpDecryptionService = account.getPgpDecryptionService();
|
||||||
|
@ -399,17 +411,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void findUnsentMessagesWithEncryption(int encryptionType, OnMessageFound onMessageFound) {
|
|
||||||
synchronized (this.messages) {
|
|
||||||
for (Message message : this.messages) {
|
|
||||||
if ((message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_WAITING)
|
|
||||||
&& (message.getEncryption() == encryptionType)) {
|
|
||||||
onMessageFound.onMessageFound(message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void findUnsentTextMessages(OnMessageFound onMessageFound) {
|
public void findUnsentTextMessages(OnMessageFound onMessageFound) {
|
||||||
final ArrayList<Message> results = new ArrayList<>();
|
final ArrayList<Message> results = new ArrayList<>();
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
|
@ -425,13 +426,24 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message findSentMessageWithUuidOrRemoteId(String id) {
|
public Message findSentMessageWithUuidOrRemoteId(String id) {
|
||||||
|
return findSentMessageWithUuidOrRemoteId(id, false, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message findSentMessageWithUuidOrRemoteId(String id, boolean ignorestatus, boolean withedits) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (Message message : this.messages) {
|
for (Message message : this.messages) {
|
||||||
if (id.equals(message.getUuid())
|
|
||||||
|| (message.getStatus() >= Message.STATUS_SEND
|
if (id.equals(message.getUuid()) || ((message.getStatus() >= Message.STATUS_SEND || ignorestatus) && id.equals(message.getRemoteMsgId()))) {
|
||||||
&& id.equals(message.getRemoteMsgId()))) {
|
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (withedits) {
|
||||||
|
for (Edit itm : message.edits) {
|
||||||
|
if (id.equals(itm.getEditedId())) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -450,8 +462,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
|
final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
|
||||||
if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {
|
if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {
|
||||||
return message;
|
return message;
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -504,10 +514,51 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Message> filterDuplicates(List<Message> list) {
|
||||||
|
HashMap<String, Message> items = new HashMap<String, Message>();
|
||||||
|
for (Message item : list) {
|
||||||
|
items.put(item.getUuid(), item);
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<Message> result = new ArrayList<Message>(items.values());
|
||||||
|
Collections.sort(result, (o1, o2) -> {
|
||||||
|
if (o1.getTimeSent() < o2.getTimeSent())
|
||||||
|
return -1;
|
||||||
|
if (o1.getTimeSent() > o2.getTimeSent())
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
public void populateWithMessages(final List<Message> messages) {
|
public void populateWithMessages(final List<Message> messages) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
messages.clear();
|
messages.clear();
|
||||||
messages.addAll(this.messages);
|
messages.addAll(filterDuplicates(this.messages));
|
||||||
|
|
||||||
|
for (int n = 0; n < messages.size(); n++) {
|
||||||
|
if (messages.get(n).isMessageDeleted()) {
|
||||||
|
messages.remove(n);
|
||||||
|
n--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.get(n).getRetractId() != null) {
|
||||||
|
if (messages.get(n).getStatus() != Message.STATUS_RECEIVED) {
|
||||||
|
messages.remove(n);
|
||||||
|
n--;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Message itm : messages) {
|
||||||
|
if (itm.isMessageDeleted()) {
|
||||||
|
if (itm.getEditedList().size() > 0) {
|
||||||
|
itm.setTime(itm.getEditedList().get(0).getTimeSent());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (Iterator<Message> iterator = messages.iterator(); iterator.hasNext(); ) {
|
for (Iterator<Message> iterator = messages.iterator(); iterator.hasNext(); ) {
|
||||||
if (iterator.next().wasMergedIntoPrevious()) {
|
if (iterator.next().wasMergedIntoPrevious()) {
|
||||||
|
@ -530,13 +581,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
public Jid getBlockedJid() {
|
public Jid getBlockedJid() {
|
||||||
return getContact().getBlockedJid();
|
return getContact().getBlockedJid();
|
||||||
}
|
}
|
||||||
public String getLastReceivedOtrMessageId() {
|
|
||||||
return this.mLastReceivedOtrMessageId;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setLastReceivedOtrMessageId(String id) {
|
|
||||||
this.mLastReceivedOtrMessageId = id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int countMessages() {
|
public int countMessages() {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
|
@ -734,112 +778,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
public void setMode(int mode) {
|
public void setMode(int mode) {
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
}
|
}
|
||||||
public SessionImpl startOtrSession(String presence, boolean sendStart) {
|
|
||||||
if (this.otrSession != null) {
|
|
||||||
return this.otrSession;
|
|
||||||
} else {
|
|
||||||
final SessionID sessionId = new SessionID(this.getJid().asBareJid().toString(),
|
|
||||||
presence,
|
|
||||||
"xmpp");
|
|
||||||
this.otrSession = new SessionImpl(sessionId, getAccount().getOtrService());
|
|
||||||
try {
|
|
||||||
if (sendStart) {
|
|
||||||
this.otrSession.startSession();
|
|
||||||
return this.otrSession;
|
|
||||||
}
|
|
||||||
return this.otrSession;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public SessionImpl getOtrSession() {
|
|
||||||
return this.otrSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void resetOtrSession() {
|
|
||||||
this.otrFingerprint = null;
|
|
||||||
this.otrSession = null;
|
|
||||||
this.mSmp.hint = null;
|
|
||||||
this.mSmp.secret = null;
|
|
||||||
this.mSmp.status = Smp.STATUS_NONE;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Smp smp() {
|
|
||||||
return mSmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean startOtrIfNeeded() {
|
|
||||||
if (this.otrSession != null && this.otrSession.getSessionStatus() != SessionStatus.ENCRYPTED) {
|
|
||||||
try {
|
|
||||||
this.otrSession.startSession();
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
this.resetOtrSession();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean endOtrIfNeeded() {
|
|
||||||
if (this.otrSession != null) {
|
|
||||||
if (this.otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
|
||||||
try {
|
|
||||||
this.otrSession.endSession();
|
|
||||||
this.resetOtrSession();
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
this.resetOtrSession();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.resetOtrSession();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasValidOtrSession() {
|
|
||||||
return this.otrSession != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public synchronized String getOtrFingerprint() {
|
|
||||||
if (this.otrFingerprint == null) {
|
|
||||||
try {
|
|
||||||
if (getOtrSession() == null || getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
|
|
||||||
this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US);
|
|
||||||
} catch (final OtrCryptoException ignored) {
|
|
||||||
return null;
|
|
||||||
} catch (final UnsupportedOperationException ignored) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.otrFingerprint;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean verifyOtrFingerprint() {
|
|
||||||
final String fingerprint = getOtrFingerprint();
|
|
||||||
if (fingerprint != null) {
|
|
||||||
getContact().addOtrFingerprint(fingerprint);
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOtrFingerprintVerified() {
|
|
||||||
return getContact().getOtrFingerprints().contains(getOtrFingerprint());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* short for is Private and Non-anonymous
|
* short for is Private and Non-anonymous
|
||||||
|
@ -876,7 +814,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getNextEncryption() {
|
public int getNextEncryption() {
|
||||||
if (!Config.supportOmemo() && !Config.supportOpenPgp() && !Config.supportOtr()) {
|
if (!Config.supportOmemo() && !Config.supportOpenPgp()) {
|
||||||
return Message.ENCRYPTION_NONE;
|
return Message.ENCRYPTION_NONE;
|
||||||
}
|
}
|
||||||
if (OmemoSetting.isAlways()) {
|
if (OmemoSetting.isAlways()) {
|
||||||
|
@ -892,10 +830,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
defaultEncryption = Message.ENCRYPTION_NONE;
|
defaultEncryption = Message.ENCRYPTION_NONE;
|
||||||
}
|
}
|
||||||
int encryption = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, defaultEncryption);
|
int encryption = this.getIntAttribute(ATTRIBUTE_NEXT_ENCRYPTION, defaultEncryption);
|
||||||
if (encryption < 0) {
|
if (encryption == Message.ENCRYPTION_OTR || encryption < 0) {
|
||||||
return defaultEncryption;
|
return defaultEncryption;
|
||||||
} else if (encryption == Message.ENCRYPTION_OTR) {
|
|
||||||
return encryption;
|
|
||||||
} else {
|
} else {
|
||||||
return encryption;
|
return encryption;
|
||||||
}
|
}
|
||||||
|
@ -909,9 +845,6 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
final String nextMessage = getAttribute(ATTRIBUTE_NEXT_MESSAGE);
|
final String nextMessage = getAttribute(ATTRIBUTE_NEXT_MESSAGE);
|
||||||
return nextMessage == null ? "" : nextMessage;
|
return nextMessage == null ? "" : nextMessage;
|
||||||
}
|
}
|
||||||
public boolean smpRequested() {
|
|
||||||
return smp().status == Smp.STATUS_CONTACT_REQUESTED;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable
|
public @Nullable
|
||||||
Draft getDraft() {
|
Draft getDraft() {
|
||||||
|
@ -934,24 +867,22 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
public void setSymmetricKey(byte[] key) {
|
|
||||||
this.symmetricKey = key;
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getSymmetricKey() {
|
|
||||||
return this.symmetricKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Bookmark getBookmark() {
|
public Bookmark getBookmark() {
|
||||||
return this.account.getBookmark(this.contactJid);
|
return this.account.getBookmark(this.contactJid);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message findDuplicateMessage(Message message) {
|
public Message findDuplicateMessage(Message message, boolean withremoteid) {
|
||||||
synchronized (this.messages) {
|
synchronized (this.messages) {
|
||||||
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
for (int i = this.messages.size() - 1; i >= 0; --i) {
|
||||||
if (this.messages.get(i).similar(message)) {
|
if (this.messages.get(i).similar(message)) {
|
||||||
return this.messages.get(i);
|
return this.messages.get(i);
|
||||||
}
|
}
|
||||||
|
if (withremoteid) {
|
||||||
|
if (this.messages.get(i).remoteMsgId != null && message.getRemoteMsgId() != null && this.messages.get(i).remoteMsgId.equals(message.getRemoteMsgId())) {
|
||||||
|
return this.messages.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1308,15 +1239,23 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
return message;
|
return message;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public class Smp {
|
|
||||||
public static final int STATUS_NONE = 0;
|
|
||||||
public static final int STATUS_CONTACT_REQUESTED = 1;
|
|
||||||
public static final int STATUS_WE_REQUESTED = 2;
|
|
||||||
public static final int STATUS_FAILED = 3;
|
|
||||||
public static final int STATUS_VERIFIED = 4;
|
|
||||||
|
|
||||||
public String secret = null;
|
public Message findDuplicateMessage(Message message) {
|
||||||
public String hint = null;
|
return findDuplicateMessage(message, false);
|
||||||
public int status = 0;
|
}
|
||||||
|
|
||||||
|
public boolean hasDuplicateMessage(Message message, boolean withremoteid) {
|
||||||
|
return findDuplicateMessage(message, withremoteid) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Message findMessageWithUuidOrRemoteId(final String id) {
|
||||||
|
synchronized (this.messages) {
|
||||||
|
for (final Message message : this.messages) {
|
||||||
|
if (message.getRemoteMsgId() != null && message.getRemoteMsgId().equals(id) || message.getUuid().equals(id)) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,16 +11,20 @@ public class Edit {
|
||||||
|
|
||||||
private final String editedId;
|
private final String editedId;
|
||||||
private final String serverMsgId;
|
private final String serverMsgId;
|
||||||
|
private String body;
|
||||||
|
private final long timeSent;
|
||||||
|
|
||||||
Edit(String editedId, String serverMsgId) {
|
Edit(String editedId, String serverMsgId, String body, long timeSent) {
|
||||||
this.editedId = editedId;
|
this.editedId = editedId;
|
||||||
this.serverMsgId = serverMsgId;
|
this.serverMsgId = serverMsgId;
|
||||||
|
this.body = body;
|
||||||
|
this.timeSent = timeSent;
|
||||||
}
|
}
|
||||||
|
|
||||||
static String toJson(List<Edit> edits) throws JSONException {
|
static String toJson(List<Edit> edits, boolean hidebody) throws JSONException {
|
||||||
JSONArray jsonArray = new JSONArray();
|
JSONArray jsonArray = new JSONArray();
|
||||||
for (Edit edit : edits) {
|
for (Edit edit : edits) {
|
||||||
jsonArray.put(edit.toJson());
|
jsonArray.put(edit.toJson(hidebody));
|
||||||
}
|
}
|
||||||
return jsonArray.toString();
|
return jsonArray.toString();
|
||||||
}
|
}
|
||||||
|
@ -46,7 +50,9 @@ public class Edit {
|
||||||
private static Edit fromJson(JSONObject jsonObject) throws JSONException {
|
private static Edit fromJson(JSONObject jsonObject) throws JSONException {
|
||||||
String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null;
|
String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null;
|
||||||
String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null;
|
String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null;
|
||||||
return new Edit(edited, serverMsgId);
|
String body = jsonObject.has("body") ? jsonObject.getString("body") : null;
|
||||||
|
long timeSent = jsonObject.has("timeSent") ? jsonObject.getLong("timeSent") : null;
|
||||||
|
return new Edit(edited, serverMsgId, body, timeSent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<Edit> fromJson(String input) {
|
static List<Edit> fromJson(String input) {
|
||||||
|
@ -65,17 +71,15 @@ public class Edit {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONObject toJson() throws JSONException {
|
private JSONObject toJson(boolean hidebody) throws JSONException {
|
||||||
JSONObject jsonObject = new JSONObject();
|
JSONObject jsonObject = new JSONObject();
|
||||||
jsonObject.put("edited_id", editedId);
|
jsonObject.put("edited_id", editedId);
|
||||||
jsonObject.put("server_msg_id", serverMsgId);
|
jsonObject.put("server_msg_id", serverMsgId);
|
||||||
|
jsonObject.put("body", hidebody ? "" : body);
|
||||||
|
jsonObject.put("timeSent", timeSent);
|
||||||
return jsonObject;
|
return jsonObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getEditedId() {
|
|
||||||
return editedId;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
@ -94,4 +98,18 @@ public class Edit {
|
||||||
result = 31 * result + (serverMsgId != null ? serverMsgId.hashCode() : 0);
|
result = 31 * result + (serverMsgId != null ? serverMsgId.hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getServerMsgId() {
|
||||||
|
return serverMsgId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEditedId() { return editedId; }
|
||||||
|
|
||||||
|
public long getTimeSent() {
|
||||||
|
return timeSent;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -41,8 +41,8 @@ public class IndividualMessage extends Message {
|
||||||
super(conversation);
|
super(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted, String bodyLanguage) {
|
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted, String bodyLanguage, String retractId) {
|
||||||
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted, bodyLanguage);
|
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted, bodyLanguage,retractId);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message createDateSeparator(Message message) {
|
public static Message createDateSeparator(Message message) {
|
||||||
|
@ -101,7 +101,8 @@ public class IndividualMessage extends Message {
|
||||||
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
|
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
|
||||||
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
|
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
|
||||||
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
|
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
|
||||||
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
|
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)),
|
||||||
|
cursor.getString(cursor.getColumnIndex(RETRACT_ID))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,7 +91,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
public static final String ME_COMMAND = "/me";
|
public static final String ME_COMMAND = "/me";
|
||||||
public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled";
|
public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled";
|
||||||
public static final String DELETED_MESSAGE_BODY = "eu.siacs.conversations.message_deleted";
|
public static final String DELETED_MESSAGE_BODY = "eu.siacs.conversations.message_deleted";
|
||||||
public static final String DELETED_MESSAGE_BODY_OLD = "de.monocles.chat.message_deleted";
|
public static final String DELETED_MESSAGE_BODY_OLD = "de.pixart.messenger.message_deleted";
|
||||||
|
public static final String RETRACT_ID = "retractId";
|
||||||
|
|
||||||
public boolean markable = false;
|
public boolean markable = false;
|
||||||
protected String conversationUuid;
|
protected String conversationUuid;
|
||||||
|
@ -111,6 +112,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
protected boolean read = true;
|
protected boolean read = true;
|
||||||
protected boolean deleted = false;
|
protected boolean deleted = false;
|
||||||
protected String remoteMsgId = null;
|
protected String remoteMsgId = null;
|
||||||
|
|
||||||
private String bodyLanguage = null;
|
private String bodyLanguage = null;
|
||||||
protected String serverMsgId = null;
|
protected String serverMsgId = null;
|
||||||
private final Conversational conversation;
|
private final Conversational conversation;
|
||||||
|
@ -120,6 +122,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
private String axolotlFingerprint = null;
|
private String axolotlFingerprint = null;
|
||||||
private String errorMessage = null;
|
private String errorMessage = null;
|
||||||
private Set<ReadByMarker> readByMarkers = new CopyOnWriteArraySet<>();
|
private Set<ReadByMarker> readByMarkers = new CopyOnWriteArraySet<>();
|
||||||
|
private String retractId = null;
|
||||||
|
|
||||||
private Boolean isGeoUri = null;
|
private Boolean isGeoUri = null;
|
||||||
private Boolean isXmppUri = null;
|
private Boolean isXmppUri = null;
|
||||||
|
@ -162,6 +165,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,6 +192,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
null,
|
null,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
null,
|
||||||
null);
|
null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,7 +202,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
final String remoteMsgId, final String relativeFilePath,
|
final String remoteMsgId, final String relativeFilePath,
|
||||||
final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted,
|
final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted,
|
||||||
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
|
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
|
||||||
final boolean markable, final boolean file_deleted, final String bodyLanguage) {
|
final boolean markable, final boolean file_deleted, final String bodyLanguage, final String retractId) {
|
||||||
this.conversation = conversation;
|
this.conversation = conversation;
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.conversationUuid = conversationUUid;
|
this.conversationUuid = conversationUUid;
|
||||||
|
@ -222,6 +227,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
this.markable = markable;
|
this.markable = markable;
|
||||||
this.file_deleted = file_deleted;
|
this.file_deleted = file_deleted;
|
||||||
this.bodyLanguage = bodyLanguage;
|
this.bodyLanguage = bodyLanguage;
|
||||||
|
this.retractId = retractId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Message fromCursor(Cursor cursor, Conversation conversation) {
|
public static Message fromCursor(Cursor cursor, Conversation conversation) {
|
||||||
|
@ -248,7 +254,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
|
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
|
||||||
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
|
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
|
||||||
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
|
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
|
||||||
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
|
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE)),
|
||||||
|
cursor.getString(cursor.getColumnIndex(RETRACT_ID))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,7 +313,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
values.put(READ, read ? 1 : 0);
|
values.put(READ, read ? 1 : 0);
|
||||||
values.put(DELETED, deleted ? 1 : 0);
|
values.put(DELETED, deleted ? 1 : 0);
|
||||||
try {
|
try {
|
||||||
values.put(EDITED, Edit.toJson(edits));
|
values.put(EDITED, Edit.toJson(edits, retractId != null || deleted));
|
||||||
} catch (JSONException e) {
|
} catch (JSONException e) {
|
||||||
Log.e(Config.LOGTAG, "error persisting json for edits", e);
|
Log.e(Config.LOGTAG, "error persisting json for edits", e);
|
||||||
}
|
}
|
||||||
|
@ -316,6 +323,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
values.put(MARKABLE, markable ? 1 : 0);
|
values.put(MARKABLE, markable ? 1 : 0);
|
||||||
values.put(FILE_DELETED, file_deleted ? 1 : 0);
|
values.put(FILE_DELETED, file_deleted ? 1 : 0);
|
||||||
values.put(BODY_LANGUAGE, bodyLanguage);
|
values.put(BODY_LANGUAGE, bodyLanguage);
|
||||||
|
values.put(RETRACT_ID, retractId);
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -486,8 +494,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
this.carbon = carbon;
|
this.carbon = carbon;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putEdited(String edited, String serverMsgId) {
|
public void putEdited(String edited, String serverMsgId, String body, long timeSent) {
|
||||||
final Edit edit = new Edit(edited, serverMsgId);
|
final Edit edit = new Edit(edited, serverMsgId, body, timeSent);
|
||||||
if (this.edits.size() < 128 && !this.edits.contains(edit)) {
|
if (this.edits.size() < 128 && !this.edits.contains(edit)) {
|
||||||
this.edits.add(edit);
|
this.edits.add(edit);
|
||||||
}
|
}
|
||||||
|
@ -526,6 +534,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
return this.transferable;
|
return this.transferable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getRetractId() {
|
||||||
|
return this.retractId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetractId(String id) {
|
||||||
|
this.retractId = id;
|
||||||
|
}
|
||||||
|
|
||||||
public synchronized void setTransferable(Transferable transferable) {
|
public synchronized void setTransferable(Transferable transferable) {
|
||||||
this.fileParams = null;
|
this.fileParams = null;
|
||||||
this.transferable = transferable;
|
this.transferable = transferable;
|
||||||
|
@ -647,11 +663,13 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isEditable() {
|
public boolean isEditable() {
|
||||||
return status != STATUS_RECEIVED && !isCarbon() && type != Message.TYPE_RTP_SESSION;
|
return status != STATUS_RECEIVED && type != Message.TYPE_RTP_SESSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean mergeable(final Message message) {
|
public boolean mergeable(final Message message) {
|
||||||
return message != null &&
|
try {
|
||||||
|
boolean mergeAllowed = conversation.getAccount().getXmppConnection().getXmppConnectionService().allowMergeMessages();
|
||||||
|
return mergeAllowed && message != null &&
|
||||||
(message.getType() == Message.TYPE_TEXT &&
|
(message.getType() == Message.TYPE_TEXT &&
|
||||||
this.getTransferable() == null &&
|
this.getTransferable() == null &&
|
||||||
message.getTransferable() == null &&
|
message.getTransferable() == null &&
|
||||||
|
@ -681,11 +699,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
!this.bodyIsOnlyEmojis() &&
|
!this.bodyIsOnlyEmojis() &&
|
||||||
!message.isXmppUri() &&
|
!message.isXmppUri() &&
|
||||||
!this.isXmppUri() &&
|
!this.isXmppUri() &&
|
||||||
|
!message.hasDeletedBody() &&
|
||||||
|
!this.hasDeletedBody() &&
|
||||||
((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint())) &&
|
((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint())) &&
|
||||||
UIHelper.sameDay(message.getTimeSent(), this.getTimeSent()) &&
|
UIHelper.sameDay(message.getTimeSent(), this.getTimeSent()) &&
|
||||||
this.getReadByMarkers().equals(message.getReadByMarkers()) &&
|
this.getReadByMarkers().equals(message.getReadByMarkers()) &&
|
||||||
!this.conversation.getJid().asBareJid().equals(Config.BUG_REPORTS)
|
!this.conversation.getJid().asBareJid().equals(Config.BUG_REPORTS)
|
||||||
);
|
);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isStatusMergeable(int a, int b) {
|
private static boolean isStatusMergeable(int a, int b) {
|
||||||
|
@ -746,6 +770,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
return this.body.trim().startsWith(ME_COMMAND);
|
return this.body.trim().startsWith(ME_COMMAND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean hasDeletedBody() {
|
||||||
|
return this.body.trim().equals(DELETED_MESSAGE_BODY) || this.body.trim().equals(DELETED_MESSAGE_BODY_OLD);
|
||||||
|
}
|
||||||
|
|
||||||
public int getMergedStatus() {
|
public int getMergedStatus() {
|
||||||
int status = this.status;
|
int status = this.status;
|
||||||
Message current = this;
|
Message current = this;
|
||||||
|
@ -807,6 +835,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Edit> getEditedList() {
|
||||||
|
return edits;
|
||||||
|
}
|
||||||
|
|
||||||
public String getEditedIdWireFormat() {
|
public String getEditedIdWireFormat() {
|
||||||
if (edits.size() > 0) {
|
if (edits.size() > 0) {
|
||||||
return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId();
|
return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId();
|
||||||
|
@ -977,6 +1009,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
return isFileOrImage() && getFileParams().url == null;
|
return isFileOrImage() && getFileParams().url == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean fileIsTransferring() {
|
||||||
|
return transferable.getStatus() == Transferable.STATUS_DOWNLOADING || transferable.getStatus() == Transferable.STATUS_UPLOADING || transferable.getStatus() == Transferable.STATUS_WAITING;
|
||||||
|
}
|
||||||
|
|
||||||
public static class FileParams {
|
public static class FileParams {
|
||||||
public String url;
|
public String url;
|
||||||
public Long size = null;
|
public Long size = null;
|
||||||
|
@ -984,7 +1020,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
public int height = 0;
|
public int height = 0;
|
||||||
public int runtime = 0;
|
public int runtime = 0;
|
||||||
public String subject = "";
|
public String subject = "";
|
||||||
|
|
||||||
public long getSize() {
|
public long getSize() {
|
||||||
return size == null ? 0 : size;
|
return size == null ? 0 : size;
|
||||||
}
|
}
|
||||||
|
@ -1066,13 +1101,28 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
||||||
}
|
}
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
final Jid nextCounterpart = conversation.getNextCounterpart();
|
final Jid nextCounterpart = conversation.getNextCounterpart();
|
||||||
if (nextCounterpart != null) {
|
return configurePrivateMessage(conversation, message, nextCounterpart, isFile);
|
||||||
message.setCounterpart(nextCounterpart);
|
|
||||||
message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(nextCounterpart));
|
|
||||||
message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean configurePrivateMessage(final Message message, final Jid counterpart) {
|
||||||
|
final Conversation conversation;
|
||||||
|
if (message.conversation instanceof Conversation) {
|
||||||
|
conversation = (Conversation) message.conversation;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return configurePrivateMessage(conversation, message, counterpart, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean configurePrivateMessage(final Conversation conversation, final Message message, final Jid counterpart, final boolean isFile) {
|
||||||
|
if (counterpart == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
message.setCounterpart(counterpart);
|
||||||
|
message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart));
|
||||||
|
message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.siacs.conversations.entities;
|
package eu.siacs.conversations.entities;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -25,7 +24,6 @@ import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.forms.Field;
|
import eu.siacs.conversations.xmpp.forms.Field;
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
|
|
||||||
@SuppressLint("DefaultLocale")
|
|
||||||
public class MucOptions {
|
public class MucOptions {
|
||||||
|
|
||||||
public static final String STATUS_CODE_SELF_PRESENCE = "110";
|
public static final String STATUS_CODE_SELF_PRESENCE = "110";
|
||||||
|
@ -213,6 +211,10 @@ public class MucOptions {
|
||||||
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
|
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean stableId() {
|
||||||
|
return getFeatures().contains("http://jabber.org/protocol/muc#stable_id");
|
||||||
|
}
|
||||||
|
|
||||||
public User deleteUser(Jid jid) {
|
public User deleteUser(Jid jid) {
|
||||||
User user = findUserByFullJid(jid);
|
User user = findUserByFullJid(jid);
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
@ -630,8 +632,8 @@ public class MucOptions {
|
||||||
OUTCAST(0, R.string.outcast),
|
OUTCAST(0, R.string.outcast),
|
||||||
NONE(1, R.string.no_affiliation);
|
NONE(1, R.string.no_affiliation);
|
||||||
|
|
||||||
private int resId;
|
private final int resId;
|
||||||
private int rank;
|
private final int rank;
|
||||||
|
|
||||||
Affiliation(int rank, int resId) {
|
Affiliation(int rank, int resId) {
|
||||||
this.resId = resId;
|
this.resId = resId;
|
||||||
|
@ -673,8 +675,8 @@ public class MucOptions {
|
||||||
PARTICIPANT(R.string.participant, 2),
|
PARTICIPANT(R.string.participant, 2),
|
||||||
NONE(R.string.no_role, 0);
|
NONE(R.string.no_role, 0);
|
||||||
|
|
||||||
private int resId;
|
private final int resId;
|
||||||
private int rank;
|
private final int rank;
|
||||||
|
|
||||||
Role(int resId, int rank) {
|
Role(int resId, int rank) {
|
||||||
this.resId = resId;
|
this.resId = resId;
|
||||||
|
@ -741,7 +743,7 @@ public class MucOptions {
|
||||||
private Jid fullJid;
|
private Jid fullJid;
|
||||||
private long pgpKeyId = 0;
|
private long pgpKeyId = 0;
|
||||||
private Avatar avatar;
|
private Avatar avatar;
|
||||||
private MucOptions options;
|
private final MucOptions options;
|
||||||
private ChatState chatState = Config.DEFAULT_CHAT_STATE;
|
private ChatState chatState = Config.DEFAULT_CHAT_STATE;
|
||||||
|
|
||||||
public User(MucOptions options, Jid fullJid) {
|
public User(MucOptions options, Jid fullJid) {
|
||||||
|
@ -852,7 +854,7 @@ public class MucOptions {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[fulljid:" + String.valueOf(fullJid) + ",realjid:" + String.valueOf(realJid) + ",affiliation" + affiliation.toString() + "]";
|
return "[fulljid:" + fullJid + ",realjid:" + realJid + ",affiliation" + affiliation.toString() + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean realJidMatchesAccount() {
|
public boolean realJidMatchesAccount() {
|
||||||
|
|
|
@ -12,7 +12,6 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
import eu.siacs.conversations.BuildConfig;
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
@ -54,12 +53,12 @@ public abstract class AbstractGenerator {
|
||||||
private final String[] MESSAGE_CORRECTION_FEATURES = {
|
private final String[] MESSAGE_CORRECTION_FEATURES = {
|
||||||
"urn:xmpp:message-correct:0"
|
"urn:xmpp:message-correct:0"
|
||||||
};
|
};
|
||||||
|
private final String[] MESSAGE_RETRACTION_FEATURES = {
|
||||||
|
"urn:xmpp:message-retract:0"
|
||||||
|
};
|
||||||
private final String[] PRIVACY_SENSITIVE = {
|
private final String[] PRIVACY_SENSITIVE = {
|
||||||
"urn:xmpp:time" //XEP-0202: Entity Time leaks time zone
|
"urn:xmpp:time" //XEP-0202: Entity Time leaks time zone
|
||||||
};
|
};
|
||||||
private final String[] OTR = {
|
|
||||||
"urn:xmpp:otr:0"
|
|
||||||
};
|
|
||||||
private final String[] VOIP_NAMESPACES = {
|
private final String[] VOIP_NAMESPACES = {
|
||||||
Namespace.JINGLE_TRANSPORT_ICE_UDP,
|
Namespace.JINGLE_TRANSPORT_ICE_UDP,
|
||||||
Namespace.JINGLE_FEATURE_AUDIO,
|
Namespace.JINGLE_FEATURE_AUDIO,
|
||||||
|
@ -70,6 +69,7 @@ public abstract class AbstractGenerator {
|
||||||
};
|
};
|
||||||
|
|
||||||
protected XmppConnectionService mXmppConnectionService;
|
protected XmppConnectionService mXmppConnectionService;
|
||||||
|
private String mVersion = null;
|
||||||
|
|
||||||
AbstractGenerator(XmppConnectionService service) {
|
AbstractGenerator(XmppConnectionService service) {
|
||||||
this.mXmppConnectionService = service;
|
this.mXmppConnectionService = service;
|
||||||
|
@ -81,11 +81,18 @@ public abstract class AbstractGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
String getIdentityVersion() {
|
String getIdentityVersion() {
|
||||||
return BuildConfig.VERSION_NAME;
|
if (mVersion == null) {
|
||||||
|
this.mVersion = PhoneHelper.getVersionName(mXmppConnectionService);
|
||||||
|
}
|
||||||
|
return this.mVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIdentityName() {
|
public String getIdentityName() {
|
||||||
return BuildConfig.APP_NAME;
|
return mXmppConnectionService.getString(R.string.app_name) + ' ' + getIdentityVersion();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserAgent() {
|
||||||
|
return System.getProperty("http.agent");
|
||||||
}
|
}
|
||||||
|
|
||||||
String getIdentityType() {
|
String getIdentityType() {
|
||||||
|
@ -122,6 +129,9 @@ public abstract class AbstractGenerator {
|
||||||
if (mXmppConnectionService.allowMessageCorrection()) {
|
if (mXmppConnectionService.allowMessageCorrection()) {
|
||||||
features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES));
|
features.addAll(Arrays.asList(MESSAGE_CORRECTION_FEATURES));
|
||||||
}
|
}
|
||||||
|
if (mXmppConnectionService.allowMessageRetraction()) {
|
||||||
|
features.addAll(Arrays.asList(MESSAGE_RETRACTION_FEATURES));
|
||||||
|
}
|
||||||
if (Config.supportOmemo()) {
|
if (Config.supportOmemo()) {
|
||||||
features.add(AxolotlService.PEP_DEVICE_LIST_NOTIFY);
|
features.add(AxolotlService.PEP_DEVICE_LIST_NOTIFY);
|
||||||
}
|
}
|
||||||
|
@ -129,9 +139,6 @@ public abstract class AbstractGenerator {
|
||||||
features.addAll(Arrays.asList(PRIVACY_SENSITIVE));
|
features.addAll(Arrays.asList(PRIVACY_SENSITIVE));
|
||||||
features.addAll(Arrays.asList(VOIP_NAMESPACES));
|
features.addAll(Arrays.asList(VOIP_NAMESPACES));
|
||||||
}
|
}
|
||||||
if (Config.supportOtr()) {
|
|
||||||
features.addAll(Arrays.asList(OTR));
|
|
||||||
}
|
|
||||||
if (mXmppConnectionService.broadcastLastActivity()) {
|
if (mXmppConnectionService.broadcastLastActivity()) {
|
||||||
features.add(Namespace.IDLE);
|
features.add(Namespace.IDLE);
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,11 +22,8 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
import net.java.otr4j.OtrException;
|
|
||||||
import net.java.otr4j.session.Session;
|
|
||||||
|
|
||||||
public class MessageGenerator extends AbstractGenerator {
|
public class MessageGenerator extends AbstractGenerator {
|
||||||
public static final String OTR_FALLBACK_MESSAGE = "I would like to start a private (OTR encrypted) conversation but your client doesn’t seem to support that";
|
|
||||||
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
|
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
|
||||||
private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
|
private static final String PGP_FALLBACK_MESSAGE = "I sent you a PGP encrypted message but your client doesn’t seem to support that.";
|
||||||
|
|
||||||
|
@ -61,9 +58,17 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
}
|
}
|
||||||
packet.setFrom(account.getJid());
|
packet.setFrom(account.getJid());
|
||||||
packet.setId(message.getUuid());
|
packet.setId(message.getUuid());
|
||||||
|
if (conversation.getMode() == Conversational.MODE_SINGLE || message.isPrivateMessage() || !conversation.getMucOptions().stableId()) {
|
||||||
packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
|
packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
|
||||||
if (message.edited()) {
|
}
|
||||||
|
if (message.edited() && !message.isMessageDeleted()) {
|
||||||
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
|
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
|
||||||
|
} else if (message.isMessageDeleted()) {
|
||||||
|
Element apply = packet.addChild("apply-to", "urn:xmpp:fasten:0").setAttribute("id", (message.getRetractId() != null ? message.getRetractId() : (message.getRemoteMsgId() != null ? message.getRemoteMsgId() : (message.getEditedIdWireFormat() != null ? message.getEditedIdWireFormat() : message.getUuid()))));
|
||||||
|
apply.addChild("retract", "urn:xmpp:message-retract:0");
|
||||||
|
packet.addChild("fallback", "urn:xmpp:fallback:0");
|
||||||
|
packet.addChild("store", "urn:xmpp:hints");
|
||||||
|
packet.setBody("This person attempted to retract a previous message, but it's unsupported by your client.");
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
@ -107,29 +112,6 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
packet.addChild("no-permanent-store", "urn:xmpp:hints");
|
packet.addChild("no-permanent-store", "urn:xmpp:hints");
|
||||||
packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store*
|
packet.addChild("no-permanent-storage", "urn:xmpp:hints"); //do not copy this. this is wrong. it is *store*
|
||||||
}
|
}
|
||||||
public MessagePacket generateOtrChat(Message message) {
|
|
||||||
Conversation conversation = (Conversation) message.getConversation();
|
|
||||||
Session otrSession = conversation.getOtrSession();
|
|
||||||
if (otrSession == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
MessagePacket packet = preparePacket(message);
|
|
||||||
addMessageHints(packet);
|
|
||||||
try {
|
|
||||||
String content;
|
|
||||||
if (message.hasFileOnRemoteHost()) {
|
|
||||||
content = message.getFileParams().url.toString();
|
|
||||||
} else {
|
|
||||||
content = message.getBody();
|
|
||||||
}
|
|
||||||
packet.setBody(otrSession.transformSending(content)[0]);
|
|
||||||
packet.addChild("encryption", "urn:xmpp:eme:0").setAttribute("namespace", "urn:xmpp:otr:0");
|
|
||||||
return packet;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public MessagePacket generateChat(Message message) {
|
public MessagePacket generateChat(Message message) {
|
||||||
MessagePacket packet = preparePacket(message);
|
MessagePacket packet = preparePacket(message);
|
||||||
|
@ -141,6 +123,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
} else {
|
} else {
|
||||||
content = message.getBody();
|
content = message.getBody();
|
||||||
}
|
}
|
||||||
|
if (!message.isMessageDeleted())
|
||||||
packet.setBody(content);
|
packet.setBody(content);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,7 +77,7 @@ public class PresenceGenerator extends AbstractGenerator {
|
||||||
Element cap = packet.addChild("c",
|
Element cap = packet.addChild("c",
|
||||||
"http://jabber.org/protocol/caps");
|
"http://jabber.org/protocol/caps");
|
||||||
cap.setAttribute("hash", "sha-1");
|
cap.setAttribute("hash", "sha-1");
|
||||||
cap.setAttribute("node", "http://monocles.de");
|
cap.setAttribute("node", "http://blabber.im");
|
||||||
cap.setAttribute("ver", capHash);
|
cap.setAttribute("ver", capHash);
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
|
|
|
@ -22,7 +22,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import eu.siacs.conversations.BuildConfig;
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
@ -55,10 +54,10 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getUserAgent() {
|
public static String getUserAgent() {
|
||||||
return String.format("%s/%s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME);
|
return System.getProperty("http.agent");
|
||||||
}
|
}
|
||||||
|
|
||||||
public HttpConnectionManager(XmppConnectionService service) {
|
public HttpConnectionManager(XmppConnectionService service) {
|
||||||
super(service);
|
super(service);
|
||||||
}
|
}
|
||||||
|
@ -149,7 +148,8 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
|
final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
|
||||||
builder.sslSocketFactory(sf, trustManager);
|
builder.sslSocketFactory(sf, trustManager);
|
||||||
builder.hostnameVerifier(new StrictHostnameVerifier());
|
builder.hostnameVerifier(new StrictHostnameVerifier());
|
||||||
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
|
} catch (final KeyManagementException ignored) {
|
||||||
|
} catch (final NoSuchAlgorithmException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.http;
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.http.HttpConnectionManager.FileTransferExecutor;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -35,8 +37,6 @@ import okhttp3.OkHttpClient;
|
||||||
import okhttp3.Request;
|
import okhttp3.Request;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
import static eu.siacs.conversations.http.HttpConnectionManager.FileTransferExecutor;
|
|
||||||
|
|
||||||
public class HttpDownloadConnection implements Transferable {
|
public class HttpDownloadConnection implements Transferable {
|
||||||
|
|
||||||
private final Message message;
|
private final Message message;
|
||||||
|
@ -114,13 +114,12 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setupFile();
|
setupFile();
|
||||||
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
|
if (this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL && this.file.getKey() == null) {
|
||||||
|| this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL)
|
|
||||||
&& this.file.getKey() == null) {
|
|
||||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||||
}
|
}
|
||||||
//TODO add auth tag size to knownFileSize
|
//TODO add auth tag size to knownFileSize
|
||||||
final Long knownFileSize = message.getFileParams().size;
|
final Long knownFileSize = message.getFileParams().size;
|
||||||
|
Log.d(Config.LOGTAG, "knownFileSize: " + knownFileSize + ", body=" + message.getBody());
|
||||||
if (knownFileSize != null && interactive) {
|
if (knownFileSize != null && interactive) {
|
||||||
this.file.setExpectedSize(knownFileSize);
|
this.file.setExpectedSize(knownFileSize);
|
||||||
download(true);
|
download(true);
|
||||||
|
@ -145,7 +144,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
|
|
||||||
private void download(final boolean interactive) {
|
private void download(final boolean interactive) {
|
||||||
changeStatus(STATUS_WAITING);
|
changeStatus(STATUS_WAITING);
|
||||||
Log.d(Config.LOGTAG,"download()",new Exception());
|
Log.d(Config.LOGTAG, "download()", new Exception());
|
||||||
FileTransferExecutor.execute(new FileDownloader(interactive));
|
FileTransferExecutor.execute(new FileDownloader(interactive));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.http;
|
package eu.siacs.conversations.http;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.http.HttpConnectionManager.FileTransferExecutor;
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.common.util.concurrent.FutureCallback;
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
@ -30,8 +32,6 @@ import okhttp3.Request;
|
||||||
import okhttp3.RequestBody;
|
import okhttp3.RequestBody;
|
||||||
import okhttp3.Response;
|
import okhttp3.Response;
|
||||||
|
|
||||||
import static eu.siacs.conversations.http.HttpConnectionManager.FileTransferExecutor;
|
|
||||||
|
|
||||||
public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener {
|
public class HttpUploadConnection implements Transferable, AbstractConnectionManager.ProgressListener {
|
||||||
|
|
||||||
static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
|
static final List<String> WHITE_LISTED_HEADERS = Arrays.asList(
|
||||||
|
|
|
@ -1,8 +1,5 @@
|
||||||
package eu.siacs.conversations.parser;
|
package eu.siacs.conversations.parser;
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
|
||||||
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
|
|
||||||
|
@ -16,14 +13,6 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import android.os.Build;
|
|
||||||
import android.text.Html;
|
|
||||||
import net.java.otr4j.session.Session;
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
|
||||||
import eu.siacs.conversations.crypto.OtrService;
|
|
||||||
import eu.siacs.conversations.entities.Presence;
|
|
||||||
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
|
||||||
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -37,6 +26,7 @@ import eu.siacs.conversations.entities.Bookmark;
|
||||||
import eu.siacs.conversations.entities.Contact;
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Conversational;
|
import eu.siacs.conversations.entities.Conversational;
|
||||||
|
import eu.siacs.conversations.entities.Edit;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
import eu.siacs.conversations.entities.ReadByMarker;
|
import eu.siacs.conversations.entities.ReadByMarker;
|
||||||
|
@ -58,15 +48,12 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
|
||||||
|
|
||||||
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
|
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
|
||||||
|
|
||||||
private static final List<String> CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin", "Adium", "Trillian");
|
|
||||||
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
|
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
|
||||||
|
|
||||||
|
|
||||||
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract");
|
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract");
|
||||||
|
|
||||||
public MessageParser(XmppConnectionService service) {
|
public MessageParser(XmppConnectionService service) {
|
||||||
|
@ -108,29 +95,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
|
Jid result = item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("jid"));
|
||||||
return result != null ? result : fallback;
|
return result != null ? result : fallback;
|
||||||
}
|
}
|
||||||
private static boolean clientMightSendHtml(Account account, Jid from) {
|
|
||||||
String resource = from.getResource();
|
|
||||||
if (resource == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
Presence presence = account.getRoster().getContact(from).getPresences().getPresencesMap().get(resource);
|
|
||||||
ServiceDiscoveryResult disco = presence == null ? null : presence.getServiceDiscoveryResult();
|
|
||||||
if (disco == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return hasIdentityKnowForSendingHtml(disco.getIdentities());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean hasIdentityKnowForSendingHtml(List<ServiceDiscoveryResult.Identity> identities) {
|
|
||||||
for (ServiceDiscoveryResult.Identity identity : identities) {
|
|
||||||
if (identity.getName() != null) {
|
|
||||||
if (CLIENTS_SENDING_HTML_IN_OTR.contains(identity.getName())) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
|
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
|
||||||
ChatState state = ChatState.parse(packet);
|
ChatState state = ChatState.parse(packet);
|
||||||
|
@ -163,66 +127,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) {
|
|
||||||
String presence;
|
|
||||||
if (from.isBareJid()) {
|
|
||||||
presence = "";
|
|
||||||
} else {
|
|
||||||
presence = from.getResource();
|
|
||||||
}
|
|
||||||
if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
}
|
|
||||||
if (!conversation.hasValidOtrSession()) {
|
|
||||||
conversation.startOtrSession(presence, false);
|
|
||||||
} else {
|
|
||||||
String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
|
|
||||||
if (!foreignPresence.equals(presence)) {
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
conversation.startOtrSession(presence, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
conversation.setLastReceivedOtrMessageId(id);
|
|
||||||
Session otrSession = conversation.getOtrSession();
|
|
||||||
body = otrSession.transformReceiving(body);
|
|
||||||
SessionStatus status = otrSession.getSessionStatus();
|
|
||||||
if (body == null && status == SessionStatus.ENCRYPTED) {
|
|
||||||
mXmppConnectionService.onOtrSessionEstablished(conversation);
|
|
||||||
return null;
|
|
||||||
} else if (body == null && status == SessionStatus.FINISHED) {
|
|
||||||
conversation.resetOtrSession();
|
|
||||||
mXmppConnectionService.updateConversationUi();
|
|
||||||
return null;
|
|
||||||
} else if (body == null || (body.isEmpty())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (body.startsWith(CryptoHelper.FILETRANSFER)) {
|
|
||||||
String key = body.substring(CryptoHelper.FILETRANSFER.length());
|
|
||||||
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (clientMightSendHtml(conversation.getAccount(), from)) {
|
|
||||||
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OTR message from bad behaving client. escaping HTML…");
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
body = Html.fromHtml(body, Html.FROM_HTML_MODE_LEGACY).toString();
|
|
||||||
} else {
|
|
||||||
body = Html.fromHtml(body).toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final OtrService otrService = conversation.getAccount().getOtrService();
|
|
||||||
Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED);
|
|
||||||
finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey()));
|
|
||||||
conversation.setLastReceivedOtrMessageId(null);
|
|
||||||
|
|
||||||
return finishedMessage;
|
|
||||||
} catch (Exception e) {
|
|
||||||
conversation.resetOtrSession();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
|
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
|
||||||
final AxolotlService service = conversation.getAccount().getAxolotlService();
|
final AxolotlService service = conversation.getAccount().getAxolotlService();
|
||||||
final XmppAxolotlMessage xmppAxolotlMessage;
|
final XmppAxolotlMessage xmppAxolotlMessage;
|
||||||
|
@ -446,14 +350,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (message != null) {
|
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
|
||||||
Conversation conversation = (Conversation) message.getConversation();
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -520,6 +416,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
final Element oob = packet.findChild("x", Namespace.OOB);
|
final Element oob = packet.findChild("x", Namespace.OOB);
|
||||||
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
|
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
|
||||||
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
|
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
|
||||||
|
|
||||||
|
final Element applyToElement = packet.findChild("apply-to", "urn:xmpp:fasten:0");
|
||||||
|
final String retractId = applyToElement != null && applyToElement.findChild("retract", "urn:xmpp:message-retract:0") != null ? applyToElement.getAttribute("id") : null;
|
||||||
|
|
||||||
final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
|
||||||
int status;
|
int status;
|
||||||
final Jid counterpart;
|
final Jid counterpart;
|
||||||
|
@ -609,20 +509,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final Message message;
|
final Message message;
|
||||||
if (body != null && body.content.startsWith("?OTR") && Config.supportOtr()) {
|
if (pgpEncrypted != null && Config.supportOpenPgp()) {
|
||||||
if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) {
|
|
||||||
message = parseOtrChat(body.content, from, remoteMsgId, conversation);
|
|
||||||
if (message == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring OTR message from " + from + " isForwarded=" + Boolean.toString(isForwarded) + ", isProperlyAddressed=" + Boolean.valueOf(isProperlyAddressed));
|
|
||||||
message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
|
|
||||||
if (body.count > 1) {
|
|
||||||
message.setBodyLanguage(body.language);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (pgpEncrypted != null && Config.supportOpenPgp()) {
|
|
||||||
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
|
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
|
||||||
} else if (axolotlEncrypted != null && Config.supportOmemo()) {
|
} else if (axolotlEncrypted != null && Config.supportOmemo()) {
|
||||||
Jid origin;
|
Jid origin;
|
||||||
|
@ -733,10 +620,14 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
|
|
||||||
if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
|
if (replacementId != null && mXmppConnectionService.allowMessageCorrection()) {
|
||||||
final Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
|
Message replacedMessage = conversation.findMessageWithRemoteIdAndCounterpart(replacementId,
|
||||||
counterpart,
|
counterpart,
|
||||||
message.getStatus() == Message.STATUS_RECEIVED,
|
message.getStatus() == Message.STATUS_RECEIVED,
|
||||||
message.isCarbon());
|
message.isCarbon());
|
||||||
|
|
||||||
|
if (replacedMessage == null) {
|
||||||
|
replacedMessage = conversation.findSentMessageWithUuidOrRemoteId(replacementId, true, true);
|
||||||
|
}
|
||||||
if (replacedMessage != null) {
|
if (replacedMessage != null) {
|
||||||
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|
||||||
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
|
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
|
||||||
|
@ -749,10 +640,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
|
Log.d(Config.LOGTAG, "replaced message '" + replacedMessage.getBody() + "' with '" + message.getBody() + "'");
|
||||||
synchronized (replacedMessage) {
|
synchronized (replacedMessage) {
|
||||||
final String uuid = replacedMessage.getUuid();
|
final String uuid = replacedMessage.getUuid();
|
||||||
|
|
||||||
|
replacedMessage.putEdited(replacedMessage.getRemoteMsgId() != null ? replacedMessage.getRemoteMsgId() : replacedMessage.getUuid(), replacedMessage.getServerMsgId(), replacedMessage.getBody(), replacedMessage.getTimeSent());
|
||||||
|
|
||||||
replacedMessage.setUuid(UUID.randomUUID().toString());
|
replacedMessage.setUuid(UUID.randomUUID().toString());
|
||||||
replacedMessage.setBody(message.getBody());
|
replacedMessage.setBody(message.getBody());
|
||||||
replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
|
|
||||||
replacedMessage.setRemoteMsgId(remoteMsgId);
|
replacedMessage.setRemoteMsgId(remoteMsgId);
|
||||||
|
replacedMessage.setTime(message.getTimeSent());
|
||||||
if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
|
if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
|
||||||
replacedMessage.setServerMsgId(message.getServerMsgId());
|
replacedMessage.setServerMsgId(message.getServerMsgId());
|
||||||
}
|
}
|
||||||
|
@ -781,11 +675,80 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (replacementId != null && !mXmppConnectionService.allowMessageCorrection() && (message.getBody().equals(DELETED_MESSAGE_BODY) || message.getBody().equals(DELETED_MESSAGE_BODY_OLD))) {
|
} else if (replacementId != null && !mXmppConnectionService.allowMessageCorrection() && (message.hasDeletedBody())) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received deleted message but LMC is deactivated");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received deleted message but LMC is deactivated");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (retractId != null && mXmppConnectionService.allowMessageRetraction()) {
|
||||||
|
final Message retractedMessage = conversation.findSentMessageWithUuidOrRemoteId(retractId, true, true);
|
||||||
|
if (retractedMessage != null) {
|
||||||
|
final boolean fingerprintsMatch = retractedMessage.getFingerprint() == null
|
||||||
|
|| retractedMessage.getFingerprint().equals(message.getFingerprint());
|
||||||
|
final boolean trueCountersMatch = retractedMessage.getTrueCounterpart() != null
|
||||||
|
&& message.getTrueCounterpart() != null
|
||||||
|
&& retractedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
|
||||||
|
final boolean mucUserMatches = query == null && retractedMessage.sameMucUser(message); //can not be checked when using mam
|
||||||
|
final boolean duplicate = conversation.hasDuplicateMessage(message);
|
||||||
|
List<Account> lAcc = mXmppConnectionService.getAccounts();
|
||||||
|
boolean activeSelf = false;
|
||||||
|
if (message.getTrueCounterpart()!=null) {
|
||||||
|
for (Account a : lAcc) {
|
||||||
|
if (a.getJid() != null && a.isOnlineAndConnected() && a.getJid().asBareJid().equals(message.getTrueCounterpart().asBareJid())) {
|
||||||
|
activeSelf = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fingerprintsMatch && ((trueCountersMatch || !conversationMultiMode || mucUserMatches || (isCarbon&&activeSelf) && !duplicate) || conversationMultiMode)) {
|
||||||
|
Log.d(Config.LOGTAG, "retracted message '" + retractedMessage.getBody() + "' with '" + message.getBody() + "'");
|
||||||
|
synchronized (retractedMessage) {
|
||||||
|
|
||||||
|
retractedMessage.setBody(mXmppConnectionService.getString(R.string.message_deleted));
|
||||||
|
retractedMessage.setRetractId(retractId);
|
||||||
|
|
||||||
|
extractChatState(mXmppConnectionService.find(account, counterpart.asBareJid()), isTypeGroupChat, packet);
|
||||||
|
|
||||||
|
message.setMessageDeleted(true);
|
||||||
|
message.setRetractId(retractId);
|
||||||
|
|
||||||
|
if (message.getStatus() > Message.STATUS_RECEIVED) {
|
||||||
|
retractedMessage.setMessageDeleted(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Edit itm : retractedMessage.getEditedList()) {
|
||||||
|
Message tmpRetractedMessage = conversation.findMessageWithUuidOrRemoteId(itm.getEditedId());
|
||||||
|
if (tmpRetractedMessage != null) {
|
||||||
|
tmpRetractedMessage.setRetractId(retractId);
|
||||||
|
mXmppConnectionService.updateMessage(tmpRetractedMessage, tmpRetractedMessage.getUuid());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mXmppConnectionService.updateMessage(retractedMessage, retractedMessage.getUuid());
|
||||||
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
|
if (mXmppConnectionService.confirmMessages()
|
||||||
|
&& retractedMessage.getStatus() == Message.STATUS_RECEIVED
|
||||||
|
&& (retractedMessage.trusted() || retractedMessage.isPrivateMessage()) //TODO do we really want to send receipts for all PMs?
|
||||||
|
&& remoteMsgId != null
|
||||||
|
&& !selfAddressed
|
||||||
|
&& !isTypeGroupChat) {
|
||||||
|
processMessageReceipts(account, packet, remoteMsgId, query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mXmppConnectionService.getNotificationService().updateNotification();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message retraction but checks are not valid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
//we deleted a carbon from ourself and the dialog allready removed it from ui
|
||||||
|
message.setMessageDeleted(true);
|
||||||
|
message.setRetractId(retractId);
|
||||||
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
|
long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
|
||||||
if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
|
if (deletionDate != 0 && message.getTimeSent() < deletionDate) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping message from " + message.getCounterpart().toString() + " because it was sent prior to our deletion date");
|
||||||
|
@ -859,12 +822,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
&& !isTypeGroupChat) {
|
&& !isTypeGroupChat) {
|
||||||
processMessageReceipts(account, packet, remoteMsgId, query);
|
processMessageReceipts(account, packet, remoteMsgId, query);
|
||||||
}
|
}
|
||||||
if (message.getStatus() == Message.STATUS_RECEIVED
|
|
||||||
&& conversation.getOtrSession() != null
|
|
||||||
&& !conversation.getOtrSession().getSessionID().getUserID()
|
|
||||||
.equals(message.getCounterpart().getResource())) {
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
}
|
|
||||||
|
|
||||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||||
final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
|
final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.persistance;
|
package eu.siacs.conversations.persistance;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.util.UpdateHelper.moveData_PAM_monocles;
|
||||||
|
|
||||||
import android.content.ContentValues;
|
import android.content.ContentValues;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
|
@ -11,6 +13,8 @@ import android.os.SystemClock;
|
||||||
import android.util.Base64;
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.common.base.Stopwatch;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.whispersystems.libsignal.IdentityKey;
|
import org.whispersystems.libsignal.IdentityKey;
|
||||||
|
@ -58,12 +62,11 @@ import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.util.UpdateHelper.moveData_PAM_monocles;
|
|
||||||
|
|
||||||
public class DatabaseBackend extends SQLiteOpenHelper {
|
public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
public static final String DATABASE_NAME = "history";
|
public static final String DATABASE_NAME = "history";
|
||||||
public static final int DATABASE_VERSION = 54; // = Conversations DATABASE_VERSION + 6
|
public static final int DATABASE_VERSION = 56; // = Conversations DATABASE_VERSION + 7
|
||||||
|
private static boolean requiresMessageIndexRebuild = false;
|
||||||
private static DatabaseBackend instance = null;
|
private static DatabaseBackend instance = null;
|
||||||
|
|
||||||
private static final String CREATE_CONTATCS_STATEMENT = "create table "
|
private static final String CREATE_CONTATCS_STATEMENT = "create table "
|
||||||
|
@ -170,14 +173,15 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
private static String CREATE_MESSAGE_TIME_INDEX = "CREATE INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")";
|
private static String CREATE_MESSAGE_TIME_INDEX = "CREATE INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")";
|
||||||
private static String CREATE_MESSAGE_CONVERSATION_INDEX = "CREATE INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")";
|
private static String CREATE_MESSAGE_CONVERSATION_INDEX = "CREATE INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")";
|
||||||
private static String CREATE_MESSAGE_DELETED_INDEX = "CREATE INDEX message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")";
|
private static String CREATE_MESSAGE_DELETED_INDEX = "CREATE INDEX message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")";
|
||||||
private static String CREATE_MESSAGE_FILE_DELETED_INDEX = "CREATE INDEX message_file_deleted_index ON " + Message.TABLENAME + "(" + Message.FILE_DELETED + ")";
|
private static String CREATE_MESSAGE_FILE_DELETED_INDEX = "create index message_file_deleted_index ON " + Message.TABLENAME + "(" + Message.FILE_DELETED + ")";
|
||||||
private static String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")";
|
private static String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")";
|
||||||
private static String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")";
|
private static String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")";
|
||||||
|
|
||||||
private static String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4(uuid TEXT PRIMARY KEY, body TEXT, tokenize = 'unicode61')";
|
private static String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')";
|
||||||
private static String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;";
|
private static String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index(rowid,uuid,body) VALUES(NEW.rowid,NEW.uuid,NEW.body); END;";
|
||||||
private static String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;";
|
private static String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE OF uuid,body ON " + Message.TABLENAME + " BEGIN UPDATE messages_index SET body=NEW.body,uuid=NEW.uuid WHERE rowid=OLD.rowid; END;";
|
||||||
private static String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(uuid,body) SELECT uuid,body FROM " + Message.TABLENAME + ";";
|
private static final String CREATE_MESSAGE_DELETE_TRIGGER = "CREATE TRIGGER after_message_delete AFTER DELETE ON " + Message.TABLENAME + " BEGIN DELETE FROM messages_index WHERE rowid=OLD.rowid; END;";
|
||||||
|
private static String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');";
|
||||||
|
|
||||||
private DatabaseBackend(Context context) {
|
private DatabaseBackend(Context context) {
|
||||||
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
super(context, DATABASE_NAME, null, DATABASE_VERSION);
|
||||||
|
@ -190,6 +194,17 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean requiresMessageIndexRebuild() {
|
||||||
|
return requiresMessageIndexRebuild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void rebuildMessagesIndex() {
|
||||||
|
final SQLiteDatabase db = getWritableDatabase();
|
||||||
|
final Stopwatch stopwatch = Stopwatch.createStarted();
|
||||||
|
db.execSQL(COPY_PREEXISTING_ENTRIES);
|
||||||
|
Log.d(Config.LOGTAG,"rebuilt message index in "+ stopwatch.stop().toString());
|
||||||
|
}
|
||||||
|
|
||||||
public static synchronized DatabaseBackend getInstance(Context context) {
|
public static synchronized DatabaseBackend getInstance(Context context) {
|
||||||
if (instance == null) {
|
if (instance == null) {
|
||||||
instance = new DatabaseBackend(context);
|
instance = new DatabaseBackend(context);
|
||||||
|
@ -247,6 +262,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
+ Message.MARKABLE + " NUMBER DEFAULT 0,"
|
+ Message.MARKABLE + " NUMBER DEFAULT 0,"
|
||||||
+ Message.FILE_DELETED + " NUMBER DEFAULT 0,"
|
+ Message.FILE_DELETED + " NUMBER DEFAULT 0,"
|
||||||
+ Message.BODY_LANGUAGE + " TEXT,"
|
+ Message.BODY_LANGUAGE + " TEXT,"
|
||||||
|
+ Message.RETRACT_ID + " TEXT,"
|
||||||
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
|
||||||
+ Message.CONVERSATION + ") REFERENCES "
|
+ Message.CONVERSATION + ") REFERENCES "
|
||||||
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
+ Conversation.TABLENAME + "(" + Conversation.UUID
|
||||||
|
@ -269,6 +285,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
db.execSQL(CREATE_MESSAGE_INDEX_TABLE);
|
db.execSQL(CREATE_MESSAGE_INDEX_TABLE);
|
||||||
db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER);
|
db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER);
|
||||||
db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER);
|
db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER);
|
||||||
|
db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -466,13 +483,13 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
if (oldVersion < 36 && newVersion >= 36) {
|
if (oldVersion < 36 && newVersion >= 36) {
|
||||||
// only rename videos, images, audios and other files directories
|
// only rename videos, images, audios and other files directories
|
||||||
final File oldPicturesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Images/");
|
final File oldPicturesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/Images/");
|
||||||
final File oldFilesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Files/");
|
final File oldFilesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/Files/");
|
||||||
final File oldAudiosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Audios/");
|
final File oldAudiosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/Audios/");
|
||||||
final File oldVideosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Videos/");
|
final File oldVideosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/Videos/");
|
||||||
|
|
||||||
if (oldPicturesDirectory.exists() && oldPicturesDirectory.isDirectory()) {
|
if (oldPicturesDirectory.exists() && oldPicturesDirectory.isDirectory()) {
|
||||||
final File newPicturesDirectory = new File(Environment.getExternalStorageDirectory() + "/Monocles Messenger/Media/Monocles Messenger Images/");
|
final File newPicturesDirectory = new File(Environment.getExternalStorageDirectory() + "/Pix-Art Messenger/Media/Pix-Art Messenger Images/");
|
||||||
newPicturesDirectory.getParentFile().mkdirs();
|
newPicturesDirectory.getParentFile().mkdirs();
|
||||||
final File[] files = oldPicturesDirectory.listFiles();
|
final File[] files = oldPicturesDirectory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
|
@ -483,7 +500,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldFilesDirectory.exists() && oldFilesDirectory.isDirectory()) {
|
if (oldFilesDirectory.exists() && oldFilesDirectory.isDirectory()) {
|
||||||
final File newFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Monocles Messenger/Media/Monocles Messenger Files/");
|
final File newFilesDirectory = new File(Environment.getExternalStorageDirectory() + "/Pix-Art Messenger/Media/Pix-Art Messenger Files/");
|
||||||
newFilesDirectory.mkdirs();
|
newFilesDirectory.mkdirs();
|
||||||
final File[] files = oldFilesDirectory.listFiles();
|
final File[] files = oldFilesDirectory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
|
@ -494,7 +511,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldAudiosDirectory.exists() && oldAudiosDirectory.isDirectory()) {
|
if (oldAudiosDirectory.exists() && oldAudiosDirectory.isDirectory()) {
|
||||||
final File newAudiosDirectory = new File(Environment.getExternalStorageDirectory() + "/Monocles Messenger/Media/Monocles Messenger Audios/");
|
final File newAudiosDirectory = new File(Environment.getExternalStorageDirectory() + "/Pix-Art Messenger/Media/Pix-Art Messenger Audios/");
|
||||||
newAudiosDirectory.mkdirs();
|
newAudiosDirectory.mkdirs();
|
||||||
final File[] files = oldAudiosDirectory.listFiles();
|
final File[] files = oldAudiosDirectory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
|
@ -505,7 +522,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVideosDirectory.exists() && oldVideosDirectory.isDirectory()) {
|
if (oldVideosDirectory.exists() && oldVideosDirectory.isDirectory()) {
|
||||||
final File newVideosDirectory = new File(Environment.getExternalStorageDirectory() + "/Monocles Messenger/Media/Monocles Messenger Videos/");
|
final File newVideosDirectory = new File(Environment.getExternalStorageDirectory() + "/Pix-Art Messenger/Media/Pix-Art Messenger Videos/");
|
||||||
newVideosDirectory.mkdirs();
|
newVideosDirectory.mkdirs();
|
||||||
final File[] files = oldVideosDirectory.listFiles();
|
final File[] files = oldVideosDirectory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
|
@ -535,13 +552,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0");
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 42 && newVersion >= 42) {
|
|
||||||
db.execSQL(CREATE_MESSAGE_INDEX_TABLE);
|
|
||||||
db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER);
|
|
||||||
db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER);
|
|
||||||
db.execSQL(COPY_PREEXISTING_ENTRIES);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (oldVersion < 43 && newVersion >= 43) {
|
if (oldVersion < 43 && newVersion >= 43) {
|
||||||
db.execSQL("DROP TRIGGER IF EXISTS after_message_delete");
|
db.execSQL("DROP TRIGGER IF EXISTS after_message_delete");
|
||||||
}
|
}
|
||||||
|
@ -594,6 +604,33 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
if (oldVersion < 54 && newVersion >= 54) {
|
if (oldVersion < 54 && newVersion >= 54) {
|
||||||
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.RTP_CAPABILITY + " TEXT");
|
db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.RTP_CAPABILITY + " TEXT");
|
||||||
}
|
}
|
||||||
|
if (oldVersion < 55 && newVersion >= 55) {
|
||||||
|
db.beginTransaction();
|
||||||
|
db.execSQL("DROP TRIGGER IF EXISTS after_message_insert;");
|
||||||
|
db.execSQL("DROP TRIGGER IF EXISTS after_message_update;");
|
||||||
|
db.execSQL("DROP TRIGGER IF EXISTS after_message_delete;");
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS messages_index;");
|
||||||
|
// a hack that should not be necessary, but
|
||||||
|
// there was at least one occurence when SQLite failed at this
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS messages_index_docsize;");
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS messages_index_segdir;");
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS messages_index_segments;");
|
||||||
|
db.execSQL("DROP TABLE IF EXISTS messages_index_stat;");
|
||||||
|
db.execSQL(CREATE_MESSAGE_INDEX_TABLE);
|
||||||
|
db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER);
|
||||||
|
db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER);
|
||||||
|
db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER);
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
requiresMessageIndexRebuild = true;
|
||||||
|
}
|
||||||
|
if (oldVersion < 56 && newVersion >= 56) {
|
||||||
|
db.beginTransaction();
|
||||||
|
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.RETRACT_ID + " TEXT;");
|
||||||
|
db.setTransactionSuccessful();
|
||||||
|
db.endTransaction();
|
||||||
|
requiresMessageIndexRebuild = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isColumnExisting(SQLiteDatabase db, String TableName, String ColumnName) {
|
private boolean isColumnExisting(SQLiteDatabase db, String TableName, String ColumnName) {
|
||||||
|
@ -832,12 +869,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
final SQLiteDatabase db = this.getReadableDatabase();
|
final SQLiteDatabase db = this.getReadableDatabase();
|
||||||
final StringBuilder SQL = new StringBuilder();
|
final StringBuilder SQL = new StringBuilder();
|
||||||
final String[] selectionArgs;
|
final String[] selectionArgs;
|
||||||
SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ',' + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?");
|
SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.rowid=messages.rowid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?");
|
||||||
if (uuid == null) {
|
if (uuid == null) {
|
||||||
selectionArgs = new String[]{FtsUtils.toMatchString(term)};
|
selectionArgs = new String[]{FtsUtils.toMatchString(term)};
|
||||||
} else {
|
} else {
|
||||||
selectionArgs = new String[]{FtsUtils.toMatchString(term), uuid};
|
selectionArgs = new String[]{FtsUtils.toMatchString(term), uuid};
|
||||||
SQL.append(" AND "+Conversation.TABLENAME+'.'+Conversation.UUID+"=?");
|
SQL.append(" AND " + Conversation.TABLENAME + '.' + Conversation.UUID + "=?");
|
||||||
}
|
}
|
||||||
SQL.append(" ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS);
|
SQL.append(" ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS);
|
||||||
Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term));
|
Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term));
|
||||||
|
@ -1118,23 +1155,23 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
|
|
||||||
public void deleteMessageInConversation(Message message) {
|
public void deleteMessageInConversation(Message message) {
|
||||||
long start = SystemClock.elapsedRealtime();
|
long start = SystemClock.elapsedRealtime();
|
||||||
|
final String uuid = message.getUuid();
|
||||||
final SQLiteDatabase db = this.getWritableDatabase();
|
final SQLiteDatabase db = this.getWritableDatabase();
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put(Message.DELETED, "1");
|
values.put(Message.DELETED, "1");
|
||||||
String[] args = {message.getUuid()};
|
String[] args = {uuid};
|
||||||
int rows = db.update("messages", values, "uuid =?", args);
|
int rows = db.update("messages", values, "uuid =?", args);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
Log.d(Config.LOGTAG, "deleted " + rows + " message in " + (SystemClock.elapsedRealtime() - start) + "ms");
|
Log.d(Config.LOGTAG, "deleted " + rows + " message (" + uuid + ") in " + (SystemClock.elapsedRealtime() - start) + "ms");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deleteMessagesInConversation(Conversation conversation) {
|
public void deleteMessagesInConversation(Conversation conversation) {
|
||||||
long start = SystemClock.elapsedRealtime();
|
long start = SystemClock.elapsedRealtime();
|
||||||
final SQLiteDatabase db = this.getWritableDatabase();
|
final SQLiteDatabase db = this.getWritableDatabase();
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
String[] args = {conversation.getUuid()};
|
final String[] args = {conversation.getUuid()};
|
||||||
db.delete("messages_index", "uuid in (select uuid from messages where conversationUuid=?)", args);
|
|
||||||
int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
|
int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args);
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
|
@ -1183,8 +1220,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
|
||||||
final String[] args = {String.valueOf(timestamp)};
|
final String[] args = {String.valueOf(timestamp)};
|
||||||
SQLiteDatabase db = this.getReadableDatabase();
|
SQLiteDatabase db = this.getReadableDatabase();
|
||||||
db.beginTransaction();
|
db.beginTransaction();
|
||||||
db.delete("messages_index", "uuid in (select uuid from messages where timeSent<?)", args);
|
db.delete(Message.TABLENAME, "timeSent<?", args);
|
||||||
num = db.delete(Message.TABLENAME, "timeSent<?", args);
|
|
||||||
db.setTransactionSuccessful();
|
db.setTransactionSuccessful();
|
||||||
db.endTransaction();
|
db.endTransaction();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package eu.siacs.conversations.persistance;
|
package eu.siacs.conversations.persistance;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.ContentResolver;
|
import android.content.ContentResolver;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -25,6 +24,7 @@ import android.os.Environment;
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.os.StatFs;
|
import android.os.StatFs;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.provider.MediaStore;
|
import android.provider.MediaStore;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.system.Os;
|
import android.system.Os;
|
||||||
|
@ -35,14 +35,12 @@ import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.LruCache;
|
import android.util.LruCache;
|
||||||
|
|
||||||
import androidx.exifinterface.media.ExifInterface;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
|
||||||
import androidx.annotation.StringRes;
|
import androidx.annotation.StringRes;
|
||||||
import androidx.core.content.FileProvider;
|
import androidx.core.content.FileProvider;
|
||||||
import com.google.common.io.ByteStreams;
|
import androidx.exifinterface.media.ExifInterface;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
@ -64,21 +62,27 @@ import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.BuildConfig;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
|
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.ui.SettingsActivity;
|
||||||
import eu.siacs.conversations.ui.util.Attachment;
|
import eu.siacs.conversations.ui.util.Attachment;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.FileUtils;
|
import eu.siacs.conversations.utils.FileUtils;
|
||||||
import eu.siacs.conversations.utils.FileWriterException;
|
import eu.siacs.conversations.utils.FileWriterException;
|
||||||
|
@ -104,6 +108,11 @@ public class FileBackend {
|
||||||
public static final String SENT_IMAGES = "Images/Sent";
|
public static final String SENT_IMAGES = "Images/Sent";
|
||||||
public static final String VIDEOS = "Videos";
|
public static final String VIDEOS = "Videos";
|
||||||
public static final String SENT_VIDEOS = "Videos/Sent";
|
public static final String SENT_VIDEOS = "Videos/Sent";
|
||||||
|
public static final String INNER_APP_DIR[] = new String[]{
|
||||||
|
Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator ,
|
||||||
|
Environment.getDataDirectory() + File.separator + "data" + File.separator + BuildConfig.APPLICATION_ID + File.separator + "files" + File.separator,
|
||||||
|
};
|
||||||
|
public static final AtomicInteger STORAGE_INDEX = new AtomicInteger(0);
|
||||||
|
|
||||||
private final XmppConnectionService mXmppConnectionService;
|
private final XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@ -111,6 +120,10 @@ public class FileBackend {
|
||||||
this.mXmppConnectionService = service;
|
this.mXmppConnectionService = service;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void switchStorage(boolean checked) {
|
||||||
|
STORAGE_INDEX.set(checked?1:0);
|
||||||
|
}
|
||||||
|
|
||||||
private void createNoMedia() {
|
private void createNoMedia() {
|
||||||
final File nomedia_files = new File(getConversationsDirectory(FILES) + ".nomedia");
|
final File nomedia_files = new File(getConversationsDirectory(FILES) + ".nomedia");
|
||||||
final File nomedia_audios = new File(getConversationsDirectory(AUDIOS) + ".nomedia");
|
final File nomedia_audios = new File(getConversationsDirectory(AUDIOS) + ".nomedia");
|
||||||
|
@ -288,6 +301,77 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void deleteOldBackups(File dir, List<Account> mAccounts) {
|
||||||
|
try {
|
||||||
|
long start = SystemClock.elapsedRealtime();
|
||||||
|
int num = 0;
|
||||||
|
if (dir == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Stack<File> dirlist = new Stack<File>();
|
||||||
|
dirlist.clear();
|
||||||
|
dirlist.push(dir);
|
||||||
|
File dirCurrent = dirlist.pop();
|
||||||
|
File[] fileList = dirCurrent.listFiles();
|
||||||
|
while (!dirlist.isEmpty()) {
|
||||||
|
if (fileList != null) {
|
||||||
|
for (File file : fileList) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
dirlist.push(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileList != null) {
|
||||||
|
ArrayList<File> fileListByAccount = new ArrayList<File>();
|
||||||
|
ArrayList<File> simpleFileList = new ArrayList<File>(Arrays.asList(fileList));
|
||||||
|
for (Account account : mAccounts) {
|
||||||
|
String jid = account.getJid().asBareJid().toString();
|
||||||
|
for (int i = 0; i < simpleFileList.size(); i++) {
|
||||||
|
File currentFile = simpleFileList.get(i);
|
||||||
|
String fileName = currentFile.getName();
|
||||||
|
if (fileName.startsWith(jid) && fileName.endsWith(".ceb")) {
|
||||||
|
fileListByAccount.add(currentFile);
|
||||||
|
simpleFileList.remove(currentFile);
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (fileListByAccount.size() > 2) {
|
||||||
|
num += expireOldBackups(fileListByAccount);
|
||||||
|
}
|
||||||
|
fileListByAccount.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Log.d(Config.LOGTAG, "deleted " + num + " old backup files in " + (SystemClock.elapsedRealtime() - start) + "ms");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int expireOldBackups(ArrayList<File> fileListByAccount) {
|
||||||
|
int num = 0;
|
||||||
|
try {
|
||||||
|
Collections.sort(fileListByAccount, new Comparator<File>() {
|
||||||
|
@Override
|
||||||
|
public int compare(File f1, File f2) {
|
||||||
|
return Long.compare(f2.lastModified(), f1.lastModified());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
fileListByAccount.subList(0, 2).clear();
|
||||||
|
for (File currentFile : fileListByAccount) {
|
||||||
|
if (currentFile.delete()) {
|
||||||
|
num++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
return num;
|
||||||
|
}
|
||||||
|
|
||||||
public void deleteFilesInDir(File dir) {
|
public void deleteFilesInDir(File dir) {
|
||||||
long start = SystemClock.elapsedRealtime();
|
long start = SystemClock.elapsedRealtime();
|
||||||
int num = 0;
|
int num = 0;
|
||||||
|
@ -295,12 +379,11 @@ public class FileBackend {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Stack<File> dirlist = new Stack<>();
|
Stack<File> dirlist = new Stack<>();
|
||||||
dirlist.clear();
|
|
||||||
dirlist.push(dir);
|
dirlist.push(dir);
|
||||||
while (!dirlist.isEmpty()) {
|
while (!dirlist.isEmpty()) {
|
||||||
File dirCurrent = dirlist.pop();
|
File dirCurrent = dirlist.pop();
|
||||||
File[] fileList = dirCurrent.listFiles();
|
File[] fileList = dirCurrent.listFiles();
|
||||||
if (fileList.length > 0) {
|
if (fileList != null && fileList.length > 0) {
|
||||||
for (File file : fileList) {
|
for (File file : fileList) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
dirlist.push(file);
|
dirlist.push(file);
|
||||||
|
@ -462,20 +545,24 @@ public class FileBackend {
|
||||||
|
|
||||||
public static String getConversationsDirectory(final String type) {
|
public static String getConversationsDirectory(final String type) {
|
||||||
if (type.equalsIgnoreCase("null")) {
|
if (type.equalsIgnoreCase("null")) {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator;
|
// return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator;
|
||||||
|
return INNER_APP_DIR[STORAGE_INDEX.get()]
|
||||||
|
+ APP_DIRECTORY + File.separator;
|
||||||
} else {
|
} else {
|
||||||
return getAppMediaDirectory() + APP_DIRECTORY + " " + type + File.separator;
|
return getAppMediaDirectory() + APP_DIRECTORY + " " + type + File.separator;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getAppMediaDirectory() {
|
public static String getAppMediaDirectory() {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Media" + File.separator;
|
// return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Media" + File.separator;
|
||||||
|
return INNER_APP_DIR[STORAGE_INDEX.get()]
|
||||||
|
+ APP_DIRECTORY + File.separator + "Media" + File.separator;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getBackupDirectory(@Nullable String app) {
|
public static String getBackupDirectory(@Nullable String app) {
|
||||||
if (app != null && (app.equalsIgnoreCase("conversations") || app.equalsIgnoreCase("Quicksy"))) {
|
if (app != null && (app.equalsIgnoreCase("conversations") || app.equalsIgnoreCase("Quicksy"))) {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + app + "/Backup/";
|
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + app + "/Backup/";
|
||||||
} else if (app != null && (app.equalsIgnoreCase("Monocles Messenger"))) {
|
} else if (app != null && (app.equalsIgnoreCase("Pix-Art Messenger"))) {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + app + File.separator + "Database" + File.separator;
|
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + app + File.separator + "Database" + File.separator;
|
||||||
} else {
|
} else {
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Database" + File.separator;
|
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Database" + File.separator;
|
||||||
|
@ -600,6 +687,8 @@ public class FileBackend {
|
||||||
ByteStreams.copy(is, os);
|
ByteStreams.copy(is, os);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new FileWriterException();
|
throw new FileWriterException();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new FileWriterException();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
os.flush();
|
os.flush();
|
||||||
|
@ -863,7 +952,7 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
DownloadableFile file = getFile(message);
|
DownloadableFile file = getFile(message);
|
||||||
final String mime = file.getMimeType();
|
final String mime = file.getMimeType();
|
||||||
if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) {
|
if ("application/pdf".equals(mime)) {
|
||||||
thumbnail = getPDFPreview(file, size);
|
thumbnail = getPDFPreview(file, size);
|
||||||
} else if (mime.startsWith("video/")) {
|
} else if (mime.startsWith("video/")) {
|
||||||
thumbnail = getVideoPreview(file, size);
|
thumbnail = getVideoPreview(file, size);
|
||||||
|
@ -887,7 +976,6 @@ public class FileBackend {
|
||||||
return thumbnail;
|
return thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private Bitmap getPDFPreview(final File file, int size) {
|
private Bitmap getPDFPreview(final File file, int size) {
|
||||||
try {
|
try {
|
||||||
final ParcelFileDescriptor mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
final ParcelFileDescriptor mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||||
|
@ -1049,7 +1137,22 @@ public class FileBackend {
|
||||||
return getUriForFile(mXmppConnectionService, file);
|
return getUriForFile(mXmppConnectionService, file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getUriForUri(Context context, Uri uri) {
|
||||||
|
if ("file".equals(uri.getScheme())) {
|
||||||
|
return getUriForFile(context, new File(uri.getPath()));
|
||||||
|
} else {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri getUriForFile(Context context, File file) {
|
public static Uri getUriForFile(Context context, File file) {
|
||||||
|
if (PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.getBoolean(SettingsActivity.USE_INNER_STORAGE, true)) {
|
||||||
|
File dataUser0File = new File(file.getAbsolutePath().replace("/data/data", "/data/user/0"));
|
||||||
|
return FileProvider.getUriForFile(context
|
||||||
|
, getAuthority(context)
|
||||||
|
, dataUser0File.exists() ? dataUser0File : file);
|
||||||
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
try {
|
try {
|
||||||
return FileProvider.getUriForFile(context, getAuthority(context), file);
|
return FileProvider.getUriForFile(context, getAuthority(context), file);
|
||||||
|
@ -1437,53 +1540,63 @@ public class FileBackend {
|
||||||
final boolean apk = mime != null && mime.equals("application/vnd.android.package-archive");
|
final boolean apk = mime != null && mime.equals("application/vnd.android.package-archive");
|
||||||
final boolean pdf = "application/pdf".equals(mime);
|
final boolean pdf = "application/pdf".equals(mime);
|
||||||
/* file params:
|
/* file params:
|
||||||
1 | 2 | 3 | 4 | 5 | 6
|
1 | 2 | 3 | 4 | 5 | 6 |
|
||||||
| image/video/pdf | a/v/gif | vcard/apk
|
| image/video/pdf | a/v/gif | vcard/apk/audio |
|
||||||
url | filesize | width | height | runtime | name
|
url | filesize | width | height | runtime | name |
|
||||||
*/
|
*/
|
||||||
final StringBuilder body = new StringBuilder();
|
final StringBuilder body = new StringBuilder();
|
||||||
if (url != null) {
|
if (url != null) {
|
||||||
body.append(url);
|
body.append(url); // 1
|
||||||
}
|
}
|
||||||
body.append('|').append(file.getSize());
|
body.append('|').append(file.getSize()); // 2
|
||||||
if (image || video || (pdf && Compatibility.runsTwentyOne())) {
|
if (image || video || pdf) {
|
||||||
try {
|
try {
|
||||||
final Dimensions dimensions;
|
final Dimensions dimensions;
|
||||||
if (video) {
|
if (video) {
|
||||||
dimensions = getVideoDimensions(file);
|
dimensions = getVideoDimensions(file);
|
||||||
} else if (pdf && Compatibility.runsTwentyOne()) {
|
} else if (pdf) {
|
||||||
dimensions = getPDFDimensions(file);
|
dimensions = getPDFDimensions(file);
|
||||||
} else {
|
} else {
|
||||||
dimensions = getImageDimensions(file);
|
dimensions = getImageDimensions(file);
|
||||||
}
|
}
|
||||||
if (dimensions.valid()) {
|
if (dimensions.valid()) {
|
||||||
body.append('|').append(dimensions.width).append('|').append(dimensions.height);
|
body.append('|')
|
||||||
|
.append(dimensions.width) // 3
|
||||||
|
.append('|')
|
||||||
|
.append(dimensions.height); // 4
|
||||||
if (isGif || video) {
|
if (isGif || video) {
|
||||||
body.append("|").append(getMediaRuntime(file, isGif));
|
body.append("|").append(getMediaRuntime(file, isGif)); // 5
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (NotAVideoFile notAVideoFile) {
|
} catch (NotAVideoFile notAVideoFile) {
|
||||||
Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was not a video file, trying to handle it as audio file");
|
Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was not a video file, trying to handle it as audio file");
|
||||||
try {
|
try {
|
||||||
body.append("|0|0|").append(getMediaRuntime(file, false)).append('|').append(getAudioTitleArtist(file));
|
body.append("|0|0|") // 3, 4
|
||||||
|
.append(getMediaRuntime(file, false)) // 5
|
||||||
|
.append('|')
|
||||||
|
.append(getAudioTitleArtist(file)); // 6
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was neither a video file nor an audio file");
|
Log.d(Config.LOGTAG, "file with mime type " + file.getMimeType() + " was neither a video file nor an audio file");
|
||||||
//fall threw
|
//fall threw
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (audio) {
|
} else if (audio) {
|
||||||
body.append("|0|0|").append(getMediaRuntime(file, false)).append('|').append(getAudioTitleArtist(file));
|
body.append("|0|0|") // 3, 4
|
||||||
|
.append(getMediaRuntime(file, false)) // 5
|
||||||
|
.append('|')
|
||||||
|
.append(getAudioTitleArtist(file)); // 6
|
||||||
} else if (vcard) {
|
} else if (vcard) {
|
||||||
body.append("|0|0|0|").append(getVCard(file));
|
body.append("|0|0|0|") // 3, 4, 5
|
||||||
|
.append(getVCard(file)); // 6
|
||||||
} else if (apk) {
|
} else if (apk) {
|
||||||
body.append("|0|0|0|").append(getAPK(file, mXmppConnectionService.getApplicationContext()));
|
body.append("|0|0|0|") // 3, 4, 5
|
||||||
|
.append(getAPK(file, mXmppConnectionService.getApplicationContext())); // 6
|
||||||
}
|
}
|
||||||
message.setBody(body.toString());
|
message.setBody(body.toString());
|
||||||
message.setFileDeleted(false);
|
message.setFileDeleted(false);
|
||||||
message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
|
message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private Dimensions getPDFDimensions(final File file) {
|
private Dimensions getPDFDimensions(final File file) {
|
||||||
final ParcelFileDescriptor fileDescriptor;
|
final ParcelFileDescriptor fileDescriptor;
|
||||||
try {
|
try {
|
||||||
|
@ -1661,7 +1774,7 @@ public class FileBackend {
|
||||||
return bitmap;
|
return bitmap;
|
||||||
}
|
}
|
||||||
DownloadableFile file = new DownloadableFile(attachment.getUri().getPath());
|
DownloadableFile file = new DownloadableFile(attachment.getUri().getPath());
|
||||||
if ("application/pdf".equals(attachment.getMime()) && Compatibility.runsTwentyOne()) {
|
if ("application/pdf".equals(attachment.getMime())) {
|
||||||
bitmap = cropCenterSquare(getPDFPreview(file, size), size);
|
bitmap = cropCenterSquare(getPDFPreview(file, size), size);
|
||||||
} else if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) {
|
} else if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) {
|
||||||
bitmap = cropCenterSquareVideo(attachment.getUri(), size);
|
bitmap = cropCenterSquareVideo(attachment.getUri(), size);
|
||||||
|
@ -1719,11 +1832,7 @@ public class FileBackend {
|
||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
final int rotation;
|
final int rotation;
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
rotation = extractRotationFromMediaRetriever(metadataRetriever);
|
rotation = extractRotationFromMediaRetriever(metadataRetriever);
|
||||||
} else {
|
|
||||||
rotation = 0;
|
|
||||||
}
|
|
||||||
boolean rotated = rotation == 90 || rotation == 270;
|
boolean rotated = rotation == 90 || rotation == 270;
|
||||||
int height;
|
int height;
|
||||||
try {
|
try {
|
||||||
|
@ -1744,7 +1853,6 @@ public class FileBackend {
|
||||||
return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
|
return rotated ? new Dimensions(width, height) : new Dimensions(height, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR1)
|
|
||||||
private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
|
private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
|
||||||
String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
|
||||||
try {
|
try {
|
||||||
|
@ -1851,30 +1959,11 @@ public class FileBackend {
|
||||||
public static boolean weOwnFile(Context context, Uri uri) {
|
public static boolean weOwnFile(Context context, Uri uri) {
|
||||||
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
|
||||||
return false;
|
return false;
|
||||||
} else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
return fileIsInFilesDir(context, uri);
|
|
||||||
} else {
|
} else {
|
||||||
return weOwnFileLollipop(uri);
|
return weOwnFileLollipop(uri);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is more than hacky but probably way better than doing nothing
|
|
||||||
* Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
|
|
||||||
* and check against those as well
|
|
||||||
*/
|
|
||||||
private static boolean fileIsInFilesDir(Context context, Uri uri) {
|
|
||||||
try {
|
|
||||||
final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
|
|
||||||
final String needle = new File(uri.getPath()).getCanonicalPath();
|
|
||||||
return needle.startsWith(haystack);
|
|
||||||
} catch (IOException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
|
|
||||||
private static boolean weOwnFileLollipop(Uri uri) {
|
private static boolean weOwnFileLollipop(Uri uri) {
|
||||||
try {
|
try {
|
||||||
File file = new File(uri.getPath());
|
File file = new File(uri.getPath());
|
||||||
|
@ -1944,11 +2033,7 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getGlobalDocumentsPath() {
|
public static String getGlobalDocumentsPath() {
|
||||||
if (Compatibility.runsNineTeen()) {
|
|
||||||
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/monocles chat/";
|
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS) + "/monocles chat/";
|
||||||
} else {
|
|
||||||
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "Documents";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getGlobalAudiosPath() {
|
public static String getGlobalAudiosPath() {
|
||||||
|
@ -1956,14 +2041,21 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveFile(final Message message, final Activity activity) {
|
public void saveFile(final Message message, final Activity activity) {
|
||||||
|
new Thread(() -> {
|
||||||
final DownloadableFile source = getFile(message);
|
final DownloadableFile source = getFile(message);
|
||||||
final File destination = new File(getDestinationToSaveFile(message));
|
final File destination = new File(getDestinationToSaveFile(message));
|
||||||
try {
|
try {
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
|
ToastCompat.makeText(activity, activity.getString(R.string.copy_file_to, destination), ToastCompat.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
copyFile(source, destination);
|
copyFile(source, destination);
|
||||||
|
activity.runOnUiThread(() -> {
|
||||||
ToastCompat.makeText(activity, activity.getString(R.string.file_copied_to, destination), ToastCompat.LENGTH_SHORT).show();
|
ToastCompat.makeText(activity, activity.getString(R.string.file_copied_to, destination), ToastCompat.LENGTH_SHORT).show();
|
||||||
|
});
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDestinationToSaveFile(Message message) {
|
public String getDestinationToSaveFile(Message message) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
import android.content.Context;
|
import static eu.siacs.conversations.entities.Transferable.VALID_CRYPTO_EXTENSIONS;
|
||||||
|
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
@ -35,8 +36,6 @@ import okio.BufferedSink;
|
||||||
import okio.Okio;
|
import okio.Okio;
|
||||||
import okio.Source;
|
import okio.Source;
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Transferable.VALID_CRYPTO_EXTENSIONS;
|
|
||||||
|
|
||||||
public class AbstractConnectionManager {
|
public class AbstractConnectionManager {
|
||||||
private static final int UI_REFRESH_THRESHOLD = Config.REFRESH_UI_INTERVAL;
|
private static final int UI_REFRESH_THRESHOLD = Config.REFRESH_UI_INTERVAL;
|
||||||
private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
|
private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
|
||||||
|
@ -119,9 +118,9 @@ public class AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getAutoAcceptFileSize() {
|
public long getAutoAcceptFileSize() {
|
||||||
long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi);
|
final long defaultValue_wifi = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_wifi);
|
||||||
long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile);
|
final long defaultValue_mobile = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_mobile);
|
||||||
long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming);
|
final long defaultValue_roaming = this.getXmppConnectionService().getResources().getInteger(R.integer.auto_accept_filesize_roaming);
|
||||||
|
|
||||||
String config = "0";
|
String config = "0";
|
||||||
if (mXmppConnectionService.isWIFI()) {
|
if (mXmppConnectionService.isWIFI()) {
|
||||||
|
@ -135,7 +134,7 @@ public class AbstractConnectionManager {
|
||||||
"auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming));
|
"auto_accept_file_size_roaming", String.valueOf(defaultValue_roaming));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return Long.parseLong(config);
|
return Long.parseLong(config) <= 0 ? -1 : Long.parseLong(config);
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
return defaultValue_mobile;
|
return defaultValue_mobile;
|
||||||
}
|
}
|
||||||
|
@ -154,7 +153,6 @@ public class AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public PowerManager.WakeLock createWakeLock(final String name) {
|
public PowerManager.WakeLock createWakeLock(final String name) {
|
||||||
final PowerManager powerManager = ContextCompat.getSystemService(mXmppConnectionService, PowerManager.class);
|
final PowerManager powerManager = ContextCompat.getSystemService(mXmppConnectionService, PowerManager.class);
|
||||||
return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
|
return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
|
||||||
|
|
|
@ -4,15 +4,17 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.media.MediaMetadataRetriever;
|
import android.media.MediaMetadataRetriever;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import net.ypresto.androidtranscoder.MediaTranscoder;
|
import com.otaliastudios.transcoder.Transcoder;
|
||||||
import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets;
|
import com.otaliastudios.transcoder.TranscoderListener;
|
||||||
|
import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileDescriptor;
|
import java.io.FileDescriptor;
|
||||||
|
@ -20,6 +22,7 @@ import java.io.FileNotFoundException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
|
@ -32,8 +35,9 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.ui.UiCallback;
|
import eu.siacs.conversations.ui.UiCallback;
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
|
import eu.siacs.conversations.utils.TranscoderStrategies;
|
||||||
|
|
||||||
public class AttachFileToConversationRunnable implements Runnable, MediaTranscoder.Listener {
|
public class AttachFileToConversationRunnable implements Runnable, TranscoderListener {
|
||||||
|
|
||||||
private final XmppConnectionService mXmppConnectionService;
|
private final XmppConnectionService mXmppConnectionService;
|
||||||
private final Message message;
|
private final Message message;
|
||||||
|
@ -56,16 +60,15 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.maxUploadSize = maxUploadSize;
|
this.maxUploadSize = maxUploadSize;
|
||||||
final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
|
final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
|
||||||
final int autoAcceptFileSize = Config.FILE_SIZE;
|
|
||||||
this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService, uri);
|
this.originalFileSize = FileBackend.getFileSize(mXmppConnectionService, uri);
|
||||||
this.isVideoMessage = !getFileBackend().useFileAsIs(uri)
|
this.isVideoMessage = !getFileBackend().useFileAsIs(uri)
|
||||||
&& (mimeType != null && mimeType.startsWith("video/")
|
&& (mimeType != null && mimeType.startsWith("video/"))
|
||||||
&& (mXmppConnectionService.getCompressVideoBitratePreference() != 0 && mXmppConnectionService.getCompressVideoResolutionPreference() != 0))
|
&& originalFileSize > Config.VIDEO_FAST_UPLOAD_SIZE
|
||||||
&& originalFileSize > autoAcceptFileSize;
|
&& !"uncompressed".equals(getVideoCompression());
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isVideoMessage() {
|
boolean isVideoMessage() {
|
||||||
return this.isVideoMessage && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
|
return this.isVideoMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processAsFile() {
|
private void processAsFile() {
|
||||||
|
@ -100,21 +103,22 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN_MR2)
|
|
||||||
private void processAsVideo() throws Exception {
|
private void processAsVideo() throws Exception {
|
||||||
Log.d(Config.LOGTAG, "processing file as video");
|
Log.d(Config.LOGTAG, "processing file as video");
|
||||||
mXmppConnectionService.startForcingForegroundNotification();
|
mXmppConnectionService.startForcingForegroundNotification();
|
||||||
isCompressingVideo = conversation.getUuid();
|
isCompressingVideo = conversation.getUuid();
|
||||||
SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
|
final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
|
||||||
message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "_komp.mp4");
|
message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + "_komp.mp4");
|
||||||
final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
|
final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
|
||||||
file.getParentFile().mkdirs();
|
if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
|
||||||
|
Log.d(Config.LOGTAG, "created parent directory for video file");
|
||||||
|
}
|
||||||
final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
|
final ParcelFileDescriptor parcelFileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
|
||||||
if (parcelFileDescriptor == null) {
|
if (parcelFileDescriptor == null) {
|
||||||
throw new FileNotFoundException("Parcel File Descriptor was null");
|
throw new FileNotFoundException("Parcel File Descriptor was null");
|
||||||
}
|
}
|
||||||
FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
|
final FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor();
|
||||||
Future<Void> future = getVideoCompressor(fileDescriptor, file, maxUploadSize);
|
final Future<Void> future = getVideoCompression(fileDescriptor, file, maxUploadSize, originalFileSize);
|
||||||
try {
|
try {
|
||||||
future.get();
|
future.get();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
|
@ -127,13 +131,13 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
isCompressingVideo = null;
|
isCompressingVideo = null;
|
||||||
processAsFile();
|
processAsFile();
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFiled() instead", e);
|
Log.d(Config.LOGTAG, "ignoring execution exception. Should get handled by onTranscodeFailed() instead", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Future<Void> getVideoCompressor(final FileDescriptor fileDescriptor, final File file, final long maxUploadSize) throws Exception {
|
private Future<Void> getVideoCompression(final FileDescriptor fileDescriptor, final File file, final long maxUploadSize, final long originalFileSize) throws Exception {
|
||||||
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
|
final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
|
||||||
try {
|
try {
|
||||||
mediaMetadataRetriever.setDataSource(fileDescriptor);
|
mediaMetadataRetriever.setDataSource(fileDescriptor);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -141,31 +145,52 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
throw new Exception(e);
|
throw new Exception(e);
|
||||||
}
|
}
|
||||||
long videoDuration;
|
long videoDuration;
|
||||||
long estimatedFileSize = maxUploadSize / 2; // keep estimated filesize half as big as maxUploadSize
|
final long estimatedFileSize = maxUploadSize / 2; // keep estimated filesize half as big as maxUploadSize
|
||||||
try {
|
try {
|
||||||
videoDuration = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) / 1000; //in seconds
|
videoDuration = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) / 1000; //in seconds
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
videoDuration = -1;
|
videoDuration = -1;
|
||||||
}
|
}
|
||||||
int bitrateAfterCompression = safeLongToInt(mXmppConnectionService.getCompressVideoBitratePreference() / 8); //in bytes
|
final int bitrateAfterCompression = safeLongToInt(mXmppConnectionService.getCompressVideoBitratePreference() / 8); //in bytes
|
||||||
long size = videoDuration * bitrateAfterCompression;
|
final long sizeAfterCompression = videoDuration * bitrateAfterCompression;
|
||||||
if (estimatedFileSize >= size) {
|
|
||||||
return MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), MediaFormatStrategyPresets.createAndroidStandardStrategy(mXmppConnectionService.getCompressVideoBitratePreference(), mXmppConnectionService.getCompressVideoResolutionPreference()), this);
|
if (sizeAfterCompression >= originalFileSize && originalFileSize <= Config.VIDEO_FAST_UPLOAD_SIZE) {
|
||||||
|
processAsFile();
|
||||||
|
onTranscodeCanceled();
|
||||||
|
return null;
|
||||||
|
} else if (estimatedFileSize >= sizeAfterCompression && sizeAfterCompression <= originalFileSize) {
|
||||||
|
return Transcoder.into(file.getAbsolutePath()).
|
||||||
|
addDataSource(mXmppConnectionService, uri)
|
||||||
|
.setVideoTrackStrategy(TranscoderStrategies.VIDEO(mXmppConnectionService.getCompressVideoBitratePreference(), mXmppConnectionService.getCompressVideoResolutionPreference()))
|
||||||
|
.setAudioTrackStrategy(mXmppConnectionService.getCompressAudioPreference())
|
||||||
|
.setListener(this)
|
||||||
|
.transcode();
|
||||||
} else {
|
} else {
|
||||||
|
DefaultAudioStrategy audioStrategy;
|
||||||
int newBitrate = safeLongToInt((estimatedFileSize / videoDuration) * 8); // in bits/sec
|
int newBitrate = safeLongToInt((estimatedFileSize / videoDuration) * 8); // in bits/sec
|
||||||
int newResoloution = 0;
|
int newResoloution = 0;
|
||||||
if (newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_bitrate)) {
|
if (newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_bitrate)) {
|
||||||
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_res);
|
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_res);
|
||||||
|
audioStrategy = TranscoderStrategies.AUDIO_LQ;
|
||||||
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.low_video_bitrate)) {
|
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.verylow_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.low_video_bitrate)) {
|
||||||
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.low_video_res);
|
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.low_video_res);
|
||||||
|
audioStrategy = TranscoderStrategies.AUDIO_LQ;
|
||||||
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.low_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.mid_video_bitrate)) {
|
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.low_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.mid_video_bitrate)) {
|
||||||
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.mid_video_res);
|
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.mid_video_res);
|
||||||
|
audioStrategy = TranscoderStrategies.AUDIO_MQ;
|
||||||
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.mid_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.high_video_bitrate)) {
|
} else if (newBitrate > mXmppConnectionService.getResources().getInteger(R.integer.mid_video_bitrate) && newBitrate <= mXmppConnectionService.getResources().getInteger(R.integer.high_video_bitrate)) {
|
||||||
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.high_video_res);
|
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.high_video_res);
|
||||||
|
audioStrategy = TranscoderStrategies.AUDIO_HQ;
|
||||||
} else {
|
} else {
|
||||||
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.high_video_res);
|
newResoloution = mXmppConnectionService.getResources().getInteger(R.integer.high_video_res);
|
||||||
|
audioStrategy = TranscoderStrategies.AUDIO_HQ;
|
||||||
}
|
}
|
||||||
return MediaTranscoder.getInstance().transcodeVideo(fileDescriptor, file.getAbsolutePath(), MediaFormatStrategyPresets.createAndroidStandardStrategy(newBitrate, newResoloution), this);
|
return Transcoder.into(file.getAbsolutePath()).
|
||||||
|
addDataSource(mXmppConnectionService, uri)
|
||||||
|
.setVideoTrackStrategy(TranscoderStrategies.VIDEO(newBitrate, newResoloution))
|
||||||
|
.setAudioTrackStrategy(audioStrategy)
|
||||||
|
.setListener(this)
|
||||||
|
.transcode();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -187,7 +212,7 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTranscodeCompleted() {
|
public void onTranscodeCompleted(int successCode) {
|
||||||
mXmppConnectionService.stopForcingForegroundNotification();
|
mXmppConnectionService.stopForcingForegroundNotification();
|
||||||
isCompressingVideo = null;
|
isCompressingVideo = null;
|
||||||
final File file = mXmppConnectionService.getFileBackend().getFile(message);
|
final File file = mXmppConnectionService.getFileBackend().getFile(message);
|
||||||
|
@ -219,10 +244,10 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTranscodeFailed(Exception e) {
|
public void onTranscodeFailed(@NonNull @NotNull Throwable exception) {
|
||||||
mXmppConnectionService.stopForcingForegroundNotification();
|
mXmppConnectionService.stopForcingForegroundNotification();
|
||||||
isCompressingVideo = null;
|
isCompressingVideo = null;
|
||||||
Log.d(Config.LOGTAG, "video transcoding failed", e);
|
Log.d(Config.LOGTAG, "video transcoding failed", exception);
|
||||||
processAsFile();
|
processAsFile();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -240,6 +265,10 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String getVideoCompression() {
|
||||||
|
return getVideoCompression(mXmppConnectionService);
|
||||||
|
}
|
||||||
|
|
||||||
public static String getVideoCompression(final Context context) {
|
public static String getVideoCompression(final Context context) {
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
|
return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
|
||||||
|
|
|
@ -101,17 +101,10 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
|
||||||
|
|
||||||
private boolean init(ViewHolder viewHolder, Message message) {
|
private boolean init(ViewHolder viewHolder, Message message) {
|
||||||
messageAdapter.getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
messageAdapter.getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);
|
||||||
if (viewHolder.darkBackground) {
|
|
||||||
viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption_OnDark);
|
|
||||||
} else {
|
|
||||||
viewHolder.runtime.setTextAppearance(this.messageAdapter.getContext(), R.style.TextAppearance_Conversations_Caption);
|
|
||||||
}
|
|
||||||
viewHolder.progress.setOnSeekBarChangeListener(this);
|
viewHolder.progress.setOnSeekBarChangeListener(this);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
||||||
ColorStateList color = ThemeHelper.AudioPlayerColor(messageAdapter.getContext());
|
ColorStateList color = ThemeHelper.AudioPlayerColor(messageAdapter.getContext());
|
||||||
viewHolder.progress.setThumbTintList(color);
|
viewHolder.progress.setThumbTintList(color);
|
||||||
viewHolder.progress.setProgressTintList(color);
|
viewHolder.progress.setProgressTintList(color);
|
||||||
}
|
|
||||||
viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
|
viewHolder.playPause.setAlpha(viewHolder.darkBackground ? 0.7f : 0.57f);
|
||||||
viewHolder.playPause.setOnClickListener(this);
|
viewHolder.playPause.setOnClickListener(this);
|
||||||
if (message == currentlyPlayingMessage) {
|
if (message == currentlyPlayingMessage) {
|
||||||
|
@ -439,6 +432,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
|
||||||
Log.i(Config.LOGTAG, "Audio focus failed.");
|
Log.i(Config.LOGTAG, "Audio focus failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ViewHolder {
|
public static class ViewHolder {
|
||||||
private TextView runtime;
|
private TextView runtime;
|
||||||
private SeekBar progress;
|
private SeekBar progress;
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class ChannelDiscoveryService {
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeMuclumbusService() {
|
void initializeMuclumbusService() {
|
||||||
final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder();
|
final OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||||
if (service.useTorToConnect()) {
|
if (service.useTorToConnect()) {
|
||||||
builder.proxy(HttpConnectionManager.getProxy());
|
builder.proxy(HttpConnectionManager.getProxy());
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.services.NotificationService.EXPORT_BACKUP_NOTIFICATION_ID;
|
||||||
import static eu.siacs.conversations.utils.Compatibility.runsTwentySix;
|
import static eu.siacs.conversations.utils.Compatibility.runsTwentySix;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
@ -17,8 +19,6 @@ import android.os.IBinder;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.ObjectOutputStream;
|
|
||||||
|
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
|
|
||||||
|
@ -81,7 +81,6 @@ public class ExportBackupService extends Service {
|
||||||
private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
|
private static final String DIRECTORY_STRING_FORMAT = FileBackend.getAppLogsDirectory() + "%s";
|
||||||
private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
|
private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 19;
|
|
||||||
private static final int PAGE_SIZE = 20;
|
private static final int PAGE_SIZE = 20;
|
||||||
private static final AtomicBoolean RUNNING = new AtomicBoolean(false);
|
private static final AtomicBoolean RUNNING = new AtomicBoolean(false);
|
||||||
private DatabaseBackend mDatabaseBackend;
|
private DatabaseBackend mDatabaseBackend;
|
||||||
|
@ -271,6 +270,7 @@ public class ExportBackupService extends Service {
|
||||||
RUNNING.set(false);
|
RUNNING.set(false);
|
||||||
if (success) {
|
if (success) {
|
||||||
notifySuccess(files, notify);
|
notifySuccess(files, notify);
|
||||||
|
FileBackend.deleteOldBackups(new File(FileBackend.getBackupDirectory(null)), this.mAccounts);
|
||||||
} else {
|
} else {
|
||||||
notifyError();
|
notifyError();
|
||||||
}
|
}
|
||||||
|
@ -313,7 +313,7 @@ public class ExportBackupService extends Service {
|
||||||
final int percentage = i * 100 / size;
|
final int percentage = i * 100 / size;
|
||||||
if (p < percentage) {
|
if (p < percentage) {
|
||||||
p = percentage;
|
p = percentage;
|
||||||
notificationManager.notify(NOTIFICATION_ID, progress.build(p));
|
notificationManager.notify(EXPORT_BACKUP_NOTIFICATION_ID, progress.build(p));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cursor != null) {
|
if (cursor != null) {
|
||||||
|
@ -327,7 +327,7 @@ public class ExportBackupService extends Service {
|
||||||
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
|
mBuilder.setContentTitle(getString(R.string.notification_create_backup_title))
|
||||||
.setSmallIcon(R.drawable.ic_archive_white_24dp)
|
.setSmallIcon(R.drawable.ic_archive_white_24dp)
|
||||||
.setProgress(1, 0, false);
|
.setProgress(1, 0, false);
|
||||||
startForeground(NOTIFICATION_ID, mBuilder.build());
|
startForeground(EXPORT_BACKUP_NOTIFICATION_ID, mBuilder.build());
|
||||||
int count = 0;
|
int count = 0;
|
||||||
final int max = this.mAccounts.size();
|
final int max = this.mAccounts.size();
|
||||||
final SecureRandom secureRandom = new SecureRandom();
|
final SecureRandom secureRandom = new SecureRandom();
|
||||||
|
@ -344,6 +344,7 @@ public class ExportBackupService extends Service {
|
||||||
final List<File> files = new ArrayList<>();
|
final List<File> files = new ArrayList<>();
|
||||||
Log.d(Config.LOGTAG, "starting backup for " + max + " accounts");
|
Log.d(Config.LOGTAG, "starting backup for " + max + " accounts");
|
||||||
for (final Account account : this.mAccounts) {
|
for (final Account account : this.mAccounts) {
|
||||||
|
try {
|
||||||
final String password = account.getPassword();
|
final String password = account.getPassword();
|
||||||
if (Strings.nullToEmpty(password).trim().isEmpty()) {
|
if (Strings.nullToEmpty(password).trim().isEmpty()) {
|
||||||
Log.d(Config.LOGTAG, String.format("skipping backup for %s because password is empty. unable to encrypt", account.getJid().asBareJid()));
|
Log.d(Config.LOGTAG, String.format("skipping backup for %s because password is empty. unable to encrypt", account.getJid().asBareJid()));
|
||||||
|
@ -356,7 +357,7 @@ public class ExportBackupService extends Service {
|
||||||
secureRandom.nextBytes(salt);
|
secureRandom.nextBytes(salt);
|
||||||
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
|
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
|
||||||
final Progress progress = new Progress(mBuilder, max, count);
|
final Progress progress = new Progress(mBuilder, max, count);
|
||||||
final File file = new File(FileBackend.getBackupDirectory(null) + account.getJid().asBareJid().toEscapedString() + ".ceb");
|
final File file = new File(FileBackend.getBackupDirectory(null) + account.getJid().asBareJid().toEscapedString() + "_" + ((new SimpleDateFormat("yyyy-MM-dd")).format(new Date())) + ".ceb");
|
||||||
files.add(file);
|
files.add(file);
|
||||||
final File directory = file.getParentFile();
|
final File directory = file.getParentFile();
|
||||||
if (directory != null && directory.mkdirs()) {
|
if (directory != null && directory.mkdirs()) {
|
||||||
|
@ -374,7 +375,6 @@ public class ExportBackupService extends Service {
|
||||||
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
IvParameterSpec ivSpec = new IvParameterSpec(IV);
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
|
||||||
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher);
|
CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher);
|
||||||
|
|
||||||
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
|
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream);
|
||||||
PrintWriter writer = new PrintWriter(gzipOutputStream);
|
PrintWriter writer = new PrintWriter(gzipOutputStream);
|
||||||
SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase();
|
SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase();
|
||||||
|
@ -389,10 +389,13 @@ public class ExportBackupService extends Service {
|
||||||
writer.close();
|
writer.close();
|
||||||
mediaScannerScanFile(file);
|
mediaScannerScanFile(file);
|
||||||
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.d(Config.LOGTAG, "backup for " + account.getJid() + " failed with " + e);
|
||||||
|
}
|
||||||
count++;
|
count++;
|
||||||
}
|
}
|
||||||
stopForeground(true);
|
stopForeground(true);
|
||||||
notificationManager.cancel(NOTIFICATION_ID);
|
notificationManager.cancel(EXPORT_BACKUP_NOTIFICATION_ID);
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,7 +468,7 @@ public class ExportBackupService extends Service {
|
||||||
if (shareFilesIntent != null) {
|
if (shareFilesIntent != null) {
|
||||||
mBuilder.addAction(R.drawable.ic_share_white_24dp, getString(R.string.share_backup_files), shareFilesIntent);
|
mBuilder.addAction(R.drawable.ic_share_white_24dp, getString(R.string.share_backup_files), shareFilesIntent);
|
||||||
}
|
}
|
||||||
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
notificationManager.notify(EXPORT_BACKUP_NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifyError() {
|
private void notifyError() {
|
||||||
|
@ -477,7 +480,7 @@ public class ExportBackupService extends Service {
|
||||||
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_failed_subtitle, FileBackend.getBackupDirectory(null))))
|
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_failed_subtitle, FileBackend.getBackupDirectory(null))))
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setSmallIcon(R.drawable.ic_warning_white_24dp);
|
.setSmallIcon(R.drawable.ic_warning_white_24dp);
|
||||||
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
notificationManager.notify(EXPORT_BACKUP_NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeToFile(Conversation conversation) {
|
private void writeToFile(Conversation conversation) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.services.NotificationService.IMPORT_BACKUP_NOTIFICATION_ID;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
@ -60,7 +62,6 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
public class ImportBackupService extends Service {
|
public class ImportBackupService extends Service {
|
||||||
|
|
||||||
private static final int NOTIFICATION_ID = 21;
|
|
||||||
private static final AtomicBoolean running = new AtomicBoolean(false);
|
private static final AtomicBoolean running = new AtomicBoolean(false);
|
||||||
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
|
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
|
||||||
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
|
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
|
||||||
|
@ -162,7 +163,7 @@ public class ImportBackupService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startForegroundService() {
|
private void startForegroundService() {
|
||||||
startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
|
startForeground(IMPORT_BACKUP_NOTIFICATION_ID, createImportBackupNotification(1, 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateImportBackupNotification(final long total, final long current) {
|
private void updateImportBackupNotification(final long total, final long current) {
|
||||||
|
@ -177,7 +178,7 @@ public class ImportBackupService extends Service {
|
||||||
}
|
}
|
||||||
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
|
||||||
try {
|
try {
|
||||||
notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress));
|
notificationManager.notify(IMPORT_BACKUP_NOTIFICATION_ID, createImportBackupNotification(max, progress));
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
Log.d(Config.LOGTAG, "unable to make notification", e);
|
Log.d(Config.LOGTAG, "unable to make notification", e);
|
||||||
}
|
}
|
||||||
|
@ -303,7 +304,7 @@ public class ImportBackupService extends Service {
|
||||||
.setAutoCancel(true)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
|
.setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
|
||||||
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
|
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
|
||||||
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
|
notificationManager.notify(IMPORT_BACKUP_NOTIFICATION_ID, mBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void stopBackgroundService() {
|
private void stopBackgroundService() {
|
||||||
|
|
|
@ -67,8 +67,8 @@ import java.security.cert.X509Certificate;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
@ -95,6 +95,7 @@ import eu.siacs.conversations.ui.MemorizingActivity;
|
||||||
* opening sockets!
|
* opening sockets!
|
||||||
*/
|
*/
|
||||||
public class MemorizingTrustManager {
|
public class MemorizingTrustManager {
|
||||||
|
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
|
||||||
|
|
||||||
final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
|
final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
|
||||||
|
@ -358,6 +359,7 @@ public class MemorizingTrustManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
|
private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
|
||||||
throws CertificateException {
|
throws CertificateException {
|
||||||
LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
|
LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
|
||||||
|
@ -525,6 +527,7 @@ public class MemorizingTrustManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
|
private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
|
||||||
|
|
||||||
si.append("\n");
|
si.append("\n");
|
||||||
if (showValidFor) {
|
if (showValidFor) {
|
||||||
try {
|
try {
|
||||||
|
@ -574,6 +577,7 @@ public class MemorizingTrustManager {
|
||||||
}
|
}
|
||||||
return si.toString();
|
return si.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the top-most entry of the activity stack.
|
* Returns the top-most entry of the activity stack.
|
||||||
*
|
*
|
||||||
|
@ -646,6 +650,7 @@ public class MemorizingTrustManager {
|
||||||
public X509TrustManager getInteractive() {
|
public X509TrustManager getInteractive() {
|
||||||
return new InteractiveMemorizingTrustManager(null);
|
return new InteractiveMemorizingTrustManager(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
|
private class NonInteractiveMemorizingTrustManager implements X509TrustManager {
|
||||||
|
|
||||||
private final String domain;
|
private final String domain;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
||||||
|
|
||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.app.NotificationChannel;
|
import android.app.NotificationChannel;
|
||||||
import android.app.NotificationChannelGroup;
|
import android.app.NotificationChannelGroup;
|
||||||
|
@ -34,6 +36,8 @@ import androidx.core.app.RemoteInput;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.drawable.IconCompat;
|
import androidx.core.graphics.drawable.IconCompat;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
@ -72,8 +76,6 @@ import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
|
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
|
||||||
|
|
||||||
public class NotificationService {
|
public class NotificationService {
|
||||||
|
|
||||||
public static final Object CATCHUP_LOCK = new Object();
|
public static final Object CATCHUP_LOCK = new Object();
|
||||||
|
@ -106,6 +108,8 @@ public class NotificationService {
|
||||||
public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
|
public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
|
||||||
private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
|
private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
|
||||||
public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 14;
|
public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 14;
|
||||||
|
public static final int IMPORT_BACKUP_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 16;
|
||||||
|
public static final int EXPORT_BACKUP_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 18;
|
||||||
private final XmppConnectionService mXmppConnectionService;
|
private final XmppConnectionService mXmppConnectionService;
|
||||||
private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
|
private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
|
||||||
private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>();
|
private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>();
|
||||||
|
@ -730,15 +734,24 @@ public class NotificationService {
|
||||||
notify(INCOMING_CALL_NOTIFICATION_ID, notification);
|
notify(INCOMING_CALL_NOTIFICATION_ID, notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Notification getOngoingCallNotification(final AbstractJingleConnection.Id id, final Set<Media> media) {
|
public Notification getOngoingCallNotification(final XmppConnectionService.OngoingCall ongoingCall) {
|
||||||
|
final AbstractJingleConnection.Id id = ongoingCall.id;
|
||||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, ONGOING_CALLS_CHANNEL_ID);
|
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mXmppConnectionService, ONGOING_CALLS_CHANNEL_ID);
|
||||||
if (media.contains(Media.VIDEO)) {
|
if (ongoingCall.media.contains(Media.VIDEO)) {
|
||||||
builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
|
builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
|
||||||
|
if (ongoingCall.reconnecting) {
|
||||||
|
builder.setContentTitle(mXmppConnectionService.getString(R.string.reconnecting_video_call));
|
||||||
|
} else {
|
||||||
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_video_call));
|
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_video_call));
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
builder.setSmallIcon(R.drawable.ic_call_white_24dp);
|
builder.setSmallIcon(R.drawable.ic_call_white_24dp);
|
||||||
|
if (ongoingCall.reconnecting) {
|
||||||
|
builder.setContentTitle(mXmppConnectionService.getString(R.string.reconnecting_call));
|
||||||
|
} else {
|
||||||
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
|
builder.setContentTitle(mXmppConnectionService.getString(R.string.ongoing_call));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
|
builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
|
||||||
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
|
||||||
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
|
||||||
|
@ -1214,17 +1227,18 @@ public class NotificationService {
|
||||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
|
||||||
.setShowsUserInterface(false)
|
.setShowsUserInterface(false)
|
||||||
.build();
|
.build();
|
||||||
String replyLabel = mXmppConnectionService.getString(R.string.reply);
|
final String replyLabel = mXmppConnectionService.getString(R.string.reply);
|
||||||
NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
|
final String lastMessageUuid = Iterables.getLast(messages).getUuid();
|
||||||
|
final NotificationCompat.Action replyAction = new NotificationCompat.Action.Builder(
|
||||||
R.drawable.ic_reply_white_24dp,
|
R.drawable.ic_reply_white_24dp,
|
||||||
replyLabel,
|
replyLabel,
|
||||||
createReplyIntent(conversation, false))
|
createReplyIntent(conversation, lastMessageUuid, false))
|
||||||
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
.setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
|
||||||
.setShowsUserInterface(false)
|
.setShowsUserInterface(false)
|
||||||
.addRemoteInput(remoteInput).build();
|
.addRemoteInput(remoteInput).build();
|
||||||
NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
final NotificationCompat.Action wearReplyAction = new NotificationCompat.Action.Builder(R.drawable.ic_wear_reply,
|
||||||
replyLabel,
|
replyLabel,
|
||||||
createReplyIntent(conversation, true)).addRemoteInput(remoteInput).build();
|
createReplyIntent(conversation, lastMessageUuid, true)).addRemoteInput(remoteInput).build();
|
||||||
mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
|
mBuilder.extend(new NotificationCompat.WearableExtender().addAction(wearReplyAction));
|
||||||
int addedActionsCount = 1;
|
int addedActionsCount = 1;
|
||||||
mBuilder.addAction(markReadAction);
|
mBuilder.addAction(markReadAction);
|
||||||
|
@ -1505,13 +1519,14 @@ public class NotificationService {
|
||||||
return PendingIntent.getService(mXmppConnectionService, 1, intent, 0);
|
return PendingIntent.getService(mXmppConnectionService, 1, intent, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createReplyIntent(Conversation conversation, boolean dismissAfterReply) {
|
private PendingIntent createReplyIntent(final Conversation conversation, final String lastMessageUuid, final boolean dismissAfterReply) {
|
||||||
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
|
final Intent intent = new Intent(mXmppConnectionService, XmppConnectionService.class);
|
||||||
intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
|
intent.setAction(XmppConnectionService.ACTION_REPLY_TO_CONVERSATION);
|
||||||
intent.putExtra("uuid", conversation.getUuid());
|
intent.putExtra("uuid", conversation.getUuid());
|
||||||
intent.putExtra("dismiss_notification", dismissAfterReply);
|
intent.putExtra("dismiss_notification", dismissAfterReply);
|
||||||
|
intent.putExtra("last_message_uuid", lastMessageUuid);
|
||||||
final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14);
|
final int id = generateRequestCode(conversation, dismissAfterReply ? 12 : 14);
|
||||||
return PendingIntent.getService(mXmppConnectionService, id, intent, 0);
|
return PendingIntent.getService(mXmppConnectionService, id, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PendingIntent createReadPendingIntent(Conversation conversation) {
|
private PendingIntent createReadPendingIntent(Conversation conversation) {
|
||||||
|
|
|
@ -0,0 +1,117 @@
|
||||||
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
|
|
||||||
|
public class ProviderService extends AsyncTask<String, Object, Boolean> {
|
||||||
|
public static List<String> providers = new ArrayList<>();
|
||||||
|
|
||||||
|
// in accordance with cat B (https://invent.kde.org/melvo/xmpp-providers/)
|
||||||
|
public static boolean REGISTRATION = true;
|
||||||
|
public static boolean FREE = true;
|
||||||
|
public static int COMPLIANCE = 90;
|
||||||
|
public static String RATING = "A";
|
||||||
|
|
||||||
|
public ProviderService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> getProviders() {
|
||||||
|
final HashSet<String> provider = new HashSet<>(Config.DOMAIN.DOMAINS);
|
||||||
|
if (!providers.isEmpty()) {
|
||||||
|
provider.addAll(providers);
|
||||||
|
}
|
||||||
|
return new ArrayList<>(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(String... params) {
|
||||||
|
StringBuilder jsonString = new StringBuilder();
|
||||||
|
boolean isError = false;
|
||||||
|
try {
|
||||||
|
Log.d(Config.LOGTAG, "ProviderService: Updating provider list from " + Config.PROVIDER_URL);
|
||||||
|
final InputStream is = HttpConnectionManager.open(Config.PROVIDER_URL, false);
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(is));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
jsonString.append(line);
|
||||||
|
}
|
||||||
|
is.close();
|
||||||
|
reader.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
parseJson(new JSONObject(jsonString.toString()));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
|
if (isError) {
|
||||||
|
Log.d(Config.LOGTAG, "ProviderService: Updating provider list failed");
|
||||||
|
}
|
||||||
|
return !isError;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void parseJson(JSONObject jsonObject) {
|
||||||
|
if (jsonObject != null) {
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < jsonObject.length(); i++) {
|
||||||
|
boolean inBandRegistration = false;
|
||||||
|
boolean freeOfCharge = false;
|
||||||
|
String ratingC2S = null;
|
||||||
|
String ratingS2S = null;
|
||||||
|
int ratingXmppComplianceTester = 0;
|
||||||
|
final String provider = jsonObject.names().getString(i);
|
||||||
|
if (provider.length() > 0) {
|
||||||
|
for (int ii = 0; ii < jsonObject.length(); ii++) {
|
||||||
|
final JSONObject json = new JSONObject(jsonObject.get(provider).toString());
|
||||||
|
String featureName = json.names().getString(ii);
|
||||||
|
final JSONObject subjson = new JSONObject(json.get(json.names().getString(ii)).toString());
|
||||||
|
if (featureName.equals("inBandRegistration")) {
|
||||||
|
inBandRegistration = subjson.getBoolean("content");
|
||||||
|
}
|
||||||
|
if (featureName.equals("ratingXmppComplianceTester")) {
|
||||||
|
ratingXmppComplianceTester = subjson.getInt("content");
|
||||||
|
}
|
||||||
|
if (featureName.equals("freeOfCharge")) {
|
||||||
|
freeOfCharge = subjson.getBoolean("content");
|
||||||
|
}
|
||||||
|
if (featureName.equals("ratingImObservatoryClientToServer")) {
|
||||||
|
ratingC2S = subjson.getString("content");
|
||||||
|
}
|
||||||
|
if (featureName.equals("ratingImObservatoryServerToServer")) {
|
||||||
|
ratingS2S = subjson.getString("content");
|
||||||
|
}
|
||||||
|
if (!Config.DOMAIN.BLACKLISTED_DOMAINS.contains(provider)
|
||||||
|
&& inBandRegistration == REGISTRATION
|
||||||
|
&& ratingXmppComplianceTester >= COMPLIANCE
|
||||||
|
&& freeOfCharge == FREE
|
||||||
|
&& (ratingC2S != null && ratingC2S.equalsIgnoreCase(RATING))
|
||||||
|
&& (ratingS2S != null && ratingS2S.equalsIgnoreCase(RATING))) {
|
||||||
|
//Log.d(Config.LOGTAG, "ProviderService: Updating provider list. Adding " + provider + " (Registration: " + inBandRegistration + " Compliance: " + ratingXmppComplianceTester + " Free: " + freeOfCharge + " Rating C2S/S2S: " + ratingC2S + "/" + ratingS2S + ")");
|
||||||
|
providers.add(provider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
227
src/main/java/eu/siacs/conversations/services/UpdateService.java
Normal file
227
src/main/java/eu/siacs/conversations/services/UpdateService.java
Normal file
|
@ -0,0 +1,227 @@
|
||||||
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import android.app.PendingIntent;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.BuildConfig;
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.http.NoSSLv3SocketFactory;
|
||||||
|
import eu.siacs.conversations.ui.UpdaterActivity;
|
||||||
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.http.HttpConnectionManager.getProxy;
|
||||||
|
|
||||||
|
public class UpdateService extends AsyncTask<String, Object, UpdateService.Wrapper> {
|
||||||
|
private boolean mUseTor;
|
||||||
|
private Context context;
|
||||||
|
private String store;
|
||||||
|
private NotificationService getNotificationService;
|
||||||
|
|
||||||
|
public UpdateService() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public UpdateService(Context context, String Store, XmppConnectionService mXmppConnectionService) {
|
||||||
|
this.context = context;
|
||||||
|
this.store = Store;
|
||||||
|
this.mUseTor = mXmppConnectionService.useTorToConnect();
|
||||||
|
this.getNotificationService = mXmppConnectionService.getNotificationService();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Wrapper doInBackground(String... params) {
|
||||||
|
StringBuilder jsonString = new StringBuilder();
|
||||||
|
boolean UpdateAvailable = false;
|
||||||
|
boolean showNoUpdateToast = false;
|
||||||
|
boolean isError = false;
|
||||||
|
|
||||||
|
if (params[0].equals("true")) {
|
||||||
|
showNoUpdateToast = true;
|
||||||
|
}
|
||||||
|
SSLContext sslcontext = null;
|
||||||
|
SSLSocketFactory NoSSLv3Factory = null;
|
||||||
|
try {
|
||||||
|
sslcontext = SSLContext.getInstance("TLSv1");
|
||||||
|
if (sslcontext != null) {
|
||||||
|
sslcontext.init(null, null, null);
|
||||||
|
NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
|
||||||
|
HttpsURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(Config.UPDATE_URL);
|
||||||
|
if (mUseTor) {
|
||||||
|
connection = (HttpsURLConnection) url.openConnection(getProxy());
|
||||||
|
} else {
|
||||||
|
connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
}
|
||||||
|
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
|
||||||
|
connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
|
||||||
|
connection.setRequestProperty("User-agent", System.getProperty("http.agent"));
|
||||||
|
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
jsonString.append(line);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
isError = true;
|
||||||
|
} finally {
|
||||||
|
if (connection != null) {
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
JSONObject json = new JSONObject(jsonString.toString());
|
||||||
|
if (json.getBoolean("success") && json.has("latestVersion") && json.has("appURI") && json.has("filesize")) {
|
||||||
|
String version = json.getString("latestVersion");
|
||||||
|
String ownVersion = BuildConfig.VERSION_NAME;
|
||||||
|
String url = json.getString("appURI");
|
||||||
|
String filesize = json.getString("filesize");
|
||||||
|
String changelog = "";
|
||||||
|
if (json.has("changelog")) {
|
||||||
|
changelog = json.getString("changelog");
|
||||||
|
}
|
||||||
|
if (checkVersion(version, ownVersion) >= 1) {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: Version " + ownVersion + " should be updated to " + version);
|
||||||
|
UpdateAvailable = true;
|
||||||
|
showNotification(url, changelog, version, filesize, store);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: Version " + ownVersion + " is up to date");
|
||||||
|
UpdateAvailable = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
Wrapper w = new Wrapper();
|
||||||
|
w.isError = isError;
|
||||||
|
w.UpdateAvailable = UpdateAvailable;
|
||||||
|
w.NoUpdate = showNoUpdateToast;
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Wrapper w) {
|
||||||
|
super.onPostExecute(w);
|
||||||
|
if (w.isError) {
|
||||||
|
showToastMessage(true, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!w.UpdateAvailable) {
|
||||||
|
showToastMessage(w.NoUpdate, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showToastMessage(boolean show, final boolean error) {
|
||||||
|
if (!show) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Handler handler = new Handler(Looper.getMainLooper());
|
||||||
|
handler.post(() -> {
|
||||||
|
String ToastMessage = "";
|
||||||
|
if (error) {
|
||||||
|
ToastMessage = context.getString(R.string.failed);
|
||||||
|
} else {
|
||||||
|
ToastMessage = context.getString(R.string.no_update_available);
|
||||||
|
}
|
||||||
|
ToastCompat.makeText(context, ToastMessage, ToastCompat.LENGTH_LONG).show();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showNotification(String url, String changelog, String version, String filesize, String store) {
|
||||||
|
Intent intent = new Intent(context, UpdaterActivity.class);
|
||||||
|
intent.putExtra("update", "MonoclesMessenger_UpdateService");
|
||||||
|
intent.putExtra("url", url);
|
||||||
|
intent.putExtra("changelog", changelog);
|
||||||
|
intent.putExtra("store", store);
|
||||||
|
PendingIntent pi = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||||
|
getNotificationService.AppUpdateServiceNotification(getNotificationService.AppUpdateNotification(pi, version, filesize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private int checkVersion(String remoteVersion, String installedVersion) {
|
||||||
|
// Use this instead of String.compareTo() for a non-lexicographical
|
||||||
|
// comparison that works for version strings. e.g. "1.10".compareTo("1.6").
|
||||||
|
//
|
||||||
|
// @param str1 a string of ordinal numbers separated by decimal points.
|
||||||
|
// @param str2 a string of ordinal numbers separated by decimal points.
|
||||||
|
// @return The result is a negative integer if str1 is _numerically_ less than str2.
|
||||||
|
// The result is a positive integer if str1 is _numerically_ greater than str2.
|
||||||
|
// The result is zero if the strings are _numerically_ equal.
|
||||||
|
// @note It does not work if "1.10" is supposed to be equal to "1.10.0".
|
||||||
|
|
||||||
|
String[] remote = null;
|
||||||
|
String[] installed = null;
|
||||||
|
String[] remoteV = null;
|
||||||
|
String[] installedV = null;
|
||||||
|
try {
|
||||||
|
installedV = installedVersion.split("[ |\\-]");
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: Version installed: " + installedV[0]);
|
||||||
|
installed = installedV[0].split("\\.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
remoteV = remoteVersion.split(" ");
|
||||||
|
if (installedV != null && installedV.length > 1) {
|
||||||
|
if (installedV[1] != null && installedV[1].toLowerCase().contains("beta")) {
|
||||||
|
remoteV[0] = remoteV[0] + ".1";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: Version on server: " + remoteV[0]);
|
||||||
|
remote = remoteV[0].split("\\.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
int i = 0;
|
||||||
|
// set index to first non-equal ordinal or length of shortest localVersion string
|
||||||
|
try {
|
||||||
|
if (remote != null && installed != null) {
|
||||||
|
while (i < remote.length && i < installed.length && remote[i].equals(installed[i])) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
// compare first non-equal ordinal number
|
||||||
|
if (i < remote.length && i < installed.length) {
|
||||||
|
int diff = Integer.valueOf(remote[i]).compareTo(Integer.valueOf(installed[i]));
|
||||||
|
return Integer.signum(diff);
|
||||||
|
}
|
||||||
|
// the strings are equal or one string is a substring of the other
|
||||||
|
// e.g. "1.2.3" = "1.2.3" or "1.2.3" < "1.2.3.4"
|
||||||
|
return Integer.signum(remote.length - installed.length);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
showToastMessage(true, true);
|
||||||
|
e.printStackTrace();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
class Wrapper {
|
||||||
|
boolean UpdateAvailable = false;
|
||||||
|
boolean NoUpdate = false;
|
||||||
|
boolean isError = false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,16 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.ALLOW_MESSAGE_CORRECTION;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.ALLOW_MESSAGE_RETRACTION;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.AUTOMATIC_ATTACHMENT_DELETION;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.CHAT_STATES;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.CONFIRM_MESSAGES;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.ENABLE_MULTI_ACCOUNTS;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.INDICATE_RECEIVED;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_OWN_ACCOUNTS;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.USE_INNER_STORAGE;
|
||||||
|
import static eu.siacs.conversations.utils.RichPreview.RICH_LINK_METADATA;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
|
@ -43,15 +54,6 @@ import android.util.DisplayMetrics;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.LruCache;
|
import android.util.LruCache;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import java.util.concurrent.Executor;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import net.java.otr4j.OtrException;
|
|
||||||
import net.java.otr4j.session.Session;
|
|
||||||
import net.java.otr4j.session.SessionID;
|
|
||||||
import net.java.otr4j.session.SessionImpl;
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
|
||||||
import eu.siacs.conversations.xmpp.jid.OtrJidHelper;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.BoolRes;
|
import androidx.annotation.BoolRes;
|
||||||
import androidx.annotation.IntegerRes;
|
import androidx.annotation.IntegerRes;
|
||||||
|
@ -60,6 +62,7 @@ import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
import com.otaliastudios.transcoder.strategy.DefaultAudioStrategy;
|
||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.openintents.openpgp.IOpenPgpService2;
|
import org.openintents.openpgp.IOpenPgpService2;
|
||||||
|
@ -90,6 +93,8 @@ import java.util.TimeZone;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
@ -151,6 +156,7 @@ import eu.siacs.conversations.utils.Resolver;
|
||||||
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
|
||||||
import eu.siacs.conversations.utils.StringUtils;
|
import eu.siacs.conversations.utils.StringUtils;
|
||||||
import eu.siacs.conversations.utils.TorServiceUtils;
|
import eu.siacs.conversations.utils.TorServiceUtils;
|
||||||
|
import eu.siacs.conversations.utils.TranscoderStrategies;
|
||||||
import eu.siacs.conversations.utils.WakeLockHelper;
|
import eu.siacs.conversations.utils.WakeLockHelper;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
@ -182,15 +188,6 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||||
import me.leolin.shortcutbadger.ShortcutBadger;
|
import me.leolin.shortcutbadger.ShortcutBadger;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.ALLOW_MESSAGE_CORRECTION;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.AUTOMATIC_ATTACHMENT_DELETION;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.CHAT_STATES;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.CONFIRM_MESSAGES;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.ENABLE_MULTI_ACCOUNTS;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.INDICATE_RECEIVED;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_OWN_ACCOUNTS;
|
|
||||||
import static eu.siacs.conversations.utils.RichPreview.RICH_LINK_METADATA;
|
|
||||||
|
|
||||||
public class XmppConnectionService extends Service {
|
public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations";
|
public static final String ACTION_REPLY_TO_CONVERSATION = "reply_to_conversations";
|
||||||
|
@ -256,18 +253,9 @@ public class XmppConnectionService extends Service {
|
||||||
Conversation conversation = find(getConversations(), contact);
|
Conversation conversation = find(getConversations(), contact);
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
if (online) {
|
if (online) {
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
if (contact.getPresences().size() == 1) {
|
if (contact.getPresences().size() == 1) {
|
||||||
sendUnsentMessages(conversation);
|
sendUnsentMessages(conversation);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
//check if the resource we are haveing a conversation with is still online
|
|
||||||
if (conversation.hasValidOtrSession()) {
|
|
||||||
String otrResource = conversation.getOtrSession().getSessionID().getUserID();
|
|
||||||
if (!(Arrays.asList(contact.getPresences().toResourceArray()).contains(otrResource))) {
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -448,9 +436,6 @@ public class XmppConnectionService extends Service {
|
||||||
if (conversation.getAccount() == account
|
if (conversation.getAccount() == account
|
||||||
&& !pendingJoin
|
&& !pendingJoin
|
||||||
&& !inProgressJoin) {
|
&& !inProgressJoin) {
|
||||||
if (!conversation.startOtrIfNeeded()) {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": couldn't start OTR with " + conversation.getContact().getJid() + " when needed");
|
|
||||||
}
|
|
||||||
sendUnsentMessages(conversation);
|
sendUnsentMessages(conversation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -505,7 +490,6 @@ public class XmppConnectionService extends Service {
|
||||||
private OpenPgpServiceConnection pgpServiceConnection;
|
private OpenPgpServiceConnection pgpServiceConnection;
|
||||||
private PgpEngine mPgpEngine = null;
|
private PgpEngine mPgpEngine = null;
|
||||||
private WakeLock wakeLock;
|
private WakeLock wakeLock;
|
||||||
private PowerManager pm;
|
|
||||||
private LruCache<String, Bitmap> mBitmapCache;
|
private LruCache<String, Bitmap> mBitmapCache;
|
||||||
private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver();
|
private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver();
|
||||||
private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver();
|
private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver();
|
||||||
|
@ -609,8 +593,8 @@ public class XmppConnectionService extends Service {
|
||||||
return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
|
return connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) {
|
public void attachImageToConversation(final Conversation conversation, final Uri uri, final String type, final UiCallback<Message> callback) {
|
||||||
final String mimeType = MimeUtils.guessMimeTypeFromUri(this, uri);
|
final String mimeType = MimeUtils.guessMimeTypeFromUriAndMime(this, uri, type);
|
||||||
final boolean compressPictures = getCompressImageResolutionPreference() != 0;
|
final boolean compressPictures = getCompressImageResolutionPreference() != 0;
|
||||||
if (!compressPictures
|
if (!compressPictures
|
||||||
|| getFileBackend().useImageAsIs(uri)
|
|| getFileBackend().useImageAsIs(uri)
|
||||||
|
@ -775,6 +759,7 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
final CharSequence body = remoteInput.getCharSequence("text_reply");
|
final CharSequence body = remoteInput.getCharSequence("text_reply");
|
||||||
final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false);
|
final boolean dismissNotification = intent.getBooleanExtra("dismiss_notification", false);
|
||||||
|
final String lastMessageUuid = intent.getStringExtra("last_message_uuid");
|
||||||
if (body == null || body.length() <= 0) {
|
if (body == null || body.length() <= 0) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -783,15 +768,7 @@ public class XmppConnectionService extends Service {
|
||||||
restoredFromDatabaseLatch.await();
|
restoredFromDatabaseLatch.await();
|
||||||
final Conversation c = findConversationByUuid(uuid);
|
final Conversation c = findConversationByUuid(uuid);
|
||||||
if (c != null) {
|
if (c != null) {
|
||||||
boolean pn = false;
|
directReply(c, body.toString(), lastMessageUuid, dismissNotification);
|
||||||
if (c.getMode() == Conversational.MODE_MULTI) {
|
|
||||||
final Message latestMessage = c.getLatestMessage();
|
|
||||||
if (latestMessage.isPrivateMessage()) {
|
|
||||||
pn = true;
|
|
||||||
c.setNextCounterpart(latestMessage.getCounterpart());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
directReply(c, body.toString(), dismissNotification, pn);
|
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.d(Config.LOGTAG, "unable to process direct reply");
|
Log.d(Config.LOGTAG, "unable to process direct reply");
|
||||||
|
@ -860,9 +837,6 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M && Build.MANUFACTURER.equals("Huawei")) {
|
|
||||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "LocationManagerService");
|
|
||||||
}
|
|
||||||
WakeLockHelper.acquire(wakeLock);
|
WakeLockHelper.acquire(wakeLock);
|
||||||
boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
|
boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
|
||||||
final HashSet<Account> pingCandidates = new HashSet<>();
|
final HashSet<Account> pingCandidates = new HashSet<>();
|
||||||
|
@ -1040,10 +1014,11 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void directReply(Conversation conversation, String body, final boolean dismissAfterReply, final boolean pn) {
|
private void directReply(final Conversation conversation, final String body, final String lastMessageUuid, final boolean dismissAfterReply) {
|
||||||
Message message = new Message(conversation, body, conversation.getNextEncryption());
|
final Message inReplyTo = lastMessageUuid == null ? null : conversation.findMessageWithUuid(lastMessageUuid);
|
||||||
if (pn) {
|
final Message message = new Message(conversation, body, conversation.getNextEncryption());
|
||||||
Message.configurePrivateMessage(message);
|
if (inReplyTo != null && inReplyTo.isPrivateMessage()) {
|
||||||
|
Message.configurePrivateMessage(message, inReplyTo.getCounterpart());
|
||||||
}
|
}
|
||||||
message.markUnread();
|
message.markUnread();
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
if (message.getEncryption() == Message.ENCRYPTION_PGP) {
|
||||||
|
@ -1173,6 +1148,23 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DefaultAudioStrategy getCompressAudioPreference() {
|
||||||
|
switch (getPreferences().getString("video_compression", getResources().getString(R.string.video_compression))) {
|
||||||
|
case "verylow":
|
||||||
|
return TranscoderStrategies.AUDIO_LQ;
|
||||||
|
case "low":
|
||||||
|
return TranscoderStrategies.AUDIO_LQ;
|
||||||
|
case "mid":
|
||||||
|
return TranscoderStrategies.AUDIO_MQ;
|
||||||
|
case "high":
|
||||||
|
return TranscoderStrategies.AUDIO_HQ;
|
||||||
|
case "uncompressed":
|
||||||
|
return TranscoderStrategies.AUDIO_HQ;
|
||||||
|
default:
|
||||||
|
return TranscoderStrategies.AUDIO_MQ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean getAttachmentChoicePreference() {
|
public boolean getAttachmentChoicePreference() {
|
||||||
return getBooleanPreference(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, R.bool.quick_share_attachment_choice);
|
return getBooleanPreference(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, R.bool.quick_share_attachment_choice);
|
||||||
}
|
}
|
||||||
|
@ -1412,6 +1404,7 @@ public class XmppConnectionService extends Service {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
|
||||||
startContactObserver();
|
startContactObserver();
|
||||||
}
|
}
|
||||||
|
FileBackend.switchStorage(usingInnerStorage());
|
||||||
FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath);
|
FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath);
|
||||||
if (Compatibility.hasStoragePermission(this)) {
|
if (Compatibility.hasStoragePermission(this)) {
|
||||||
Log.d(Config.LOGTAG, "starting file observer");
|
Log.d(Config.LOGTAG, "starting file observer");
|
||||||
|
@ -1437,7 +1430,7 @@ public class XmppConnectionService extends Service {
|
||||||
this.pgpServiceConnection.bindToService();
|
this.pgpServiceConnection.bindToService();
|
||||||
}
|
}
|
||||||
|
|
||||||
this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
|
final PowerManager pm = ContextCompat.getSystemService(this, PowerManager.class);
|
||||||
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.LOGTAG + ":Service");
|
this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.LOGTAG + ":Service");
|
||||||
toggleForegroundService();
|
toggleForegroundService();
|
||||||
updateUnreadCountBadge();
|
updateUnreadCountBadge();
|
||||||
|
@ -1574,8 +1567,8 @@ public class XmppConnectionService extends Service {
|
||||||
toggleForegroundService(false);
|
toggleForegroundService(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setOngoingCall(AbstractJingleConnection.Id id, Set<Media> media) {
|
public void setOngoingCall(AbstractJingleConnection.Id id, Set<Media> media, final boolean reconnecting) {
|
||||||
ongoingCall.set(new OngoingCall(id, media));
|
ongoingCall.set(new OngoingCall(id, media, reconnecting));
|
||||||
toggleForegroundService(false);
|
toggleForegroundService(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1591,7 +1584,7 @@ public class XmppConnectionService extends Service {
|
||||||
final Notification notification;
|
final Notification notification;
|
||||||
final int id;
|
final int id;
|
||||||
if (ongoing != null) {
|
if (ongoing != null) {
|
||||||
notification = this.mNotificationService.getOngoingCallNotification(ongoing.id, ongoing.media);
|
notification = this.mNotificationService.getOngoingCallNotification(ongoing);
|
||||||
id = NotificationService.ONGOING_CALL_NOTIFICATION_ID;
|
id = NotificationService.ONGOING_CALL_NOTIFICATION_ID;
|
||||||
startForeground(id, notification);
|
startForeground(id, notification);
|
||||||
mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID);
|
mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID);
|
||||||
|
@ -1776,11 +1769,6 @@ public class XmppConnectionService extends Service {
|
||||||
databaseBackend.updateConversation(conversation);
|
databaseBackend.updateConversation(conversation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
|
||||||
conversation.endOtrIfNeeded();
|
|
||||||
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
|
|
||||||
message1 -> markMessage(message1, Message.STATUS_SEND_FAILED));
|
|
||||||
}
|
|
||||||
|
|
||||||
final boolean inProgressJoin = isJoinInProgress(conversation);
|
final boolean inProgressJoin = isJoinInProgress(conversation);
|
||||||
|
|
||||||
|
@ -1813,30 +1801,6 @@ public class XmppConnectionService extends Service {
|
||||||
packet = mMessageGenerator.generatePgpChat(message);
|
packet = mMessageGenerator.generatePgpChat(message);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_OTR:
|
|
||||||
SessionImpl otrSession = conversation.getOtrSession();
|
|
||||||
if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
|
||||||
try {
|
|
||||||
message.setCounterpart(OtrJidHelper.fromSessionID(otrSession.getSessionID()));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (message.needsUploading()) {
|
|
||||||
mJingleConnectionManager.startJingleFileTransfer(message);
|
|
||||||
} else {
|
|
||||||
packet = mMessageGenerator.generateOtrChat(message);
|
|
||||||
}
|
|
||||||
} else if (otrSession == null) {
|
|
||||||
if (message.fixCounterpart()) {
|
|
||||||
conversation.startOtrSession(message.getCounterpart().getResource(), true);
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fix counterpart for OTR message to contact " + message.getCounterpart());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " OTR session with " + message.getContact() + " is in wrong state: " + otrSession.getSessionStatus().toString());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
||||||
if (message.needsUploading()) {
|
if (message.needsUploading()) {
|
||||||
|
@ -1889,12 +1853,6 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Message.ENCRYPTION_OTR:
|
|
||||||
if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": create otr session without starting for " + message.getContact().getJid());
|
|
||||||
conversation.startOtrSession(message.getCounterpart().getResource(), false);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case Message.ENCRYPTION_AXOLOTL:
|
case Message.ENCRYPTION_AXOLOTL:
|
||||||
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
||||||
break;
|
break;
|
||||||
|
@ -2148,6 +2106,14 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void pushBookmarks(Account account) {
|
||||||
|
if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
|
||||||
|
pushBookmarksPep(account);
|
||||||
|
} else {
|
||||||
|
pushBookmarksPrivateXml(account);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void pushBookmarksPrivateXml(Account account) {
|
private void pushBookmarksPrivateXml(Account account) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": pushing bookmarks via private xml");
|
||||||
IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
|
IqPacket iqPacket = new IqPacket(IqPacket.TYPE.SET);
|
||||||
|
@ -2166,7 +2132,6 @@ public class XmppConnectionService extends Service {
|
||||||
storage.addChild(bookmark);
|
storage.addChild(bookmark);
|
||||||
}
|
}
|
||||||
pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARKS, storage, "current", PublishOptions.persistentWhitelistAccess());
|
pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARKS, storage, "current", PublishOptions.persistentWhitelistAccess());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options) {
|
private void pushNodeAndEnforcePublishOptions(final Account account, final String node, final Element element, final String id, final Bundle options) {
|
||||||
|
@ -2219,7 +2184,10 @@ public class XmppConnectionService extends Service {
|
||||||
long diffConversationsRestore = SystemClock.elapsedRealtime() - startTimeConversationsRestore;
|
long diffConversationsRestore = SystemClock.elapsedRealtime() - startTimeConversationsRestore;
|
||||||
Log.d(Config.LOGTAG, "finished restoring conversations in " + diffConversationsRestore + "ms");
|
Log.d(Config.LOGTAG, "finished restoring conversations in " + diffConversationsRestore + "ms");
|
||||||
Runnable runnable = () -> {
|
Runnable runnable = () -> {
|
||||||
long deletionDate = getAutomaticMessageDeletionDate();
|
if (DatabaseBackend.requiresMessageIndexRebuild()) {
|
||||||
|
DatabaseBackend.getInstance(this).rebuildMessagesIndex();
|
||||||
|
}
|
||||||
|
final long deletionDate = getAutomaticMessageDeletionDate();
|
||||||
mLastExpiryRun.set(SystemClock.elapsedRealtime());
|
mLastExpiryRun.set(SystemClock.elapsedRealtime());
|
||||||
if (deletionDate > 0) {
|
if (deletionDate > 0) {
|
||||||
Log.d(Config.LOGTAG, "deleting messages that are older than " + AbstractGenerator.getTimestamp(deletionDate));
|
Log.d(Config.LOGTAG, "deleting messages that are older than " + AbstractGenerator.getTimestamp(deletionDate));
|
||||||
|
@ -3728,35 +3696,26 @@ public class XmppConnectionService extends Service {
|
||||||
|
|
||||||
public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
|
public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) {
|
||||||
final Jid jid = user.asBareJid();
|
final Jid jid = user.asBareJid();
|
||||||
IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
|
final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString());
|
||||||
sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() {
|
sendIqPacket(conference.getAccount(), request, (account, response) -> {
|
||||||
@Override
|
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
|
||||||
conference.getMucOptions().changeAffiliation(jid, affiliation);
|
conference.getMucOptions().changeAffiliation(jid, affiliation);
|
||||||
getAvatarService().clear(conference);
|
getAvatarService().clear(conference);
|
||||||
|
if (callback != null) {
|
||||||
callback.onAffiliationChangedSuccessful(jid);
|
callback.onAffiliationChangedSuccessful(jid);
|
||||||
} else {
|
} else {
|
||||||
callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation);
|
Log.d(Config.LOGTAG, "changed affiliation of " + user + " to " + affiliation);
|
||||||
}
|
}
|
||||||
|
} else if (callback != null) {
|
||||||
|
callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation);
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "unable to change affiliation");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) {
|
|
||||||
List<Jid> jids = new ArrayList<>();
|
|
||||||
for (MucOptions.User user : conference.getMucOptions().getUsers()) {
|
|
||||||
if (user.getAffiliation() == before && user.getRealJid() != null) {
|
|
||||||
jids.add(user.getRealJid());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString());
|
|
||||||
sendIqPacket(conference.getAccount(), request, mDefaultIqHandler);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) {
|
public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) {
|
||||||
IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
|
IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString());
|
||||||
Log.d(Config.LOGTAG, request.toString());
|
|
||||||
sendIqPacket(conference.getAccount(), request, (account, packet) -> {
|
sendIqPacket(conference.getAccount(), request, (account, packet) -> {
|
||||||
if (packet.getType() != IqPacket.TYPE.RESULT) {
|
if (packet.getType() != IqPacket.TYPE.RESULT) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick);
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick);
|
||||||
|
@ -3798,12 +3757,6 @@ public class XmppConnectionService extends Service {
|
||||||
if (conversation.getAccount() == account) {
|
if (conversation.getAccount() == account) {
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
leaveMuc(conversation, true);
|
leaveMuc(conversation, true);
|
||||||
} else {
|
|
||||||
if (conversation.endOtrIfNeeded()) {
|
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid()
|
|
||||||
+ ": ended otr session with "
|
|
||||||
+ conversation.getJid());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3865,67 +3818,6 @@ public class XmppConnectionService extends Service {
|
||||||
pushContactToServer(contact, null);
|
pushContactToServer(contact, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void onOtrSessionEstablished(Conversation conversation) {
|
|
||||||
final Account account = conversation.getAccount();
|
|
||||||
final Session otrSession = conversation.getOtrSession();
|
|
||||||
Log.d(Config.LOGTAG,
|
|
||||||
account.getJid().asBareJid() + " otr session established with "
|
|
||||||
+ conversation.getJid() + "/"
|
|
||||||
+ otrSession.getSessionID().getUserID());
|
|
||||||
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onMessageFound(Message message) {
|
|
||||||
SessionID id = otrSession.getSessionID();
|
|
||||||
try {
|
|
||||||
message.setCounterpart(Jid.of(id.getAccountID() + "/" + id.getUserID()));
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (message.needsUploading()) {
|
|
||||||
mJingleConnectionManager.startJingleFileTransfer(message);
|
|
||||||
} else {
|
|
||||||
MessagePacket outPacket = mMessageGenerator.generateOtrChat(message);
|
|
||||||
if (outPacket != null) {
|
|
||||||
mMessageGenerator.addDelay(outPacket, message.getTimeSent());
|
|
||||||
message.setStatus(Message.STATUS_SEND);
|
|
||||||
databaseBackend.updateMessage(message, false);
|
|
||||||
sendMessagePacket(account, outPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateConversationUi();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean renewSymmetricKey(Conversation conversation) {
|
|
||||||
Account account = conversation.getAccount();
|
|
||||||
byte[] symmetricKey = new byte[32];
|
|
||||||
this.mRandom.nextBytes(symmetricKey);
|
|
||||||
Session otrSession = conversation.getOtrSession();
|
|
||||||
if (otrSession != null) {
|
|
||||||
MessagePacket packet = new MessagePacket();
|
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
|
||||||
packet.setFrom(account.getJid());
|
|
||||||
MessageGenerator.addMessageHints(packet);
|
|
||||||
packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
|
|
||||||
+ otrSession.getSessionID().getUserID());
|
|
||||||
try {
|
|
||||||
packet.setBody(otrSession
|
|
||||||
.transformSending(CryptoHelper.FILETRANSFER
|
|
||||||
+ CryptoHelper.bytesToHex(symmetricKey))[0]);
|
|
||||||
sendMessagePacket(account, packet);
|
|
||||||
conversation.setSymmetricKey(symmetricKey);
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private void pushContactToServer(final Contact contact, final String preAuth) {
|
private void pushContactToServer(final Contact contact, final String preAuth) {
|
||||||
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
contact.resetOption(Contact.Options.DIRTY_DELETE);
|
||||||
contact.setOption(Contact.Options.DIRTY_PUSH);
|
contact.setOption(Contact.Options.DIRTY_PUSH);
|
||||||
|
@ -3939,10 +3831,10 @@ public class XmppConnectionService extends Service {
|
||||||
iq.query(Namespace.ROSTER).addChild(contact.asElement());
|
iq.query(Namespace.ROSTER).addChild(contact.asElement());
|
||||||
account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler);
|
account.getXmppConnection().sendIqPacket(iq, mDefaultIqHandler);
|
||||||
if (sendUpdates) {
|
if (sendUpdates) {
|
||||||
sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact, preAuth));
|
sendPresencePacket(account, mPresenceGenerator.sendPresenceUpdatesTo(contact));
|
||||||
}
|
}
|
||||||
if (ask) {
|
if (ask) {
|
||||||
sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact));
|
sendPresencePacket(account, mPresenceGenerator.requestPresenceUpdatesFrom(contact, preAuth));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
syncRoster(contact.getAccount());
|
syncRoster(contact.getAccount());
|
||||||
|
@ -4403,9 +4295,13 @@ public class XmppConnectionService extends Service {
|
||||||
new Thread(() -> reconnectAccount(account, false, true)).start();
|
new Thread(() -> reconnectAccount(account, false, true)).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void invite(Conversation conversation, Jid contact) {
|
public void invite(final Conversation conversation, final Jid contact) {
|
||||||
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": inviting " + contact + " to " + conversation.getJid().asBareJid());
|
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": inviting " + contact + " to " + conversation.getJid().asBareJid());
|
||||||
MessagePacket packet = mMessageGenerator.invite(conversation, contact);
|
final MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.asBareJid());
|
||||||
|
if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) {
|
||||||
|
changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null);
|
||||||
|
}
|
||||||
|
final MessagePacket packet = mMessageGenerator.invite(conversation, contact);
|
||||||
sendMessagePacket(conversation.getAccount(), packet);
|
sendMessagePacket(conversation.getAccount(), packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4537,10 +4433,18 @@ public class XmppConnectionService extends Service {
|
||||||
return getBooleanPreference(CONFIRM_MESSAGES, R.bool.confirm_messages);
|
return getBooleanPreference(CONFIRM_MESSAGES, R.bool.confirm_messages);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean usingInnerStorage() {
|
||||||
|
return getBooleanPreference(USE_INNER_STORAGE, R.bool.use_inner_storage);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean allowMessageCorrection() {
|
public boolean allowMessageCorrection() {
|
||||||
return getBooleanPreference(ALLOW_MESSAGE_CORRECTION, R.bool.allow_message_correction);
|
return getBooleanPreference(ALLOW_MESSAGE_CORRECTION, R.bool.allow_message_correction);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean allowMessageRetraction() {
|
||||||
|
return getBooleanPreference(ALLOW_MESSAGE_RETRACTION, R.bool.allow_message_retraction);
|
||||||
|
}
|
||||||
|
|
||||||
public boolean sendChatStates() {
|
public boolean sendChatStates() {
|
||||||
return getBooleanPreference(CHAT_STATES, R.bool.chat_states);
|
return getBooleanPreference(CHAT_STATES, R.bool.chat_states);
|
||||||
}
|
}
|
||||||
|
@ -4565,7 +4469,6 @@ public class XmppConnectionService extends Service {
|
||||||
return getBooleanPreference(SettingsActivity.WARN_UNENCRYPTED_CHAT, R.bool.warn_unencrypted_chat);
|
return getBooleanPreference(SettingsActivity.WARN_UNENCRYPTED_CHAT, R.bool.warn_unencrypted_chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public boolean hideYouAreNotParticipating() {
|
public boolean hideYouAreNotParticipating() {
|
||||||
return getBooleanPreference(SettingsActivity.HIDE_YOU_ARE_NOT_PARTICIPATING, R.bool.hide_you_are_not_participating);
|
return getBooleanPreference(SettingsActivity.HIDE_YOU_ARE_NOT_PARTICIPATING, R.bool.hide_you_are_not_participating);
|
||||||
}
|
}
|
||||||
|
@ -4586,6 +4489,14 @@ public class XmppConnectionService extends Service {
|
||||||
return getBooleanPreference(SHOW_OWN_ACCOUNTS, R.bool.show_own_accounts);
|
return getBooleanPreference(SHOW_OWN_ACCOUNTS, R.bool.show_own_accounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean allowMergeMessages() {
|
||||||
|
return getBooleanPreference("allowmergemessages", R.bool.allowmergemessages);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean showTextFormatting() {
|
||||||
|
return getBooleanPreference("showtextformatting", R.bool.showtextformatting);
|
||||||
|
}
|
||||||
|
|
||||||
public int unreadCount() {
|
public int unreadCount() {
|
||||||
int count = 0;
|
int count = 0;
|
||||||
for (Conversation conversation : getConversations()) {
|
for (Conversation conversation : getConversations()) {
|
||||||
|
@ -5252,10 +5163,7 @@ public class XmppConnectionService extends Service {
|
||||||
boolean performedVerification = false;
|
boolean performedVerification = false;
|
||||||
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
|
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
|
||||||
for (XmppUri.Fingerprint fp : fingerprints) {
|
for (XmppUri.Fingerprint fp : fingerprints) {
|
||||||
if (fp.type == XmppUri.FingerprintType.OTR) {
|
if (fp.type == XmppUri.FingerprintType.OMEMO) {
|
||||||
performedVerification |= contact.addOtrFingerprint(fp.fingerprint);
|
|
||||||
needsRosterWrite |= performedVerification;
|
|
||||||
} else if (fp.type == XmppUri.FingerprintType.OMEMO) {
|
|
||||||
String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", "");
|
String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", "");
|
||||||
FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint);
|
FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint);
|
||||||
if (fingerprintStatus != null) {
|
if (fingerprintStatus != null) {
|
||||||
|
@ -5491,12 +5399,14 @@ public class XmppConnectionService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class OngoingCall {
|
public static class OngoingCall {
|
||||||
private final AbstractJingleConnection.Id id;
|
public final AbstractJingleConnection.Id id;
|
||||||
private final Set<Media> media;
|
public final Set<Media> media;
|
||||||
|
public final boolean reconnecting;
|
||||||
|
|
||||||
public OngoingCall(AbstractJingleConnection.Id id, Set<Media> media) {
|
public OngoingCall(AbstractJingleConnection.Id id, Set<Media> media, final boolean reconnecting) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
this.media = media;
|
this.media = media;
|
||||||
|
this.reconnecting = reconnecting;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -5504,12 +5414,12 @@ public class XmppConnectionService extends Service {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
OngoingCall that = (OngoingCall) o;
|
OngoingCall that = (OngoingCall) o;
|
||||||
return Objects.equal(id, that.id);
|
return reconnecting == that.reconnecting && Objects.equal(id, that.id) && Objects.equal(media, that.media);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode(id);
|
return Objects.hashCode(id, media, reconnecting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ public class AboutActivity extends XmppActivity {
|
||||||
setContentView(R.layout.activity_about);
|
setContentView(R.layout.activity_about);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
configureActionBar(getSupportActionBar());
|
configureActionBar(getSupportActionBar());
|
||||||
setTitle(getString(R.string.title_activity_about_x, getString(R.string.app_name)));
|
|
||||||
aboutmessage = findViewById(R.id.aboutmessage);
|
aboutmessage = findViewById(R.id.aboutmessage);
|
||||||
libraries = findViewById(R.id.libraries);
|
libraries = findViewById(R.id.libraries);
|
||||||
Button privacyButton = findViewById(R.id.show_privacy_policy);
|
Button privacyButton = findViewById(R.id.show_privacy_policy);
|
||||||
|
|
|
@ -5,23 +5,18 @@ import android.content.Intent;
|
||||||
import android.preference.Preference;
|
import android.preference.Preference;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
|
|
||||||
import eu.siacs.conversations.BuildConfig;
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.utils.PhoneHelper;
|
import eu.siacs.conversations.utils.PhoneHelper;
|
||||||
|
|
||||||
public class AboutPreference extends Preference {
|
public class AboutPreference extends Preference {
|
||||||
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
|
public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
|
||||||
super(context, attrs, defStyle);
|
super(context, attrs, defStyle);
|
||||||
setSummaryAndTitle(context);
|
setSummary();
|
||||||
}
|
}
|
||||||
|
|
||||||
public AboutPreference(final Context context, final AttributeSet attrs) {
|
public AboutPreference(final Context context, final AttributeSet attrs) {
|
||||||
super(context, attrs);
|
super(context, attrs);
|
||||||
setSummaryAndTitle(context);
|
setSummary();
|
||||||
}
|
|
||||||
private void setSummaryAndTitle(final Context context) {
|
|
||||||
setSummary(String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME));
|
|
||||||
setTitle(context.getString(R.string.title_activity_about_x, BuildConfig.APP_NAME));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,5 +25,9 @@ public class AboutPreference extends Preference {
|
||||||
final Intent intent = new Intent(getContext(), AboutActivity.class);
|
final Intent intent = new Intent(getContext(), AboutActivity.class);
|
||||||
getContext().startActivity(intent);
|
getContext().startActivity(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setSummary() {
|
||||||
|
setSummary(getContext().getString(R.string.app_name) + ' ' + PhoneHelper.getVersionName(getContext()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,10 +4,10 @@ import static eu.siacs.conversations.entities.Bookmark.printableValue;
|
||||||
import static eu.siacs.conversations.ui.util.IntroHelper.showIntro;
|
import static eu.siacs.conversations.ui.util.IntroHelper.showIntro;
|
||||||
import static eu.siacs.conversations.utils.StringUtils.changed;
|
import static eu.siacs.conversations.utils.StringUtils.changed;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
|
@ -114,6 +114,14 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public static void open(final Activity activity, final Conversation conversation) {
|
||||||
|
Intent intent = new Intent(activity, ConferenceDetailsActivity.class);
|
||||||
|
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
||||||
|
intent.putExtra("uuid", conversation.getUuid());
|
||||||
|
activity.startActivity(intent);
|
||||||
|
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
|
}
|
||||||
|
|
||||||
private OnClickListener mNotifyStatusClickListener = new OnClickListener() {
|
private OnClickListener mNotifyStatusClickListener = new OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
@ -269,8 +277,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
if (mConversation != null) {
|
if (mConversation != null) {
|
||||||
final Bookmark bookmark = mConversation.getBookmark();
|
final Bookmark bookmark = mConversation.getBookmark();
|
||||||
if (bookmark != null) {
|
if (bookmark != null) {
|
||||||
|
this.binding.autojoinCheckbox.setEnabled(!getBooleanPreference("autojoin", R.bool.autojoin));
|
||||||
bookmark.setAutojoin(this.binding.autojoinCheckbox.isChecked());
|
bookmark.setAutojoin(this.binding.autojoinCheckbox.isChecked());
|
||||||
xmppConnectionService.createBookmark(mConversation.getAccount(), bookmark);
|
xmppConnectionService.pushBookmarks(mConversation.getAccount());
|
||||||
updateView();
|
updateView();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -330,8 +339,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
this.mAdvancedMode = !menuItem.isChecked();
|
this.mAdvancedMode = !menuItem.isChecked();
|
||||||
menuItem.setChecked(this.mAdvancedMode);
|
menuItem.setChecked(this.mAdvancedMode);
|
||||||
getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
|
getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).apply();
|
||||||
final boolean online = mConversation != null && mConversation.getMucOptions().online();
|
|
||||||
this.binding.mucInfoMore.setVisibility(this.mAdvancedMode && online ? View.VISIBLE : View.GONE);
|
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
updateView();
|
updateView();
|
||||||
break;
|
break;
|
||||||
|
@ -483,7 +490,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
final MenuItem share = menu.findItem(R.id.action_share);
|
final MenuItem share = menu.findItem(R.id.action_share);
|
||||||
share.setVisible(!groupChat);
|
share.setVisible(!groupChat);
|
||||||
final MenuItem menuMessageNotification = menu.findItem(R.id.action_message_notifications);
|
final MenuItem menuMessageNotification = menu.findItem(R.id.action_message_notifications);
|
||||||
if (Compatibility.runsTwentySix()) {
|
if (Compatibility.runsTwentySix() && xmppConnectionServiceBound) {
|
||||||
menuMessageNotification.setVisible(xmppConnectionService.hasIndividualNotification(mConversation));
|
menuMessageNotification.setVisible(xmppConnectionService.hasIndividualNotification(mConversation));
|
||||||
} else {
|
} else {
|
||||||
menuMessageNotification.setVisible(false);
|
menuMessageNotification.setVisible(false);
|
||||||
|
@ -598,7 +605,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
}
|
}
|
||||||
if (printableValue(subject)) {
|
if (printableValue(subject)) {
|
||||||
SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
|
SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
|
||||||
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
|
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor(), true);
|
||||||
MyLinkify.addLinks(spannable, false);
|
MyLinkify.addLinks(spannable, false);
|
||||||
this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
|
this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
|
||||||
this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
|
this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
|
||||||
|
@ -630,18 +637,15 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
|
this.binding.mucInfoMam.setText(R.string.server_info_unavailable);
|
||||||
}
|
}
|
||||||
if (bookmark != null) {
|
if (bookmark != null) {
|
||||||
|
this.binding.autojoinCheckbox.setEnabled(!getBooleanPreference("autojoin", R.bool.autojoin));
|
||||||
this.binding.autojoinCheckbox.setVisibility(View.VISIBLE);
|
this.binding.autojoinCheckbox.setVisibility(View.VISIBLE);
|
||||||
if (bookmark.autojoin()) {
|
this.binding.autojoinCheckbox.setChecked(bookmark.autojoin());
|
||||||
this.binding.autojoinCheckbox.setChecked(true);
|
|
||||||
} else {
|
|
||||||
this.binding.autojoinCheckbox.setChecked(false);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
this.binding.autojoinCheckbox.setVisibility(View.GONE);
|
this.binding.autojoinCheckbox.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
|
if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
|
||||||
if (mAdvancedMode) {
|
if (mAdvancedMode) {
|
||||||
this.binding.destroy.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY);
|
this.binding.destroy.getBackground().setTint(getWarningButtonColor());
|
||||||
this.binding.destroy.setTextColor(getWarningTextColor());
|
this.binding.destroy.setTextColor(getWarningTextColor());
|
||||||
this.binding.destroy.setVisibility(View.VISIBLE);
|
this.binding.destroy.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -667,12 +671,12 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
});
|
});
|
||||||
LeaveMucDialog.create().show();
|
LeaveMucDialog.create().show();
|
||||||
});
|
});
|
||||||
this.binding.leaveMuc.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY);
|
this.binding.leaveMuc.getBackground().setTint(getWarningButtonColor());
|
||||||
this.binding.leaveMuc.setTextColor(getWarningTextColor());
|
this.binding.leaveMuc.setTextColor(getWarningTextColor());
|
||||||
this.binding.addContactButton.setVisibility(View.VISIBLE);
|
this.binding.addContactButton.setVisibility(View.VISIBLE);
|
||||||
if (mConversation.getBookmark() != null) {
|
if (mConversation.getBookmark() != null) {
|
||||||
this.binding.addContactButton.setText(R.string.delete_bookmark);
|
this.binding.addContactButton.setText(R.string.delete_bookmark);
|
||||||
this.binding.addContactButton.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY);
|
this.binding.addContactButton.getBackground().setTint(getWarningButtonColor());
|
||||||
this.binding.addContactButton.setTextColor(getWarningTextColor());
|
this.binding.addContactButton.setTextColor(getWarningTextColor());
|
||||||
this.binding.addContactButton.setOnClickListener(v2 -> {
|
this.binding.addContactButton.setOnClickListener(v2 -> {
|
||||||
final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ConferenceDetailsActivity.this);
|
final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ConferenceDetailsActivity.this);
|
||||||
|
@ -682,6 +686,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
deleteFromRosterDialog.setPositiveButton(getString(R.string.delete),
|
deleteFromRosterDialog.setPositiveButton(getString(R.string.delete),
|
||||||
(dialog, which) -> {
|
(dialog, which) -> {
|
||||||
deleteBookmark();
|
deleteBookmark();
|
||||||
|
recreate();
|
||||||
});
|
});
|
||||||
deleteFromRosterDialog.create().show();
|
deleteFromRosterDialog.create().show();
|
||||||
});
|
});
|
||||||
|
@ -691,6 +696,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
this.binding.addContactButton.setTextColor(getDefaultButtonTextColor());
|
this.binding.addContactButton.setTextColor(getDefaultButtonTextColor());
|
||||||
this.binding.addContactButton.setOnClickListener(v2 -> {
|
this.binding.addContactButton.setOnClickListener(v2 -> {
|
||||||
saveAsBookmark();
|
saveAsBookmark();
|
||||||
|
recreate();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.util.IntroHelper.showIntro;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -7,7 +9,6 @@ import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.PorterDuff;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -28,8 +29,6 @@ import android.widget.CompoundButton;
|
||||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
@ -84,8 +83,6 @@ import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
||||||
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.util.IntroHelper.showIntro;
|
|
||||||
|
|
||||||
public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded {
|
public class ContactDetailsActivity extends OmemoActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnMediaLoaded {
|
||||||
public static final String ACTION_VIEW_CONTACT = "view_contact";
|
public static final String ACTION_VIEW_CONTACT = "view_contact";
|
||||||
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
|
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
|
||||||
|
@ -102,6 +99,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
xmppConnectionService.deleteContactOnServer(contact);
|
xmppConnectionService.deleteContactOnServer(contact);
|
||||||
|
recreate();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
|
private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
|
||||||
|
@ -161,6 +159,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showAddToPhoneBookDialog() {
|
private void showAddToPhoneBookDialog() {
|
||||||
|
//TODO check if isQuicksy and contact is on quicksy.im domain
|
||||||
|
// store in final boolean. show different message. use phone number for add
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle(getString(R.string.action_add_phone_book));
|
builder.setTitle(getString(R.string.action_add_phone_book));
|
||||||
builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString()));
|
builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toEscapedString()));
|
||||||
|
@ -170,6 +170,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
intent.setType(Contacts.CONTENT_ITEM_TYPE);
|
intent.setType(Contacts.CONTENT_ITEM_TYPE);
|
||||||
intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toEscapedString());
|
intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toEscapedString());
|
||||||
intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER);
|
intent.putExtra(Intents.Insert.IM_PROTOCOL, CommonDataKinds.Im.PROTOCOL_JABBER);
|
||||||
|
//TODO for modern use we want PROTOCOL_CUSTOM and an extra field with a value of 'XMPP'
|
||||||
|
// however we don’t have such a field and thus have to use the legacy PROTOCOL_JABBER
|
||||||
intent.putExtra("finishActivityOnSaveCompleted", true);
|
intent.putExtra("finishActivityOnSaveCompleted", true);
|
||||||
try {
|
try {
|
||||||
startActivityForResult(intent, 0);
|
startActivityForResult(intent, 0);
|
||||||
|
@ -319,6 +321,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (grantResults.length > 0)
|
if (grantResults.length > 0)
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
|
if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
|
||||||
|
@ -553,7 +556,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
binding.detailsReceivePresence.setOnCheckedChangeListener(null);
|
binding.detailsReceivePresence.setOnCheckedChangeListener(null);
|
||||||
binding.addContactButton.setVisibility(View.VISIBLE);
|
binding.addContactButton.setVisibility(View.VISIBLE);
|
||||||
binding.addContactButton.setText(getString(R.string.action_delete_contact));
|
binding.addContactButton.setText(getString(R.string.action_delete_contact));
|
||||||
binding.addContactButton.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY);
|
binding.addContactButton.getBackground().setTint(getWarningButtonColor());
|
||||||
binding.addContactButton.setTextColor(getWarningTextColor());
|
binding.addContactButton.setTextColor(getWarningTextColor());
|
||||||
binding.addContactButton.setOnClickListener(view -> {
|
binding.addContactButton.setOnClickListener(view -> {
|
||||||
final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ContactDetailsActivity.this);
|
final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ContactDetailsActivity.this);
|
||||||
|
@ -677,26 +680,6 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
binding.detailsContactKeys.removeAllViews();
|
binding.detailsContactKeys.removeAllViews();
|
||||||
boolean hasKeys = false;
|
boolean hasKeys = false;
|
||||||
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
if (Config.supportOtr()) {
|
|
||||||
for (final String otrFingerprint : contact.getOtrFingerprints()) {
|
|
||||||
hasKeys = true;
|
|
||||||
View view = inflater.inflate(R.layout.contact_key, binding.detailsContactKeys, false);
|
|
||||||
TextView key = view.findViewById(R.id.key);
|
|
||||||
TextView keyType = view.findViewById(R.id.key_type);
|
|
||||||
ImageButton removeButton = view
|
|
||||||
.findViewById(R.id.button_remove);
|
|
||||||
removeButton.setVisibility(View.VISIBLE);
|
|
||||||
key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
|
|
||||||
if (otrFingerprint != null && otrFingerprint.equalsIgnoreCase(messageFingerprint)) {
|
|
||||||
keyType.setText(R.string.otr_fingerprint_selected_message);
|
|
||||||
keyType.setTextColor(ContextCompat.getColor(this, R.color.accent));
|
|
||||||
} else {
|
|
||||||
keyType.setText(R.string.otr_fingerprint);
|
|
||||||
}
|
|
||||||
binding.detailsContactKeys.addView(view);
|
|
||||||
removeButton.setOnClickListener(v -> confirmToDeleteFingerprint(otrFingerprint));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
|
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
|
||||||
if (Config.supportOmemo() && axolotlService != null) {
|
if (Config.supportOmemo() && axolotlService != null) {
|
||||||
final Collection<XmppAxolotlSession> sessions = axolotlService.findSessionsForContact(contact);
|
final Collection<XmppAxolotlSession> sessions = axolotlService.findSessionsForContact(contact);
|
||||||
|
@ -785,20 +768,6 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
protected void confirmToDeleteFingerprint(final String fingerprint) {
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
builder.setTitle(R.string.delete_fingerprint);
|
|
||||||
builder.setMessage(R.string.sure_delete_fingerprint);
|
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
|
||||||
builder.setPositiveButton(R.string.delete,
|
|
||||||
(dialog, which) -> {
|
|
||||||
if (contact.deleteOtrFingerprint(fingerprint)) {
|
|
||||||
populateView();
|
|
||||||
xmppConnectionService.syncRosterToDisk(contact.getAccount());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onBackendConnected() {
|
public void onBackendConnected() {
|
||||||
if (accountJid != null && contactJid != null) {
|
if (accountJid != null && contactJid != null) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -29,7 +29,10 @@
|
||||||
|
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.HIDE_MEMORY_WARNING;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.MIN_ANDROID_SDK21_SHOWN;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
|
@ -53,9 +56,7 @@ import android.view.KeyEvent;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.PopupMenu;
|
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.IdRes;
|
import androidx.annotation.IdRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -77,6 +78,7 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.crypto.OmemoSetting;
|
import eu.siacs.conversations.crypto.OmemoSetting;
|
||||||
import eu.siacs.conversations.databinding.ActivityConversationsBinding;
|
import eu.siacs.conversations.databinding.ActivityConversationsBinding;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Conversational;
|
import eu.siacs.conversations.entities.Conversational;
|
||||||
import eu.siacs.conversations.entities.MucOptions;
|
import eu.siacs.conversations.entities.MucOptions;
|
||||||
|
@ -87,13 +89,13 @@ import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnConversationRead;
|
import eu.siacs.conversations.ui.interfaces.OnConversationRead;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
|
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
|
import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
|
||||||
|
import eu.siacs.conversations.ui.util.ActionBarUtil;
|
||||||
import eu.siacs.conversations.ui.util.ActivityResult;
|
import eu.siacs.conversations.ui.util.ActivityResult;
|
||||||
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
||||||
import eu.siacs.conversations.ui.util.IntroHelper;
|
import eu.siacs.conversations.ui.util.IntroHelper;
|
||||||
import eu.siacs.conversations.ui.util.PendingItem;
|
import eu.siacs.conversations.ui.util.PendingItem;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
import eu.siacs.conversations.ui.util.UpdateHelper;
|
import eu.siacs.conversations.ui.util.UpdateHelper;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
import eu.siacs.conversations.utils.EmojiWrapper;
|
||||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||||
import eu.siacs.conversations.utils.MenuDoubleTabUtil;
|
import eu.siacs.conversations.utils.MenuDoubleTabUtil;
|
||||||
|
@ -106,10 +108,6 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.HIDE_MEMORY_WARNING;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.MIN_ANDROID_SDK21_SHOWN;
|
|
||||||
|
|
||||||
public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoomDestroy {
|
public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoomDestroy {
|
||||||
|
|
||||||
public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.VIEW";
|
public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.VIEW";
|
||||||
|
@ -125,7 +123,9 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
public static final String ACTION_DESTROY_MUC = "eu.siacs.conversations.DESTROY_MUC";
|
public static final String ACTION_DESTROY_MUC = "eu.siacs.conversations.DESTROY_MUC";
|
||||||
public static final int REQUEST_OPEN_MESSAGE = 0x9876;
|
public static final int REQUEST_OPEN_MESSAGE = 0x9876;
|
||||||
public static final int REQUEST_PLAY_PAUSE = 0x5432;
|
public static final int REQUEST_PLAY_PAUSE = 0x5432;
|
||||||
private static List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
|
public static final String EXTRA_TYPE = "type";
|
||||||
|
|
||||||
|
private static final List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
|
||||||
ACTION_VIEW_CONVERSATION,
|
ACTION_VIEW_CONVERSATION,
|
||||||
Intent.ACTION_SEND,
|
Intent.ACTION_SEND,
|
||||||
Intent.ACTION_SEND_MULTIPLE
|
Intent.ACTION_SEND_MULTIPLE
|
||||||
|
@ -215,6 +215,16 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (useInternalUpdater()) {
|
||||||
|
if (xmppConnectionService.getAccounts().size() != 0) {
|
||||||
|
if (xmppConnectionService.hasInternetConnection()) {
|
||||||
|
if (xmppConnectionService.isWIFI() || (xmppConnectionService.isMobile() && !xmppConnectionService.isMobileRoaming())) {
|
||||||
|
AppUpdate(xmppConnectionService.installedFrom());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
|
for (@IdRes int id : FRAGMENT_ID_NOTIFICATION_ORDER) {
|
||||||
notifyFragmentOfBackendConnected(id);
|
notifyFragmentOfBackendConnected(id);
|
||||||
}
|
}
|
||||||
|
@ -276,7 +286,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showOutdatedVersionWarning() {
|
private void showOutdatedVersionWarning() {
|
||||||
if (Compatibility.runsTwentyOne() || getPreferences().getBoolean(MIN_ANDROID_SDK21_SHOWN, false)) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP || getPreferences().getBoolean(MIN_ANDROID_SDK21_SHOWN, false)) {
|
||||||
Log.d(Config.LOGTAG, "Device is running Android >= SDK 21");
|
Log.d(Config.LOGTAG, "Device is running Android >= SDK 21");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -302,6 +312,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
|
getPreferences().edit().putBoolean(getBatteryOptimizationPreferenceKey(), false).apply();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean getAttachmentChoicePreference() {
|
||||||
|
return getBooleanPreference(SettingsActivity.QUICK_SHARE_ATTACHMENT_CHOICE, R.bool.quick_share_attachment_choice);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean warnUnecryptedChat() {
|
||||||
|
return getBooleanPreference(SettingsActivity.WARN_UNENCRYPTED_CHAT, R.bool.warn_unencrypted_chat);
|
||||||
|
}
|
||||||
|
|
||||||
private void openBatteryOptimizationDialogIfNeeded() {
|
private void openBatteryOptimizationDialogIfNeeded() {
|
||||||
if (hasAccountWithoutPush()
|
if (hasAccountWithoutPush()
|
||||||
&& isOptimizingBattery()
|
&& isOptimizingBattery()
|
||||||
|
@ -320,9 +338,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
ToastCompat.makeText(this, R.string.device_does_not_support_battery_op, ToastCompat.LENGTH_SHORT).show();
|
ToastCompat.makeText(this, R.string.device_does_not_support_battery_op, ToastCompat.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
|
|
||||||
builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
|
builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain());
|
||||||
}
|
|
||||||
final AlertDialog dialog = builder.create();
|
final AlertDialog dialog = builder.create();
|
||||||
dialog.setCanceledOnTouchOutside(false);
|
dialog.setCanceledOnTouchOutside(false);
|
||||||
dialog.show();
|
dialog.show();
|
||||||
|
@ -349,12 +365,12 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
|
try {
|
||||||
totalMemory = FileBackend.getDiskSize();
|
totalMemory = FileBackend.getDiskSize();
|
||||||
mediaUsage = FileBackend.getDirectorySize(new File(FileBackend.getAppMediaDirectory()));
|
mediaUsage = FileBackend.getDirectorySize(new File(FileBackend.getAppMediaDirectory()));
|
||||||
try {
|
|
||||||
relativeUsage = ((double) mediaUsage / (double) totalMemory);
|
relativeUsage = ((double) mediaUsage / (double) totalMemory);
|
||||||
try {
|
try {
|
||||||
percentUsage = String.format("%.2f", relativeUsage * 100) + " %";
|
percentUsage = String.format(Locale.getDefault(),"%.2f", relativeUsage * 100) + " %";
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
percentUsage = String.format(Locale.ENGLISH,"%.2f", relativeUsage * 100) + " %";
|
percentUsage = String.format(Locale.ENGLISH,"%.2f", relativeUsage * 100) + " %";
|
||||||
|
@ -611,8 +627,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openConversation(Conversation conversation, Bundle extras) {
|
private void openConversation(Conversation conversation, Bundle extras) {
|
||||||
|
final FragmentManager fragmentManager = getFragmentManager();
|
||||||
|
executePendingTransactions(fragmentManager);
|
||||||
|
ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment);
|
||||||
xmppConnectionService.updateNotificationChannels();
|
xmppConnectionService.updateNotificationChannels();
|
||||||
ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment);
|
|
||||||
final boolean mainNeedsRefresh;
|
final boolean mainNeedsRefresh;
|
||||||
if (conversationFragment == null) {
|
if (conversationFragment == null) {
|
||||||
mainNeedsRefresh = false;
|
mainNeedsRefresh = false;
|
||||||
|
@ -648,6 +666,14 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
IntroHelper.showIntro(this, conversation.getMode() == Conversational.MODE_MULTI);
|
IntroHelper.showIntro(this, conversation.getMode() == Conversational.MODE_MULTI);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void executePendingTransactions(final FragmentManager fragmentManager) {
|
||||||
|
try {
|
||||||
|
fragmentManager.executePendingTransactions();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
Log.e(Config.LOGTAG,"unable to execute pending fragment transactions");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public boolean onXmppUriClicked(Uri uri) {
|
public boolean onXmppUriClicked(Uri uri) {
|
||||||
XmppUri xmppUri = new XmppUri(uri);
|
XmppUri xmppUri = new XmppUri(uri);
|
||||||
if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
|
if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
|
||||||
|
@ -660,6 +686,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("NonConstantResourceId")
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
||||||
|
@ -692,13 +719,13 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
|
intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
//case R.id.action_check_updates:
|
case R.id.action_check_updates:
|
||||||
// if (xmppConnectionService.hasInternetConnection()) {
|
if (xmppConnectionService.hasInternetConnection()) {
|
||||||
// openInstallFromUnknownSourcesDialogIfNeeded(true);
|
openInstallFromUnknownSourcesDialogIfNeeded(true);
|
||||||
// } else {
|
} else {
|
||||||
// ToastCompat.makeText(this, R.string.account_status_no_internet, ToastCompat.LENGTH_LONG).show();
|
ToastCompat.makeText(this, R.string.account_status_no_internet, ToastCompat.LENGTH_LONG).show();
|
||||||
// }
|
}
|
||||||
// break;
|
break;
|
||||||
case R.id.action_invite_user:
|
case R.id.action_invite_user:
|
||||||
inviteUser();
|
inviteUser();
|
||||||
break;
|
break;
|
||||||
|
@ -726,6 +753,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStart() {
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
final int theme = findTheme();
|
final int theme = findTheme();
|
||||||
if (this.mTheme != theme) {
|
if (this.mTheme != theme) {
|
||||||
this.mSkipBackgroundBinding = true;
|
this.mSkipBackgroundBinding = true;
|
||||||
|
@ -835,8 +863,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
|
|
||||||
private void invalidateActionBarTitle() {
|
private void invalidateActionBarTitle() {
|
||||||
final ActionBar actionBar = getSupportActionBar();
|
final ActionBar actionBar = getSupportActionBar();
|
||||||
if (actionBar != null) {
|
if (actionBar == null) {
|
||||||
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
|
return;
|
||||||
|
}
|
||||||
|
final FragmentManager fragmentManager = getFragmentManager();
|
||||||
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
||||||
if (mainFragment instanceof ConversationFragment) {
|
if (mainFragment instanceof ConversationFragment) {
|
||||||
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
|
@ -850,36 +881,15 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
TextView abtitle = findViewById(android.R.id.text1);
|
TextView abtitle = findViewById(android.R.id.text1);
|
||||||
TextView absubtitle = findViewById(android.R.id.text2);
|
TextView absubtitle = findViewById(android.R.id.text2);
|
||||||
abtitle.setText(EmojiWrapper.transform(conversation.getName()));
|
abtitle.setText(EmojiWrapper.transform(conversation.getName()));
|
||||||
abtitle.setOnClickListener(view1 -> {
|
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
||||||
switchToContactDetails(conversation.getContact());
|
|
||||||
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
||||||
Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
|
|
||||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
|
||||||
intent.putExtra("uuid", conversation.getUuid());
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
abtitle.setSelected(true);
|
abtitle.setSelected(true);
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE && !conversation.withSelf()) {
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||||
|
if (!conversation.withSelf()) {
|
||||||
ChatState state = conversation.getIncomingChatState();
|
ChatState state = conversation.getIncomingChatState();
|
||||||
if (state == ChatState.COMPOSING) {
|
if (state == ChatState.COMPOSING) {
|
||||||
absubtitle.setText(getString(R.string.is_typing));
|
absubtitle.setText(getString(R.string.is_typing));
|
||||||
absubtitle.setVisibility(View.VISIBLE);
|
absubtitle.setVisibility(View.VISIBLE);
|
||||||
absubtitle.setTypeface(null, Typeface.BOLD_ITALIC);
|
absubtitle.setTypeface(null, Typeface.BOLD_ITALIC);
|
||||||
absubtitle.setSelected(true);
|
absubtitle.setSelected(true);
|
||||||
absubtitle.setOnClickListener(view13 -> {
|
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
||||||
switchToContactDetails(conversation.getContact());
|
|
||||||
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
||||||
Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
|
|
||||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
|
||||||
intent.putExtra("uuid", conversation.getUuid());
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
if (showLastSeen && conversation.getContact().getLastseen() > 0 && conversation.getContact().getPresences().allOrNonSupport(Namespace.IDLE)) {
|
if (showLastSeen && conversation.getContact().getLastseen() > 0 && conversation.getContact().getPresences().allOrNonSupport(Namespace.IDLE)) {
|
||||||
absubtitle.setText(UIHelper.lastseen(getApplicationContext(), conversation.getContact().isActive(), conversation.getContact().getLastseen()));
|
absubtitle.setText(UIHelper.lastseen(getApplicationContext(), conversation.getContact().isActive(), conversation.getContact().getLastseen()));
|
||||||
|
@ -889,17 +899,10 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
absubtitle.setVisibility(View.GONE);
|
absubtitle.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
absubtitle.setSelected(true);
|
absubtitle.setSelected(true);
|
||||||
absubtitle.setOnClickListener(view14 -> {
|
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
||||||
switchToContactDetails(conversation.getContact());
|
|
||||||
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
||||||
Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
|
|
||||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
|
||||||
intent.putExtra("uuid", conversation.getUuid());
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
|
absubtitle.setText(null);
|
||||||
|
absubtitle.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
ChatState state = ChatState.COMPOSING;
|
ChatState state = ChatState.COMPOSING;
|
||||||
|
@ -938,18 +941,11 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
absubtitle.setSelected(true);
|
absubtitle.setSelected(true);
|
||||||
absubtitle.setOnClickListener(view15 -> {
|
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
|
||||||
switchToContactDetails(conversation.getContact());
|
|
||||||
} else if (conversation.getMode() == Conversation.MODE_MULTI) {
|
|
||||||
Intent intent = new Intent(ConversationsActivity.this, ConferenceDetailsActivity.class);
|
|
||||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
|
||||||
intent.putExtra("uuid", conversation.getUuid());
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
ActionBarUtil.setCustomActionBarOnClickListener(
|
||||||
|
binding.toolbar,
|
||||||
|
(v) -> openConversationDetails(conversation)
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -960,39 +956,20 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
actionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.header_background)));
|
actionBar.setBackgroundDrawable(new ColorDrawable(getResources().getColor(R.color.header_background)));
|
||||||
actionBar.setSubtitle(null);
|
actionBar.setSubtitle(null);
|
||||||
actionBar.setDisplayHomeAsUpEnabled(false);
|
actionBar.setDisplayHomeAsUpEnabled(false);
|
||||||
|
ActionBarUtil.resetCustomActionBarOnClickListeners(binding.toolbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openConversationDetails(final Conversation conversation) {
|
||||||
|
if (conversation.getMode() == Conversational.MODE_MULTI) {
|
||||||
|
ConferenceDetailsActivity.open(this, conversation);
|
||||||
|
} else {
|
||||||
|
final Contact contact = conversation.getContact();
|
||||||
|
if (contact.isSelf()) {
|
||||||
|
switchToAccount(conversation.getAccount());
|
||||||
|
} else {
|
||||||
|
switchToContactDetails(contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public void verifyOtrSessionDialog(final Conversation conversation, View view) {
|
|
||||||
if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
|
|
||||||
ToastCompat.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (view == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
PopupMenu popup = new PopupMenu(this, view);
|
|
||||||
popup.inflate(R.menu.verification_choices);
|
|
||||||
popup.setOnMenuItemClickListener(menuItem -> {
|
|
||||||
Intent intent = new Intent(ConversationsActivity.this, VerifyOTRActivity.class);
|
|
||||||
intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
|
|
||||||
intent.putExtra("contact", conversation.getContact().getJid().asBareJid().toString());
|
|
||||||
intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString());
|
|
||||||
switch (menuItem.getItemId()) {
|
|
||||||
case R.id.scan_fingerprint:
|
|
||||||
intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT);
|
|
||||||
break;
|
|
||||||
case R.id.ask_question:
|
|
||||||
intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION);
|
|
||||||
break;
|
|
||||||
case R.id.manual_verification:
|
|
||||||
intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
popup.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1000,17 +977,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
if (performRedirectIfNecessary(conversation, false)) {
|
if (performRedirectIfNecessary(conversation, false)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment);
|
final FragmentManager fragmentManager = getFragmentManager();
|
||||||
|
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
|
||||||
if (mainFragment instanceof ConversationFragment) {
|
if (mainFragment instanceof ConversationFragment) {
|
||||||
try {
|
try {
|
||||||
getFragmentManager().popBackStack();
|
fragmentManager.popBackStack();
|
||||||
} catch (IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
|
Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e);
|
||||||
//this usually means activity is no longer active; meaning on the next open we will run through this again
|
//this usually means activity is no longer active; meaning on the next open we will run through this again
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment);
|
final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
|
||||||
if (secondaryFragment instanceof ConversationFragment) {
|
if (secondaryFragment instanceof ConversationFragment) {
|
||||||
if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
|
if (((ConversationFragment) secondaryFragment).getConversation() == conversation) {
|
||||||
Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
|
Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation);
|
||||||
|
@ -1072,6 +1050,25 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
runOnUiThread(() -> ToastCompat.makeText(this, resId, ToastCompat.LENGTH_SHORT).show());
|
runOnUiThread(() -> ToastCompat.makeText(this, resId, ToastCompat.LENGTH_SHORT).show());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void AppUpdate(String Store) {
|
||||||
|
String PREFS_NAME = "UpdateTimeStamp";
|
||||||
|
SharedPreferences UpdateTimeStamp = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||||
|
long lastUpdateTime = UpdateTimeStamp.getLong("lastUpdateTime", 0);
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: LastUpdateTime: " + lastUpdateTime);
|
||||||
|
if ((lastUpdateTime + (Config.UPDATE_CHECK_TIMER * 1000)) < System.currentTimeMillis()) {
|
||||||
|
lastUpdateTime = System.currentTimeMillis();
|
||||||
|
SharedPreferences.Editor editor = UpdateTimeStamp.edit();
|
||||||
|
editor.putLong("lastUpdateTime", lastUpdateTime);
|
||||||
|
editor.apply();
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: CurrentTime: " + lastUpdateTime);
|
||||||
|
if (Store == null) {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater started");
|
||||||
|
openInstallFromUnknownSourcesDialogIfNeeded(false);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater stopped");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRoomDestroySucceeded() {
|
public void onRoomDestroySucceeded() {
|
||||||
|
|
|
@ -37,7 +37,6 @@ public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOn
|
||||||
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
|
||||||
setSupportActionBar((Toolbar) binding.toolbar);
|
setSupportActionBar((Toolbar) binding.toolbar);
|
||||||
configureActionBar(getSupportActionBar(), true);
|
configureActionBar(getSupportActionBar(), true);
|
||||||
this.binding.shareButton.setOnClickListener(v -> share());
|
|
||||||
if (bundle != null && bundle.containsKey("invite")) {
|
if (bundle != null && bundle.containsKey("invite")) {
|
||||||
this.easyOnboardingInvite = bundle.getParcelable("invite");
|
this.easyOnboardingInvite = bundle.getParcelable("invite");
|
||||||
if (this.easyOnboardingInvite != null) {
|
if (this.easyOnboardingInvite != null) {
|
||||||
|
@ -100,7 +99,7 @@ public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOn
|
||||||
final Point size = new Point();
|
final Point size = new Point();
|
||||||
getWindowManager().getDefaultDisplay().getSize(size);
|
getWindowManager().getDefaultDisplay().getSize(size);
|
||||||
final int width = Math.min(size.x, size.y);
|
final int width = Math.min(size.x, size.y);
|
||||||
final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableLink(), width);
|
final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableUri(), width);
|
||||||
binding.qrCode.setImageBitmap(bitmap);
|
binding.qrCode.setImageBitmap(bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
@ -33,7 +36,6 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
@ -86,9 +88,6 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
|
||||||
|
|
||||||
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
|
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
|
||||||
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
|
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
|
||||||
|
|
||||||
|
@ -428,6 +427,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
if (view.getId() == R.id.hostname) {
|
if (view.getId() == R.id.hostname) {
|
||||||
resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
|
resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example;
|
||||||
}
|
}
|
||||||
|
if (view.getId() == R.id.port) {
|
||||||
|
resId = R.string.port_example;
|
||||||
|
}
|
||||||
final int res = resId;
|
final int res = resId;
|
||||||
new Handler().postDelayed(() -> et.setHint(res), 500);
|
new Handler().postDelayed(() -> et.setHint(res), 500);
|
||||||
} else {
|
} else {
|
||||||
|
@ -648,6 +650,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
|
this.binding.hostname.setOnFocusChangeListener(mEditTextFocusListener);
|
||||||
this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
|
this.binding.clearDevices.setOnClickListener(v -> showWipePepDialog());
|
||||||
this.binding.port.setText(String.valueOf(Resolver.DEFAULT_PORT_XMPP));
|
this.binding.port.setText(String.valueOf(Resolver.DEFAULT_PORT_XMPP));
|
||||||
|
this.binding.port.setOnFocusChangeListener(mEditTextFocusListener);
|
||||||
this.binding.port.addTextChangedListener(mTextWatcher);
|
this.binding.port.addTextChangedListener(mTextWatcher);
|
||||||
this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
|
this.binding.saveButton.setOnClickListener(this.mSaveButtonClickListener);
|
||||||
this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
|
this.binding.cancelButton.setOnClickListener(this.mCancelButtonClickListener);
|
||||||
|
@ -1142,7 +1145,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
|
binding.accountPassword.getEditableText().append(this.mAccount.getPassword());
|
||||||
binding.accountPassword.setText(this.mAccount.getPassword());
|
binding.accountPassword.setText(this.mAccount.getPassword());
|
||||||
this.binding.hostname.setText("");
|
this.binding.hostname.setText("");
|
||||||
|
if (this.mAccount.getHostname().length() > 0) {
|
||||||
this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
|
this.binding.hostname.getEditableText().append(this.mAccount.getHostname());
|
||||||
|
} else {
|
||||||
|
this.binding.hostname.getEditableText().append(this.mAccount.getDomain());
|
||||||
|
}
|
||||||
this.binding.port.setText("");
|
this.binding.port.setText("");
|
||||||
this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
|
this.binding.port.getEditableText().append(String.valueOf(this.mAccount.getPort()));
|
||||||
this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
|
this.binding.namePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
|
||||||
|
@ -1298,25 +1305,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
||||||
} else {
|
} else {
|
||||||
this.binding.pgpFingerprintBox.setVisibility(View.GONE);
|
this.binding.pgpFingerprintBox.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
final String otrFingerprint = this.mAccount.getOtrFingerprint();
|
|
||||||
if (otrFingerprint != null && Config.supportOtr()) {
|
|
||||||
if ("otr".equals(messageFingerprint)) {
|
|
||||||
this.binding.otrFingerprintDesc.setTextColor(ContextCompat.getColor(this, R.color.accent));
|
|
||||||
}
|
|
||||||
this.binding.otrFingerprintBox.setVisibility(View.VISIBLE);
|
|
||||||
this.binding.otrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
|
|
||||||
this.binding.actionCopyToClipboard.setVisibility(View.VISIBLE);
|
|
||||||
this.binding.actionCopyToClipboard.setOnClickListener(v -> {
|
|
||||||
if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(otrFingerprint), R.string.otr_fingerprint)) {
|
|
||||||
ToastCompat.makeText(
|
|
||||||
EditAccountActivity.this,
|
|
||||||
R.string.toast_message_otr_fingerprint,
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.binding.otrFingerprintBox.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
|
final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
|
||||||
if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
|
if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
|
||||||
this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
|
this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -138,7 +138,9 @@ public class ImportBackupActivity extends XmppActivity implements ServiceConnect
|
||||||
try {
|
try {
|
||||||
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
|
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
|
||||||
showEnterPasswordDialog(backupFile, finishOnCancel);
|
showEnterPasswordDialog(backupFile, finishOnCancel);
|
||||||
} catch (final IOException | IllegalArgumentException e) {
|
} catch (final IOException e) {
|
||||||
|
Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
Log.d(Config.LOGTAG, "unable to open backup file " + uri, e);
|
Log.d(Config.LOGTAG, "unable to open backup file " + uri, e);
|
||||||
Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
|
Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,74 +1,289 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
|
import android.annotation.TargetApi;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.BitmapFactory;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
import android.location.LocationManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
|
import android.provider.Settings;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.MenuItem;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.annotation.BoolRes;
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.osmdroid.api.IGeoPoint;
|
||||||
|
import org.osmdroid.api.IMapController;
|
||||||
|
import org.osmdroid.config.Configuration;
|
||||||
|
import org.osmdroid.config.IConfigurationProvider;
|
||||||
|
import org.osmdroid.tileprovider.tilesource.TileSourceFactory;
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import org.osmdroid.views.CustomZoomButtonsController;
|
||||||
|
import org.osmdroid.views.MapView;
|
||||||
|
import org.osmdroid.views.overlay.CopyrightOverlay;
|
||||||
|
import org.osmdroid.views.overlay.Overlay;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
|
import eu.siacs.conversations.ui.util.LocationHelper;
|
||||||
|
import eu.siacs.conversations.ui.widget.Marker;
|
||||||
|
import eu.siacs.conversations.ui.widget.MyLocation;
|
||||||
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
|
|
||||||
public abstract class LocationActivity extends XmppActivity implements LocationListener {
|
public abstract class LocationActivity extends XmppActivity implements LocationListener {
|
||||||
private LocationManager locationManager;
|
protected LocationManager locationManager;
|
||||||
public static final int REQUEST_LOCATION_PERMISSION = 0x682f41;
|
protected boolean hasLocationFeature;
|
||||||
|
|
||||||
|
public static final int REQUEST_CODE_CREATE = 0;
|
||||||
|
public static final int REQUEST_CODE_FAB_PRESSED = 1;
|
||||||
|
public static final int REQUEST_CODE_SNACKBAR_PRESSED = 2;
|
||||||
|
|
||||||
|
protected static final String KEY_LOCATION = "loc";
|
||||||
|
protected static final String KEY_ZOOM_LEVEL = "zoom";
|
||||||
|
|
||||||
|
protected Location myLoc = null;
|
||||||
|
private MapView map = null;
|
||||||
|
protected IMapController mapController = null;
|
||||||
|
|
||||||
|
protected Bitmap marker_icon;
|
||||||
|
|
||||||
|
protected void clearMarkers() {
|
||||||
|
synchronized (this.map.getOverlays()) {
|
||||||
|
for (final Overlay overlay : this.map.getOverlays()) {
|
||||||
|
if (overlay instanceof Marker || overlay instanceof MyLocation) {
|
||||||
|
this.map.getOverlays().remove(overlay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
protected void updateLocationMarkers() {
|
||||||
|
clearMarkers();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
final Context ctx = getApplicationContext();
|
||||||
|
setTheme(ThemeHelper.find(this));
|
||||||
|
|
||||||
|
final PackageManager packageManager = ctx.getPackageManager();
|
||||||
|
hasLocationFeature = packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION) ||
|
||||||
|
packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_GPS) ||
|
||||||
|
packageManager.hasSystemFeature(PackageManager.FEATURE_LOCATION_NETWORK);
|
||||||
this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
this.locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
||||||
|
this.marker_icon = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.marker);
|
||||||
|
|
||||||
|
// Ask for location permissions if location services are enabled and we're
|
||||||
|
// just starting the activity (we don't want to keep pestering them on every
|
||||||
|
// screen rotation or if there's no point because it's disabled anyways).
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
|
||||||
|
requestPermissions(REQUEST_CODE_CREATE);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void gotoLoc() throws UnsupportedOperationException;
|
final IConfigurationProvider config = Configuration.getInstance();
|
||||||
|
config.load(ctx, getPreferences());
|
||||||
|
//config.setUserAgentValue(BuildConfig.APPLICATION_ID + "/" + BuildConfig.VERSION_CODE);
|
||||||
|
if (QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor)) {
|
||||||
|
config.setHttpProxy(HttpConnectionManager.getProxy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract void setmLastLocation(final Location location);
|
@Override
|
||||||
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
|
||||||
|
final IGeoPoint center = map.getMapCenter();
|
||||||
|
outState.putParcelable(KEY_LOCATION, new GeoPoint(
|
||||||
|
center.getLatitude(),
|
||||||
|
center.getLongitude()
|
||||||
|
));
|
||||||
|
outState.putDouble(KEY_ZOOM_LEVEL, map.getZoomLevelDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
|
||||||
|
if (savedInstanceState.containsKey(KEY_LOCATION)) {
|
||||||
|
mapController.setCenter(savedInstanceState.getParcelable(KEY_LOCATION));
|
||||||
|
}
|
||||||
|
if (savedInstanceState.containsKey(KEY_ZOOM_LEVEL)) {
|
||||||
|
mapController.setZoom(savedInstanceState.getDouble(KEY_ZOOM_LEVEL));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setupMapView(MapView mapView, final GeoPoint pos) {
|
||||||
|
map = mapView;
|
||||||
|
map.getOverlays().add(new CopyrightOverlay(this));
|
||||||
|
map.setTileSource(TileSourceFactory.MAPNIK);
|
||||||
|
map.getZoomController().setVisibility(CustomZoomButtonsController.Visibility.NEVER);
|
||||||
|
map.setMultiTouchControls(true);
|
||||||
|
map.setTilesScaledToDpi(false);
|
||||||
|
mapController = map.getController();
|
||||||
|
mapController.setZoom(Config.Map.INITIAL_ZOOM_LEVEL);
|
||||||
|
mapController.setCenter(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void gotoLoc() {
|
||||||
|
gotoLoc(map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void gotoLoc(final boolean setZoomLevel);
|
||||||
|
|
||||||
|
protected abstract void setMyLoc(final Location location);
|
||||||
|
|
||||||
protected void requestLocationUpdates() {
|
protected void requestLocationUpdates() {
|
||||||
if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
|
if (!hasLocationFeature || locationManager == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Location lastKnownLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
|
||||||
if (lastKnownLocation != null) {
|
Log.d(Config.LOGTAG, "Requesting location updates...");
|
||||||
setmLastLocation(lastKnownLocation);
|
final Location lastKnownLocationGps;
|
||||||
|
final Location lastKnownLocationNetwork;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
gotoLoc();
|
if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)) {
|
||||||
} catch (final UnsupportedOperationException ignored) {
|
lastKnownLocationGps = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
|
||||||
|
|
||||||
|
if (lastKnownLocationGps != null) {
|
||||||
|
setMyLoc(lastKnownLocationGps);
|
||||||
}
|
}
|
||||||
|
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
|
||||||
|
Config.Map.LOCATION_FIX_SPACE_DELTA, this);
|
||||||
|
} else {
|
||||||
|
lastKnownLocationGps = null;
|
||||||
}
|
}
|
||||||
if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)
|
|
||||||
&& locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) {
|
if (locationManager.getAllProviders().contains(LocationManager.NETWORK_PROVIDER)) {
|
||||||
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.LOCATION_FIX_TIME_DELTA, Config.LOCATION_FIX_SPACE_DELTA, this);
|
lastKnownLocationNetwork = locationManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
|
||||||
|
if (lastKnownLocationNetwork != null && LocationHelper.isBetterLocation(lastKnownLocationNetwork,
|
||||||
|
lastKnownLocationGps)) {
|
||||||
|
setMyLoc(lastKnownLocationNetwork);
|
||||||
}
|
}
|
||||||
if (locationManager.getAllProviders().contains(LocationManager.GPS_PROVIDER)
|
locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, Config.Map.LOCATION_FIX_TIME_DELTA,
|
||||||
&& locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
Config.Map.LOCATION_FIX_SPACE_DELTA, this);
|
||||||
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, Config.LOCATION_FIX_TIME_DELTA, Config.LOCATION_FIX_SPACE_DELTA, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If something else is also querying for location more frequently than we are, the battery is already being
|
// If something else is also querying for location more frequently than we are, the battery is already being
|
||||||
// drained. Go ahead and use the existing locations as often as we can get them.
|
// drained. Go ahead and use the existing locations as often as we can get them.
|
||||||
if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
|
if (locationManager.getAllProviders().contains(LocationManager.PASSIVE_PROVIDER)) {
|
||||||
locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
|
locationManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, 0, 0, this);
|
||||||
}
|
}
|
||||||
|
} catch (final SecurityException ignored) {
|
||||||
|
// Do nothing if the users device has no location providers.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void pauseLocationUpdates() {
|
protected void pauseLocationUpdates() throws SecurityException {
|
||||||
|
if (locationManager != null) {
|
||||||
locationManager.removeUpdates(this);
|
locationManager.removeUpdates(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case android.R.id.home:
|
||||||
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
|
Configuration.getInstance().save(this, getPreferences());
|
||||||
|
map.onPause();
|
||||||
|
try {
|
||||||
pauseLocationUpdates();
|
pauseLocationUpdates();
|
||||||
|
} catch (final SecurityException ignored) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void updateUi();
|
||||||
|
|
||||||
|
protected boolean mapAtInitialLoc() {
|
||||||
|
return map.getZoomLevelDouble() == Config.Map.INITIAL_ZOOM_LEVEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
this.setmLastLocation(null);
|
Configuration.getInstance().load(this, getPreferences());
|
||||||
if (hasLocationPermission(REQUEST_LOCATION_PERMISSION)) {
|
map.onResume();
|
||||||
|
this.setMyLoc(null);
|
||||||
|
requestLocationUpdates();
|
||||||
|
updateLocationMarkers();
|
||||||
|
updateUi();
|
||||||
|
map.setTileSource(TileSourceFactory.MAPNIK);
|
||||||
|
map.setTilesScaledToDpi(true);
|
||||||
|
|
||||||
|
if (mapAtInitialLoc()) {
|
||||||
|
gotoLoc();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
protected boolean hasLocationPermissions() {
|
||||||
|
return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
|
||||||
|
checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
|
protected void requestPermissions(final int request_code) {
|
||||||
|
if (!hasLocationPermissions()) {
|
||||||
|
requestPermissions(
|
||||||
|
new String[]{
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||||
|
},
|
||||||
|
request_code
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
|
@NonNull final String[] permissions,
|
||||||
|
@NonNull final int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
for (int i = 0; i < grantResults.length; i++) {
|
||||||
|
if (Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[i]) ||
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[i])) {
|
||||||
|
if (grantResults[i] == PackageManager.PERMISSION_GRANTED) {
|
||||||
requestLocationUpdates();
|
requestLocationUpdates();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public SharedPreferences getPreferences() {
|
||||||
|
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean getBooleanPreference(String name, @BoolRes int res) {
|
||||||
|
return getPreferences().getBoolean(name, getResources().getBoolean(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected boolean isLocationEnabled() {
|
||||||
|
try {
|
||||||
|
final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
|
||||||
|
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
|
||||||
|
} catch (final Settings.SettingNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -5,37 +5,31 @@ import android.content.pm.ActivityInfo;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.CompoundButton;
|
import android.widget.CompoundButton;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.ActivityMagicCreateBinding;
|
import eu.siacs.conversations.databinding.ActivityMagicCreateBinding;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.services.ProviderService;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.InstallReferrerUtils;
|
import eu.siacs.conversations.utils.InstallReferrerUtils;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
public class MagicCreateActivity extends XmppActivity implements TextWatcher, AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener {
|
public class MagicCreateActivity extends XmppActivity implements TextWatcher, AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
private void setupHyperlink() {
|
|
||||||
TextView linkTextView = findViewById(R.id.activity_main_link);
|
|
||||||
linkTextView.setMovementMethod(LinkMovementMethod.getInstance());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean useOwnProvider = false;
|
private boolean useOwnProvider = false;
|
||||||
private boolean registerFromUri = false;
|
private boolean registerFromUri = false;
|
||||||
public static final String EXTRA_DOMAIN = "domain";
|
public static final String EXTRA_DOMAIN = "domain";
|
||||||
|
@ -86,10 +80,17 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
|
||||||
}
|
}
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_magic_create);
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_magic_create);
|
||||||
final List<String> domains = Arrays.asList(getResources().getStringArray(R.array.domains));
|
final List<String> domains = ProviderService.getProviders();
|
||||||
Collections.sort(domains, String::compareToIgnoreCase);
|
Collections.sort(domains, String::compareToIgnoreCase);
|
||||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_selectable_list_item, domains);
|
final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_selectable_list_item, domains);
|
||||||
int defaultServer = adapter.getPosition("monocles.de");
|
try {
|
||||||
|
if (new ProviderService().execute().get()) {
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
int defaultServer = adapter.getPosition(Config.DOMAIN.getRandomServer());
|
||||||
if (registerFromUri && !useOwnProvider && (this.preAuth != null || domain != null)) {
|
if (registerFromUri && !useOwnProvider && (this.preAuth != null || domain != null)) {
|
||||||
binding.server.setEnabled(false);
|
binding.server.setEnabled(false);
|
||||||
binding.server.setVisibility(View.GONE);
|
binding.server.setVisibility(View.GONE);
|
||||||
|
@ -188,7 +189,6 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
binding.username.addTextChangedListener(this);
|
binding.username.addTextChangedListener(this);
|
||||||
setupHyperlink();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String updateDomain() {
|
private String updateDomain() {
|
||||||
|
@ -230,7 +230,7 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad
|
||||||
private void updateFullJidInformation(String username) {
|
private void updateFullJidInformation(String username) {
|
||||||
if (useOwnProvider && !registerFromUri) {
|
if (useOwnProvider && !registerFromUri) {
|
||||||
this.domain = updateDomain();
|
this.domain = updateDomain();
|
||||||
} else if (!registerFromUri){
|
} else if (!registerFromUri) {
|
||||||
this.domain = binding.server.getSelectedItem().toString();
|
this.domain = binding.server.getSelectedItem().toString();
|
||||||
}
|
}
|
||||||
if (username.trim().isEmpty()) {
|
if (username.trim().isEmpty()) {
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -35,9 +38,6 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
|
||||||
|
|
||||||
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
|
public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
|
||||||
|
|
||||||
private final String STATE_SELECTED_ACCOUNT = "selected_account";
|
private final String STATE_SELECTED_ACCOUNT = "selected_account";
|
||||||
|
@ -212,6 +212,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (grantResults.length > 0) {
|
if (grantResults.length > 0) {
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
switch (requestCode) {
|
switch (requestCode) {
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.AUDIOS;
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.FILES;
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.IMAGES;
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.VIDEOS;
|
||||||
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
|
@ -14,31 +19,26 @@ import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.utils.ThemeHelper;
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.AUDIOS;
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.FILES;
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.IMAGES;
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.VIDEOS;
|
|
||||||
|
|
||||||
public class MemoryManagementActivity extends XmppActivity {
|
public class MemoryManagementActivity extends XmppActivity {
|
||||||
|
|
||||||
private TextView disk_storage;
|
private static TextView disk_storage;
|
||||||
private TextView media_usage;
|
private static TextView media_usage;
|
||||||
private ImageButton delete_media;
|
private ImageButton delete_media;
|
||||||
private TextView pictures_usage;
|
private static TextView pictures_usage;
|
||||||
private ImageButton delete_pictures;
|
private ImageButton delete_pictures;
|
||||||
private TextView videos_usage;
|
private static TextView videos_usage;
|
||||||
private ImageButton delete_videos;
|
private ImageButton delete_videos;
|
||||||
private TextView files_usage;
|
private static TextView files_usage;
|
||||||
private ImageButton delete_files;
|
private ImageButton delete_files;
|
||||||
private TextView audios_usage;
|
private static TextView audios_usage;
|
||||||
private ImageButton delete_audios;
|
private ImageButton delete_audios;
|
||||||
|
|
||||||
String totalMemory = "...";
|
static String totalMemory = "...";
|
||||||
String mediaUsage = "...";
|
static String mediaUsage = "...";
|
||||||
String picturesUsage = "...";
|
static String picturesUsage = "...";
|
||||||
String videosUsage = "...";
|
static String videosUsage = "...";
|
||||||
String filesUsage = "...";
|
static String filesUsage = "...";
|
||||||
String audiosUsage = "...";
|
static String audiosUsage = "...";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshUiReal() {
|
protected void refreshUiReal() {
|
||||||
|
@ -117,7 +117,7 @@ public class MemoryManagementActivity extends XmppActivity {
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
class getMemoryUsages extends AsyncTask<Void, Void, Void> {
|
static class getMemoryUsages extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
|
|
|
@ -114,7 +114,7 @@ public class MucUsersActivity extends XmppActivity implements XmppConnectionServ
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAffiliationChangeFailed(Jid jid, int resId) {
|
public void onAffiliationChangeFailed(Jid jid, int resId) {
|
||||||
displayToast(getString(resId, jid.asBareJid().toEscapedString()));
|
displayToast(getString(resId, jid.asBareJid().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -252,4 +252,4 @@ public class RecordingActivity extends AppCompatActivity implements View.OnClick
|
||||||
});
|
});
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,7 +2,6 @@ package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import static java.util.Arrays.asList;
|
import static java.util.Arrays.asList;
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
|
import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
|
||||||
import eu.siacs.conversations.ui.util.Rationals;
|
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
@ -37,6 +36,7 @@ import com.google.common.util.concurrent.FutureCallback;
|
||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||||
|
import org.webrtc.RendererCommon;
|
||||||
import org.webrtc.SurfaceViewRenderer;
|
import org.webrtc.SurfaceViewRenderer;
|
||||||
import org.webrtc.VideoTrack;
|
import org.webrtc.VideoTrack;
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ import eu.siacs.conversations.services.AppRTCAudioManager;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.MainThreadExecutor;
|
import eu.siacs.conversations.ui.util.MainThreadExecutor;
|
||||||
|
import eu.siacs.conversations.ui.util.Rationals;
|
||||||
import eu.siacs.conversations.utils.Namespace;
|
import eu.siacs.conversations.utils.Namespace;
|
||||||
import eu.siacs.conversations.utils.PermissionUtils;
|
import eu.siacs.conversations.utils.PermissionUtils;
|
||||||
import eu.siacs.conversations.utils.TimeFrameUtils;
|
import eu.siacs.conversations.utils.TimeFrameUtils;
|
||||||
|
@ -67,7 +68,6 @@ import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
|
|
||||||
public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {
|
public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {
|
||||||
|
|
||||||
public static final String EXTRA_WITH = "with";
|
public static final String EXTRA_WITH = "with";
|
||||||
|
@ -80,8 +80,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
|
|
||||||
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
|
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
|
||||||
|
|
||||||
private boolean shouldAllowBack = false;
|
|
||||||
|
|
||||||
private static final List<RtpEndUserState> END_CARD = Arrays.asList(
|
private static final List<RtpEndUserState> END_CARD = Arrays.asList(
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.SECURITY_ERROR,
|
RtpEndUserState.SECURITY_ERROR,
|
||||||
|
@ -97,7 +95,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
);
|
);
|
||||||
private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList(
|
private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList(
|
||||||
RtpEndUserState.CONNECTING,
|
RtpEndUserState.CONNECTING,
|
||||||
RtpEndUserState.CONNECTED
|
RtpEndUserState.CONNECTED,
|
||||||
|
RtpEndUserState.RECONNECTING
|
||||||
|
);
|
||||||
|
private static final List<RtpEndUserState> STATES_CONSIDERED_CONNECTED = Arrays.asList(
|
||||||
|
RtpEndUserState.CONNECTED,
|
||||||
|
RtpEndUserState.RECONNECTING
|
||||||
|
);
|
||||||
|
private static final List<RtpEndUserState> STATES_SHOWING_PIP_PLACEHOLDER = Arrays.asList(
|
||||||
|
RtpEndUserState.ACCEPTING_CALL,
|
||||||
|
RtpEndUserState.CONNECTING,
|
||||||
|
RtpEndUserState.RECONNECTING
|
||||||
);
|
);
|
||||||
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
|
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
|
||||||
private static final int REQUEST_ACCEPT_CALL = 0x1111;
|
private static final int REQUEST_ACCEPT_CALL = 0x1111;
|
||||||
|
@ -484,11 +492,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (shouldAllowBack) {
|
endCall();
|
||||||
|
}
|
||||||
super.onBackPressed();
|
super.onBackPressed();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUserLeaveHint() {
|
public void onUserLeaveHint() {
|
||||||
|
@ -504,7 +511,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
|
|
||||||
private boolean isConnected() {
|
private boolean isConnected() {
|
||||||
final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
return connection != null && connection.getEndUserState() == RtpEndUserState.CONNECTED;
|
return connection != null && STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean switchToPictureInPicture() {
|
private boolean switchToPictureInPicture() {
|
||||||
|
@ -533,6 +540,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
Log.w(Config.LOGTAG, "unable to enter picture in picture mode", e);
|
Log.w(Config.LOGTAG, "unable to enter picture in picture mode", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAspectRatioChanged(final Rational rational) {
|
public void onAspectRatioChanged(final Rational rational) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) {
|
||||||
|
@ -636,8 +644,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
surfaceViewRenderer.setVisibility(View.VISIBLE);
|
surfaceViewRenderer.setVisibility(View.VISIBLE);
|
||||||
try {
|
try {
|
||||||
surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
|
surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
|
||||||
} catch (IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
|
//Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
|
||||||
}
|
}
|
||||||
surfaceViewRenderer.setEnableHardwareScaler(true);
|
surfaceViewRenderer.setEnableHardwareScaler(true);
|
||||||
}
|
}
|
||||||
|
@ -649,7 +657,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
private void updateStateDisplay(final RtpEndUserState state, final Set<Media> media) {
|
private void updateStateDisplay(final RtpEndUserState state, final Set<Media> media) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case INCOMING_CALL:
|
case INCOMING_CALL:
|
||||||
shouldAllowBack = false;
|
|
||||||
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
||||||
if (media.contains(Media.VIDEO)) {
|
if (media.contains(Media.VIDEO)) {
|
||||||
setTitle(R.string.rtp_state_incoming_video_call);
|
setTitle(R.string.rtp_state_incoming_video_call);
|
||||||
|
@ -658,53 +665,45 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CONNECTING:
|
case CONNECTING:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_connecting);
|
setTitle(R.string.rtp_state_connecting);
|
||||||
break;
|
break;
|
||||||
case CONNECTED:
|
case CONNECTED:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_connected);
|
setTitle(R.string.rtp_state_connected);
|
||||||
break;
|
break;
|
||||||
|
case RECONNECTING:
|
||||||
|
setTitle(R.string.rtp_state_reconnecting);
|
||||||
|
break;
|
||||||
case ACCEPTING_CALL:
|
case ACCEPTING_CALL:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_accepting_call);
|
setTitle(R.string.rtp_state_accepting_call);
|
||||||
break;
|
break;
|
||||||
case ENDING_CALL:
|
case ENDING_CALL:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_ending_call);
|
setTitle(R.string.rtp_state_ending_call);
|
||||||
break;
|
break;
|
||||||
case FINDING_DEVICE:
|
case FINDING_DEVICE:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_finding_device);
|
setTitle(R.string.rtp_state_finding_device);
|
||||||
break;
|
break;
|
||||||
case RINGING:
|
case RINGING:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_ringing);
|
setTitle(R.string.rtp_state_ringing);
|
||||||
break;
|
break;
|
||||||
case DECLINED_OR_BUSY:
|
case DECLINED_OR_BUSY:
|
||||||
shouldAllowBack = true;
|
|
||||||
setTitle(R.string.rtp_state_declined_or_busy);
|
setTitle(R.string.rtp_state_declined_or_busy);
|
||||||
break;
|
break;
|
||||||
case CONNECTIVITY_ERROR:
|
case CONNECTIVITY_ERROR:
|
||||||
shouldAllowBack = true;
|
|
||||||
setTitle(R.string.rtp_state_connectivity_error);
|
setTitle(R.string.rtp_state_connectivity_error);
|
||||||
break;
|
break;
|
||||||
case CONNECTIVITY_LOST_ERROR:
|
case CONNECTIVITY_LOST_ERROR:
|
||||||
setTitle(R.string.rtp_state_connectivity_lost_error);
|
setTitle(R.string.rtp_state_connectivity_lost_error);
|
||||||
break;
|
break;
|
||||||
case RETRACTED:
|
case RETRACTED:
|
||||||
shouldAllowBack = false;
|
|
||||||
setTitle(R.string.rtp_state_retracted);
|
setTitle(R.string.rtp_state_retracted);
|
||||||
break;
|
break;
|
||||||
case APPLICATION_ERROR:
|
case APPLICATION_ERROR:
|
||||||
shouldAllowBack = true;
|
|
||||||
setTitle(R.string.rtp_state_application_failure);
|
setTitle(R.string.rtp_state_application_failure);
|
||||||
break;
|
break;
|
||||||
case SECURITY_ERROR:
|
case SECURITY_ERROR:
|
||||||
setTitle(R.string.rtp_state_security_error);
|
setTitle(R.string.rtp_state_security_error);
|
||||||
break;
|
break;
|
||||||
case ENDED:
|
case ENDED:
|
||||||
shouldAllowBack = true;
|
|
||||||
throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
|
throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
|
||||||
default:
|
default:
|
||||||
throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
|
throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
|
||||||
|
@ -816,7 +815,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private void updateInCallButtonConfiguration(final RtpEndUserState state, final Set<Media> media) {
|
private void updateInCallButtonConfiguration(final RtpEndUserState state, final Set<Media> media) {
|
||||||
if (state == RtpEndUserState.CONNECTED && !isPictureInPicture()) {
|
if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
|
||||||
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
||||||
if (media.contains(Media.VIDEO)) {
|
if (media.contains(Media.VIDEO)) {
|
||||||
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
||||||
|
@ -944,14 +943,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
this.binding.duration.setVisibility(View.GONE);
|
this.binding.duration.setVisibility(View.GONE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final long rtpConnectionStarted = connection.getRtpConnectionStarted();
|
if (connection.zeroDuration()) {
|
||||||
final long rtpConnectionEnded = connection.getRtpConnectionEnded();
|
|
||||||
if (rtpConnectionStarted != 0) {
|
|
||||||
final long ended = rtpConnectionEnded == 0 ? SystemClock.elapsedRealtime() : rtpConnectionEnded;
|
|
||||||
this.binding.duration.setText(TimeFrameUtils.formatTimePassed(rtpConnectionStarted, ended, false));
|
|
||||||
this.binding.duration.setVisibility(View.VISIBLE);
|
|
||||||
} else {
|
|
||||||
this.binding.duration.setVisibility(View.GONE);
|
this.binding.duration.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
this.binding.duration.setText(TimeFrameUtils.formatElapsedTime(connection.getCallDuration(), false));
|
||||||
|
this.binding.duration.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -959,17 +955,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) {
|
if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) {
|
||||||
binding.localVideo.setVisibility(View.GONE);
|
binding.localVideo.setVisibility(View.GONE);
|
||||||
binding.localVideo.release();
|
binding.localVideo.release();
|
||||||
binding.remoteVideo.setVisibility(View.GONE);
|
binding.remoteVideoWrapper.setVisibility(View.GONE);
|
||||||
binding.remoteVideo.release();
|
binding.remoteVideo.release();
|
||||||
binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
|
binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
|
||||||
if (isPictureInPicture()) {
|
if (isPictureInPicture()) {
|
||||||
|
binding.appBarLayout.setVisibility(View.GONE);
|
||||||
|
binding.pipPlaceholder.setVisibility(View.VISIBLE);
|
||||||
if (Arrays.asList(
|
if (Arrays.asList(
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.CONNECTIVITY_ERROR,
|
RtpEndUserState.CONNECTIVITY_ERROR,
|
||||||
RtpEndUserState.SECURITY_ERROR)
|
RtpEndUserState.SECURITY_ERROR)
|
||||||
.contains(state)) {
|
.contains(state)) {
|
||||||
binding.appBarLayout.setVisibility(View.GONE);
|
|
||||||
binding.pipPlaceholder.setVisibility(View.VISIBLE);
|
|
||||||
binding.pipWarning.setVisibility(View.VISIBLE);
|
binding.pipWarning.setVisibility(View.VISIBLE);
|
||||||
binding.pipWaiting.setVisibility(View.GONE);
|
binding.pipWaiting.setVisibility(View.GONE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -983,9 +979,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (isPictureInPicture() && (state == RtpEndUserState.CONNECTING || state == RtpEndUserState.ACCEPTING_CALL)) {
|
if (isPictureInPicture() && STATES_SHOWING_PIP_PLACEHOLDER.contains(state)) {
|
||||||
binding.localVideo.setVisibility(View.GONE);
|
binding.localVideo.setVisibility(View.GONE);
|
||||||
binding.remoteVideo.setVisibility(View.GONE);
|
binding.remoteVideoWrapper.setVisibility(View.GONE);
|
||||||
binding.appBarLayout.setVisibility(View.GONE);
|
binding.appBarLayout.setVisibility(View.GONE);
|
||||||
binding.pipPlaceholder.setVisibility(View.VISIBLE);
|
binding.pipPlaceholder.setVisibility(View.VISIBLE);
|
||||||
binding.pipWarning.setVisibility(View.GONE);
|
binding.pipWarning.setVisibility(View.GONE);
|
||||||
|
@ -1007,12 +1003,18 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (remoteVideoTrack.isPresent()) {
|
if (remoteVideoTrack.isPresent()) {
|
||||||
ensureSurfaceViewRendererIsSetup(binding.remoteVideo);
|
ensureSurfaceViewRendererIsSetup(binding.remoteVideo);
|
||||||
addSink(remoteVideoTrack.get(), binding.remoteVideo);
|
addSink(remoteVideoTrack.get(), binding.remoteVideo);
|
||||||
|
binding.remoteVideo.setScalingType(
|
||||||
|
RendererCommon.ScalingType.SCALE_ASPECT_FILL,
|
||||||
|
RendererCommon.ScalingType.SCALE_ASPECT_FIT
|
||||||
|
);
|
||||||
if (state == RtpEndUserState.CONNECTED) {
|
if (state == RtpEndUserState.CONNECTED) {
|
||||||
binding.appBarLayout.setVisibility(View.GONE);
|
binding.appBarLayout.setVisibility(View.GONE);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
binding.remoteVideoWrapper.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
binding.appBarLayout.setVisibility(View.VISIBLE);
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
binding.remoteVideo.setVisibility(View.GONE);
|
binding.remoteVideoWrapper.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if (isPictureInPicture() && !requireRtpConnection().isMicrophoneEnabled()) {
|
if (isPictureInPicture() && !requireRtpConnection().isMicrophoneEnabled()) {
|
||||||
binding.pipLocalMicOffIndicator.setVisibility(View.VISIBLE);
|
binding.pipLocalMicOffIndicator.setVisibility(View.VISIBLE);
|
||||||
|
@ -1021,7 +1023,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
binding.remoteVideo.setVisibility(View.GONE);
|
binding.remoteVideoWrapper.setVisibility(View.GONE);
|
||||||
binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
|
binding.pipLocalMicOffIndicator.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.BROADCAST_LAST_ACTIVITY;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.CHAT_STATES;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.CONFIRM_MESSAGES;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.EASY_DOWNLOADER;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.FORBID_SCREENSHOTS;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_LINKS_INSIDE;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_MAPS_INSIDE;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.USE_INNER_STORAGE;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.USE_INVIDIOUS;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -15,19 +25,12 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.ActivitySetSettingsBinding;
|
import eu.siacs.conversations.databinding.ActivitySetSettingsBinding;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.utils.AccountUtils;
|
import eu.siacs.conversations.utils.AccountUtils;
|
||||||
import eu.siacs.conversations.utils.FirstStartManager;
|
import eu.siacs.conversations.utils.FirstStartManager;
|
||||||
import eu.siacs.conversations.utils.ThemeHelper;
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.BROADCAST_LAST_ACTIVITY;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.CHAT_STATES;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.CONFIRM_MESSAGES;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.FORBID_SCREENSHOTS;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_LINKS_INSIDE;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_MAPS_INSIDE;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.USE_INVIDIOUS;
|
|
||||||
|
|
||||||
public class SetSettingsActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate {
|
public class SetSettingsActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate {
|
||||||
ActivitySetSettingsBinding binding;
|
ActivitySetSettingsBinding binding;
|
||||||
Account account;
|
Account account;
|
||||||
|
@ -38,6 +41,8 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
static final int CONFIRMMESSAGES = 5;
|
static final int CONFIRMMESSAGES = 5;
|
||||||
static final int LASTSEEN = 6;
|
static final int LASTSEEN = 6;
|
||||||
static final int INVIDIOUS = 7;
|
static final int INVIDIOUS = 7;
|
||||||
|
static final int INNER_STORAGE = 8;
|
||||||
|
static final int EASY_DOWNLOADER_ATTACHMENTS = 9;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshUiReal() {
|
protected void refreshUiReal() {
|
||||||
|
@ -69,6 +74,8 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
this.binding.actionInfoConfirmMessages.setOnClickListener(string -> showInfo(CONFIRMMESSAGES));
|
this.binding.actionInfoConfirmMessages.setOnClickListener(string -> showInfo(CONFIRMMESSAGES));
|
||||||
this.binding.actionInfoLastSeen.setOnClickListener(string -> showInfo(LASTSEEN));
|
this.binding.actionInfoLastSeen.setOnClickListener(string -> showInfo(LASTSEEN));
|
||||||
this.binding.actionInfoInvidious.setOnClickListener(string -> showInfo(INVIDIOUS));
|
this.binding.actionInfoInvidious.setOnClickListener(string -> showInfo(INVIDIOUS));
|
||||||
|
this.binding.actionInfoUsingInnerStorage.setOnClickListener(string -> showInfo(INNER_STORAGE));
|
||||||
|
this.binding.actionInfoEasyDownloader.setOnClickListener(string -> showInfo(EASY_DOWNLOADER_ATTACHMENTS));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void getDefaults() {
|
private void getDefaults() {
|
||||||
|
@ -79,6 +86,8 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
this.binding.confirmMessages.setChecked(getResources().getBoolean(R.bool.confirm_messages));
|
this.binding.confirmMessages.setChecked(getResources().getBoolean(R.bool.confirm_messages));
|
||||||
this.binding.lastSeen.setChecked(getResources().getBoolean(R.bool.last_activity));
|
this.binding.lastSeen.setChecked(getResources().getBoolean(R.bool.last_activity));
|
||||||
this.binding.invidious.setChecked(getResources().getBoolean(R.bool.use_invidious));
|
this.binding.invidious.setChecked(getResources().getBoolean(R.bool.use_invidious));
|
||||||
|
this.binding.usingInnerStorage.setChecked(getResources().getBoolean(R.bool.use_inner_storage));
|
||||||
|
this.binding.easyDownloader.setChecked(getResources().getBoolean(R.bool.easy_downloader));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void next(View view) {
|
private void next(View view) {
|
||||||
|
@ -96,7 +105,6 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showInfo(int setting) {
|
private void showInfo(int setting) {
|
||||||
Log.d(Config.LOGTAG, "STRING " + setting);
|
|
||||||
String title;
|
String title;
|
||||||
String message;
|
String message;
|
||||||
switch (setting) {
|
switch (setting) {
|
||||||
|
@ -128,6 +136,14 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
title = getString(R.string.pref_use_invidious);
|
title = getString(R.string.pref_use_invidious);
|
||||||
message = getString(R.string.pref_use_invidious_summary);
|
message = getString(R.string.pref_use_invidious_summary);
|
||||||
break;
|
break;
|
||||||
|
case INNER_STORAGE:
|
||||||
|
title = getString(R.string.pref_use_inner_storage);
|
||||||
|
message = getString(R.string.pref_use_inner_storage_summary);
|
||||||
|
break;
|
||||||
|
case EASY_DOWNLOADER_ATTACHMENTS:
|
||||||
|
title = getString(R.string.pref_easy_downloader);
|
||||||
|
message = getString(R.string.pref_easy_downloader_summary);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
title = getString(R.string.error);
|
title = getString(R.string.error);
|
||||||
message = getString(R.string.error);
|
message = getString(R.string.error);
|
||||||
|
@ -143,41 +159,16 @@ public class SetSettingsActivity extends XmppActivity implements XmppConnectionS
|
||||||
|
|
||||||
private void setSettings() {
|
private void setSettings() {
|
||||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
if (this.binding.forbidScreenshots.isChecked()) {
|
preferences.edit().putBoolean(FORBID_SCREENSHOTS, this.binding.forbidScreenshots.isChecked()).apply();
|
||||||
preferences.edit().putBoolean(FORBID_SCREENSHOTS, true).apply();
|
preferences.edit().putBoolean(SHOW_LINKS_INSIDE, this.binding.showLinks.isChecked()).apply();
|
||||||
} else {
|
preferences.edit().putBoolean(SHOW_MAPS_INSIDE, this.binding.showMappreview.isChecked()).apply();
|
||||||
preferences.edit().putBoolean(FORBID_SCREENSHOTS, false).apply();
|
preferences.edit().putBoolean(CHAT_STATES, this.binding.chatStates.isChecked()).apply();
|
||||||
}
|
preferences.edit().putBoolean(CONFIRM_MESSAGES, this.binding.confirmMessages.isChecked()).apply();
|
||||||
if (this.binding.showLinks.isChecked()) {
|
preferences.edit().putBoolean(BROADCAST_LAST_ACTIVITY, this.binding.lastSeen.isChecked()).apply();
|
||||||
preferences.edit().putBoolean(SHOW_LINKS_INSIDE, true).apply();
|
preferences.edit().putBoolean(USE_INVIDIOUS, this.binding.invidious.isChecked()).apply();
|
||||||
} else {
|
preferences.edit().putBoolean(USE_INNER_STORAGE, this.binding.usingInnerStorage.isChecked()).apply();
|
||||||
preferences.edit().putBoolean(SHOW_LINKS_INSIDE, false).apply();
|
preferences.edit().putBoolean(EASY_DOWNLOADER, this.binding.easyDownloader.isChecked()).apply();
|
||||||
}
|
FileBackend.switchStorage(xmppConnectionService.usingInnerStorage());
|
||||||
if (this.binding.showMappreview.isChecked()) {
|
|
||||||
preferences.edit().putBoolean(SHOW_MAPS_INSIDE, true).apply();
|
|
||||||
} else {
|
|
||||||
preferences.edit().putBoolean(SHOW_MAPS_INSIDE, false).apply();
|
|
||||||
}
|
|
||||||
if (this.binding.chatStates.isChecked()) {
|
|
||||||
preferences.edit().putBoolean(CHAT_STATES, true).apply();
|
|
||||||
} else {
|
|
||||||
preferences.edit().putBoolean(CHAT_STATES, false).apply();
|
|
||||||
}
|
|
||||||
if (this.binding.confirmMessages.isChecked()) {
|
|
||||||
preferences.edit().putBoolean(CONFIRM_MESSAGES, true).apply();
|
|
||||||
} else {
|
|
||||||
preferences.edit().putBoolean(CONFIRM_MESSAGES, false).apply();
|
|
||||||
}
|
|
||||||
if (this.binding.lastSeen.isChecked()) {
|
|
||||||
preferences.edit().putBoolean(BROADCAST_LAST_ACTIVITY, true).apply();
|
|
||||||
} else {
|
|
||||||
preferences.edit().putBoolean(BROADCAST_LAST_ACTIVITY, false).apply();
|
|
||||||
}
|
|
||||||
if (this.binding.invidious.isChecked()) {
|
|
||||||
preferences.edit().putBoolean(USE_INVIDIOUS, true).apply();
|
|
||||||
} else {
|
|
||||||
preferences.edit().putBoolean(USE_INVIDIOUS, false).apply();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -16,11 +16,6 @@ import android.preference.PreferenceCategory;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.preference.PreferenceScreen;
|
import android.preference.PreferenceScreen;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.ObjectInputStream;
|
|
||||||
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
@ -86,16 +81,16 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
public static final String CONFIRM_MESSAGES = "confirm_messages";
|
public static final String CONFIRM_MESSAGES = "confirm_messages";
|
||||||
public static final String INDICATE_RECEIVED = "indicate_received";
|
public static final String INDICATE_RECEIVED = "indicate_received";
|
||||||
public static final String USE_INVIDIOUS = "use_invidious";
|
public static final String USE_INVIDIOUS = "use_invidious";
|
||||||
|
public static final String USE_INNER_STORAGE = "use_inner_storage";
|
||||||
public static final String INVIDIOUS_HOST = "invidious_host";
|
public static final String INVIDIOUS_HOST = "invidious_host";
|
||||||
public static final String MAPPREVIEW_HOST = "mappreview_host";
|
public static final String MAPPREVIEW_HOST = "mappreview_host";
|
||||||
public static final String ALLOW_MESSAGE_CORRECTION = "allow_message_correction";
|
public static final String ALLOW_MESSAGE_CORRECTION = "allow_message_correction";
|
||||||
|
public static final String ALLOW_MESSAGE_RETRACTION = "allow_message_retraction";
|
||||||
public static final String USE_UNICOLORED_CHATBG = "unicolored_chatbg";
|
public static final String USE_UNICOLORED_CHATBG = "unicolored_chatbg";
|
||||||
public static final String EASY_DOWNLOADER = "easy_downloader";
|
public static final String EASY_DOWNLOADER = "easy_downloader";
|
||||||
public static final String MIN_ANDROID_SDK21_SHOWN = "min_android_sdk21_shown";
|
public static final String MIN_ANDROID_SDK21_SHOWN = "min_android_sdk21_shown";
|
||||||
public static final String INDIVIDUAL_NOTIFICATION_PREFIX = "individual_notification_set_";
|
public static final String INDIVIDUAL_NOTIFICATION_PREFIX = "individual_notification_set_";
|
||||||
public static final String PAUSE_VOICE = "pause_voice_on_move_from_ear";
|
public static final String PAUSE_VOICE = "pause_voice_on_move_from_ear";
|
||||||
public static final String ENABLE_OTR_ENCRYPTION = "enable_otr_encryption";
|
|
||||||
|
|
||||||
|
|
||||||
public static final int REQUEST_CREATE_BACKUP = 0xbf8701;
|
public static final int REQUEST_CREATE_BACKUP = 0xbf8701;
|
||||||
public static final int REQUEST_IMPORT_SETTINGS = 0xbf8702;
|
public static final int REQUEST_IMPORT_SETTINGS = 0xbf8702;
|
||||||
|
@ -564,6 +559,7 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
TREAT_VIBRATE_AS_SILENT,
|
TREAT_VIBRATE_AS_SILENT,
|
||||||
MANUALLY_CHANGE_PRESENCE,
|
MANUALLY_CHANGE_PRESENCE,
|
||||||
BROADCAST_LAST_ACTIVITY);
|
BROADCAST_LAST_ACTIVITY);
|
||||||
|
FileBackend.switchStorage(preferences.getBoolean(USE_INNER_STORAGE, true));
|
||||||
if (name.equals(OMEMO_SETTING)) {
|
if (name.equals(OMEMO_SETTING)) {
|
||||||
OmemoSetting.load(this, preferences);
|
OmemoSetting.load(this, preferences);
|
||||||
changeOmemoSettingSummary();
|
changeOmemoSettingSummary();
|
||||||
|
@ -594,7 +590,6 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
||||||
if (grantResults.length > 0) {
|
if (grantResults.length > 0) {
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (requestCode == REQUEST_CREATE_BACKUP) {
|
if (requestCode == REQUEST_CREATE_BACKUP) {
|
||||||
|
@ -661,6 +656,11 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
ex.printStackTrace();
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (success) {
|
||||||
|
ToastCompat.makeText(this, R.string.success_import_settings, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
} else {
|
||||||
|
ToastCompat.makeText(this, R.string.error_import_settings, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
return success;
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,67 +1,63 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
import android.location.Address;
|
import android.location.Address;
|
||||||
import android.location.Geocoder;
|
import android.location.Geocoder;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
import android.location.LocationListener;
|
import android.location.LocationListener;
|
||||||
import android.location.LocationManager;
|
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.text.Html;
|
||||||
import android.text.TextUtils;
|
import android.view.View;
|
||||||
import android.webkit.WebView;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import androidx.coordinatorlayout.widget.CoordinatorLayout;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import com.google.android.material.snackbar.Snackbar;
|
import com.google.android.material.snackbar.Snackbar;
|
||||||
|
import com.google.common.math.DoubleMath;
|
||||||
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.osmdroid.api.IGeoPoint;
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.math.RoundingMode;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.utils.LocationHelper;
|
import eu.siacs.conversations.databinding.ActivityShareLocactionBinding;
|
||||||
|
import eu.siacs.conversations.ui.util.LocationHelper;
|
||||||
|
import eu.siacs.conversations.ui.widget.Marker;
|
||||||
|
import eu.siacs.conversations.ui.widget.MyLocation;
|
||||||
|
import eu.siacs.conversations.utils.LocationProvider;
|
||||||
import eu.siacs.conversations.utils.ThemeHelper;
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
|
||||||
|
|
||||||
public class ShareLocationActivity extends LocationActivity implements LocationListener {
|
public class ShareLocationActivity extends LocationActivity implements LocationListener {
|
||||||
|
|
||||||
LocationManager locationManager;
|
|
||||||
private Location mLastLocation;
|
|
||||||
private Button mCancelButton;
|
|
||||||
private Button mShareButton;
|
|
||||||
private String mLocationName;
|
|
||||||
private Snackbar snackBar;
|
private Snackbar snackBar;
|
||||||
|
private ActivityShareLocactionBinding binding;
|
||||||
|
private boolean marker_fixed_to_loc = false;
|
||||||
|
private static final String KEY_FIXED_TO_LOC = "fixed_to_loc";
|
||||||
|
private Boolean noAskAgain = false;
|
||||||
|
|
||||||
private static String getAddress(Context context, Location location) {
|
@Override
|
||||||
double longitude = location.getLongitude();
|
protected void onSaveInstanceState(@NonNull final Bundle outState) {
|
||||||
double latitude = location.getLatitude();
|
super.onSaveInstanceState(outState);
|
||||||
String address = "";
|
outState.putBoolean(KEY_FIXED_TO_LOC, marker_fixed_to_loc);
|
||||||
if (latitude != 0 && longitude != 0) {
|
}
|
||||||
try {
|
|
||||||
Geocoder geoCoder = new Geocoder(context, Locale.getDefault());
|
|
||||||
List<Address> addresses = geoCoder.getFromLocation(latitude, longitude, 1);
|
|
||||||
if (addresses != null && addresses.size() > 0) {
|
|
||||||
Address Address = addresses.get(0);
|
|
||||||
StringBuilder strAddress = new StringBuilder("");
|
|
||||||
|
|
||||||
if (Address.getAddressLine(0).length() > 0) {
|
@Override
|
||||||
strAddress.append(Address.getAddressLine(0));
|
protected void onRestoreInstanceState(@NonNull final Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
if (savedInstanceState.containsKey(KEY_FIXED_TO_LOC)) {
|
||||||
|
this.marker_fixed_to_loc = savedInstanceState.getBoolean(KEY_FIXED_TO_LOC);
|
||||||
}
|
}
|
||||||
address = strAddress.toString().replace(", ", "<br>");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -77,62 +73,99 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_share_locaction);
|
|
||||||
setTitle(getString(R.string.share_location));
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_share_locaction);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar((Toolbar) binding.toolbar);
|
||||||
configureActionBar(getSupportActionBar());
|
configureActionBar(getSupportActionBar());
|
||||||
locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
|
setupMapView(binding.map, LocationProvider.getGeoPoint(this));
|
||||||
|
|
||||||
mLocationName = getString(R.string.me);
|
this.binding.cancelButton.setOnClickListener(view -> {
|
||||||
|
|
||||||
mCancelButton = findViewById(R.id.cancel_button);
|
|
||||||
mCancelButton.setOnClickListener(view -> {
|
|
||||||
setResult(RESULT_CANCELED);
|
setResult(RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
mShareButton = findViewById(R.id.share_button);
|
|
||||||
mShareButton.setOnClickListener(view -> {
|
|
||||||
if (mLastLocation != null) {
|
|
||||||
Intent result = new Intent();
|
|
||||||
result.putExtra("latitude", mLastLocation.getLatitude());
|
|
||||||
result.putExtra("longitude", mLastLocation.getLongitude());
|
|
||||||
result.putExtra("altitude", mLastLocation.getAltitude());
|
|
||||||
result.putExtra("accuracy", (int) mLastLocation.getAccuracy());
|
|
||||||
setResult(RESULT_OK, result);
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
final CoordinatorLayout snackBarCoordinator = findViewById(R.id.snackbarCoordinator);
|
this.snackBar = Snackbar.make(this.binding.snackbarCoordinator, R.string.location_sharing_disabled, Snackbar.LENGTH_INDEFINITE);
|
||||||
if (snackBarCoordinator != null) {
|
this.snackBar.setAction(R.string.enable, view -> {
|
||||||
this.snackBar = Snackbar.make(snackBarCoordinator, R.string.location_sharing_disabled, Snackbar.LENGTH_INDEFINITE);
|
if (isLocationEnabledAndAllowed()) {
|
||||||
snackBar.setAction(R.string.enable, view -> {
|
updateUi();
|
||||||
showLocation(null, "");
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) {
|
||||||
if (isLocationEnabled()) {
|
requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED);
|
||||||
if (hasLocationPermission(LocationActivity.REQUEST_LOCATION_PERMISSION)) {
|
} else if (!isLocationEnabled()) {
|
||||||
requestLocationUpdates();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
setShareButtonEnabled(false);
|
|
||||||
if (hasLocationPermission(LocationActivity.REQUEST_LOCATION_PERMISSION)) {
|
|
||||||
requestLocationUpdates();
|
|
||||||
}
|
|
||||||
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
ThemeHelper.fix(this.snackBar);
|
ThemeHelper.fix(this.snackBar);
|
||||||
|
|
||||||
|
this.binding.shareButton.setOnClickListener(this::shareLocation);
|
||||||
|
|
||||||
|
this.marker_fixed_to_loc = isLocationEnabledAndAllowed();
|
||||||
|
|
||||||
|
this.binding.fab.setOnClickListener(view -> {
|
||||||
|
if (!marker_fixed_to_loc) {
|
||||||
|
if (!isLocationEnabled()) {
|
||||||
|
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||||
|
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
requestPermissions(REQUEST_CODE_FAB_PRESSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toggleFixedLocation();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void shareLocation(final View view) {
|
||||||
|
final Intent result = new Intent();
|
||||||
|
if (marker_fixed_to_loc && myLoc != null) {
|
||||||
|
result.putExtra("latitude", myLoc.getLatitude());
|
||||||
|
result.putExtra("longitude", myLoc.getLongitude());
|
||||||
|
result.putExtra("altitude", myLoc.getAltitude());
|
||||||
|
result.putExtra("accuracy", DoubleMath.roundToInt(myLoc.getAccuracy(), RoundingMode.HALF_UP));
|
||||||
|
} else {
|
||||||
|
final IGeoPoint markerPoint = this.binding.map.getMapCenter();
|
||||||
|
result.putExtra("latitude", markerPoint.getLatitude());
|
||||||
|
result.putExtra("longitude", markerPoint.getLongitude());
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK, result);
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
|
@NonNull final String[] permissions,
|
||||||
|
@NonNull final int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
|
||||||
|
if (grantResults.length > 0 &&
|
||||||
|
grantResults[0] != PackageManager.PERMISSION_GRANTED &&
|
||||||
|
Build.VERSION.SDK_INT >= 23 &&
|
||||||
|
permissions.length > 0 &&
|
||||||
|
(
|
||||||
|
Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) ||
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) ||
|
||||||
|
Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0])
|
||||||
|
) &&
|
||||||
|
!shouldShowRequestPermissionRationale(permissions[0])) {
|
||||||
|
noAskAgain = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!noAskAgain && requestCode == REQUEST_CODE_SNACKBAR_PRESSED && !isLocationEnabled() && hasLocationPermissions()) {
|
||||||
|
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||||
|
}
|
||||||
|
updateUi();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void gotoLoc(final boolean setZoomLevel) {
|
||||||
|
if (this.myLoc != null && mapController != null) {
|
||||||
|
if (setZoomLevel) {
|
||||||
|
mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL);
|
||||||
|
}
|
||||||
|
mapController.animateTo(new GeoPoint(this.myLoc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void gotoLoc() throws UnsupportedOperationException {
|
protected void setMyLoc(final Location location) {
|
||||||
new getAddressAsync(this).execute();
|
this.myLoc = location;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void setmLastLocation(Location location) {
|
|
||||||
this.mLastLocation = location;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -141,24 +174,60 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
protected void updateLocationMarkers() {
|
||||||
super.onResume();
|
super.updateLocationMarkers();
|
||||||
if (isLocationEnabled()) {
|
if (this.myLoc != null) {
|
||||||
this.snackBar.dismiss();
|
this.binding.map.getOverlays().add(new MyLocation(this, null, this.myLoc));
|
||||||
showLocation(null, "");
|
if (this.marker_fixed_to_loc) {
|
||||||
|
this.binding.map.getOverlays().add(new Marker(marker_icon, new GeoPoint(this.myLoc)));
|
||||||
|
new getAddressAsync(this, this.myLoc).execute();
|
||||||
} else {
|
} else {
|
||||||
this.snackBar.show();
|
this.binding.map.getOverlays().add(new Marker(marker_icon));
|
||||||
|
new getAddressAsync(this, getMarkerPosition()).execute();
|
||||||
}
|
}
|
||||||
setShareButtonEnabled(false);
|
} else {
|
||||||
|
this.binding.map.getOverlays().add(new Marker(marker_icon));
|
||||||
|
hideAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Location getMarkerPosition() {
|
||||||
|
final IGeoPoint markerPoint = this.binding.map.getMapCenter();
|
||||||
|
final Location location = new Location("");
|
||||||
|
location.setLatitude(markerPoint.getLatitude());
|
||||||
|
location.setLongitude(markerPoint.getLongitude());
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAddress(final Location myLoc) {
|
||||||
|
this.binding.address.setText(Html.fromHtml(getAddress(this, myLoc)));
|
||||||
|
if (Html.fromHtml(getAddress(this, myLoc)).length() > 0) {
|
||||||
|
this.binding.address.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
hideAddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideAddress() {
|
||||||
|
this.binding.address.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLocationChanged(final Location location) {
|
public void onLocationChanged(final Location location) {
|
||||||
if (LocationHelper.isBetterLocation(location, this.mLastLocation)) {
|
if (this.myLoc == null) {
|
||||||
setShareButtonEnabled(true);
|
this.marker_fixed_to_loc = true;
|
||||||
this.mLastLocation = location;
|
}
|
||||||
|
updateUi();
|
||||||
|
if (LocationHelper.isBetterLocation(location, this.myLoc)) {
|
||||||
|
final Location oldLoc = this.myLoc;
|
||||||
|
this.myLoc = location;
|
||||||
|
|
||||||
|
// Don't jump back to the users location if they're not moving (more or less).
|
||||||
|
if (oldLoc == null || (this.marker_fixed_to_loc && this.myLoc.distanceTo(oldLoc) > 1)) {
|
||||||
gotoLoc();
|
gotoLoc();
|
||||||
}
|
}
|
||||||
|
updateLocationMarkers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -176,91 +245,92 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.KITKAT)
|
private boolean isLocationEnabledAndAllowed() {
|
||||||
private boolean isLocationEnabledKitkat() {
|
return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled();
|
||||||
try {
|
|
||||||
final int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
|
|
||||||
return locationMode != Settings.Secure.LOCATION_MODE_OFF;
|
|
||||||
} catch (final Settings.SettingNotFoundException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
private void toggleFixedLocation() {
|
||||||
private boolean isLocationEnabledLegacy() {
|
this.marker_fixed_to_loc = isLocationEnabledAndAllowed() && !this.marker_fixed_to_loc;
|
||||||
final String locationProviders = Settings.Secure.getString(getContentResolver(),
|
if (this.marker_fixed_to_loc) {
|
||||||
Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
|
gotoLoc(false);
|
||||||
return !TextUtils.isEmpty(locationProviders);
|
}
|
||||||
|
updateLocationMarkers();
|
||||||
|
updateUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLocationEnabled() {
|
@Override
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
protected void updateUi() {
|
||||||
return isLocationEnabledKitkat();
|
if (!hasLocationFeature || noAskAgain || isLocationEnabledAndAllowed()) {
|
||||||
|
this.snackBar.dismiss();
|
||||||
} else {
|
} else {
|
||||||
return isLocationEnabledLegacy();
|
this.snackBar.show();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setShareButtonEnabled(final boolean enabled) {
|
if (isLocationEnabledAndAllowed()) {
|
||||||
if (enabled) {
|
this.binding.fab.setVisibility(View.VISIBLE);
|
||||||
this.mShareButton.setEnabled(true);
|
runOnUiThread(() -> {
|
||||||
this.mShareButton.setText(R.string.share);
|
this.binding.fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp :
|
||||||
|
R.drawable.ic_gps_not_fixed_white_24dp);
|
||||||
|
this.binding.fab.setContentDescription(getResources().getString(
|
||||||
|
marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location
|
||||||
|
));
|
||||||
|
this.binding.fab.invalidate();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
this.mShareButton.setEnabled(false);
|
this.binding.fab.setVisibility(View.GONE);
|
||||||
this.mShareButton.setText(R.string.locating);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLocation(@Nullable Location location, String address) {
|
private static String getAddress(final Context context, final Location location) {
|
||||||
|
final double longitude = location.getLongitude();
|
||||||
|
final double latitude = location.getLatitude();
|
||||||
|
String address = "";
|
||||||
|
if (latitude != 0 && longitude != 0) {
|
||||||
try {
|
try {
|
||||||
if (location == null && TextUtils.isEmpty(address)) { // no location and no address available
|
final Geocoder geoCoder = new Geocoder(context, Locale.getDefault());
|
||||||
final WebView webView = findViewById(R.id.webView);
|
final List<Address> addresses = geoCoder.getFromLocation(latitude, longitude, 1);
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
if (addresses != null && addresses.size() > 0) {
|
||||||
webView.loadUrl("file:///android_asset/map.html");
|
final Address Address = addresses.get(0);
|
||||||
} else if (location != null && TextUtils.isEmpty(address)) { // location but no address available
|
StringBuilder strAddress = new StringBuilder("");
|
||||||
String LocationName = "<b>" + mLocationName + "</b>";
|
|
||||||
final WebView webView = findViewById(R.id.webView);
|
if (Address.getAddressLine(0).length() > 0) {
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
strAddress.append(Address.getAddressLine(0));
|
||||||
webView.loadUrl("javascript:toCoordinates(" + mLastLocation.getLatitude() + "," + mLastLocation.getLongitude() + "," + "'" + LocationName + "'" + ");");
|
}
|
||||||
} else if (location != null && !TextUtils.isEmpty(address)) { // location and address available
|
address = strAddress.toString().replace(", ", "<br>");
|
||||||
String LocationName = "<b>" + mLocationName + "</b><br>" + address;
|
|
||||||
final WebView webView = findViewById(R.id.webView);
|
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
|
||||||
webView.loadUrl("javascript:toCoordinates(" + mLastLocation.getLatitude() + "," + mLastLocation.getLongitude() + "," + "'" + LocationName + "'" + ");");
|
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
ToastCompat.makeText(this, R.string.error, ToastCompat.LENGTH_LONG);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
|
||||||
private class getAddressAsync extends AsyncTask<Void, Void, Void> {
|
private class getAddressAsync extends AsyncTask<Void, Void, Void> {
|
||||||
String address = null;
|
String address = null;
|
||||||
|
Location location;
|
||||||
|
|
||||||
private WeakReference<ShareLocationActivity> activityReference;
|
private WeakReference<ShareLocationActivity> activityReference;
|
||||||
|
|
||||||
getAddressAsync(ShareLocationActivity context) {
|
getAddressAsync(final ShareLocationActivity context, final Location location) {
|
||||||
activityReference = new WeakReference<>(context);
|
activityReference = new WeakReference<>(context);
|
||||||
|
this.location = location;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
showLocation(mLastLocation, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
if (mLastLocation != null) {
|
address = getAddress(ShareLocationActivity.this, this.location);
|
||||||
address = getAddress(ShareLocationActivity.this, mLastLocation);
|
|
||||||
}
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void result) {
|
protected void onPostExecute(Void result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
showLocation(mLastLocation, address);
|
showAddress(this.location);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -33,7 +33,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
|
||||||
refreshUi();
|
refreshUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Share {
|
private static class Share {
|
||||||
|
public String type;
|
||||||
ArrayList<Uri> uris = new ArrayList<>();
|
ArrayList<Uri> uris = new ArrayList<>();
|
||||||
public String account;
|
public String account;
|
||||||
public String contact;
|
public String contact;
|
||||||
|
@ -65,6 +66,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (grantResults.length > 0)
|
if (grantResults.length > 0)
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (requestCode == REQUEST_STORAGE_PERMISSION) {
|
if (requestCode == REQUEST_STORAGE_PERMISSION) {
|
||||||
|
@ -135,6 +137,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
|
||||||
} else if (type != null && uri != null) {
|
} else if (type != null && uri != null) {
|
||||||
this.share.uris.clear();
|
this.share.uris.clear();
|
||||||
this.share.uris.add(uri);
|
this.share.uris.add(uri);
|
||||||
|
this.share.type = type;
|
||||||
} else {
|
} else {
|
||||||
this.share.text = text;
|
this.share.text = text;
|
||||||
this.share.asQuote = asQuote;
|
this.share.asQuote = asQuote;
|
||||||
|
@ -187,6 +190,9 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
|
||||||
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
|
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
|
||||||
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris);
|
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris);
|
||||||
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
if (share.type != null) {
|
||||||
|
intent.putExtra(ConversationsActivity.EXTRA_TYPE, share.type);
|
||||||
|
}
|
||||||
} else if (share.text != null) {
|
} else if (share.text != null) {
|
||||||
intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, share.text);
|
intent.putExtra(Intent.EXTRA_TEXT, share.text);
|
||||||
|
|
|
@ -1,24 +1,28 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
|
import android.content.ClipData;
|
||||||
|
import android.content.ClipboardManager;
|
||||||
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.location.Address;
|
import android.location.Address;
|
||||||
import android.location.Geocoder;
|
import android.location.Geocoder;
|
||||||
import android.location.Location;
|
import android.location.Location;
|
||||||
|
import android.location.LocationListener;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
import android.text.Html;
|
||||||
import android.text.TextUtils;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.webkit.WebView;
|
import android.view.View;
|
||||||
|
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -26,79 +30,107 @@ import java.util.Locale;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.utils.MenuDoubleTabUtil;
|
import eu.siacs.conversations.databinding.ActivityShowLocationBinding;
|
||||||
|
import eu.siacs.conversations.ui.util.LocationHelper;
|
||||||
|
import eu.siacs.conversations.ui.widget.Marker;
|
||||||
|
import eu.siacs.conversations.ui.widget.MyLocation;
|
||||||
|
import eu.siacs.conversations.utils.LocationProvider;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
public class ShowLocationActivity extends XmppActivity {
|
|
||||||
FloatingActionButton fab;
|
|
||||||
private Location location;
|
|
||||||
private String mLocationName;
|
|
||||||
|
|
||||||
private static String getAddress(Context context, Location location) {
|
public class ShowLocationActivity extends LocationActivity implements LocationListener {
|
||||||
double longitude = location.getLongitude();
|
|
||||||
double latitude = location.getLatitude();
|
|
||||||
String address = "";
|
|
||||||
if (latitude != 0 && longitude != 0) {
|
|
||||||
try {
|
|
||||||
Geocoder geoCoder = new Geocoder(context, Locale.getDefault());
|
|
||||||
List<Address> addresses = geoCoder.getFromLocation(latitude, longitude, 1);
|
|
||||||
if (addresses != null && addresses.size() > 0) {
|
|
||||||
Address Address = addresses.get(0);
|
|
||||||
StringBuilder strAddress = new StringBuilder("");
|
|
||||||
|
|
||||||
if (Address.getAddressLine(0).length() > 0) {
|
private GeoPoint loc = LocationProvider.FALLBACK;
|
||||||
strAddress.append(Address.getAddressLine(0));
|
private ActivityShowLocationBinding binding;
|
||||||
}
|
private String name;
|
||||||
address = strAddress.toString().replace(", ", "<br>");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
private Uri createGeoUri() {
|
||||||
e.printStackTrace();
|
return Uri.parse("geo:" + this.loc.getLatitude() + "," + this.loc.getLongitude());
|
||||||
}
|
|
||||||
}
|
|
||||||
return address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.activity_show_locaction);
|
|
||||||
setTitle(getString(R.string.show_location));
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_show_location);
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
setSupportActionBar((Toolbar) binding.toolbar);
|
||||||
|
|
||||||
configureActionBar(getSupportActionBar());
|
configureActionBar(getSupportActionBar());
|
||||||
showLocation(null, "");
|
setupMapView(this.binding.map, this.loc);
|
||||||
Intent intent = getIntent();
|
|
||||||
|
|
||||||
this.mLocationName = intent != null ? intent.getStringExtra("name") : null;
|
this.binding.fab.setOnClickListener(view -> startNavigation());
|
||||||
|
|
||||||
if (intent != null && intent.hasExtra("longitude") && intent.hasExtra("latitude")) {
|
final Intent intent = getIntent();
|
||||||
double longitude = intent.getDoubleExtra("longitude", 0);
|
if (intent != null) {
|
||||||
double latitude = intent.getDoubleExtra("latitude", 0);
|
this.name = intent.hasExtra("name") ? intent.getStringExtra("name") : null;
|
||||||
this.location = new Location("");
|
if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) {
|
||||||
this.location.setLatitude(latitude);
|
final double longitude = intent.getDoubleExtra("longitude", 0);
|
||||||
this.location.setLongitude(longitude);
|
final double latitude = intent.getDoubleExtra("latitude", 0);
|
||||||
Log.d(Config.LOGTAG, "Location: lat: " + latitude + " long: " + longitude);
|
this.loc = new GeoPoint(latitude, longitude);
|
||||||
markAndCenterOnLocation(this.location);
|
}
|
||||||
fab = findViewById(R.id.fab);
|
|
||||||
fab.setOnClickListener(v -> {
|
}
|
||||||
navigate(this.location);
|
updateLocationMarkers();
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void gotoLoc(final boolean setZoomLevel) {
|
||||||
|
if (this.loc != null && mapController != null) {
|
||||||
|
if (setZoomLevel) {
|
||||||
|
mapController.setZoom(Config.Map.FINAL_ZOOM_LEVEL);
|
||||||
|
}
|
||||||
|
mapController.animateTo(new GeoPoint(this.loc));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markAndCenterOnLocation(final Location location) {
|
@Override
|
||||||
if (location == null) {
|
public void onRequestPermissionsResult(final int requestCode,
|
||||||
Log.d(Config.LOGTAG, "No location given");
|
@NonNull final String[] permissions,
|
||||||
return;
|
@NonNull final int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
updateUi();
|
||||||
}
|
}
|
||||||
double longitude = location.getLongitude();
|
|
||||||
double latitude = location.getLatitude();
|
@Override
|
||||||
if (latitude != 0 && longitude != 0) {
|
protected void setMyLoc(final Location location) {
|
||||||
new getAddressAsync(this).execute();
|
this.myLoc = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
|
// Inflate the menu; this adds items to the action bar if it is present.
|
||||||
|
getMenuInflater().inflate(R.menu.menu_show_location, menu);
|
||||||
|
updateUi();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void updateLocationMarkers() {
|
||||||
|
super.updateLocationMarkers();
|
||||||
|
if (this.myLoc != null) {
|
||||||
|
this.binding.map.getOverlays().add(new MyLocation(this, null, this.myLoc));
|
||||||
|
}
|
||||||
|
this.binding.map.getOverlays().add(new Marker(this.marker_icon, this.loc));
|
||||||
|
new getAddressAsync(this, this.loc, this.name).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showAddress(final GeoPoint loc, final String name) {
|
||||||
|
this.binding.address.setText(Html.fromHtml(getAddress(this, loc, name)));
|
||||||
|
if (Html.fromHtml(getAddress(this, loc, name)).length() > 0) {
|
||||||
|
this.binding.address.setVisibility(View.VISIBLE);
|
||||||
|
} else {
|
||||||
|
hideAddress();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public SharedPreferences getPreferences() {
|
private void hideAddress() {
|
||||||
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
this.binding.address.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -112,89 +144,140 @@ public class ShowLocationActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onOptionsItemSelected(MenuItem item) {
|
public boolean onOptionsItemSelected(final MenuItem item) {
|
||||||
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
switch (item.getItemId()) {
|
switch (item.getItemId()) {
|
||||||
case android.R.id.home:
|
case R.id.action_copy_location:
|
||||||
finish();
|
final ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
|
||||||
|
if (clipboard != null) {
|
||||||
|
final ClipData clip = ClipData.newPlainText("location", createGeoUri().toString());
|
||||||
|
clipboard.setPrimaryClip(clip);
|
||||||
|
ToastCompat.makeText(this, R.string.url_copied_to_clipboard, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case R.id.action_share_location:
|
||||||
|
final Intent shareIntent = new Intent();
|
||||||
|
shareIntent.setAction(Intent.ACTION_SEND);
|
||||||
|
shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString());
|
||||||
|
shareIntent.setType("text/plain");
|
||||||
|
try {
|
||||||
|
startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
|
||||||
|
} catch (final ActivityNotFoundException e) {
|
||||||
|
//This should happen only on faulty androids because normally chooser is always available
|
||||||
|
ToastCompat.makeText(this, R.string.no_application_found_to_open_file, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void startNavigation() {
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(
|
||||||
MenuInflater inflater = getMenuInflater();
|
"google.navigation:q=" +
|
||||||
return true;
|
this.loc.getLatitude() + "," + this.loc.getLongitude()
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onSaveInstanceState(Bundle outState) {
|
protected void updateUi() {
|
||||||
super.onSaveInstanceState(outState);
|
final Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse("google.navigation:q=0,0"));
|
||||||
|
final ComponentName component = i.resolveActivity(getPackageManager());
|
||||||
|
this.binding.fab.setVisibility(component == null ? View.GONE : View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showLocation(Location location, String address) {
|
@Override
|
||||||
|
public void onLocationChanged(final Location location) {
|
||||||
|
if (LocationHelper.isBetterLocation(location, this.myLoc)) {
|
||||||
|
this.myLoc = location;
|
||||||
|
updateLocationMarkers();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStatusChanged(final String provider, final int status, final Bundle extras) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderEnabled(final String provider) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onProviderDisabled(final String provider) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getAddress(final Context context, final GeoPoint location, final String name) {
|
||||||
|
final double longitude = location.getLongitude();
|
||||||
|
final double latitude = location.getLatitude();
|
||||||
|
String address = "";
|
||||||
|
if (latitude != 0 && longitude != 0) {
|
||||||
try {
|
try {
|
||||||
if (location != null && TextUtils.isEmpty(address)) { // location but no address available
|
final Geocoder geoCoder = new Geocoder(context, Locale.getDefault());
|
||||||
String LocationName = "<b>" + mLocationName + "</b>";
|
final List<Address> addresses = geoCoder.getFromLocation(latitude, longitude, 1);
|
||||||
final WebView webView = findViewById(R.id.webView);
|
if (addresses != null && addresses.size() > 0) {
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
final Address Address = addresses.get(0);
|
||||||
webView.loadUrl("file:///android_asset/map.html?lat=" + location.getLatitude() + "&lon=" + location.getLongitude() + "&name=" + LocationName);
|
StringBuilder strAddress = new StringBuilder("");
|
||||||
} else if (location != null && !TextUtils.isEmpty(address)) { // location and address available
|
if (name != null && name.length() > 0) {
|
||||||
String LocationName = "<b>" + mLocationName + "</b><br>" + address;
|
strAddress.append("<b>");
|
||||||
final WebView webView = findViewById(R.id.webView);
|
strAddress.append(name);
|
||||||
webView.getSettings().setJavaScriptEnabled(true);
|
strAddress.append(":</b><br>");
|
||||||
webView.loadUrl("javascript:toCoordinates(" + location.getLatitude() + "," + location.getLongitude() + "," + "'" + LocationName + "'" + ");");
|
}
|
||||||
|
if (Address.getAddressLine(0).length() > 0) {
|
||||||
|
strAddress.append(Address.getAddressLine(0));
|
||||||
|
}
|
||||||
|
address = strAddress.toString().replace(", ", "<br>");
|
||||||
|
} else {
|
||||||
|
StringBuilder strAddress = new StringBuilder("");
|
||||||
|
if (name != null && name.length() > 0) {
|
||||||
|
strAddress.append("<b>");
|
||||||
|
strAddress.append(name);
|
||||||
|
strAddress.append("</b>");
|
||||||
|
}
|
||||||
|
address = strAddress.toString();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
ToastCompat.makeText(this, R.string.error, ToastCompat.LENGTH_LONG);
|
StringBuilder strAddress = new StringBuilder("");
|
||||||
|
if (name != null && name.length() > 0) {
|
||||||
|
strAddress.append("<b>");
|
||||||
|
strAddress.append(name);
|
||||||
|
strAddress.append("</b>");
|
||||||
|
}
|
||||||
|
address = strAddress.toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return address;
|
||||||
private void navigate(Location location) {
|
|
||||||
if (location == null) {
|
|
||||||
Log.d(Config.LOGTAG, "No location given");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
double longitude = location.getLongitude();
|
|
||||||
double latitude = location.getLatitude();
|
|
||||||
try {
|
|
||||||
Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse("google.navigation:q=" + String.valueOf(latitude) + "," + String.valueOf(longitude)));
|
|
||||||
startActivity(intent);
|
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
|
||||||
} catch (ActivityNotFoundException e) {
|
|
||||||
ToastCompat.makeText(this, R.string.no_application_found_to_display_location, ToastCompat.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class getAddressAsync extends AsyncTask<Void, Void, Void> {
|
private class getAddressAsync extends AsyncTask<Void, Void, Void> {
|
||||||
String address = null;
|
String address = null;
|
||||||
|
String name = null;
|
||||||
|
GeoPoint location;
|
||||||
|
|
||||||
private WeakReference<ShowLocationActivity> activityReference;
|
private WeakReference<ShowLocationActivity> activityReference;
|
||||||
|
|
||||||
getAddressAsync(ShowLocationActivity context) {
|
getAddressAsync(final ShowLocationActivity context, final GeoPoint location, final String name) {
|
||||||
activityReference = new WeakReference<>(context);
|
activityReference = new WeakReference<>(context);
|
||||||
|
this.location = location;
|
||||||
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
showLocation(location, "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Void doInBackground(Void... params) {
|
protected Void doInBackground(Void... params) {
|
||||||
address = getAddress(ShowLocationActivity.this, location);
|
address = getAddress(ShowLocationActivity.this, this.location, this.name);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPostExecute(Void result) {
|
protected void onPostExecute(Void result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
showLocation(location, address);
|
showAddress(this.location, this.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,6 +9,7 @@ import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.ColorStateList;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -31,15 +32,18 @@ import android.widget.AutoCompleteTextView;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
|
import android.widget.PopupMenu;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.MenuRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
@ -49,6 +53,8 @@ import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
|
||||||
import com.google.android.material.textfield.TextInputLayout;
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.leinardi.android.speeddial.SpeedDialActionItem;
|
||||||
|
import com.leinardi.android.speeddial.SpeedDialView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -263,7 +269,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
Toolbar toolbar = (Toolbar) binding.toolbar;
|
Toolbar toolbar = (Toolbar) binding.toolbar;
|
||||||
setSupportActionBar(toolbar);
|
setSupportActionBar(toolbar);
|
||||||
configureActionBar(getSupportActionBar());
|
configureActionBar(getSupportActionBar());
|
||||||
binding.speedDial.inflate(R.menu.start_conversation_fab_submenu);
|
inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu);
|
||||||
binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
|
binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
|
||||||
binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -330,6 +336,22 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
binding.speedDial.getMainFab().setSupportImageTintList(ColorStateList.valueOf(getResources().getColor(R.color.realwhite)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void inflateFab(final SpeedDialView speedDialView, final @MenuRes int menuRes) {
|
||||||
|
speedDialView.clearActionItems();
|
||||||
|
final PopupMenu popupMenu = new PopupMenu(this, new View(this));
|
||||||
|
popupMenu.inflate(menuRes);
|
||||||
|
final Menu menu = popupMenu.getMenu();
|
||||||
|
for (int i = 0; i < menu.size(); i++) {
|
||||||
|
final MenuItem menuItem = menu.getItem(i);
|
||||||
|
final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
|
||||||
|
.setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null)
|
||||||
|
.setFabImageTintColor(ContextCompat.getColor(this, R.color.white))
|
||||||
|
.create();
|
||||||
|
speedDialView.addActionItem(actionItem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isValidJid(String input) {
|
public static boolean isValidJid(String input) {
|
||||||
|
|
443
src/main/java/eu/siacs/conversations/ui/UpdaterActivity.java
Normal file
443
src/main/java/eu/siacs/conversations/ui/UpdaterActivity.java
Normal file
|
@ -0,0 +1,443 @@
|
||||||
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
|
import android.app.ProgressDialog;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.ResolveInfo;
|
||||||
|
import android.net.ConnectivityManager;
|
||||||
|
import android.net.NetworkInfo;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Build;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.PowerManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.net.ssl.HttpsURLConnection;
|
||||||
|
import javax.net.ssl.SSLContext;
|
||||||
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.http.NoSSLv3SocketFactory;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.ui.util.CustomTab;
|
||||||
|
import eu.siacs.conversations.utils.WakeLockHelper;
|
||||||
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.Config.monocles;
|
||||||
|
import static eu.siacs.conversations.http.HttpConnectionManager.getProxy;
|
||||||
|
import static eu.siacs.conversations.services.XmppConnectionService.FDroid;
|
||||||
|
import static eu.siacs.conversations.services.XmppConnectionService.PlayStore;
|
||||||
|
|
||||||
|
public class UpdaterActivity extends XmppActivity {
|
||||||
|
static final private String FileName = "update.apk";
|
||||||
|
String appURI = "";
|
||||||
|
String changelog = "";
|
||||||
|
Integer filesize = 0;
|
||||||
|
String store;
|
||||||
|
ProgressDialog mProgressDialog;
|
||||||
|
DownloadTask downloadTask;
|
||||||
|
TextView textView;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
//set activity
|
||||||
|
setContentView(R.layout.activity_updater);
|
||||||
|
this.mTheme = findTheme();
|
||||||
|
setTheme(this.mTheme);
|
||||||
|
|
||||||
|
textView = findViewById(R.id.updater);
|
||||||
|
|
||||||
|
mProgressDialog = new ProgressDialog(UpdaterActivity.this) {
|
||||||
|
//show warning on back pressed
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
showCancelDialog();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mProgressDialog.setMessage(getString(R.string.download_started));
|
||||||
|
mProgressDialog.setProgressNumberFormat(null);
|
||||||
|
mProgressDialog.setIndeterminate(true);
|
||||||
|
mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
|
||||||
|
mProgressDialog.setCancelable(false);
|
||||||
|
mProgressDialog.setCanceledOnTouchOutside(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void refreshUiReal() {
|
||||||
|
//ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStart() {
|
||||||
|
super.onStart();
|
||||||
|
this.mTheme = findTheme();
|
||||||
|
setTheme(this.mTheme);
|
||||||
|
setTitle(getString(R.string.update_service));
|
||||||
|
textView.setText(R.string.update_info);
|
||||||
|
setSupportActionBar(findViewById(R.id.toolbar));
|
||||||
|
configureActionBar(getSupportActionBar());
|
||||||
|
if (getIntent() != null && getIntent().getStringExtra("update").equals("PixArtMessenger_UpdateService")) {
|
||||||
|
try {
|
||||||
|
appURI = getIntent().getStringExtra("url");
|
||||||
|
} catch (Exception e) {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getText(R.string.failed), ToastCompat.LENGTH_LONG).show();
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
changelog = getIntent().getStringExtra("changelog");
|
||||||
|
} catch (Exception e) {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getText(R.string.failed), ToastCompat.LENGTH_LONG).show();
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
store = getIntent().getStringExtra("store");
|
||||||
|
} catch (Exception e) {
|
||||||
|
store = null;
|
||||||
|
}
|
||||||
|
//delete old downloaded localVersion files
|
||||||
|
File dir = new File(FileBackend.getAppUpdateDirectory());
|
||||||
|
if (dir.isDirectory()) {
|
||||||
|
String[] children = dir.list();
|
||||||
|
for (String aChildren : children) {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: delete old update files " + aChildren + " in " + dir);
|
||||||
|
new File(dir, aChildren).delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//oh yeah we do need an upgrade, let the user know send an alert message
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(UpdaterActivity.this);
|
||||||
|
builder.setCancelable(false);
|
||||||
|
//open link to changelog
|
||||||
|
//if the user agrees to upgrade
|
||||||
|
builder.setMessage(getString(R.string.install_update))
|
||||||
|
.setPositiveButton(R.string.update, (dialog, id) -> {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: downloading " + FileName + " from " + appURI);
|
||||||
|
//ask for permissions on devices >= SDK 23
|
||||||
|
if (isStoragePermissionGranted() && isNetworkAvailable(getApplicationContext())) {
|
||||||
|
//start downloading the file using the download manager
|
||||||
|
if (store != null && store.equalsIgnoreCase(PlayStore)) {
|
||||||
|
Uri uri = Uri.parse("market://details?id=" + getString(R.string.applicationId));
|
||||||
|
Intent marketIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
PackageManager manager = getApplicationContext().getPackageManager();
|
||||||
|
List<ResolveInfo> infos = manager.queryIntentActivities(marketIntent, 0);
|
||||||
|
if (infos.size() > 0) {
|
||||||
|
startActivity(marketIntent);
|
||||||
|
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
uri = Uri.parse("https://" + monocles());
|
||||||
|
CustomTab.openTab(this, uri, isDarkTheme());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ToastCompat.makeText(this, R.string.no_application_found_to_open_link, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (store != null && store.equalsIgnoreCase(FDroid)) {
|
||||||
|
Uri uri = Uri.parse("https://f-droid.org/de/packages/" + getString(R.string.applicationId) + "/");
|
||||||
|
Intent marketIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
PackageManager manager = getApplicationContext().getPackageManager();
|
||||||
|
List<ResolveInfo> infos = manager.queryIntentActivities(marketIntent, 0);
|
||||||
|
if (infos.size() > 0) {
|
||||||
|
startActivity(marketIntent);
|
||||||
|
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
|
} else {
|
||||||
|
uri = Uri.parse("https://" + monocles());
|
||||||
|
try {
|
||||||
|
CustomTab.openTab(this, uri, isDarkTheme());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ToastCompat.makeText(this, R.string.no_application_found_to_open_link, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getText(R.string.download_started), ToastCompat.LENGTH_LONG).show();
|
||||||
|
downloadTask = new DownloadTask(UpdaterActivity.this);
|
||||||
|
downloadTask.execute(appURI);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: failed - has storage permissions " + isStoragePermissionGranted() + " and internet " + isNetworkAvailable(getApplicationContext()));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNeutralButton(R.string.changelog, (dialog, id) -> {
|
||||||
|
Uri uri = Uri.parse(Config.CHANGELOG_URL); // missing 'http://' will cause crash
|
||||||
|
try {
|
||||||
|
CustomTab.openTab(this, uri, isDarkTheme());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ToastCompat.makeText(this, R.string.no_application_found_to_open_link, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
} finally {
|
||||||
|
//restart updater to show dialog again after coming back after opening changelog
|
||||||
|
recreate();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.remind_later, (dialog, id) -> {
|
||||||
|
// User cancelled the dialog
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
});
|
||||||
|
//show the alert message
|
||||||
|
builder.create().show();
|
||||||
|
} else {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getText(R.string.failed), ToastCompat.LENGTH_LONG).show();
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onBackendConnected() {
|
||||||
|
//ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||||
|
super.onRestoreInstanceState(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
//check for internet connection
|
||||||
|
private boolean isNetworkAvailable(Context context) {
|
||||||
|
ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||||
|
if (connectivity != null) {
|
||||||
|
NetworkInfo[] info = connectivity.getAllNetworkInfo();
|
||||||
|
if (info != null) {
|
||||||
|
for (NetworkInfo anInfo : info) {
|
||||||
|
if (anInfo.getState() == NetworkInfo.State.CONNECTED) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStoragePermissionGranted() {
|
||||||
|
if (Build.VERSION.SDK_INT >= 23) {
|
||||||
|
if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && checkSelfPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else { //permission is automatically granted on sdk<23 upon installation
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//show warning on back pressed
|
||||||
|
@Override
|
||||||
|
public void onBackPressed() {
|
||||||
|
showCancelDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showCancelDialog() {
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setMessage(R.string.cancel_update)
|
||||||
|
.setCancelable(false)
|
||||||
|
.setPositiveButton(R.string.yes, (dialog, id) -> {
|
||||||
|
if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
|
||||||
|
downloadTask.cancel(true);
|
||||||
|
}
|
||||||
|
if (mProgressDialog.isShowing()) {
|
||||||
|
mProgressDialog.dismiss();
|
||||||
|
}
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
})
|
||||||
|
.setNegativeButton(R.string.no, (dialog, id) -> dialog.cancel());
|
||||||
|
final AlertDialog alert = builder.create();
|
||||||
|
alert.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
|
||||||
|
downloadTask.cancel(true);
|
||||||
|
}
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onStop() {
|
||||||
|
super.onStop();
|
||||||
|
if (downloadTask != null && !downloadTask.getStatus().equals(AsyncTask.Status.FINISHED)) {
|
||||||
|
downloadTask.cancel(true);
|
||||||
|
}
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DownloadTask extends AsyncTask<String, Integer, String> {
|
||||||
|
XmppActivity activity;
|
||||||
|
File dir = new File(FileBackend.getAppUpdateDirectory());
|
||||||
|
File file = new File(dir, FileName);
|
||||||
|
XmppConnectionService xmppConnectionService;
|
||||||
|
private Context context;
|
||||||
|
private PowerManager.WakeLock mWakeLock;
|
||||||
|
private long startTime = 0;
|
||||||
|
private boolean mUseTor;
|
||||||
|
|
||||||
|
DownloadTask(Context context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
startTime = System.currentTimeMillis();
|
||||||
|
// take CPU lock to prevent CPU from going off if the user
|
||||||
|
// presses the power button during download
|
||||||
|
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
|
||||||
|
if (pm != null) {
|
||||||
|
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, getClass().getName());
|
||||||
|
mWakeLock.acquire();
|
||||||
|
mUseTor = xmppConnectionService != null && xmppConnectionService.useTorToConnect();
|
||||||
|
}
|
||||||
|
mProgressDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Integer... progress) {
|
||||||
|
super.onProgressUpdate(progress);
|
||||||
|
// if we get here, length is known, now set indeterminate to false
|
||||||
|
mProgressDialog.setIndeterminate(false);
|
||||||
|
mProgressDialog.setMax(100);
|
||||||
|
mProgressDialog.setProgress(progress[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String doInBackground(String... sUrl) {
|
||||||
|
InputStream is = null;
|
||||||
|
OutputStream os = null;
|
||||||
|
SSLContext sslcontext = null;
|
||||||
|
SSLSocketFactory NoSSLv3Factory = null;
|
||||||
|
try {
|
||||||
|
sslcontext = SSLContext.getInstance("TLSv1");
|
||||||
|
if (sslcontext != null) {
|
||||||
|
sslcontext.init(null, null, null);
|
||||||
|
NoSSLv3Factory = new NoSSLv3SocketFactory(sslcontext.getSocketFactory());
|
||||||
|
}
|
||||||
|
} catch (NoSuchAlgorithmException | KeyManagementException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(NoSSLv3Factory);
|
||||||
|
HttpsURLConnection connection = null;
|
||||||
|
try {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: save file to " + file.toString());
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: download update from url: " + sUrl[0] + " to file name: " + file.toString());
|
||||||
|
|
||||||
|
URL url = new URL(sUrl[0]);
|
||||||
|
|
||||||
|
if (mUseTor) {
|
||||||
|
connection = (HttpsURLConnection) url.openConnection(getProxy());
|
||||||
|
} else {
|
||||||
|
connection = (HttpsURLConnection) url.openConnection();
|
||||||
|
}
|
||||||
|
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
|
||||||
|
connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
|
||||||
|
connection.setRequestProperty("User-agent", System.getProperty("http.agent"));
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
// expect HTTP 200 OK, so we don't mistakenly save error report
|
||||||
|
// instead of the file
|
||||||
|
if (connection.getResponseCode() != HttpsURLConnection.HTTP_OK) {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getText(R.string.failed), ToastCompat.LENGTH_LONG).show();
|
||||||
|
return connection.getResponseCode() + ": " + connection.getResponseMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
// this will be useful to display download percentage
|
||||||
|
// might be -1: server did not report the length
|
||||||
|
int fileLength = connection.getContentLength();
|
||||||
|
|
||||||
|
// create folders
|
||||||
|
File parentDirectory = file.getParentFile();
|
||||||
|
if (parentDirectory.mkdirs()) {
|
||||||
|
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
// download the file
|
||||||
|
is = connection.getInputStream();
|
||||||
|
os = new FileOutputStream(file);
|
||||||
|
|
||||||
|
byte[] data = new byte[4096];
|
||||||
|
long total = 0;
|
||||||
|
int count;
|
||||||
|
while ((count = is.read(data)) != -1) {
|
||||||
|
// allow canceling with back button
|
||||||
|
if (isCancelled()) {
|
||||||
|
is.close();
|
||||||
|
return "canceled";
|
||||||
|
}
|
||||||
|
total += count;
|
||||||
|
// publishing the progress....
|
||||||
|
if (fileLength > 0) // only if total length is known
|
||||||
|
publishProgress((int) (total * 100 / fileLength));
|
||||||
|
os.write(data, 0, count);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
return e.toString();
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
if (os != null)
|
||||||
|
os.close();
|
||||||
|
if (is != null)
|
||||||
|
is.close();
|
||||||
|
} catch (IOException ignored) {
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection != null)
|
||||||
|
connection.disconnect();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(String result) {
|
||||||
|
WakeLockHelper.release(mWakeLock);
|
||||||
|
mProgressDialog.dismiss();
|
||||||
|
if (result != null) {
|
||||||
|
ToastCompat.makeText(getApplicationContext(), getString(R.string.failed), ToastCompat.LENGTH_LONG).show();
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: failed with " + result);
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
} else {
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater: download ready in " + ((System.currentTimeMillis() - startTime) / 1000) + " sec");
|
||||||
|
|
||||||
|
//start the installation of the latest localVersion
|
||||||
|
Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
|
||||||
|
installIntent.setDataAndType(FileBackend.getUriForFile(UpdaterActivity.this, file), "application/vnd.android.package-archive");
|
||||||
|
installIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
|
||||||
|
installIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
|
||||||
|
installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||||
|
installIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
startActivity(installIntent);
|
||||||
|
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
|
UpdaterActivity.this.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,17 +7,27 @@ import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.databinding.ActivityUriHandlerBinding;
|
||||||
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
import eu.siacs.conversations.utils.ProvisioningUtils;
|
import eu.siacs.conversations.utils.ProvisioningUtils;
|
||||||
|
@ -25,6 +35,11 @@ import eu.siacs.conversations.utils.SignupUtils;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
import okhttp3.Call;
|
||||||
|
import okhttp3.Callback;
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public class UriHandlerActivity extends AppCompatActivity {
|
public class UriHandlerActivity extends AppCompatActivity {
|
||||||
|
|
||||||
|
@ -34,7 +49,9 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789;
|
private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN = 0x6789;
|
||||||
private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790;
|
private static final int REQUEST_CAMERA_PERMISSIONS_TO_SCAN_AND_PROVISION = 0x6790;
|
||||||
private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n");
|
private static final Pattern V_CARD_XMPP_PATTERN = Pattern.compile("\nIMPP([^:]*):(xmpp:.+)\n");
|
||||||
private boolean handled = false;
|
private static final Pattern LINK_HEADER_PATTERN = Pattern.compile("<(.*?)>");
|
||||||
|
private ActivityUriHandlerBinding binding;
|
||||||
|
private Call call;
|
||||||
|
|
||||||
public static void scan(final Activity activity) {
|
public static void scan(final Activity activity) {
|
||||||
scan(activity, false);
|
scan(activity, false);
|
||||||
|
@ -77,9 +94,7 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.handled = savedInstanceState != null && savedInstanceState.getBoolean("handled", false);
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler);
|
||||||
getLayoutInflater().inflate(R.layout.toolbar, findViewById(android.R.id.content));
|
|
||||||
setSupportActionBar(findViewById(R.id.toolbar));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -88,23 +103,17 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
handleIntent(getIntent());
|
handleIntent(getIntent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
|
||||||
savedInstanceState.putBoolean("handled", this.handled);
|
|
||||||
super.onSaveInstanceState(savedInstanceState);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(final Intent intent) {
|
public void onNewIntent(final Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
handleIntent(intent);
|
handleIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUri(Uri uri) {
|
private boolean handleUri(final Uri uri) {
|
||||||
handleUri(uri, false);
|
return handleUri(uri, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleUri(Uri uri, final boolean scanned) {
|
private boolean handleUri(final Uri uri, final boolean scanned) {
|
||||||
final Intent intent;
|
final Intent intent;
|
||||||
final XmppUri xmppUri = new XmppUri(uri);
|
final XmppUri xmppUri = new XmppUri(uri);
|
||||||
final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(true);
|
final List<Jid> accounts = DatabaseBackend.getInstance(this).getAccountJids(true);
|
||||||
|
@ -113,40 +122,39 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
final Jid jid = xmppUri.getJid();
|
final Jid jid = xmppUri.getJid();
|
||||||
if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
|
if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
|
||||||
if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) {
|
if (jid.getEscapedLocal() != null && accounts.contains(jid.asBareJid())) {
|
||||||
ToastCompat.makeText(this, R.string.account_already_exists, ToastCompat.LENGTH_LONG).show();
|
showError(R.string.account_already_exists);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth, true);
|
intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth, true);
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
|
if (accounts.size() == 0 && xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
|
||||||
intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
|
intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
|
||||||
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
} else if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
|
||||||
|
showError(R.string.account_registrations_are_not_supported);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
if (accounts.size() == 0) {
|
if (accounts.size() == 0) {
|
||||||
if (xmppUri.isValidJid()) {
|
if (xmppUri.isValidJid()) {
|
||||||
intent = SignupUtils.getSignUpIntent(this);
|
intent = SignupUtils.getSignUpIntent(this);
|
||||||
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
} else {
|
} else {
|
||||||
ToastCompat.makeText(this, R.string.invalid_jid, ToastCompat.LENGTH_SHORT).show();
|
showError(R.string.invalid_jid);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) {
|
if (xmppUri.isAction(XmppUri.ACTION_MESSAGE)) {
|
||||||
final Jid jid = xmppUri.getJid();
|
final Jid jid = xmppUri.getJid();
|
||||||
final String body = xmppUri.getBody();
|
final String body = xmppUri.getBody();
|
||||||
if (jid != null) {
|
if (jid != null) {
|
||||||
Class clazz;
|
final Class<?> clazz = findShareViaAccountClass();
|
||||||
try {
|
|
||||||
clazz = Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
clazz = null;
|
|
||||||
}
|
|
||||||
if (clazz != null) {
|
if (clazz != null) {
|
||||||
intent = new Intent(this, clazz);
|
intent = new Intent(this, clazz);
|
||||||
intent.putExtra("contact", jid.toEscapedString());
|
intent.putExtra("contact", jid.toEscapedString());
|
||||||
|
@ -176,32 +184,93 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
intent.putExtra("scanned", scanned);
|
intent.putExtra("scanned", scanned);
|
||||||
intent.setData(uri);
|
intent.setData(uri);
|
||||||
} else {
|
} else {
|
||||||
ToastCompat.makeText(this, R.string.invalid_jid, ToastCompat.LENGTH_SHORT).show();
|
showError(R.string.invalid_jid);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleIntent(Intent data) {
|
private void checkForLinkHeader(final HttpUrl url) {
|
||||||
if (handled) {
|
Log.d(Config.LOGTAG, "checking for link header on " + url);
|
||||||
|
this.call = HttpConnectionManager.OK_HTTP_CLIENT.newCall(new Request.Builder()
|
||||||
|
.url(url)
|
||||||
|
.head()
|
||||||
|
.build());
|
||||||
|
this.call.enqueue(new Callback() {
|
||||||
|
@Override
|
||||||
|
public void onFailure(@NotNull Call call, @NotNull IOException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to check HTTP url", e);
|
||||||
|
showError(R.string.no_xmpp_adddress_found);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResponse(@NotNull Call call, @NotNull Response response) {
|
||||||
|
if (response.isSuccessful()) {
|
||||||
|
final String linkHeader = response.header("Link");
|
||||||
|
if (linkHeader != null && processLinkHeader(linkHeader)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (data == null || data.getAction() == null) {
|
}
|
||||||
|
showError(R.string.no_xmpp_adddress_found);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean processLinkHeader(final String header) {
|
||||||
|
final Matcher matcher = LINK_HEADER_PATTERN.matcher(header);
|
||||||
|
if (matcher.find()) {
|
||||||
|
final String group = matcher.group();
|
||||||
|
final String link = group.substring(1, group.length() - 1);
|
||||||
|
if (handleUri(Uri.parse(link))) {
|
||||||
finish();
|
finish();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showError(@StringRes int error) {
|
||||||
|
this.binding.progress.setVisibility(View.INVISIBLE);
|
||||||
|
this.binding.error.setText(error);
|
||||||
|
this.binding.error.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> findShareViaAccountClass() {
|
||||||
|
try {
|
||||||
|
return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
|
||||||
|
} catch (final ClassNotFoundException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleIntent(final Intent data) {
|
||||||
|
final String action = data == null ? null : data.getAction();
|
||||||
|
if (action == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handled = true;
|
switch (action) {
|
||||||
switch (data.getAction()) {
|
case Intent.ACTION_MAIN:
|
||||||
|
binding.progress.setVisibility(call != null && !call.isCanceled() ? View.VISIBLE : View.INVISIBLE);
|
||||||
|
break;
|
||||||
case Intent.ACTION_VIEW:
|
case Intent.ACTION_VIEW:
|
||||||
case Intent.ACTION_SENDTO:
|
case Intent.ACTION_SENDTO:
|
||||||
handleUri(data.getData());
|
if (handleUri(data.getData())) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case ACTION_SCAN_QR_CODE:
|
case ACTION_SCAN_QR_CODE:
|
||||||
Intent intent = new Intent(this, ScanActivity.class);
|
Log.d(Config.LOGTAG, "scan. allow=" + allowProvisioning());
|
||||||
startActivityForResult(intent, REQUEST_SCAN_QR_CODE);
|
setIntent(createMainIntent());
|
||||||
return;
|
startActivityForResult(new Intent(this, ScanActivity.class), REQUEST_SCAN_QR_CODE);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
finish();
|
}
|
||||||
|
private Intent createMainIntent() {
|
||||||
|
final Intent intent = new Intent(Intent.ACTION_MAIN);
|
||||||
|
intent.putExtra(EXTRA_ALLOW_PROVISIONING, allowProvisioning());
|
||||||
|
return intent;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean allowProvisioning() {
|
private boolean allowProvisioning() {
|
||||||
|
@ -213,6 +282,7 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
|
public void onActivityResult(final int requestCode, final int resultCode, final Intent intent) {
|
||||||
super.onActivityResult(requestCode, requestCode, intent);
|
super.onActivityResult(requestCode, requestCode, intent);
|
||||||
if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) {
|
if (requestCode == REQUEST_SCAN_QR_CODE && resultCode == RESULT_OK) {
|
||||||
|
final boolean allowProvisioning = allowProvisioning();
|
||||||
final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
|
final String result = intent.getStringExtra(ScanActivity.INTENT_EXTRA_RESULT);
|
||||||
if (Strings.isNullOrEmpty(result)) {
|
if (Strings.isNullOrEmpty(result)) {
|
||||||
finish();
|
finish();
|
||||||
|
@ -221,22 +291,38 @@ public class UriHandlerActivity extends AppCompatActivity {
|
||||||
if (result.startsWith("BEGIN:VCARD\n")) {
|
if (result.startsWith("BEGIN:VCARD\n")) {
|
||||||
final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result);
|
final Matcher matcher = V_CARD_XMPP_PATTERN.matcher(result);
|
||||||
if (matcher.find()) {
|
if (matcher.find()) {
|
||||||
handleUri(Uri.parse(matcher.group(2)), true);
|
if (handleUri(Uri.parse(matcher.group(2)), true)) {
|
||||||
}
|
|
||||||
finish();
|
finish();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
showError(R.string.no_xmpp_adddress_found);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
} else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning()) {
|
} else if (QuickConversationsService.isConversations() && looksLikeJsonObject(result) && allowProvisioning) {
|
||||||
ProvisioningUtils.provision(this, result);
|
ProvisioningUtils.provision(this, result);
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleUri(Uri.parse(result), true);
|
final Uri uri = Uri.parse(result.trim());
|
||||||
}
|
if (allowProvisioning && "https".equalsIgnoreCase(uri.getScheme()) && !Config.INVITE_DOMAIN.equalsIgnoreCase(uri.getHost())) {
|
||||||
|
final HttpUrl httpUrl = HttpUrl.parse(uri.toString());
|
||||||
|
if (httpUrl != null) {
|
||||||
|
checkForLinkHeader(httpUrl);
|
||||||
|
} else {
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
} else if (handleUri(uri, true)) {
|
||||||
|
finish();
|
||||||
|
} else {
|
||||||
|
setIntent(new Intent(Intent.ACTION_VIEW, uri));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static boolean looksLikeJsonObject(final String input) {
|
private static boolean looksLikeJsonObject(final String input) {
|
||||||
final String trimmed = Strings.emptyToNull(input).trim();
|
final String trimmed = Strings.nullToEmpty(input).trim();
|
||||||
return trimmed.charAt(0) == '{' && trimmed.charAt(trimmed.length() - 1) == '}';
|
return trimmed.charAt(0) == '{' && trimmed.charAt(trimmed.length() - 1) == '}';
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,450 +0,0 @@
|
||||||
package eu.siacs.conversations.ui;
|
|
||||||
|
|
||||||
import android.content.DialogInterface;
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
|
||||||
import android.view.View;
|
|
||||||
import android.widget.Button;
|
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.LinearLayout;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.appcompat.app.ActionBar;
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
|
|
||||||
import net.java.otr4j.OtrException;
|
|
||||||
import net.java.otr4j.session.Session;
|
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.R;
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
|
||||||
import eu.siacs.conversations.entities.Contact;
|
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
|
||||||
|
|
||||||
public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
|
|
||||||
|
|
||||||
public static final String ACTION_VERIFY_CONTACT = "verify_contact";
|
|
||||||
public static final int MODE_SCAN_FINGERPRINT = -0x0502;
|
|
||||||
public static final int MODE_ASK_QUESTION = 0x0503;
|
|
||||||
public static final int MODE_ANSWER_QUESTION = 0x0504;
|
|
||||||
public static final int MODE_MANUAL_VERIFICATION = 0x0505;
|
|
||||||
|
|
||||||
private LinearLayout mManualVerificationArea;
|
|
||||||
private LinearLayout mSmpVerificationArea;
|
|
||||||
private TextView mRemoteFingerprint;
|
|
||||||
private TextView mYourFingerprint;
|
|
||||||
private TextView mVerificationExplain;
|
|
||||||
private TextView mStatusMessage;
|
|
||||||
private TextView mSharedSecretHint;
|
|
||||||
private EditText mSharedSecretHintEditable;
|
|
||||||
private EditText mSharedSecretSecret;
|
|
||||||
private Button mLeftButton;
|
|
||||||
private Button mRightButton;
|
|
||||||
private Account mAccount;
|
|
||||||
private Conversation mConversation;
|
|
||||||
private int mode = MODE_MANUAL_VERIFICATION;
|
|
||||||
private XmppUri mPendingUri = null;
|
|
||||||
|
|
||||||
private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialogInterface, int click) {
|
|
||||||
mConversation.verifyOtrFingerprint();
|
|
||||||
xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
|
|
||||||
ToastCompat.makeText(VerifyOTRActivity.this, R.string.verified, Toast.LENGTH_SHORT).show();
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(final View view) {
|
|
||||||
if (isAccountOnline()) {
|
|
||||||
final String question = mSharedSecretHintEditable.getText().toString();
|
|
||||||
final String secret = mSharedSecretSecret.getText().toString();
|
|
||||||
if (question.trim().isEmpty()) {
|
|
||||||
mSharedSecretHintEditable.requestFocus();
|
|
||||||
mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty));
|
|
||||||
} else if (secret.trim().isEmpty()) {
|
|
||||||
mSharedSecretSecret.requestFocus();
|
|
||||||
mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty));
|
|
||||||
} else {
|
|
||||||
mSharedSecretSecret.setError(null);
|
|
||||||
mSharedSecretHintEditable.setError(null);
|
|
||||||
initSmp(question, secret);
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
if (isAccountOnline()) {
|
|
||||||
abortSmp();
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
if (isAccountOnline()) {
|
|
||||||
final String question = mSharedSecretHintEditable.getText().toString();
|
|
||||||
final String secret = mSharedSecretSecret.getText().toString();
|
|
||||||
respondSmp(question, secret);
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
mConversation.smp().status = Conversation.Smp.STATUS_NONE;
|
|
||||||
mConversation.smp().hint = null;
|
|
||||||
mConversation.smp().secret = null;
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private View.OnClickListener mFinishListener = new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
mConversation.smp().status = Conversation.Smp.STATUS_NONE;
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
protected boolean initSmp(final String question, final String secret) {
|
|
||||||
final Session session = mConversation.getOtrSession();
|
|
||||||
if (session != null) {
|
|
||||||
try {
|
|
||||||
session.initSmp(question, secret);
|
|
||||||
mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
|
|
||||||
mConversation.smp().secret = secret;
|
|
||||||
mConversation.smp().hint = question;
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean abortSmp() {
|
|
||||||
final Session session = mConversation.getOtrSession();
|
|
||||||
if (session != null) {
|
|
||||||
try {
|
|
||||||
session.abortSmp();
|
|
||||||
mConversation.smp().status = Conversation.Smp.STATUS_NONE;
|
|
||||||
mConversation.smp().hint = null;
|
|
||||||
mConversation.smp().secret = null;
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean respondSmp(final String question, final String secret) {
|
|
||||||
final Session session = mConversation.getOtrSession();
|
|
||||||
if (session != null) {
|
|
||||||
try {
|
|
||||||
session.respondSmp(question, secret);
|
|
||||||
return true;
|
|
||||||
} catch (OtrException e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean verifyWithUri(XmppUri uri) {
|
|
||||||
Contact contact = mConversation.getContact();
|
|
||||||
if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.hasFingerprints()) {
|
|
||||||
xmppConnectionService.verifyFingerprints(contact, uri.getFingerprints());
|
|
||||||
ToastCompat.makeText(this, R.string.verified, Toast.LENGTH_SHORT).show();
|
|
||||||
updateView();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
ToastCompat.makeText(this, R.string.could_not_verify_fingerprint, Toast.LENGTH_SHORT).show();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAccountOnline() {
|
|
||||||
if (this.mAccount.getStatus() != Account.State.ONLINE) {
|
|
||||||
ToastCompat.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean handleIntent(Intent intent) {
|
|
||||||
if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
|
|
||||||
this.mAccount = extractAccount(intent);
|
|
||||||
if (this.mAccount == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this.mConversation = this.xmppConnectionService.find(this.mAccount, Jid.of(intent.getExtras().getString("contact")));
|
|
||||||
if (this.mConversation == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
} catch (final IllegalArgumentException ignored) {
|
|
||||||
ignored.printStackTrace();
|
|
||||||
return false;
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION);
|
|
||||||
// todo scan OTR fingerprint
|
|
||||||
if (this.mode == MODE_SCAN_FINGERPRINT) {
|
|
||||||
Log.d(Config.LOGTAG, "Scan OTR fingerprint is not implemented in this version");
|
|
||||||
//new IntentIntegrator(this).initiateScan();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
|
|
||||||
// todo onActivityResult for OTR scan
|
|
||||||
Log.d(Config.LOGTAG, "Scan OTR fingerprint result is not implemented in this version");
|
|
||||||
/*if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
|
|
||||||
IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
|
|
||||||
if (scanResult != null && scanResult.getFormatName() != null) {
|
|
||||||
String data = scanResult.getContents();
|
|
||||||
XmppUri uri = new XmppUri(data);
|
|
||||||
if (xmppConnectionServiceBound) {
|
|
||||||
verifyWithUri(uri);
|
|
||||||
finish();
|
|
||||||
} else {
|
|
||||||
this.mPendingUri = uri;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
finish();
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
super.onActivityResult(requestCode, requestCode, intent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onBackendConnected() {
|
|
||||||
if (handleIntent(getIntent())) {
|
|
||||||
updateView();
|
|
||||||
} else if (mPendingUri != null) {
|
|
||||||
verifyWithUri(mPendingUri);
|
|
||||||
finish();
|
|
||||||
mPendingUri = null;
|
|
||||||
}
|
|
||||||
setIntent(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateView() {
|
|
||||||
if (this.mConversation != null && this.mConversation.hasValidOtrSession()) {
|
|
||||||
final ActionBar actionBar = getSupportActionBar();
|
|
||||||
this.mVerificationExplain.setText(R.string.no_otr_session_found);
|
|
||||||
invalidateOptionsMenu();
|
|
||||||
switch (this.mode) {
|
|
||||||
case MODE_ASK_QUESTION:
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setTitle(R.string.ask_question);
|
|
||||||
}
|
|
||||||
this.updateViewAskQuestion();
|
|
||||||
break;
|
|
||||||
case MODE_ANSWER_QUESTION:
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setTitle(R.string.smp_requested);
|
|
||||||
}
|
|
||||||
this.updateViewAnswerQuestion();
|
|
||||||
break;
|
|
||||||
case MODE_MANUAL_VERIFICATION:
|
|
||||||
default:
|
|
||||||
if (actionBar != null) {
|
|
||||||
actionBar.setTitle(R.string.manually_verify);
|
|
||||||
}
|
|
||||||
this.updateViewManualVerification();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.mManualVerificationArea.setVisibility(View.GONE);
|
|
||||||
this.mSmpVerificationArea.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateViewManualVerification() {
|
|
||||||
this.mVerificationExplain.setText(R.string.manual_verification_explanation);
|
|
||||||
this.mManualVerificationArea.setVisibility(View.VISIBLE);
|
|
||||||
this.mSmpVerificationArea.setVisibility(View.GONE);
|
|
||||||
this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
|
|
||||||
this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint()));
|
|
||||||
if (this.mConversation.isOtrFingerprintVerified()) {
|
|
||||||
deactivateButton(this.mRightButton, R.string.verified);
|
|
||||||
activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener);
|
|
||||||
} else {
|
|
||||||
activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener);
|
|
||||||
activateButton(this.mRightButton, R.string.verify, new View.OnClickListener() {
|
|
||||||
@Override
|
|
||||||
public void onClick(View view) {
|
|
||||||
showManuallyVerifyDialog();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateViewAskQuestion() {
|
|
||||||
this.mManualVerificationArea.setVisibility(View.GONE);
|
|
||||||
this.mSmpVerificationArea.setVisibility(View.VISIBLE);
|
|
||||||
this.mVerificationExplain.setText(R.string.smp_explain_question);
|
|
||||||
final int smpStatus = this.mConversation.smp().status;
|
|
||||||
switch (smpStatus) {
|
|
||||||
case Conversation.Smp.STATUS_WE_REQUESTED:
|
|
||||||
this.mStatusMessage.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
|
|
||||||
this.mSharedSecretSecret.setVisibility(View.VISIBLE);
|
|
||||||
this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint);
|
|
||||||
this.mSharedSecretSecret.setText(this.mConversation.smp().secret);
|
|
||||||
this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener);
|
|
||||||
this.deactivateButton(this.mRightButton, R.string.in_progress);
|
|
||||||
break;
|
|
||||||
case Conversation.Smp.STATUS_FAILED:
|
|
||||||
this.mStatusMessage.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
|
|
||||||
this.mSharedSecretSecret.setVisibility(View.VISIBLE);
|
|
||||||
this.mSharedSecretSecret.requestFocus();
|
|
||||||
this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
|
|
||||||
this.deactivateButton(this.mLeftButton, R.string.cancel);
|
|
||||||
this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener);
|
|
||||||
break;
|
|
||||||
case Conversation.Smp.STATUS_VERIFIED:
|
|
||||||
this.mSharedSecretHintEditable.setText("");
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretSecret.setText("");
|
|
||||||
this.mSharedSecretSecret.setVisibility(View.GONE);
|
|
||||||
this.mStatusMessage.setVisibility(View.VISIBLE);
|
|
||||||
this.deactivateButton(this.mLeftButton, R.string.cancel);
|
|
||||||
this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
this.mStatusMessage.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
|
|
||||||
this.mSharedSecretSecret.setVisibility(View.VISIBLE);
|
|
||||||
this.activateButton(this.mLeftButton, R.string.cancel, this.mFinishListener);
|
|
||||||
this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void updateViewAnswerQuestion() {
|
|
||||||
this.mManualVerificationArea.setVisibility(View.GONE);
|
|
||||||
this.mSmpVerificationArea.setVisibility(View.VISIBLE);
|
|
||||||
this.mVerificationExplain.setText(R.string.smp_explain_answer);
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHint.setVisibility(View.VISIBLE);
|
|
||||||
this.deactivateButton(this.mLeftButton, R.string.cancel);
|
|
||||||
final int smpStatus = this.mConversation.smp().status;
|
|
||||||
switch (smpStatus) {
|
|
||||||
case Conversation.Smp.STATUS_CONTACT_REQUESTED:
|
|
||||||
this.mStatusMessage.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHint.setText(this.mConversation.smp().hint);
|
|
||||||
this.activateButton(this.mRightButton, R.string.respond, this.mRespondSharedSecretListener);
|
|
||||||
break;
|
|
||||||
case Conversation.Smp.STATUS_VERIFIED:
|
|
||||||
this.mSharedSecretHintEditable.setText("");
|
|
||||||
this.mSharedSecretHintEditable.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretHint.setVisibility(View.GONE);
|
|
||||||
this.mSharedSecretSecret.setText("");
|
|
||||||
this.mSharedSecretSecret.setVisibility(View.GONE);
|
|
||||||
this.mStatusMessage.setVisibility(View.VISIBLE);
|
|
||||||
this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
|
|
||||||
break;
|
|
||||||
case Conversation.Smp.STATUS_FAILED:
|
|
||||||
default:
|
|
||||||
this.mSharedSecretSecret.requestFocus();
|
|
||||||
this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
|
|
||||||
this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void activateButton(Button button, int text, View.OnClickListener listener) {
|
|
||||||
button.setEnabled(true);
|
|
||||||
button.setText(text);
|
|
||||||
button.setOnClickListener(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void deactivateButton(Button button, int text) {
|
|
||||||
button.setEnabled(false);
|
|
||||||
button.setText(text);
|
|
||||||
button.setOnClickListener(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_verify_otr);
|
|
||||||
this.mRemoteFingerprint = findViewById(R.id.remote_fingerprint);
|
|
||||||
this.mYourFingerprint = findViewById(R.id.your_fingerprint);
|
|
||||||
this.mLeftButton = findViewById(R.id.left_button);
|
|
||||||
this.mRightButton = findViewById(R.id.right_button);
|
|
||||||
this.mVerificationExplain = findViewById(R.id.verification_explanation);
|
|
||||||
this.mStatusMessage = findViewById(R.id.status_message);
|
|
||||||
this.mSharedSecretSecret = findViewById(R.id.shared_secret_secret);
|
|
||||||
this.mSharedSecretHintEditable = findViewById(R.id.shared_secret_hint_editable);
|
|
||||||
this.mSharedSecretHint = findViewById(R.id.shared_secret_hint);
|
|
||||||
this.mManualVerificationArea = findViewById(R.id.manual_verification_area);
|
|
||||||
this.mSmpVerificationArea = findViewById(R.id.smp_verification_area);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
|
||||||
super.onCreateOptionsMenu(menu);
|
|
||||||
getMenuInflater().inflate(R.menu.verify_otr, menu);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void showManuallyVerifyDialog() {
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
builder.setTitle(R.string.manually_verify);
|
|
||||||
builder.setMessage(R.string.are_you_sure_verify_fingerprint);
|
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
|
||||||
builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected String getShareableUri() {
|
|
||||||
if (mAccount != null) {
|
|
||||||
return mAccount.getShareableUri();
|
|
||||||
} else {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void onConversationUpdate() {
|
|
||||||
refreshUi();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void refreshUiReal() {
|
|
||||||
updateView();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,9 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.Config.DISALLOW_REGISTRATION_IN_UI;
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
||||||
|
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ActivityNotFoundException;
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -8,12 +12,10 @@ import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.security.KeyChain;
|
import android.security.KeyChain;
|
||||||
import android.security.KeyChainAliasCallback;
|
import android.security.KeyChainAliasCallback;
|
||||||
import android.text.method.LinkMovementMethod;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.ActionBar;
|
import androidx.appcompat.app.ActionBar;
|
||||||
|
@ -37,13 +39,8 @@ import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
import static eu.siacs.conversations.Config.DISALLOW_REGISTRATION_IN_UI;
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.readGranted;
|
|
||||||
|
|
||||||
public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback {
|
public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback {
|
||||||
|
|
||||||
|
|
||||||
private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
|
private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
|
||||||
private static final int REQUEST_READ_EXTERNAL_STORAGE = 0XD737;
|
private static final int REQUEST_READ_EXTERNAL_STORAGE = 0XD737;
|
||||||
|
|
||||||
|
@ -106,7 +103,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(Intent intent) {
|
public void onNewIntent(final Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
|
@ -232,6 +229,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||||
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
|
UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
|
||||||
if (grantResults.length > 0) {
|
if (grantResults.length > 0) {
|
||||||
if (allGranted(grantResults)) {
|
if (allGranted(grantResults)) {
|
||||||
|
|
|
@ -1,13 +1,9 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.Manifest;
|
import static eu.siacs.conversations.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
|
||||||
import android.util.Pair;
|
import static eu.siacs.conversations.ui.SettingsActivity.USE_INTERNAL_UPDATER;
|
||||||
import net.java.otr4j.session.SessionID;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
@ -91,6 +87,7 @@ import eu.siacs.conversations.services.AvatarService;
|
||||||
import eu.siacs.conversations.services.BarcodeProvider;
|
import eu.siacs.conversations.services.BarcodeProvider;
|
||||||
import eu.siacs.conversations.services.EmojiService;
|
import eu.siacs.conversations.services.EmojiService;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
|
import eu.siacs.conversations.services.UpdateService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
|
@ -109,10 +106,6 @@ import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
import pl.droidsonroids.gif.GifDrawable;
|
import pl.droidsonroids.gif.GifDrawable;
|
||||||
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.ENABLE_OTR_ENCRYPTION;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.USE_BUNDLED_EMOJIS;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.USE_INTERNAL_UPDATER;
|
|
||||||
|
|
||||||
public abstract class XmppActivity extends ActionBarActivity {
|
public abstract class XmppActivity extends ActionBarActivity {
|
||||||
|
|
||||||
protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
|
protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
|
||||||
|
@ -426,17 +419,7 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
|
|
||||||
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
|
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
|
||||||
final Contact contact = conversation.getContact();
|
final Contact contact = conversation.getContact();
|
||||||
if (conversation.hasValidOtrSession()) {
|
if (contact.showInRoster() || contact.isSelf()) {
|
||||||
SessionID id = conversation.getOtrSession().getSessionID();
|
|
||||||
Jid jid;
|
|
||||||
try {
|
|
||||||
jid = Jid.of(id.getAccountID() + "/" + id.getUserID());
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
jid = null;
|
|
||||||
}
|
|
||||||
conversation.setNextCounterpart(jid);
|
|
||||||
listener.onPresenceSelected();
|
|
||||||
} else if (contact.showInRoster() || contact.isSelf()) {
|
|
||||||
final Presences presences = contact.getPresences();
|
final Presences presences = contact.getPresences();
|
||||||
if (presences.size() == 0) {
|
if (presences.size() == 0) {
|
||||||
if (contact.isSelf()) {
|
if (contact.isSelf()) {
|
||||||
|
@ -507,8 +490,8 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
return getBooleanPreference("unicolored_chatbg", R.bool.use_unicolored_chatbg) || getPreferences().getString(SettingsActivity.THEME, getString(R.string.theme)).equals("black");
|
return getBooleanPreference("unicolored_chatbg", R.bool.use_unicolored_chatbg) || getPreferences().getString(SettingsActivity.THEME, getString(R.string.theme)).equals("black");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean enableOTR() {
|
public boolean showDateInQuotes() {
|
||||||
return getBooleanPreference(ENABLE_OTR_ENCRYPTION, R.bool.enable_otr);
|
return getBooleanPreference("show_date_in_quotes", R.bool.show_date_in_quotes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBubbleColor(final View v, final int backgroundColor, final int borderColor) {
|
public void setBubbleColor(final View v, final int backgroundColor, final int borderColor) {
|
||||||
|
@ -788,7 +771,10 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
builder.setTitle(contact.getJid().toString());
|
builder.setTitle(contact.getJid().toString());
|
||||||
builder.setMessage(getString(R.string.not_in_roster));
|
builder.setMessage(getString(R.string.not_in_roster));
|
||||||
builder.setNegativeButton(getString(R.string.cancel), null);
|
builder.setNegativeButton(getString(R.string.cancel), null);
|
||||||
builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> xmppConnectionService.createContact(contact, true));
|
builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> {
|
||||||
|
xmppConnectionService.createContact(contact, true);
|
||||||
|
recreate();
|
||||||
|
});
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -923,56 +909,6 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showPresenceSelectionDialog(Presences presences, final Conversation conversation, final OnPresenceSelected listener) {
|
|
||||||
final Contact contact = conversation.getContact();
|
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
|
||||||
builder.setTitle(getString(R.string.choose_presence));
|
|
||||||
final String[] resourceArray = presences.toResourceArray();
|
|
||||||
Pair<Map<String, String>, Map<String, String>> typeAndName = presences.toTypeAndNameMap();
|
|
||||||
final Map<String, String> resourceTypeMap = typeAndName.first;
|
|
||||||
final Map<String, String> resourceNameMap = typeAndName.second;
|
|
||||||
final String[] readableIdentities = new String[resourceArray.length];
|
|
||||||
final AtomicInteger selectedResource = new AtomicInteger(0);
|
|
||||||
for (int i = 0; i < resourceArray.length; ++i) {
|
|
||||||
String resource = resourceArray[i];
|
|
||||||
if (resource.equals(contact.getLastResource())) {
|
|
||||||
selectedResource.set(i);
|
|
||||||
}
|
|
||||||
String type = resourceTypeMap.get(resource);
|
|
||||||
String name = resourceNameMap.get(resource);
|
|
||||||
if (type != null) {
|
|
||||||
if (Collections.frequency(resourceTypeMap.values(), type) == 1) {
|
|
||||||
readableIdentities[i] = PresenceSelector.translateType(this, type);
|
|
||||||
} else if (name != null) {
|
|
||||||
if (Collections.frequency(resourceNameMap.values(), name) == 1
|
|
||||||
|| CryptoHelper.UUID_PATTERN.matcher(resource).matches()) {
|
|
||||||
readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + name + ")";
|
|
||||||
} else {
|
|
||||||
readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + name + " / " + resource + ")";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
readableIdentities[i] = PresenceSelector.translateType(this, type) + " (" + resource + ")";
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
readableIdentities[i] = resource;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
builder.setSingleChoiceItems(readableIdentities,
|
|
||||||
selectedResource.get(),
|
|
||||||
(dialog, which) -> selectedResource.set(which));
|
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
|
||||||
builder.setPositiveButton(R.string.ok, (dialog, which) -> {
|
|
||||||
try {
|
|
||||||
Jid next = Jid.of(contact.getJid().getLocal(), contact.getJid().getDomain(), resourceArray[selectedResource.get()]);
|
|
||||||
conversation.setNextCounterpart(next);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
conversation.setNextCounterpart(null);
|
|
||||||
}
|
|
||||||
listener.onPresenceSelected();
|
|
||||||
});
|
|
||||||
builder.create().show();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
|
||||||
super.onActivityResult(requestCode, resultCode, data);
|
super.onActivityResult(requestCode, resultCode, data);
|
||||||
if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
|
if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
|
||||||
|
@ -1036,14 +972,11 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
ToastCompat.makeText(this, R.string.no_accounts, ToastCompat.LENGTH_SHORT).show();
|
ToastCompat.makeText(this, R.string.no_accounts, ToastCompat.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!selectAccountToStartEasyInvite()) {
|
||||||
if (!xmppConnectionService.multipleAccounts()) {
|
if (!xmppConnectionService.multipleAccounts()) {
|
||||||
Account mAccount = xmppConnectionService.getAccounts().get(0);
|
final Account mAccount = xmppConnectionService.getAccounts().get(0);
|
||||||
if (EasyOnboardingInvite.hasAccountSupport(mAccount)) {
|
final String user = Jid.ofEscaped(mAccount.getJid()).getLocal();
|
||||||
selectAccountToStartEasyInvite();
|
final String domain = Jid.ofEscaped(mAccount.getJid()).getDomain().toEscapedString();
|
||||||
} else {
|
|
||||||
String user = Jid.ofEscaped(mAccount.getJid()).getLocal();
|
|
||||||
String domain = Jid.ofEscaped(mAccount.getJid()).getDomain().toEscapedString();
|
|
||||||
String inviteURL;
|
String inviteURL;
|
||||||
try {
|
try {
|
||||||
inviteURL = new getAdHocInviteUri(mAccount.getXmppConnection(), mAccount).execute().get();
|
inviteURL = new getAdHocInviteUri(mAccount.getXmppConnection(), mAccount).execute().get();
|
||||||
|
@ -1058,14 +991,13 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
inviteURL = Config.inviteUserURL + user + "/" + domain;
|
inviteURL = Config.inviteUserURL + user + "/" + domain;
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "Invite uri = " + inviteURL);
|
Log.d(Config.LOGTAG, "Invite uri = " + inviteURL);
|
||||||
String inviteText = getString(R.string.InviteText, user);
|
final String inviteText = getString(R.string.InviteText, user);
|
||||||
Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
final Intent intent = new Intent(android.content.Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name));
|
intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name));
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL);
|
intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL);
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.invite_contact)));
|
startActivity(Intent.createChooser(intent, getString(R.string.invite_contact)));
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
builder.setTitle(R.string.chooce_account);
|
builder.setTitle(R.string.chooce_account);
|
||||||
|
@ -1085,13 +1017,10 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
StartConversationActivity.populateAccountSpinner(this, mActivatedAccounts, spinner);
|
StartConversationActivity.populateAccountSpinner(this, mActivatedAccounts, spinner);
|
||||||
builder.setPositiveButton(R.string.ok,
|
builder.setPositiveButton(R.string.ok,
|
||||||
(dialog, id) -> {
|
(dialog, id) -> {
|
||||||
String selection = spinner.getSelectedItem().toString();
|
final String selection = spinner.getSelectedItem().toString();
|
||||||
Account mAccount = xmppConnectionService.findAccountByJid(Jid.of(selection).asBareJid());
|
final Account mAccount = xmppConnectionService.findAccountByJid(Jid.of(selection).asBareJid());
|
||||||
if (EasyOnboardingInvite.hasAccountSupport(mAccount)) {
|
final String user = Jid.of(mAccount.getJid()).getLocal();
|
||||||
selectAccountToStartEasyInvite();
|
final String domain = Jid.of(mAccount.getJid()).getDomain().toEscapedString();
|
||||||
} else {
|
|
||||||
String user = Jid.of(mAccount.getJid()).getLocal();
|
|
||||||
String domain = Jid.of(mAccount.getJid()).getDomain().toEscapedString();
|
|
||||||
String inviteURL;
|
String inviteURL;
|
||||||
try {
|
try {
|
||||||
inviteURL = new getAdHocInviteUri(mAccount.getXmppConnection(), mAccount).execute().get();
|
inviteURL = new getAdHocInviteUri(mAccount.getXmppConnection(), mAccount).execute().get();
|
||||||
|
@ -1106,30 +1035,31 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
inviteURL = Config.inviteUserURL + user + "/" + domain;
|
inviteURL = Config.inviteUserURL + user + "/" + domain;
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "Invite uri = " + inviteURL);
|
Log.d(Config.LOGTAG, "Invite uri = " + inviteURL);
|
||||||
String inviteText = getString(R.string.InviteText, user);
|
final String inviteText = getString(R.string.InviteText, user);
|
||||||
Intent intent = new Intent(Intent.ACTION_SEND);
|
final Intent intent = new Intent(Intent.ACTION_SEND);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name));
|
intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name));
|
||||||
intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL);
|
intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL);
|
||||||
startActivity(Intent.createChooser(intent, getString(R.string.invite_contact)));
|
startActivity(Intent.createChooser(intent, getString(R.string.invite_contact)));
|
||||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
builder.create().show();
|
builder.create().show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void selectAccountToStartEasyInvite() {
|
private boolean selectAccountToStartEasyInvite() {
|
||||||
final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(this.xmppConnectionService);
|
final List<Account> accounts = EasyOnboardingInvite.getSupportingAccounts(this.xmppConnectionService);
|
||||||
if (accounts.size() == 0) {
|
if (accounts.size() == 0) {
|
||||||
//This can technically happen if opening the menu item races with accounts reconnecting or something
|
//This can technically happen if opening the menu item races with accounts reconnecting or something
|
||||||
ToastCompat.makeText(this, R.string.no_active_accounts_support_this, ToastCompat.LENGTH_LONG).show();
|
ToastCompat.makeText(this, R.string.no_active_accounts_support_this, ToastCompat.LENGTH_LONG).show();
|
||||||
|
return false;
|
||||||
} else if (accounts.size() == 1) {
|
} else if (accounts.size() == 1) {
|
||||||
openEasyInviteScreen(accounts.get(0));
|
openEasyInviteScreen(accounts.get(0));
|
||||||
} else {
|
} else {
|
||||||
final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0));
|
final AtomicReference<Account> selectedAccount = new AtomicReference<>(accounts.get(0));
|
||||||
final android.app.AlertDialog.Builder alertDialogBuilder = new android.app.AlertDialog.Builder(this);
|
final androidx.appcompat.app.AlertDialog.Builder alertDialogBuilder = new androidx.appcompat.app.AlertDialog.Builder(this);
|
||||||
alertDialogBuilder.setTitle(R.string.choose_account);
|
alertDialogBuilder.setTitle(R.string.choose_account);
|
||||||
final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]);
|
final String[] asStrings = Collections2.transform(accounts, a -> a.getJid().asBareJid().toEscapedString()).toArray(new String[0]);
|
||||||
alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which)));
|
alertDialogBuilder.setSingleChoiceItems(asStrings, 0, (dialog, which) -> selectedAccount.set(accounts.get(which)));
|
||||||
|
@ -1137,6 +1067,7 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get()));
|
alertDialogBuilder.setPositiveButton(R.string.ok, (dialog, which) -> openEasyInviteScreen(selectedAccount.get()));
|
||||||
alertDialogBuilder.create().show();
|
alertDialogBuilder.create().show();
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openEasyInviteScreen(final Account account) {
|
private void openEasyInviteScreen(final Account account) {
|
||||||
|
@ -1457,7 +1388,44 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
return installFromUnknownSource;
|
return installFromUnknownSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void openInstallFromUnknownSourcesDialogIfNeeded(boolean showToast) {
|
||||||
|
String ShowToast;
|
||||||
|
if (showToast == true) {
|
||||||
|
ShowToast = "true";
|
||||||
|
} else {
|
||||||
|
ShowToast = "false";
|
||||||
|
}
|
||||||
|
if (!installFromUnknownSourceAllowed() && xmppConnectionService.installedFrom() == null) {
|
||||||
|
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||||
|
builder.setTitle(R.string.install_from_unknown_sources_disabled);
|
||||||
|
builder.setMessage(R.string.install_from_unknown_sources_disabled_dialog);
|
||||||
|
builder.setPositiveButton(R.string.next, (dialog, which) -> {
|
||||||
|
Intent intent;
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= 26) {
|
||||||
|
intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
|
||||||
|
Uri uri = Uri.parse("package:" + getPackageName());
|
||||||
|
intent.setData(uri);
|
||||||
|
} else {
|
||||||
|
intent = new Intent(Settings.ACTION_SECURITY_SETTINGS);
|
||||||
|
}
|
||||||
|
Log.d(Config.LOGTAG, "Allow install from unknown sources for Android SDK " + Build.VERSION.SDK_INT + " intent " + intent.toString());
|
||||||
|
try {
|
||||||
|
startActivityForResult(intent, REQUEST_UNKNOWN_SOURCE_OP);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
ToastCompat.makeText(XmppActivity.this, R.string.device_does_not_support_unknown_source_op, ToastCompat.LENGTH_SHORT).show();
|
||||||
|
} finally {
|
||||||
|
UpdateService task = new UpdateService(this, xmppConnectionService.installedFrom(), xmppConnectionService);
|
||||||
|
task.executeOnExecutor(UpdateService.THREAD_POOL_EXECUTOR, ShowToast);
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater started");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
builder.create().show();
|
||||||
|
} else {
|
||||||
|
UpdateService task = new UpdateService(this, xmppConnectionService.installedFrom(), xmppConnectionService);
|
||||||
|
task.executeOnExecutor(UpdateService.THREAD_POOL_EXECUTOR, ShowToast);
|
||||||
|
Log.d(Config.LOGTAG, "AppUpdater started");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ShowAvatarPopup(final Activity activity, final AvatarService.Avatarable user) {
|
public void ShowAvatarPopup(final Activity activity, final AvatarService.Avatarable user) {
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
|
||||||
|
|
|
@ -1,10 +1,13 @@
|
||||||
package eu.siacs.conversations.ui.adapter;
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
||||||
|
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.text.Spannable;
|
import android.text.Spannable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.style.StyleSpan;
|
import android.text.style.StyleSpan;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
|
@ -32,15 +35,12 @@ import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
import eu.siacs.conversations.utils.EmojiWrapper;
|
||||||
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
||||||
|
import eu.siacs.conversations.utils.StylingHelper;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||||
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
|
||||||
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
|
||||||
|
|
||||||
public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
|
public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapter.ConversationViewHolder> {
|
||||||
|
|
||||||
private static final float INACTIVE_ALPHA = 0.4684f;
|
private static final float INACTIVE_ALPHA = 0.4684f;
|
||||||
|
@ -162,12 +162,12 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
|
||||||
}
|
}
|
||||||
final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
|
final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
|
||||||
if (showPreviewText) {
|
if (showPreviewText) {
|
||||||
if (message.getBody().equals(DELETED_MESSAGE_BODY)) {
|
if (message.hasDeletedBody()) {
|
||||||
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(activity.getString(R.string.message_deleted))));
|
|
||||||
} else if (message.getBody().equals(DELETED_MESSAGE_BODY_OLD)) {
|
|
||||||
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(activity.getString(R.string.message_deleted))));
|
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(activity.getString(R.string.message_deleted))));
|
||||||
} else {
|
} else {
|
||||||
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(replaceYoutube(activity.getApplicationContext(), preview.first.toString()))));
|
SpannableStringBuilder body = new SpannableStringBuilder(replaceYoutube(activity.getApplicationContext(), preview.first.toString()));
|
||||||
|
StylingHelper.format(body, viewHolder.binding.conversationLastmsg.getCurrentTextColor(), true);
|
||||||
|
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(body)));
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
|
viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
|
||||||
|
|
|
@ -104,8 +104,6 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
||||||
attr = R.attr.media_preview_document;
|
attr = R.attr.media_preview_document;
|
||||||
} else if (mime.equals("application/gpx+xml")) {
|
} else if (mime.equals("application/gpx+xml")) {
|
||||||
attr = R.attr.media_preview_tour;
|
attr = R.attr.media_preview_tour;
|
||||||
} else if (mime.startsWith("image/")) {
|
|
||||||
attr = R.attr.media_preview_image;
|
|
||||||
} else {
|
} else {
|
||||||
attr = R.attr.media_preview_unknown;
|
attr = R.attr.media_preview_unknown;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package eu.siacs.conversations.ui.adapter;
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
@ -21,6 +25,7 @@ import java.util.concurrent.RejectedExecutionException;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.MediaPreviewBinding;
|
import eu.siacs.conversations.databinding.MediaPreviewBinding;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.ui.ConversationFragment;
|
import eu.siacs.conversations.ui.ConversationFragment;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
import eu.siacs.conversations.ui.util.Attachment;
|
import eu.siacs.conversations.ui.util.Attachment;
|
||||||
|
@ -89,6 +94,21 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
|
||||||
notifyItemRemoved(pos);
|
notifyItemRemoved(pos);
|
||||||
conversationFragment.toggleInputMethod();
|
conversationFragment.toggleInputMethod();
|
||||||
});
|
});
|
||||||
|
holder.binding.mediaPreview.setOnClickListener(v -> view(context, attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void view(final Context context, Attachment attachment) {
|
||||||
|
final Intent view = new Intent(Intent.ACTION_VIEW);
|
||||||
|
final Uri uri = FileBackend.getUriForUri(context, attachment.getUri());
|
||||||
|
view.setDataAndType(uri, attachment.getMime());
|
||||||
|
view.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||||
|
try {
|
||||||
|
context.startActivity(view);
|
||||||
|
} catch (ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
|
||||||
|
} catch (final SecurityException e) {
|
||||||
|
Toast.makeText(context, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addMediaPreviews(List<Attachment> attachments) {
|
public void addMediaPreviews(List<Attachment> attachments) {
|
||||||
|
|
|
@ -1,11 +1,24 @@
|
||||||
package eu.siacs.conversations.ui.adapter;
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
|
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||||
|
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
||||||
|
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.formatTime;
|
||||||
|
import static eu.siacs.conversations.persistance.FileBackend.safeLongToInt;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.PLAY_GIF_INSIDE;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_LINKS_INSIDE;
|
||||||
|
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_MAPS_INSIDE;
|
||||||
|
import static eu.siacs.conversations.ui.util.MyLinkify.removeTrackingParameter;
|
||||||
|
import static eu.siacs.conversations.ui.util.MyLinkify.removeTrailingBracket;
|
||||||
|
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Rect;
|
import android.graphics.Rect;
|
||||||
import android.graphics.Typeface;
|
import android.graphics.Typeface;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
|
@ -36,7 +49,6 @@ import android.widget.TextView;
|
||||||
|
|
||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.core.graphics.ColorUtils;
|
|
||||||
import androidx.core.graphics.drawable.DrawableCompat;
|
import androidx.core.graphics.drawable.DrawableCompat;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
@ -71,6 +83,7 @@ import eu.siacs.conversations.ui.text.DividerSpan;
|
||||||
import eu.siacs.conversations.ui.text.QuoteSpan;
|
import eu.siacs.conversations.ui.text.QuoteSpan;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.MyLinkify;
|
import eu.siacs.conversations.ui.util.MyLinkify;
|
||||||
|
import eu.siacs.conversations.ui.util.QuoteHelper;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
import eu.siacs.conversations.ui.util.ViewUtil;
|
import eu.siacs.conversations.ui.util.ViewUtil;
|
||||||
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
|
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
|
||||||
|
@ -90,18 +103,6 @@ import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
import pl.droidsonroids.gif.GifImageView;
|
import pl.droidsonroids.gif.GifImageView;
|
||||||
|
|
||||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
|
||||||
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.formatTime;
|
|
||||||
import static eu.siacs.conversations.persistance.FileBackend.safeLongToInt;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.PLAY_GIF_INSIDE;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_LINKS_INSIDE;
|
|
||||||
import static eu.siacs.conversations.ui.SettingsActivity.SHOW_MAPS_INSIDE;
|
|
||||||
import static eu.siacs.conversations.ui.util.MyLinkify.removeTrackingParameter;
|
|
||||||
import static eu.siacs.conversations.ui.util.MyLinkify.removeTrailingBracket;
|
|
||||||
import static eu.siacs.conversations.ui.util.MyLinkify.replaceYoutube;
|
|
||||||
|
|
||||||
public class MessageAdapter extends ArrayAdapter<Message> {
|
public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
|
|
||||||
public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
|
public static final String DATE_SEPARATOR_BODY = "DATE_SEPARATOR";
|
||||||
|
@ -209,7 +210,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.indicatorReceived.setVisibility(View.GONE);
|
viewHolder.indicatorReceived.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if (viewHolder.edit_indicator != null) {
|
if (viewHolder.edit_indicator != null) {
|
||||||
if (message.edited()) {
|
if (message.edited() && message.getRetractId() == null) {
|
||||||
viewHolder.edit_indicator.setVisibility(View.VISIBLE);
|
viewHolder.edit_indicator.setVisibility(View.VISIBLE);
|
||||||
viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp);
|
viewHolder.edit_indicator.setImageResource(darkBackground ? R.drawable.ic_mode_edit_white_18dp : R.drawable.ic_mode_edit_black_18dp);
|
||||||
viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
|
viewHolder.edit_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
|
||||||
|
@ -217,6 +218,15 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.edit_indicator.setVisibility(View.GONE);
|
viewHolder.edit_indicator.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (viewHolder.retract_indicator != null) {
|
||||||
|
if (message.getRetractId() != null) {
|
||||||
|
viewHolder.retract_indicator.setVisibility(View.VISIBLE);
|
||||||
|
viewHolder.retract_indicator.setImageResource(darkBackground ? R.drawable.ic_delete_white_18dp : R.drawable.ic_delete_black_18dp);
|
||||||
|
viewHolder.retract_indicator.setAlpha(darkBackground ? 0.7f : 0.57f);
|
||||||
|
} else {
|
||||||
|
viewHolder.retract_indicator.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
final Transferable transferable = message.getTransferable();
|
final Transferable transferable = message.getTransferable();
|
||||||
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
|
||||||
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
&& message.getMergedStatus() <= Message.STATUS_RECEIVED;
|
||||||
|
@ -236,7 +246,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
case Message.STATUS_UNSEND:
|
case Message.STATUS_UNSEND:
|
||||||
if (transferable != null) {
|
if (transferable != null) {
|
||||||
info = getContext().getString(R.string.sending);
|
info = getContext().getString(R.string.sending);
|
||||||
showProgress(viewHolder, transferable);
|
showProgress(viewHolder, transferable, message);
|
||||||
} else {
|
} else {
|
||||||
info = getContext().getString(R.string.sending);
|
info = getContext().getString(R.string.sending);
|
||||||
}
|
}
|
||||||
|
@ -405,10 +415,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
viewHolder.messageBody.setText(text);
|
viewHolder.messageBody.setText(text);
|
||||||
showProgress(viewHolder, message.getTransferable());
|
showProgress(viewHolder, message.getTransferable(), message);
|
||||||
if (darkBackground) {
|
if (darkBackground) {
|
||||||
viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary_OnDark);
|
viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Secondary_OnDark);
|
||||||
} else {
|
} else {
|
||||||
|
@ -417,16 +427,35 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.messageBody.setTextIsSelectable(false);
|
viewHolder.messageBody.setTextIsSelectable(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showProgress(final ViewHolder viewHolder, final Transferable transferable) {
|
private void showProgress(final ViewHolder viewHolder, final Transferable transferable, final Message message) {
|
||||||
if (transferable != null) {
|
if (transferable != null) {
|
||||||
if (transferable.getStatus() == Transferable.STATUS_DOWNLOADING || transferable.getStatus() == Transferable.STATUS_UPLOADING || transferable.getStatus() == Transferable.STATUS_WAITING) {
|
if (message.fileIsTransferring()) {
|
||||||
viewHolder.progressBar.setVisibility(View.VISIBLE);
|
viewHolder.transfer.setVisibility(View.VISIBLE);
|
||||||
viewHolder.progressBar.setProgress(transferable.getProgress());
|
viewHolder.progressBar.setProgress(transferable.getProgress());
|
||||||
|
Drawable icon = activity.getResources().getDrawable(R.drawable.ic_cancel_black_24dp);
|
||||||
|
Drawable drawable = DrawableCompat.wrap(icon);
|
||||||
|
DrawableCompat.setTint(drawable, StyledAttributes.getColor(getContext(), R.attr.colorAccent));
|
||||||
|
viewHolder.cancel_transfer.setImageDrawable(drawable);
|
||||||
|
viewHolder.cancel_transfer.setEnabled(true);
|
||||||
|
viewHolder.cancel_transfer.setOnClickListener(v -> {
|
||||||
|
try {
|
||||||
|
if (activity instanceof ConversationsActivity) {
|
||||||
|
ConversationFragment conversationFragment = ConversationFragment.get(activity);
|
||||||
|
if (conversationFragment != null) {
|
||||||
|
activity.invalidateOptionsMenu();
|
||||||
|
conversationFragment.cancelTransmission(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
viewHolder.cancel_transfer.setEnabled(false);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +464,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
if (darkBackground) {
|
if (darkBackground) {
|
||||||
viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji_OnDark);
|
viewHolder.messageBody.setTextAppearance(getContext(), R.style.TextAppearance_Conversations_Body1_Emoji_OnDark);
|
||||||
|
@ -486,7 +515,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
});
|
});
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.GONE);
|
viewHolder.messageBody.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -511,6 +540,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
*/
|
*/
|
||||||
private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) {
|
private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) {
|
||||||
boolean startsWithQuote = false;
|
boolean startsWithQuote = false;
|
||||||
|
int quoteDepth = 1;
|
||||||
|
while (QuoteHelper.bodyContainsQuoteStart(body) && quoteDepth <= Config.QUOTE_MAX_DEPTH) {
|
||||||
char previous = '\n';
|
char previous = '\n';
|
||||||
int lineStart = -1;
|
int lineStart = -1;
|
||||||
int lineTextStart = -1;
|
int lineTextStart = -1;
|
||||||
|
@ -519,8 +550,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
char current = body.length() > i ? body.charAt(i) : '\n';
|
char current = body.length() > i ? body.charAt(i) : '\n';
|
||||||
if (lineStart == -1) {
|
if (lineStart == -1) {
|
||||||
if (previous == '\n') {
|
if (previous == '\n') {
|
||||||
if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i))
|
if (i < body.length() && QuoteHelper.isPositionQuoteStart(body, i)) {
|
||||||
|| current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) {
|
|
||||||
// Line start with quote
|
// Line start with quote
|
||||||
lineStart = i;
|
lineStart = i;
|
||||||
if (quoteStart == -1) quoteStart = i;
|
if (quoteStart == -1) quoteStart = i;
|
||||||
|
@ -554,6 +584,8 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
// Apply spans to finishing open quote
|
// Apply spans to finishing open quote
|
||||||
applyQuoteSpan(body, quoteStart, body.length(), darkBackground);
|
applyQuoteSpan(body, quoteStart, body.length(), darkBackground);
|
||||||
}
|
}
|
||||||
|
quoteDepth++;
|
||||||
|
}
|
||||||
return startsWithQuote;
|
return startsWithQuote;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,7 +593,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
viewHolder.messageBody.setVisibility(View.VISIBLE);
|
||||||
if (darkBackground) {
|
if (darkBackground) {
|
||||||
|
@ -636,7 +668,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
body.setSpan(new RelativeSizeSpan(1.5f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
body.setSpan(new RelativeSizeSpan(1.5f), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor());
|
StylingHelper.format(body, viewHolder.messageBody.getCurrentTextColor(), true);
|
||||||
if (highlightedTerm != null) {
|
if (highlightedTerm != null) {
|
||||||
StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody));
|
StylingHelper.highlight(activity, body, highlightedTerm, StylingHelper.isDarkText(viewHolder.messageBody));
|
||||||
}
|
}
|
||||||
|
@ -659,7 +691,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.download_button.setVisibility(View.VISIBLE);
|
viewHolder.download_button.setVisibility(View.VISIBLE);
|
||||||
viewHolder.download_button.setText(text);
|
viewHolder.download_button.setText(text);
|
||||||
final Drawable icon = activity.getResources().getDrawable(R.drawable.ic_download_grey600_48dp);
|
final Drawable icon = activity.getResources().getDrawable(R.drawable.ic_download_grey600_48dp);
|
||||||
|
@ -675,7 +707,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
final String mimeType = message.getMimeType();
|
final String mimeType = message.getMimeType();
|
||||||
if (mimeType != null && message.getMimeType().contains("vcard")) {
|
if (mimeType != null && message.getMimeType().contains("vcard")) {
|
||||||
try {
|
try {
|
||||||
|
@ -764,7 +796,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
String url;
|
String url;
|
||||||
if (message.isWebUri()) {
|
if (message.isWebUri()) {
|
||||||
url = removeTrackingParameter(Uri.parse(message.getBody().trim())).toString();
|
url = removeTrackingParameter(Uri.parse(message.getBody().trim())).toString();
|
||||||
|
@ -786,9 +818,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
} else {
|
} else {
|
||||||
scaledH = (int) (100 / ((double) 100 / target));
|
scaledH = (int) (100 / ((double) 100 / target));
|
||||||
}
|
}
|
||||||
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, scaledH);
|
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
|
||||||
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
|
layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
|
||||||
viewHolder.richlinkview.setLayoutParams(layoutParams);
|
viewHolder.richlinkview.setLayoutParams(layoutParams);
|
||||||
|
viewHolder.richlinkview.setMinimumHeight(scaledH);
|
||||||
final String weburl;
|
final String weburl;
|
||||||
if (link.startsWith("http://") || link.startsWith("https://")) {
|
if (link.startsWith("http://") || link.startsWith("https://")) {
|
||||||
weburl = removeTrailingBracket(link);
|
weburl = removeTrailingBracket(link);
|
||||||
|
@ -817,7 +850,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
final String url = GeoHelper.MapPreviewUri(message, activity);
|
final String url = GeoHelper.MapPreviewUri(message, activity);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
if (mShowMapsInside) {
|
if (mShowMapsInside) {
|
||||||
showImages(mShowMapsInside, 0, false, viewHolder);
|
showImages(mShowMapsInside, 0, false, viewHolder);
|
||||||
final double target = activity.getResources().getDimension(R.dimen.image_preview_width);
|
final double target = activity.getResources().getDimension(R.dimen.image_preview_width);
|
||||||
|
@ -858,10 +891,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) {
|
private void displayAudioMessage(ViewHolder viewHolder, Message message, boolean darkBackground) {
|
||||||
|
final Resources res = activity.getResources();
|
||||||
|
viewHolder.messageBody.setWidth((int) res.getDimension(R.dimen.audio_player_width));
|
||||||
toggleWhisperInfo(viewHolder, message, showTitle(message), darkBackground);
|
toggleWhisperInfo(viewHolder, message, showTitle(message), darkBackground);
|
||||||
showImages(false, viewHolder);
|
showImages(false, viewHolder);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
final RelativeLayout audioPlayer = viewHolder.audioPlayer;
|
final RelativeLayout audioPlayer = viewHolder.audioPlayer;
|
||||||
audioPlayer.setVisibility(View.VISIBLE);
|
audioPlayer.setVisibility(View.VISIBLE);
|
||||||
|
@ -899,9 +934,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.download_button.setVisibility(View.GONE);
|
viewHolder.download_button.setVisibility(View.GONE);
|
||||||
viewHolder.audioPlayer.setVisibility(View.GONE);
|
viewHolder.audioPlayer.setVisibility(View.GONE);
|
||||||
viewHolder.richlinkview.setVisibility(View.GONE);
|
viewHolder.richlinkview.setVisibility(View.GONE);
|
||||||
viewHolder.progressBar.setVisibility(View.GONE);
|
viewHolder.transfer.setVisibility(View.GONE);
|
||||||
final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
|
||||||
if (!file.exists()) {
|
if (!file.exists() && !message.isFileDeleted()) {
|
||||||
|
markFileDeleted(message);
|
||||||
|
displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted), darkBackground, message);
|
||||||
ToastCompat.makeText(activity, R.string.file_deleted, ToastCompat.LENGTH_SHORT).show();
|
ToastCompat.makeText(activity, R.string.file_deleted, ToastCompat.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1092,6 +1129,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.resend_button = view.findViewById(R.id.resend_button);
|
viewHolder.resend_button = view.findViewById(R.id.resend_button);
|
||||||
viewHolder.indicator = view.findViewById(R.id.security_indicator);
|
viewHolder.indicator = view.findViewById(R.id.security_indicator);
|
||||||
viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator);
|
viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator);
|
||||||
|
viewHolder.retract_indicator = view.findViewById(R.id.retract_indicator);
|
||||||
viewHolder.images = view.findViewById(R.id.images);
|
viewHolder.images = view.findViewById(R.id.images);
|
||||||
viewHolder.mediaduration = view.findViewById(R.id.media_duration);
|
viewHolder.mediaduration = view.findViewById(R.id.media_duration);
|
||||||
viewHolder.image = view.findViewById(R.id.message_image);
|
viewHolder.image = view.findViewById(R.id.message_image);
|
||||||
|
@ -1101,7 +1139,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.user = view.findViewById(R.id.message_user);
|
viewHolder.user = view.findViewById(R.id.message_user);
|
||||||
viewHolder.time = view.findViewById(R.id.message_time);
|
viewHolder.time = view.findViewById(R.id.message_time);
|
||||||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||||
|
viewHolder.transfer = view.findViewById(R.id.transfer);
|
||||||
viewHolder.progressBar = view.findViewById(R.id.progressBar);
|
viewHolder.progressBar = view.findViewById(R.id.progressBar);
|
||||||
|
viewHolder.cancel_transfer = view.findViewById(R.id.cancel_transfer);
|
||||||
break;
|
break;
|
||||||
case RECEIVED:
|
case RECEIVED:
|
||||||
view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false);
|
view = activity.getLayoutInflater().inflate(R.layout.message_received, parent, false);
|
||||||
|
@ -1113,6 +1153,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.answer_button = view.findViewById(R.id.answer);
|
viewHolder.answer_button = view.findViewById(R.id.answer);
|
||||||
viewHolder.indicator = view.findViewById(R.id.security_indicator);
|
viewHolder.indicator = view.findViewById(R.id.security_indicator);
|
||||||
viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator);
|
viewHolder.edit_indicator = view.findViewById(R.id.edit_indicator);
|
||||||
|
viewHolder.retract_indicator = view.findViewById(R.id.retract_indicator);
|
||||||
viewHolder.images = view.findViewById(R.id.images);
|
viewHolder.images = view.findViewById(R.id.images);
|
||||||
viewHolder.mediaduration = view.findViewById(R.id.media_duration);
|
viewHolder.mediaduration = view.findViewById(R.id.media_duration);
|
||||||
viewHolder.image = view.findViewById(R.id.message_image);
|
viewHolder.image = view.findViewById(R.id.message_image);
|
||||||
|
@ -1123,7 +1164,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.time = view.findViewById(R.id.message_time);
|
viewHolder.time = view.findViewById(R.id.message_time);
|
||||||
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
viewHolder.indicatorReceived = view.findViewById(R.id.indicator_received);
|
||||||
viewHolder.encryption = view.findViewById(R.id.message_encryption);
|
viewHolder.encryption = view.findViewById(R.id.message_encryption);
|
||||||
|
viewHolder.transfer = view.findViewById(R.id.transfer);
|
||||||
viewHolder.progressBar = view.findViewById(R.id.progressBar);
|
viewHolder.progressBar = view.findViewById(R.id.progressBar);
|
||||||
|
viewHolder.cancel_transfer = view.findViewById(R.id.cancel_transfer);
|
||||||
break;
|
break;
|
||||||
case STATUS:
|
case STATUS:
|
||||||
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
|
view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
|
||||||
|
@ -1153,9 +1196,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
|
viewHolder.status_message.setText(DateUtils.formatDateTime(activity, message.getTimeSent(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR));
|
||||||
}
|
}
|
||||||
viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.date_bubble_dark : R.drawable.date_bubble);
|
viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.date_bubble_dark : R.drawable.date_bubble);
|
||||||
|
|
||||||
int date_bubble_color = ColorUtils.setAlphaComponent(StyledAttributes.getColor(activity, R.attr.colorAccent), 80); //set alpha to date_bubble
|
|
||||||
activity.setBubbleColor(viewHolder.message_box, (date_bubble_color), -1); // themed color
|
|
||||||
return view;
|
return view;
|
||||||
} else if (type == RTP_SESSION) {
|
} else if (type == RTP_SESSION) {
|
||||||
final boolean isDarkTheme = activity.isDarkTheme();
|
final boolean isDarkTheme = activity.isDarkTheme();
|
||||||
|
@ -1179,9 +1219,6 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
viewHolder.indicatorReceived.setImageResource(RtpSessionStatus.getDrawable(received, rtpSessionStatus.successful, isDarkTheme));
|
viewHolder.indicatorReceived.setImageResource(RtpSessionStatus.getDrawable(received, rtpSessionStatus.successful, isDarkTheme));
|
||||||
viewHolder.indicatorReceived.setAlpha(isDarkTheme ? 0.7f : 0.57f);
|
viewHolder.indicatorReceived.setAlpha(isDarkTheme ? 0.7f : 0.57f);
|
||||||
viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.date_bubble_dark : R.drawable.date_bubble);
|
viewHolder.message_box.setBackgroundResource(darkBackground ? R.drawable.date_bubble_dark : R.drawable.date_bubble);
|
||||||
|
|
||||||
int date_bubble_color = ColorUtils.setAlphaComponent(StyledAttributes.getColor(activity, R.attr.colorAccent), 80); //set alpha to date bubble
|
|
||||||
activity.setBubbleColor(viewHolder.message_box, (date_bubble_color), -1); //themed color
|
|
||||||
return view;
|
return view;
|
||||||
} else if (type == STATUS) {
|
} else if (type == STATUS) {
|
||||||
if ("LOAD_MORE".equals(message.getBody())) {
|
if ("LOAD_MORE".equals(message.getBody())) {
|
||||||
|
@ -1240,6 +1277,10 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
} else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
|
} else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||||
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground);
|
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground);
|
||||||
} else {
|
} else {
|
||||||
|
if (!activity.xmppConnectionService.getFileBackend().getFile(message).exists() && !message.isFileDeleted()) {
|
||||||
|
markFileDeleted(message);
|
||||||
|
displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted), darkBackground, message);
|
||||||
|
}
|
||||||
if (checkFileExistence(message, view, viewHolder)) {
|
if (checkFileExistence(message, view, viewHolder)) {
|
||||||
markFileExisting(message);
|
markFileExisting(message);
|
||||||
} else {
|
} else {
|
||||||
|
@ -1348,10 +1389,21 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void markFileExisting(Message message) {
|
private void markFileExisting(Message message) {
|
||||||
|
new Thread(() -> {
|
||||||
Log.d(Config.LOGTAG, "Found and restored orphaned file " + message.getRelativeFilePath());
|
Log.d(Config.LOGTAG, "Found and restored orphaned file " + message.getRelativeFilePath());
|
||||||
message.setFileDeleted(false);
|
message.setFileDeleted(false);
|
||||||
activity.xmppConnectionService.updateMessage(message, false);
|
activity.xmppConnectionService.updateMessage(message, false);
|
||||||
activity.xmppConnectionService.updateConversation((Conversation) message.getConversation());
|
activity.xmppConnectionService.updateConversation((Conversation) message.getConversation());
|
||||||
|
}).start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void markFileDeleted(Message message) {
|
||||||
|
new Thread(() -> {
|
||||||
|
Log.d(Config.LOGTAG, "Mark file deleted " + message.getRelativeFilePath());
|
||||||
|
message.setFileDeleted(true);
|
||||||
|
activity.xmppConnectionService.updateMessage(message, false);
|
||||||
|
activity.xmppConnectionService.updateConversation((Conversation) message.getConversation());
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkFileExistence(Message message, View view, ViewHolder viewHolder) {
|
private boolean checkFileExistence(Message message, View view, ViewHolder viewHolder) {
|
||||||
|
@ -1436,6 +1488,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
|
|
||||||
public Button load_more_messages;
|
public Button load_more_messages;
|
||||||
public ImageView edit_indicator;
|
public ImageView edit_indicator;
|
||||||
|
public ImageView retract_indicator;
|
||||||
public RelativeLayout audioPlayer;
|
public RelativeLayout audioPlayer;
|
||||||
public RelativeLayout images;
|
public RelativeLayout images;
|
||||||
protected LinearLayout message_box;
|
protected LinearLayout message_box;
|
||||||
|
@ -1455,7 +1508,9 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
protected ImageView contact_picture;
|
protected ImageView contact_picture;
|
||||||
protected TextView status_message;
|
protected TextView status_message;
|
||||||
protected TextView encryption;
|
protected TextView encryption;
|
||||||
|
protected RelativeLayout transfer;
|
||||||
protected ProgressBar progressBar;
|
protected ProgressBar progressBar;
|
||||||
|
protected ImageButton cancel_transfer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setBubbleBackgroundColor(final View viewHolder, final int type,
|
public void setBubbleBackgroundColor(final View viewHolder, final int type,
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
package eu.siacs.conversations.ui.adapter;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY;
|
||||||
|
import static eu.siacs.conversations.entities.Message.DELETED_MESSAGE_BODY_OLD;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.animation.Animation;
|
||||||
|
import android.view.animation.AnimationUtils;
|
||||||
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.ui.adapter.model.MessageLogModel;
|
||||||
|
|
||||||
|
public class MessageLogAdapter extends ArrayAdapter<MessageLogModel> implements View.OnClickListener {
|
||||||
|
|
||||||
|
private final ArrayList<MessageLogModel> dataSet;
|
||||||
|
Context mContext;
|
||||||
|
|
||||||
|
// View lookup cache
|
||||||
|
private static class ViewHolder {
|
||||||
|
TextView txtLineNr;
|
||||||
|
TextView txtBody;
|
||||||
|
TextView txtTimeSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MessageLogAdapter(ArrayList<MessageLogModel> data, Context context) {
|
||||||
|
super(context, R.layout.message_log_item, data);
|
||||||
|
this.dataSet = data;
|
||||||
|
this.mContext = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastPosition = -1;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View getView(int position, View convertView, ViewGroup parent) {
|
||||||
|
// Get the data item for this position
|
||||||
|
MessageLogModel dataModel = getItem(position);
|
||||||
|
// Check if an existing view is being reused, otherwise inflate the view
|
||||||
|
ViewHolder viewHolder; // view lookup cache stored in tag
|
||||||
|
|
||||||
|
final View result;
|
||||||
|
|
||||||
|
if (convertView == null) {
|
||||||
|
|
||||||
|
viewHolder = new ViewHolder();
|
||||||
|
LayoutInflater inflater = LayoutInflater.from(getContext());
|
||||||
|
convertView = inflater.inflate(R.layout.message_log_item, parent, false);
|
||||||
|
viewHolder.txtLineNr = convertView.findViewById(R.id.nr);
|
||||||
|
viewHolder.txtBody = convertView.findViewById(R.id.body);
|
||||||
|
viewHolder.txtTimeSent = convertView.findViewById(R.id.timeSent);
|
||||||
|
|
||||||
|
result = convertView;
|
||||||
|
|
||||||
|
convertView.setTag(viewHolder);
|
||||||
|
} else {
|
||||||
|
viewHolder = (ViewHolder) convertView.getTag();
|
||||||
|
result = convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
Animation animation = AnimationUtils.loadAnimation(mContext, (position > lastPosition) ? R.anim.ufb : R.anim.dft);
|
||||||
|
result.startAnimation(animation);
|
||||||
|
lastPosition = position;
|
||||||
|
viewHolder.txtLineNr.setText(String.valueOf(position + 1));
|
||||||
|
viewHolder.txtBody.setText(preview(dataModel));
|
||||||
|
viewHolder.txtTimeSent.setText(getTimeSentFormated(dataModel.getTimeSent()));
|
||||||
|
// Return the completed view to render on screen
|
||||||
|
return convertView;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getTimeSentFormated(long timeSent) {
|
||||||
|
return android.text.format.DateFormat.getDateFormat(getContext()).format(new Date(timeSent)) + " " + android.text.format.DateFormat.getTimeFormat(getContext()).format(new Date(timeSent));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String preview(MessageLogModel dataModel) {
|
||||||
|
if (hasDeletedBody(dataModel.getBody())) {
|
||||||
|
return getContext().getString(R.string.message_deleted);
|
||||||
|
} else {
|
||||||
|
return dataModel.getBody();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasDeletedBody(String message) {
|
||||||
|
return message.trim().equals(DELETED_MESSAGE_BODY) || message.trim().equals(DELETED_MESSAGE_BODY_OLD);
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,7 +67,7 @@ public class UserAdapter extends ListAdapter<MucOptions.User, UserAdapter.ViewHo
|
||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
|
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
|
||||||
final MucOptions.User user = getItem(position);
|
final MucOptions.User user = getItem(position);
|
||||||
AvatarWorkerTask.loadAvatar(user, viewHolder.binding.contactPhoto, R.dimen.avatar);
|
AvatarWorkerTask.loadAvatar(user, viewHolder.binding.contactPhoto, R.dimen.avatar_on_details_screen_size);
|
||||||
viewHolder.binding.getRoot().setOnClickListener(v -> {
|
viewHolder.binding.getRoot().setOnClickListener(v -> {
|
||||||
final XmppActivity activity = XmppActivity.find(v);
|
final XmppActivity activity = XmppActivity.find(v);
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
|
@ -115,8 +115,6 @@ public class UserAdapter extends ListAdapter<MucOptions.User, UserAdapter.ViewHo
|
||||||
} else {
|
} else {
|
||||||
viewHolder.binding.key.setVisibility(View.GONE);
|
viewHolder.binding.key.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MucOptions.User getSelectedUser() {
|
public MucOptions.User getSelectedUser() {
|
||||||
|
|
|
@ -58,7 +58,6 @@ public class UserPreviewAdapter extends ListAdapter<MucOptions.User, UserPreview
|
||||||
return selectedUser;
|
return selectedUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class ViewHolder extends RecyclerView.ViewHolder {
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
|
||||||
private final UserPreviewBinding binding;
|
private final UserPreviewBinding binding;
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
package eu.siacs.conversations.ui.adapter.model;
|
||||||
|
|
||||||
|
public class MessageLogModel {
|
||||||
|
|
||||||
|
String body;
|
||||||
|
long timeSent;
|
||||||
|
|
||||||
|
public MessageLogModel(String body, long timeSent) {
|
||||||
|
|
||||||
|
this.body = body;
|
||||||
|
this.timeSent = timeSent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBody() {
|
||||||
|
return body;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getTimeSent() {
|
||||||
|
return timeSent;
|
||||||
|
}
|
||||||
|
}
|
114
src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java
Normal file
114
src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.view.View;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
|
||||||
|
public class ActionBarUtil {
|
||||||
|
|
||||||
|
public static void resetActionBarOnClickListeners(@NonNull View view) {
|
||||||
|
final View title = findActionBarTitle(view);
|
||||||
|
final View subtitle = findActionBarSubTitle(view);
|
||||||
|
if (title != null) {
|
||||||
|
title.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
subtitle.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void resetCustomActionBarOnClickListeners(@NonNull View view) {
|
||||||
|
final View title = view.findViewById(android.R.id.text1);
|
||||||
|
final View subtitle = view.findViewById(android.R.id.text2);
|
||||||
|
if (title != null) {
|
||||||
|
title.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
subtitle.setOnClickListener(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setActionBarOnClickListener(@NonNull View view,
|
||||||
|
@NonNull final View.OnClickListener onClickListener) {
|
||||||
|
final View title = findActionBarTitle(view);
|
||||||
|
final View subtitle = findActionBarSubTitle(view);
|
||||||
|
if (title != null) {
|
||||||
|
title.setOnClickListener(onClickListener);
|
||||||
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
subtitle.setOnClickListener(onClickListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setCustomActionBarOnClickListener(@NonNull View view,
|
||||||
|
@NonNull final View.OnClickListener onClickListener) {
|
||||||
|
final View title = view.findViewById(android.R.id.text1);
|
||||||
|
final View subtitle = view.findViewById(android.R.id.text2);
|
||||||
|
if (title != null) {
|
||||||
|
title.setOnClickListener(onClickListener);
|
||||||
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
subtitle.setOnClickListener(onClickListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable
|
||||||
|
View findActionBarTitle(@NonNull View root) {
|
||||||
|
return findActionBarItem(root, "action_bar_title", "mTitleTextView");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable
|
||||||
|
View findActionBarSubTitle(@NonNull View root) {
|
||||||
|
return findActionBarItem(root, "action_bar_subtitle", "mSubtitleTextView");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @Nullable
|
||||||
|
View findActionBarItem(@NonNull View root,
|
||||||
|
@NonNull String resourceName,
|
||||||
|
@NonNull String toolbarFieldName) {
|
||||||
|
View result = findViewSupportOrAndroid(root, resourceName);
|
||||||
|
|
||||||
|
if (result == null) {
|
||||||
|
View actionBar = findViewSupportOrAndroid(root, "action_bar");
|
||||||
|
if (actionBar != null) {
|
||||||
|
result = reflectiveRead(actionBar, toolbarFieldName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null && root.getClass().getName().endsWith("widget.Toolbar")) {
|
||||||
|
result = reflectiveRead(root, toolbarFieldName);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ConstantConditions")
|
||||||
|
private static @Nullable
|
||||||
|
View findViewSupportOrAndroid(@NonNull View root,
|
||||||
|
@NonNull String resourceName) {
|
||||||
|
Context context = root.getContext();
|
||||||
|
View result = null;
|
||||||
|
if (result == null) {
|
||||||
|
int supportID = context.getResources().getIdentifier(resourceName, "id", context.getPackageName());
|
||||||
|
result = root.findViewById(supportID);
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
int androidID = context.getResources().getIdentifier(resourceName, "id", "android");
|
||||||
|
result = root.findViewById(androidID);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static <T> T reflectiveRead(@NonNull Object object, @NonNull String fieldName) {
|
||||||
|
try {
|
||||||
|
Field field = object.getClass().getDeclaredField(fieldName);
|
||||||
|
field.setAccessible(true);
|
||||||
|
return (T) field.get(object);
|
||||||
|
} catch (final Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -46,7 +46,6 @@ import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
|
|
||||||
public class Attachment implements Parcelable {
|
public class Attachment implements Parcelable {
|
||||||
|
@ -138,10 +137,10 @@ public class Attachment implements Parcelable {
|
||||||
return Collections.singletonList(new Attachment(uri, type, mime));
|
return Collections.singletonList(new Attachment(uri, type, mime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Attachment> of(final Context context, List<Uri> uris) {
|
public static List<Attachment> of(final Context context, List<Uri> uris, final String type) {
|
||||||
List<Attachment> attachments = new ArrayList<>();
|
final List<Attachment> attachments = new ArrayList<>();
|
||||||
for (Uri uri : uris) {
|
for (final Uri uri : uris) {
|
||||||
final String mime = MimeUtils.guessMimeTypeFromUri(context, uri);
|
final String mime = MimeUtils.guessMimeTypeFromUriAndMime(context, uri, type);
|
||||||
attachments.add(new Attachment(uri, mime != null && isImage(mime) ? Type.IMAGE : Type.FILE, mime));
|
attachments.add(new Attachment(uri, mime != null && isImage(mime) ? Type.IMAGE : Type.FILE, mime));
|
||||||
}
|
}
|
||||||
return attachments;
|
return attachments;
|
||||||
|
@ -182,7 +181,7 @@ public class Attachment implements Parcelable {
|
||||||
private static boolean renderFileThumbnail(final String mime) {
|
private static boolean renderFileThumbnail(final String mime) {
|
||||||
return mime.startsWith("video/")
|
return mime.startsWith("video/")
|
||||||
|| isImage(mime)
|
|| isImage(mime)
|
||||||
|| (Compatibility.runsTwentyOne() && "application/pdf".equals(mime));
|
|| "application/pdf".equals(mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getUri() {
|
public Uri getUri() {
|
||||||
|
|
|
@ -132,7 +132,6 @@ public class ConversationMenuConfigurator {
|
||||||
if (conversation.getNextEncryption() != Message.ENCRYPTION_NONE) {
|
if (conversation.getNextEncryption() != Message.ENCRYPTION_NONE) {
|
||||||
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
|
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
|
||||||
}
|
}
|
||||||
otr.setVisible(Config.supportOtr() && activity.enableOTR());
|
|
||||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||||
otr.setVisible(false);
|
otr.setVisible(false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,8 +11,6 @@ import androidx.browser.customtabs.CustomTabsIntent;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.Compatibility.runsTwentyOne;
|
|
||||||
|
|
||||||
public class CustomTab {
|
public class CustomTab {
|
||||||
public static void openTab(Context context, Uri uri, boolean dark) throws ActivityNotFoundException {
|
public static void openTab(Context context, Uri uri, boolean dark) throws ActivityNotFoundException {
|
||||||
CustomTabsIntent.Builder tabBuilder = new CustomTabsIntent.Builder();
|
CustomTabsIntent.Builder tabBuilder = new CustomTabsIntent.Builder();
|
||||||
|
@ -25,9 +23,7 @@ public class CustomTab {
|
||||||
tabBuilder.setColorScheme(dark ? CustomTabsIntent.COLOR_SCHEME_DARK : CustomTabsIntent.COLOR_SCHEME_LIGHT);
|
tabBuilder.setColorScheme(dark ? CustomTabsIntent.COLOR_SCHEME_DARK : CustomTabsIntent.COLOR_SCHEME_LIGHT);
|
||||||
tabBuilder.setShareState(CustomTabsIntent.SHARE_STATE_ON);
|
tabBuilder.setShareState(CustomTabsIntent.SHARE_STATE_ON);
|
||||||
CustomTabsIntent customTabsIntent = tabBuilder.build();
|
CustomTabsIntent customTabsIntent = tabBuilder.build();
|
||||||
if (runsTwentyOne()) {
|
|
||||||
customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
customTabsIntent.intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||||
}
|
|
||||||
customTabsIntent.intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
customTabsIntent.intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
|
||||||
customTabsIntent.launchUrl(context, uri);
|
customTabsIntent.launchUrl(context, uri);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,10 +50,7 @@ public class GridManager {
|
||||||
|
|
||||||
private static ColumnInfo calculateColumnCount(Context context, int availableWidth, @DimenRes int desiredSize) {
|
private static ColumnInfo calculateColumnCount(Context context, int availableWidth, @DimenRes int desiredSize) {
|
||||||
final float desiredWidth = context.getResources().getDimension(desiredSize);
|
final float desiredWidth = context.getResources().getDimension(desiredSize);
|
||||||
int columns = Math.round(availableWidth / desiredWidth);
|
final int columns = Math.round(availableWidth / desiredWidth);
|
||||||
if (columns < 1) {
|
|
||||||
columns = 1;
|
|
||||||
}
|
|
||||||
final int realWidth = availableWidth / columns;
|
final int realWidth = availableWidth / columns;
|
||||||
Log.d(Config.LOGTAG, "desired=" + desiredWidth + " real=" + realWidth);
|
Log.d(Config.LOGTAG, "desired=" + desiredWidth + " real=" + realWidth);
|
||||||
return new ColumnInfo(columns, realWidth);
|
return new ColumnInfo(columns, realWidth);
|
||||||
|
|
121
src/main/java/eu/siacs/conversations/ui/util/KeyboardUtils.java
Normal file
121
src/main/java/eu/siacs/conversations/ui/util/KeyboardUtils.java
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Rect;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.ViewTreeObserver;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Based on the following Stackoverflow answer:
|
||||||
|
* http://stackoverflow.com/questions/2150078/how-to-check-visibility-of-software-keyboard-in-android
|
||||||
|
* https://github.com/ravindu1024/android-keyboardlistener/tree/master/keyboard-listener/src/main/java/com/rw/keyboardlistener
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("WeakerAccess")
|
||||||
|
public class KeyboardUtils implements ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
private final static int MAGIC_NUMBER = 200;
|
||||||
|
|
||||||
|
private SoftKeyboardToggleListener mCallback;
|
||||||
|
private View mRootView;
|
||||||
|
private Boolean prevValue = null;
|
||||||
|
private float mScreenDensity;
|
||||||
|
private static HashMap<SoftKeyboardToggleListener, KeyboardUtils> sListenerMap = new HashMap<>();
|
||||||
|
|
||||||
|
public interface SoftKeyboardToggleListener {
|
||||||
|
void onToggleSoftKeyboard(boolean isVisible);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onGlobalLayout() {
|
||||||
|
Rect r = new Rect();
|
||||||
|
mRootView.getWindowVisibleDisplayFrame(r);
|
||||||
|
|
||||||
|
int heightDiff = mRootView.getRootView().getHeight() - (r.bottom - r.top);
|
||||||
|
float dp = heightDiff / mScreenDensity;
|
||||||
|
boolean isVisible = dp > MAGIC_NUMBER;
|
||||||
|
|
||||||
|
if (mCallback != null && (prevValue == null || isVisible != prevValue)) {
|
||||||
|
prevValue = isVisible;
|
||||||
|
mCallback.onToggleSoftKeyboard(isVisible);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new keyboard listener
|
||||||
|
*
|
||||||
|
* @param act calling activity
|
||||||
|
* @param listener callback
|
||||||
|
*/
|
||||||
|
public static void addKeyboardToggleListener(Activity act, SoftKeyboardToggleListener listener) {
|
||||||
|
removeKeyboardToggleListener(listener);
|
||||||
|
|
||||||
|
sListenerMap.put(listener, new KeyboardUtils(act, listener));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a registered listener
|
||||||
|
*
|
||||||
|
* @param listener {@link SoftKeyboardToggleListener}
|
||||||
|
*/
|
||||||
|
public static void removeKeyboardToggleListener(SoftKeyboardToggleListener listener) {
|
||||||
|
if (sListenerMap.containsKey(listener)) {
|
||||||
|
KeyboardUtils k = sListenerMap.get(listener);
|
||||||
|
k.removeListener();
|
||||||
|
|
||||||
|
sListenerMap.remove(listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all registered keyboard listeners
|
||||||
|
*/
|
||||||
|
public static void removeAllKeyboardToggleListeners() {
|
||||||
|
for (SoftKeyboardToggleListener l : sListenerMap.keySet())
|
||||||
|
sListenerMap.get(l).removeListener();
|
||||||
|
|
||||||
|
sListenerMap.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manually toggle soft keyboard visibility
|
||||||
|
*
|
||||||
|
* @param context calling context
|
||||||
|
*/
|
||||||
|
public static void toggleKeyboardVisibility(Context context) {
|
||||||
|
InputMethodManager inputMethodManager = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (inputMethodManager != null)
|
||||||
|
inputMethodManager.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Force closes the soft keyboard
|
||||||
|
*
|
||||||
|
* @param activeView the view with the keyboard focus
|
||||||
|
*/
|
||||||
|
public static void forceCloseKeyboard(View activeView) {
|
||||||
|
InputMethodManager inputMethodManager = (InputMethodManager) activeView.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
if (inputMethodManager != null)
|
||||||
|
inputMethodManager.hideSoftInputFromWindow(activeView.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeListener() {
|
||||||
|
mCallback = null;
|
||||||
|
|
||||||
|
mRootView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyboardUtils(Activity act, SoftKeyboardToggleListener listener) {
|
||||||
|
mCallback = listener;
|
||||||
|
|
||||||
|
mRootView = ((ViewGroup) act.findViewById(android.R.id.content)).getChildAt(0);
|
||||||
|
mRootView.getViewTreeObserver().addOnGlobalLayoutListener(this);
|
||||||
|
|
||||||
|
mScreenDensity = act.getResources().getDisplayMetrics().density;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
|
||||||
|
public final class LocationHelper {
|
||||||
|
/**
|
||||||
|
* Parses a lat long string in the form "lat,long".
|
||||||
|
*
|
||||||
|
* @param latlong A string in the form "lat,long"
|
||||||
|
* @return A GeoPoint representing the lat,long string.
|
||||||
|
* @throws NumberFormatException If an invalid lat or long is specified.
|
||||||
|
*/
|
||||||
|
public static GeoPoint parseLatLong(final String latlong) throws NumberFormatException {
|
||||||
|
if (latlong == null || latlong.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] parts = latlong.split(",");
|
||||||
|
if (parts[1].contains("?")) {
|
||||||
|
parts[1] = parts[1].substring(0, parts[1].indexOf("?"));
|
||||||
|
}
|
||||||
|
return new GeoPoint(Double.valueOf(parts[0]), Double.valueOf(parts[1]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isSameProvider(final String provider1, final String provider2) {
|
||||||
|
if (provider1 == null) {
|
||||||
|
return provider2 == null;
|
||||||
|
}
|
||||||
|
return provider1.equals(provider2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isBetterLocation(final Location location, final Location prevLoc) {
|
||||||
|
if (prevLoc == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the new location fix is newer or older
|
||||||
|
final long timeDelta = location.getTime() - prevLoc.getTime();
|
||||||
|
final boolean isSignificantlyNewer = timeDelta > Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
||||||
|
final boolean isSignificantlyOlder = timeDelta < -Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
||||||
|
final boolean isNewer = timeDelta > 0;
|
||||||
|
|
||||||
|
if (isSignificantlyNewer) {
|
||||||
|
return true;
|
||||||
|
} else if (isSignificantlyOlder) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check whether the new location fix is more or less accurate
|
||||||
|
final int accuracyDelta = (int) (location.getAccuracy() - prevLoc.getAccuracy());
|
||||||
|
final boolean isLessAccurate = accuracyDelta > 0;
|
||||||
|
final boolean isMoreAccurate = accuracyDelta < 0;
|
||||||
|
final boolean isSignificantlyLessAccurate = accuracyDelta > 200;
|
||||||
|
|
||||||
|
// Check if the old and new location are from the same provider
|
||||||
|
final boolean isFromSameProvider = isSameProvider(location.getProvider(), prevLoc.getProvider());
|
||||||
|
|
||||||
|
// Determine location quality using a combination of timeliness and accuracy
|
||||||
|
if (isMoreAccurate) {
|
||||||
|
return true;
|
||||||
|
} else if (isNewer && !isLessAccurate) {
|
||||||
|
return true;
|
||||||
|
} else return isNewer && !isSignificantlyLessAccurate && isFromSameProvider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,18 +32,22 @@ package eu.siacs.conversations.ui.util;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.SpannableString;
|
import android.text.SpannableString;
|
||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.text.util.Linkify;
|
import android.text.util.Linkify;
|
||||||
|
import android.util.Base64;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.webkit.URLUtil;
|
import android.webkit.URLUtil;
|
||||||
|
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
@ -53,12 +57,32 @@ import eu.siacs.conversations.ui.SettingsActivity;
|
||||||
import eu.siacs.conversations.ui.text.FixedURLSpan;
|
import eu.siacs.conversations.ui.text.FixedURLSpan;
|
||||||
import eu.siacs.conversations.utils.GeoHelper;
|
import eu.siacs.conversations.utils.GeoHelper;
|
||||||
import eu.siacs.conversations.utils.Patterns;
|
import eu.siacs.conversations.utils.Patterns;
|
||||||
import eu.siacs.conversations.utils.TrackingHelper;
|
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
|
|
||||||
public class MyLinkify {
|
public class MyLinkify {
|
||||||
|
|
||||||
private static final Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)\\/(((?!(\"|'|<)).)*)");
|
private final static Pattern youtubePattern = Pattern.compile("(www\\.|m\\.)?(youtube\\.com|youtu\\.be|youtube-nocookie\\.com)/(((?!([\"'<])).)*)");
|
||||||
|
private final static String youtubeURLPattern = "(?:youtube(?:-nocookie)?\\.com\\/(?:[^\\/\\n\\s]+\\/\\S+\\/|(?:v|e(?:mbed)?)\\/|\\S*?[?&]v=)|youtu\\.be\\/)([a-zA-Z0-9_-]{11})";
|
||||||
|
|
||||||
|
public static boolean isYoutubeUrl(String url) {
|
||||||
|
return !url.isEmpty() && url.matches("(?i:http|https):\\/\\/" + youtubePattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getYoutubeVideoId(String url) {
|
||||||
|
if (url == null || url.trim().length() <= 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Pattern pattern = Pattern.compile(youtubeURLPattern, Pattern.CASE_INSENSITIVE);
|
||||||
|
final Matcher matcher = pattern.matcher(url);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getYoutubeImageUrl(String url) {
|
||||||
|
return "https://img.youtube.com/vi/" + getYoutubeVideoId(url) + "/0.jpg";
|
||||||
|
}
|
||||||
|
|
||||||
public static String replaceYoutube(Context context, String content) {
|
public static String replaceYoutube(Context context, String content) {
|
||||||
return replaceYoutube(context, new SpannableStringBuilder(content)).toString();
|
return replaceYoutube(context, new SpannableStringBuilder(content)).toString();
|
||||||
|
@ -69,30 +93,53 @@ public class MyLinkify {
|
||||||
if (useInvidious(context)) {
|
if (useInvidious(context)) {
|
||||||
while (matcher.find()) {
|
while (matcher.find()) {
|
||||||
final String youtubeId = matcher.group(3);
|
final String youtubeId = matcher.group(3);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
if (matcher.group(2) != null && matcher.group(2).equals("youtu.be")) {
|
||||||
if (matcher.group(2) != null && Objects.equals(matcher.group(2), "youtu.be")) {
|
content = new SpannableStringBuilder(content.toString().replaceAll("https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/watch?v=" + youtubeId + "&local=true")));
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll("(?i)https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/watch?v=" + youtubeId + "&local=true")));
|
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll(">" + Pattern.quote(matcher.group()), Matcher.quoteReplacement(">" + invidiousHost(context) + "/watch?v=" + youtubeId + "&local=true")));
|
|
||||||
} else {
|
} else {
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll("(?i)https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/" + youtubeId + "&local=true")));
|
content = new SpannableStringBuilder(content.toString().replaceAll("https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/" + youtubeId + "&local=true")));
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll(">" + Pattern.quote(matcher.group()), Matcher.quoteReplacement(">" + invidiousHost(context) + "/" + youtubeId + "&local=true")));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (matcher.group(2) != null && matcher.group(2) == "youtu.be") {
|
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll("(?i)https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/watch?v=" + youtubeId + "&local=true")));
|
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll(">" + Pattern.quote(matcher.group()), Matcher.quoteReplacement(">" + invidiousHost(context) + "/watch?v=" + youtubeId + "&local=true")));
|
|
||||||
} else {
|
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll("(?i)https://" + Pattern.quote(matcher.group()), Matcher.quoteReplacement("https://" + invidiousHost(context) + "/" + youtubeId + "&local=true")));
|
|
||||||
content = new SpannableStringBuilder(content.toString().replaceAll(">" + Pattern.quote(matcher.group()), Matcher.quoteReplacement(">" + invidiousHost(context) + "/" + youtubeId + "&local=true")));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/M66B/FairEmail/blob/master/app/src/main/java/eu/faircode/email/AdapterMessage.java
|
// https://github.com/M66B/FairEmail/blob/master/app/src/main/java/eu/faircode/email/UriHelper.java
|
||||||
|
// https://github.com/newhouse/url-tracking-stripper
|
||||||
|
private static final List<String> PARANOID_QUERY = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
// https://en.wikipedia.org/wiki/UTM_parameters
|
||||||
|
"awt_a", // AWeber
|
||||||
|
"awt_l", // AWeber
|
||||||
|
"awt_m", // AWeber
|
||||||
|
|
||||||
|
"icid", // Adobe
|
||||||
|
"gclid", // Google
|
||||||
|
"gclsrc", // Google ads
|
||||||
|
"dclid", // DoubleClick (Google)
|
||||||
|
"fbclid", // Facebook
|
||||||
|
"igshid", // Instagram
|
||||||
|
|
||||||
|
"mc_cid", // MailChimp
|
||||||
|
"mc_eid", // MailChimp
|
||||||
|
|
||||||
|
"zanpid", // Zanox (Awin)
|
||||||
|
|
||||||
|
"kclickid" // https://support.freespee.com/hc/en-us/articles/202577831-Kenshoo-integration
|
||||||
|
));
|
||||||
|
|
||||||
|
// https://github.com/snarfed/granary/blob/master/granary/facebook.py#L1789
|
||||||
|
|
||||||
|
private static final List<String> FACEBOOK_WHITELIST_PATH = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
"/nd/", "/n/", "/story.php"
|
||||||
|
));
|
||||||
|
|
||||||
|
private static final List<String> FACEBOOK_WHITELIST_QUERY = Collections.unmodifiableList(Arrays.asList(
|
||||||
|
"story_fbid", "fbid", "id", "comment_id"
|
||||||
|
));
|
||||||
|
|
||||||
public static SpannableString removeTrackingParameter(Uri uri) {
|
public static SpannableString removeTrackingParameter(Uri uri) {
|
||||||
|
if (uri.isOpaque())
|
||||||
|
return new SpannableString(uri.toString());
|
||||||
|
|
||||||
boolean changed = false;
|
boolean changed = false;
|
||||||
Uri url;
|
Uri url;
|
||||||
Uri.Builder builder;
|
Uri.Builder builder;
|
||||||
|
@ -101,6 +148,12 @@ public class MyLinkify {
|
||||||
!TextUtils.isEmpty(uri.getQueryParameter("url"))) {
|
!TextUtils.isEmpty(uri.getQueryParameter("url"))) {
|
||||||
changed = true;
|
changed = true;
|
||||||
url = Uri.parse(uri.getQueryParameter("url"));
|
url = Uri.parse(uri.getQueryParameter("url"));
|
||||||
|
} else if ("https".equals(uri.getScheme()) &&
|
||||||
|
"smex-ctp.trendmicro.com".equals(uri.getHost()) &&
|
||||||
|
"/wis/clicktime/v1/query".equals(uri.getPath()) &&
|
||||||
|
!TextUtils.isEmpty(uri.getQueryParameter("url"))) {
|
||||||
|
changed = true;
|
||||||
|
url = Uri.parse(uri.getQueryParameter("url"));
|
||||||
} else if ("https".equals(uri.getScheme()) &&
|
} else if ("https".equals(uri.getScheme()) &&
|
||||||
"www.google.com".equals(uri.getHost()) &&
|
"www.google.com".equals(uri.getHost()) &&
|
||||||
uri.getPath() != null &&
|
uri.getPath() != null &&
|
||||||
|
@ -121,26 +174,53 @@ public class MyLinkify {
|
||||||
}
|
}
|
||||||
changed = (result != null);
|
changed = (result != null);
|
||||||
url = (result == null ? uri : result);
|
url = (result == null ? uri : result);
|
||||||
} else {
|
} else if (uri.getQueryParameterNames().size() == 1) {
|
||||||
url = uri;
|
// Sophos Email Appliance
|
||||||
|
Uri result = null;
|
||||||
|
String key = uri.getQueryParameterNames().iterator().next();
|
||||||
|
if (TextUtils.isEmpty(uri.getQueryParameter(key)))
|
||||||
|
try {
|
||||||
|
String data = new String(Base64.decode(key, Base64.DEFAULT));
|
||||||
|
int v = data.indexOf("ver=");
|
||||||
|
int u = data.indexOf("&&url=");
|
||||||
|
if (v == 0 && u > 0)
|
||||||
|
result = Uri.parse(URLDecoder.decode(data.substring(u + 6), StandardCharsets.UTF_8.name()));
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
}
|
}
|
||||||
|
changed = (result != null);
|
||||||
|
url = (result == null ? uri : result);
|
||||||
|
} else
|
||||||
|
url = uri;
|
||||||
if (url.isOpaque()) {
|
if (url.isOpaque()) {
|
||||||
return new SpannableString(uri.toString());
|
return new SpannableString(uri.toString());
|
||||||
//return uri;
|
|
||||||
}
|
}
|
||||||
builder = url.buildUpon();
|
builder = url.buildUpon();
|
||||||
builder.clearQuery();
|
builder.clearQuery();
|
||||||
for (String key : url.getQueryParameterNames())
|
String host = uri.getHost();
|
||||||
|
String path = uri.getPath();
|
||||||
|
if (host != null)
|
||||||
|
host = host.toLowerCase(Locale.ROOT);
|
||||||
|
if (path != null)
|
||||||
|
path = path.toLowerCase(Locale.ROOT);
|
||||||
|
boolean first = "www.facebook.com".equals(host);
|
||||||
|
for (String key : url.getQueryParameterNames()) {
|
||||||
// https://en.wikipedia.org/wiki/UTM_parameters
|
// https://en.wikipedia.org/wiki/UTM_parameters
|
||||||
// https://docs.oracle.com/en/cloud/saas/marketing/eloqua-user/Help/EloquaAsynchronousTrackingScripts/EloquaTrackingParameters.htm
|
// https://docs.oracle.com/en/cloud/saas/marketing/eloqua-user/Help/EloquaAsynchronousTrackingScripts/EloquaTrackingParameters.htm
|
||||||
if (key.toLowerCase(Locale.ROOT).startsWith("utm_") ||
|
String lkey = key.toLowerCase(Locale.ROOT);
|
||||||
key.toLowerCase(Locale.ROOT).startsWith("elq") ||
|
if (PARANOID_QUERY.contains(lkey) ||
|
||||||
TrackingHelper.TRACKING_PARAMETER.contains(key.toLowerCase(Locale.ROOT)) ||
|
lkey.startsWith("utm_") ||
|
||||||
("snr".equals(key) && "store.steampowered.com".equals(uri.getHost())))
|
lkey.startsWith("elq") ||
|
||||||
|
((host != null && host.endsWith("facebook.com")) &&
|
||||||
|
!first &&
|
||||||
|
FACEBOOK_WHITELIST_PATH.contains(path) &&
|
||||||
|
!FACEBOOK_WHITELIST_QUERY.contains(lkey)) ||
|
||||||
|
("store.steampowered.com".equals(host) &&
|
||||||
|
"snr".equals(lkey)))
|
||||||
changed = true;
|
changed = true;
|
||||||
else if (!TextUtils.isEmpty(key))
|
else if (!TextUtils.isEmpty(key))
|
||||||
for (String value : url.getQueryParameters(key)) {
|
for (String value : url.getQueryParameters(key)) {
|
||||||
Log.i(Config.LOGTAG, "Query " + key + "=" + value);
|
Log.d(Config.LOGTAG, "Query " + key + "=" + value);
|
||||||
Uri suri = Uri.parse(value);
|
Uri suri = Uri.parse(value);
|
||||||
if ("http".equals(suri.getScheme()) || "https".equals(suri.getScheme())) {
|
if ("http".equals(suri.getScheme()) || "https".equals(suri.getScheme())) {
|
||||||
Uri s = Uri.parse(removeTrackingParameter(suri).toString());
|
Uri s = Uri.parse(removeTrackingParameter(suri).toString());
|
||||||
|
@ -151,6 +231,8 @@ public class MyLinkify {
|
||||||
}
|
}
|
||||||
builder.appendQueryParameter(key, value);
|
builder.appendQueryParameter(key, value);
|
||||||
}
|
}
|
||||||
|
first = false;
|
||||||
|
}
|
||||||
return (changed ? new SpannableString(builder.build().toString()) : new SpannableString(uri.toString()));
|
return (changed ? new SpannableString(builder.build().toString()) : new SpannableString(uri.toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +284,6 @@ public class MyLinkify {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
if (end < cs.length()) {
|
if (end < cs.length()) {
|
||||||
// Reject strings that were probably matched only because they contain a dot followed by
|
// Reject strings that were probably matched only because they contain a dot followed by
|
||||||
// by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java)
|
// by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java)
|
||||||
|
@ -210,8 +291,6 @@ public class MyLinkify {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -221,21 +300,8 @@ public class MyLinkify {
|
||||||
};
|
};
|
||||||
|
|
||||||
private static boolean isAlphabetic(final int code) {
|
private static boolean isAlphabetic(final int code) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
|
||||||
return Character.isAlphabetic(code);
|
return Character.isAlphabetic(code);
|
||||||
}
|
}
|
||||||
switch (Character.getType(code)) {
|
|
||||||
case Character.UPPERCASE_LETTER:
|
|
||||||
case Character.LOWERCASE_LETTER:
|
|
||||||
case Character.TITLECASE_LETTER:
|
|
||||||
case Character.MODIFIER_LETTER:
|
|
||||||
case Character.OTHER_LETTER:
|
|
||||||
case Character.LETTER_NUMBER:
|
|
||||||
return true;
|
|
||||||
default:
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String invidiousHost(Context context) {
|
private static String invidiousHost(Context context) {
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
@ -248,8 +314,7 @@ public class MyLinkify {
|
||||||
|
|
||||||
private static boolean useInvidious(Context context) {
|
private static boolean useInvidious(Context context) {
|
||||||
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
boolean invidious = sharedPreferences.getBoolean(SettingsActivity.USE_INVIDIOUS, context.getResources().getBoolean(R.bool.use_invidious));
|
return sharedPreferences.getBoolean(SettingsActivity.USE_INVIDIOUS, context.getResources().getBoolean(R.bool.use_invidious));
|
||||||
return invidious;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void addLinks(Editable body, boolean includeGeo) {
|
public static void addLinks(Editable body, boolean includeGeo) {
|
||||||
|
|
|
@ -1,51 +1,114 @@
|
||||||
package eu.siacs.conversations.ui.util;
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.utils.MessageUtils;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
|
|
||||||
public class QuoteHelper {
|
public class QuoteHelper {
|
||||||
|
|
||||||
public static boolean isPositionQuoteCharacter(CharSequence body, int pos){
|
|
||||||
return body.charAt(pos) == '>';
|
public static final char QUOTE_CHAR = '>';
|
||||||
|
public static final char QUOTE_END_CHAR = '<'; // used for one check, not for actual quoting
|
||||||
|
public static final char QUOTE_ALT_CHAR = '»';
|
||||||
|
public static final char QUOTE_ALT_END_CHAR = '«';
|
||||||
|
|
||||||
|
public static boolean isPositionQuoteCharacter(CharSequence body, int pos) {
|
||||||
|
// second part of logical check actually goes against the logic indicated in the method name, since it also checks for context
|
||||||
|
// but it's very useful
|
||||||
|
return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos) {
|
||||||
|
return body.charAt(pos) == QUOTE_END_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPositionAltQuoteCharacter(CharSequence body, int pos) {
|
||||||
|
return body.charAt(pos) == QUOTE_ALT_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos) {
|
||||||
|
return body.charAt(pos) == QUOTE_ALT_END_CHAR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPositionAltQuoteStart(CharSequence body, int pos) {
|
||||||
|
return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) {
|
public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) {
|
||||||
return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos +1 );
|
return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 'Prequote' means anything we require or can accept in front of a QuoteChar
|
// 'Prequote' means anything we require or can accept in front of a QuoteChar
|
||||||
public static boolean isPositionPrecededByPrequote(CharSequence body, int pos){
|
public static boolean isPositionPrecededByPreQuote(CharSequence body, int pos) {
|
||||||
return UIHelper.isPositionPrecededByLineStart(body, pos);
|
return UIHelper.isPositionPrecededByLineStart(body, pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isPositionQuoteStart (CharSequence body, int pos){
|
public static boolean isPositionQuoteStart(CharSequence body, int pos) {
|
||||||
return isPositionQuoteCharacter(body, pos)
|
return (isPositionQuoteCharacter(body, pos)
|
||||||
&& isPositionPrecededByPrequote(body, pos)
|
&& isPositionPrecededByPreQuote(body, pos)
|
||||||
&& (UIHelper.isPositionFollowedByWhitespace(body, pos)
|
&& (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos)
|
||||||
|| isPositionFollowedByQuoteChar(body, pos));
|
|| isPositionFollowedByQuoteChar(body, pos)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean bodyContainsQuoteStart (CharSequence body){
|
public static boolean bodyContainsQuoteStart(CharSequence body) {
|
||||||
for (int i = 0; i < body.length(); i++){
|
for (int i = 0; i < body.length(); i++) {
|
||||||
if (isPositionQuoteStart(body, i)){
|
if (isPositionQuoteStart(body, i)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
/*public static int getQuoteColors(XmppActivity activity, boolean darkBackground, int quoteDepth){
|
|
||||||
int[] colorsLight = R.style.ConversationsTheme_Dark;
|
|
||||||
int[] colorsDark = Config.QUOTE_COLOR_ARRAY_DARK;
|
|
||||||
|
|
||||||
Collections.rotate(Collections.singletonList(colorsLight), quoteDepth);
|
public static boolean isPositionFollowedByAltQuoteEnd(CharSequence body, int pos) {
|
||||||
Collections.rotate(Collections.singletonList(colorsDark), quoteDepth);
|
if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
boolean previousWasWhitespace = false;
|
||||||
|
for (int i = pos + 1; i < body.length(); i++) {
|
||||||
|
char c = body.charAt(i);
|
||||||
|
if (c == '\n' || isPositionAltQuoteCharacter(body, i)) {
|
||||||
|
return false;
|
||||||
|
} else if (isPositionAltQuoteEndCharacter(body, i) && !previousWasWhitespace) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
previousWasWhitespace = Character.isWhitespace(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Arrays.stream(colorsLight).toArray();
|
public static boolean isNestedTooDeeply(CharSequence line) {
|
||||||
|
if (isPositionQuoteStart(line, 0)) {
|
||||||
|
int nestingDepth = 1;
|
||||||
|
for (int i = 1; i < line.length(); i++) {
|
||||||
|
if (isPositionQuoteStart(line, i)) {
|
||||||
|
nestingDepth++;
|
||||||
|
}
|
||||||
|
if (nestingDepth > (Config.QUOTING_MAX_DEPTH - 1)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int quoteColors = darkBackground ? ContextCompat.getColor(activity, colorsLight[quoteDepth-1])
|
public static String replaceAltQuoteCharsInText(String text) {
|
||||||
: ContextCompat.getColor(activity, colorsDark[quoteDepth-1]);
|
for (int i = 0; i < text.length(); i++) {
|
||||||
|
if (isPositionAltQuoteStart(text, i)) {
|
||||||
|
text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
Collections.rotate
|
public static boolean isMessageQuoteable(Message m){
|
||||||
|
if (m.isTypeText() && MessageUtils.prepareQuote(m).length() > 0) {
|
||||||
return quoteColors;
|
return true;
|
||||||
};*/
|
}
|
||||||
|
if (m.isFileOrImage()){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -28,12 +28,12 @@ import me.drakeet.support.toast.ToastCompat;
|
||||||
public class UpdateHelper {
|
public class UpdateHelper {
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
||||||
private static final String INSTALL_DATE = "2020-11-01";
|
private static final String INSTALL_DATE = "2020-11-01";
|
||||||
private static final String monocles_message = "MONOCLES_CHAT_UPDATE_MESSAGE";
|
private static final String monocles_message = "MONOCLES.IM_UPDATE_MESSAGE";
|
||||||
private static boolean moveData = true;
|
private static boolean moveData = true;
|
||||||
private static boolean dataMoved = false;
|
private static boolean dataMoved = false;
|
||||||
|
|
||||||
private static final File PAM_MainDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/");
|
private static final File PAM_MainDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/");
|
||||||
private static final File Monocles_MainDirectory = new File(Environment.getExternalStorageDirectory() + "/monocles chat/");
|
private static final File monocles_MainDirectory = new File(Environment.getExternalStorageDirectory() + "/monocles chat/");
|
||||||
private static final File PAM_PicturesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Images/");
|
private static final File PAM_PicturesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Images/");
|
||||||
private static final File PAM_FilesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Files/");
|
private static final File PAM_FilesDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Files/");
|
||||||
private static final File PAM_AudiosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Audios/");
|
private static final File PAM_AudiosDirectory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Monocles Messenger/Media/Monocles Messenger Audios/");
|
||||||
|
@ -115,7 +115,7 @@ public class UpdateHelper {
|
||||||
|
|
||||||
private static void checkOldData() {
|
private static void checkOldData() {
|
||||||
if (PAM_MainDirectory.exists() && PAM_MainDirectory.isDirectory()) {
|
if (PAM_MainDirectory.exists() && PAM_MainDirectory.isDirectory()) {
|
||||||
if (Monocles_MainDirectory.exists() && Monocles_MainDirectory.isDirectory()) {
|
if (monocles_MainDirectory.exists() && monocles_MainDirectory.isDirectory()) {
|
||||||
moveData = false;
|
moveData = false;
|
||||||
} else {
|
} else {
|
||||||
moveData = true;
|
moveData = true;
|
||||||
|
@ -180,16 +180,16 @@ public class UpdateHelper {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (PAM_MainDirectory.exists() && PAM_MainDirectory.isDirectory()) {
|
if (PAM_MainDirectory.exists() && PAM_MainDirectory.isDirectory()) {
|
||||||
Monocles_MainDirectory.mkdirs();
|
monocles_MainDirectory.mkdirs();
|
||||||
final File[] files = PAM_MainDirectory.listFiles();
|
final File[] files = PAM_MainDirectory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (PAM_MainDirectory.renameTo(Monocles_MainDirectory)) {
|
if (PAM_MainDirectory.renameTo(monocles_MainDirectory)) {
|
||||||
dataMoved = true;
|
dataMoved = true;
|
||||||
Log.d(Config.LOGTAG, "moved " + PAM_MainDirectory.getAbsolutePath() + " to " + Monocles_MainDirectory.getAbsolutePath());
|
Log.d(Config.LOGTAG, "moved " + PAM_MainDirectory.getAbsolutePath() + " to " + monocles_MainDirectory.getAbsolutePath());
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, "could not move " + PAM_MainDirectory.getAbsolutePath() + " to " + Monocles_MainDirectory.getAbsolutePath());
|
Log.d(Config.LOGTAG, "could not move " + PAM_MainDirectory.getAbsolutePath() + " to " + monocles_MainDirectory.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -265,7 +265,7 @@ public class UpdateHelper {
|
||||||
private static boolean PAMInstalled(Activity activity) {
|
private static boolean PAMInstalled(Activity activity) {
|
||||||
PackageManager pm = activity.getPackageManager();
|
PackageManager pm = activity.getPackageManager();
|
||||||
try {
|
try {
|
||||||
return pm.getApplicationLabel(pm.getApplicationInfo("de.monocles.chat", 0)).equals("Monocles Messenger");
|
return pm.getApplicationLabel(pm.getApplicationInfo("de.pixart.messenger", 0)).equals("Monocles Messenger");
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
30
src/main/java/eu/siacs/conversations/ui/util/UriHelper.java
Normal file
30
src/main/java/eu/siacs/conversations/ui/util/UriHelper.java
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package eu.siacs.conversations.ui.util;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper methods for parsing URI's.
|
||||||
|
*/
|
||||||
|
public final class UriHelper {
|
||||||
|
/**
|
||||||
|
* Parses a query string into a hashmap.
|
||||||
|
*
|
||||||
|
* @param q The query string to split.
|
||||||
|
* @return A hashmap containing the key-value pairs from the query string.
|
||||||
|
*/
|
||||||
|
public static HashMap<String, String> parseQueryString(final String q) {
|
||||||
|
if (q == null || q.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final String[] query = q.split("&");
|
||||||
|
// TODO: Look up the HashMap implementation and figure out what the load factor is and make sure we're not reallocating here.
|
||||||
|
final HashMap<String, String> queryMap = new HashMap<>(query.length);
|
||||||
|
for (final String param : query) {
|
||||||
|
final String[] pair = param.split("=");
|
||||||
|
queryMap.put(pair[0], pair.length == 2 && !pair[1].isEmpty() ? pair[1] : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return queryMap;
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import java.util.concurrent.Executors;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.ui.util.QuoteHelper;
|
||||||
|
|
||||||
public class EditMessage extends EmojiWrapperEditText {
|
public class EditMessage extends EmojiWrapperEditText {
|
||||||
|
|
||||||
|
@ -141,7 +142,8 @@ public class EditMessage extends EmojiWrapperEditText {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void insertAsQuote(String text) {
|
public void insertAsQuote(String text) {
|
||||||
text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", "");
|
text = QuoteHelper.replaceAltQuoteCharsInText(text);
|
||||||
|
text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", "");
|
||||||
Editable editable = getEditableText();
|
Editable editable = getEditableText();
|
||||||
int position = getSelectionEnd();
|
int position = getSelectionEnd();
|
||||||
if (position == -1) position = editable.length();
|
if (position == -1) position = editable.length();
|
||||||
|
|
54
src/main/java/eu/siacs/conversations/ui/widget/Marker.java
Normal file
54
src/main/java/eu/siacs/conversations/ui/widget/Marker.java
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package eu.siacs.conversations.ui.widget;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Point;
|
||||||
|
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import org.osmdroid.views.MapView;
|
||||||
|
import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An immutable marker overlay.
|
||||||
|
*/
|
||||||
|
public class Marker extends SimpleLocationOverlay {
|
||||||
|
private final GeoPoint position;
|
||||||
|
private final Bitmap icon;
|
||||||
|
private final Point mapPoint;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a marker overlay which will be drawn at the current Geographical position.
|
||||||
|
*
|
||||||
|
* @param icon A bitmap icon for the marker
|
||||||
|
* @param position The geographic position where the marker will be drawn (if it is inside the view)
|
||||||
|
*/
|
||||||
|
public Marker(final Bitmap icon, final GeoPoint position) {
|
||||||
|
super(icon);
|
||||||
|
this.icon = icon;
|
||||||
|
this.position = position;
|
||||||
|
this.mapPoint = new Point();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a marker overlay which will be drawn centered in the view.
|
||||||
|
*
|
||||||
|
* @param icon A bitmap icon for the marker
|
||||||
|
*/
|
||||||
|
public Marker(final Bitmap icon) {
|
||||||
|
this(icon, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(final Canvas c, final MapView view, final boolean shadow) {
|
||||||
|
super.draw(c, view, shadow);
|
||||||
|
|
||||||
|
// If no position was set for the marker, draw it centered in the view.
|
||||||
|
view.getProjection().toPixels(this.position == null ? view.getMapCenter() : position, mapPoint);
|
||||||
|
|
||||||
|
c.drawBitmap(icon,
|
||||||
|
mapPoint.x - icon.getWidth() / 2,
|
||||||
|
mapPoint.y - icon.getHeight(),
|
||||||
|
null);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
package eu.siacs.conversations.ui.widget;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.Canvas;
|
||||||
|
import android.graphics.Paint;
|
||||||
|
import android.graphics.Point;
|
||||||
|
import android.location.Location;
|
||||||
|
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
import org.osmdroid.util.TileSystem;
|
||||||
|
import org.osmdroid.views.MapView;
|
||||||
|
import org.osmdroid.views.overlay.mylocation.SimpleLocationOverlay;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
|
|
||||||
|
public class MyLocation extends SimpleLocationOverlay {
|
||||||
|
private final GeoPoint position;
|
||||||
|
private final float accuracy;
|
||||||
|
private final Point mapCenterPoint;
|
||||||
|
private final Paint fill;
|
||||||
|
private final Paint outline;
|
||||||
|
|
||||||
|
public MyLocation(final Context ctx, final Bitmap icon, final Location position) {
|
||||||
|
super(icon);
|
||||||
|
this.mapCenterPoint = new Point();
|
||||||
|
this.fill = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
final int accent = StyledAttributes.getColor(ctx, R.attr.colorAccent);
|
||||||
|
fill.setColor(accent);
|
||||||
|
fill.setStyle(Paint.Style.FILL);
|
||||||
|
this.outline = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||||
|
outline.setColor(accent);
|
||||||
|
outline.setAlpha(50);
|
||||||
|
outline.setStyle(Paint.Style.FILL);
|
||||||
|
this.position = new GeoPoint(position);
|
||||||
|
this.accuracy = position.getAccuracy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void draw(final Canvas c, final MapView view, final boolean shadow) {
|
||||||
|
super.draw(c, view, shadow);
|
||||||
|
|
||||||
|
view.getProjection().toPixels(position, mapCenterPoint);
|
||||||
|
c.drawCircle(mapCenterPoint.x, mapCenterPoint.y,
|
||||||
|
Math.max(Config.Map.MY_LOCATION_INDICATOR_SIZE + Config.Map.MY_LOCATION_INDICATOR_OUTLINE_SIZE,
|
||||||
|
accuracy / (float) TileSystem.GroundResolution(position.getLatitude(), view.getZoomLevel())
|
||||||
|
), this.outline);
|
||||||
|
c.drawCircle(mapCenterPoint.x, mapCenterPoint.y, Config.Map.MY_LOCATION_INDICATOR_SIZE, this.fill);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,13 +9,14 @@ import android.widget.ImageView;
|
||||||
import android.widget.LinearLayout;
|
import android.widget.LinearLayout;
|
||||||
import android.widget.RelativeLayout;
|
import android.widget.RelativeLayout;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
||||||
import com.squareup.picasso.Picasso;
|
import com.squareup.picasso.Picasso;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.util.CustomTab;
|
import eu.siacs.conversations.ui.util.CustomTab;
|
||||||
|
@ -174,7 +175,6 @@ public class RichLinkView extends RelativeLayout {
|
||||||
|
|
||||||
private static Map<String, MetaData> linkMap = new HashMap<>();
|
private static Map<String, MetaData> linkMap = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
public void setLink(final String url, final String filename, final boolean dataSaverDisabled, final XmppConnectionService mXmppConnectionService, final int color, final RichPreview.ViewListener viewListener) {
|
public void setLink(final String url, final String filename, final boolean dataSaverDisabled, final XmppConnectionService mXmppConnectionService, final int color, final RichPreview.ViewListener viewListener) {
|
||||||
MetaData data = linkMap.get(url);
|
MetaData data = linkMap.get(url);
|
||||||
if (data == null) {
|
if (data == null) {
|
||||||
|
@ -188,8 +188,8 @@ public class RichLinkView extends RelativeLayout {
|
||||||
linkMap.put(url, metaData);
|
linkMap.put(url, metaData);
|
||||||
}
|
}
|
||||||
initView(dataSaverDisabled, color);
|
initView(dataSaverDisabled, color);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(Exception e) {
|
public void onError(Exception e) {
|
||||||
viewListener.onError(e);
|
viewListener.onError(e);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
@ -25,8 +27,6 @@ import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.ui.SettingsActivity;
|
import eu.siacs.conversations.ui.SettingsActivity;
|
||||||
import eu.siacs.conversations.ui.SettingsFragment;
|
import eu.siacs.conversations.ui.SettingsFragment;
|
||||||
|
|
||||||
import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
|
|
||||||
|
|
||||||
public class Compatibility {
|
public class Compatibility {
|
||||||
private static final List<String> UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList(
|
private static final List<String> UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList(
|
||||||
SettingsActivity.SHOW_FOREGROUND_SERVICE,
|
SettingsActivity.SHOW_FOREGROUND_SERVICE,
|
||||||
|
@ -47,14 +47,6 @@ public class Compatibility {
|
||||||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
|
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean runsTwentyOne() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean runsNineTeen() {
|
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean runsTwentySix() {
|
public static boolean runsTwentySix() {
|
||||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||||
}
|
}
|
||||||
|
@ -94,6 +86,7 @@ public class Compatibility {
|
||||||
return true; //when in doubt…
|
return true; //when in doubt…
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean targetsThirty(Context context) {
|
private static boolean targetsThirty(Context context) {
|
||||||
try {
|
try {
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
final PackageManager packageManager = context.getPackageManager();
|
||||||
|
@ -117,6 +110,7 @@ public class Compatibility {
|
||||||
return true; //when in doubt…
|
return true; //when in doubt…
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean runsAndTargetsTwentyFour(Context context) {
|
public static boolean runsAndTargetsTwentyFour(Context context) {
|
||||||
return runsTwentyFour() && targetsTwentyFour(context);
|
return runsTwentyFour() && targetsTwentyFour(context);
|
||||||
}
|
}
|
||||||
|
@ -124,7 +118,6 @@ public class Compatibility {
|
||||||
public static boolean runsAndTargetsTwentySix(Context context) {
|
public static boolean runsAndTargetsTwentySix(Context context) {
|
||||||
return runsTwentySix() && targetsTwentySix(context);
|
return runsTwentySix() && targetsTwentySix(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean runsAndTargetsThirty(Context context) {
|
public static boolean runsAndTargetsThirty(Context context) {
|
||||||
return runsThirty() && targetsThirty(context);
|
return runsThirty() && targetsThirty(context);
|
||||||
}
|
}
|
||||||
|
@ -180,14 +173,9 @@ public class Compatibility {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
|
||||||
public static boolean hasFeatureCamera(final Context context) {
|
public static boolean hasFeatureCamera(final Context context) {
|
||||||
final PackageManager packageManager = context.getPackageManager();
|
final PackageManager packageManager = context.getPackageManager();
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
|
||||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||||
} else {
|
|
||||||
return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -16,9 +16,9 @@ import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
|
|
||||||
public class EasyOnboardingInvite implements Parcelable {
|
public class EasyOnboardingInvite implements Parcelable {
|
||||||
|
|
||||||
private String domain;
|
private final String domain;
|
||||||
private String uri;
|
private final String uri;
|
||||||
private String landingUrl;
|
private final String landingUrl;
|
||||||
|
|
||||||
protected EasyOnboardingInvite(Parcel in) {
|
protected EasyOnboardingInvite(Parcel in) {
|
||||||
domain = in.readString();
|
domain = in.readString();
|
||||||
|
@ -61,7 +61,6 @@ public class EasyOnboardingInvite implements Parcelable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return getSupportingAccounts(service).size() > 0;
|
return getSupportingAccounts(service).size() > 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Account> getSupportingAccounts(final XmppConnectionService service) {
|
public static List<Account> getSupportingAccounts(final XmppConnectionService service) {
|
||||||
|
@ -88,6 +87,9 @@ public class EasyOnboardingInvite implements Parcelable {
|
||||||
return Strings.isNullOrEmpty(landingUrl) ? uri : landingUrl;
|
return Strings.isNullOrEmpty(landingUrl) ? uri : landingUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getShareableUri() {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
public String getDomain() {
|
public String getDomain() {
|
||||||
return domain;
|
return domain;
|
||||||
|
|
|
@ -1,161 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2011 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package eu.siacs.conversations.utils;
|
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
|
|
||||||
public class ExifHelper {
|
|
||||||
private static final String TAG = "CameraExif";
|
|
||||||
|
|
||||||
public static int getOrientation(InputStream is) {
|
|
||||||
if (is == null) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] buf = new byte[8];
|
|
||||||
int length = 0;
|
|
||||||
|
|
||||||
// ISO/IEC 10918-1:1993(E)
|
|
||||||
while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) {
|
|
||||||
int marker = buf[1] & 0xFF;
|
|
||||||
|
|
||||||
// Check if the marker is a padding.
|
|
||||||
if (marker == 0xFF) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the marker is SOI or TEM.
|
|
||||||
if (marker == 0xD8 || marker == 0x01) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
// Check if the marker is EOI or SOS.
|
|
||||||
if (marker == 0xD9 || marker == 0xDA) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the length and check if it is reasonable.
|
|
||||||
if (!read(is, buf, 2)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
length = pack(buf, 0, 2, false);
|
|
||||||
if (length < 2) {
|
|
||||||
Log.e(TAG, "Invalid length");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
length -= 2;
|
|
||||||
|
|
||||||
// Break if the marker is EXIF in APP1.
|
|
||||||
if (marker == 0xE1 && length >= 6) {
|
|
||||||
if (!read(is, buf, 6)) return 0;
|
|
||||||
length -= 6;
|
|
||||||
if (pack(buf, 0, 4, false) == 0x45786966 &&
|
|
||||||
pack(buf, 4, 2, false) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip other markers.
|
|
||||||
try {
|
|
||||||
is.skip(length);
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
length = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// JEITA CP-3451 Exif Version 2.2
|
|
||||||
if (length > 8) {
|
|
||||||
int offset = 0;
|
|
||||||
byte[] jpeg = new byte[length];
|
|
||||||
if (!read(is, jpeg, length)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identify the byte order.
|
|
||||||
int tag = pack(jpeg, offset, 4, false);
|
|
||||||
if (tag != 0x49492A00 && tag != 0x4D4D002A) {
|
|
||||||
Log.e(TAG, "Invalid byte order");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
boolean littleEndian = (tag == 0x49492A00);
|
|
||||||
|
|
||||||
// Get the offset and check if it is reasonable.
|
|
||||||
int count = pack(jpeg, offset + 4, 4, littleEndian) + 2;
|
|
||||||
if (count < 10 || count > length) {
|
|
||||||
Log.e(TAG, "Invalid offset");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
offset += count;
|
|
||||||
length -= count;
|
|
||||||
|
|
||||||
// Get the count and go through all the elements.
|
|
||||||
count = pack(jpeg, offset - 2, 2, littleEndian);
|
|
||||||
while (count-- > 0 && length >= 12) {
|
|
||||||
// Get the tag and check if it is orientation.
|
|
||||||
tag = pack(jpeg, offset, 2, littleEndian);
|
|
||||||
if (tag == 0x0112) {
|
|
||||||
// We do not really care about type and count, do we?
|
|
||||||
int orientation = pack(jpeg, offset + 8, 2, littleEndian);
|
|
||||||
switch (orientation) {
|
|
||||||
case 1:
|
|
||||||
return 0;
|
|
||||||
case 3:
|
|
||||||
return 180;
|
|
||||||
case 6:
|
|
||||||
return 90;
|
|
||||||
case 8:
|
|
||||||
return 270;
|
|
||||||
}
|
|
||||||
Log.i(TAG, "Unsupported orientation");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
offset += 12;
|
|
||||||
length -= 12;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i(TAG, "Orientation not found");
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int pack(byte[] bytes, int offset, int length,
|
|
||||||
boolean littleEndian) {
|
|
||||||
int step = 1;
|
|
||||||
if (littleEndian) {
|
|
||||||
offset += length - 1;
|
|
||||||
step = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int value = 0;
|
|
||||||
while (length-- > 0) {
|
|
||||||
value = (value << 8) | (bytes[offset] & 0xFF);
|
|
||||||
offset += step;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean read(InputStream is, byte[] buf, int length) {
|
|
||||||
try {
|
|
||||||
return is.read(buf, 0, length) == length;
|
|
||||||
} catch (IOException ex) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -49,7 +49,7 @@ public class GeoHelper {
|
||||||
} catch (NumberFormatException nfe) {
|
} catch (NumberFormatException nfe) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return getMappreviewHost(activity) + "?center=" + latitude + "," + longitude + "&size=500x500&markers=" + latitude + "," + longitude + "&zoom=" + Config.DEFAULT_ZOOM;
|
return getMappreviewHost(activity) + "?center=" + latitude + "," + longitude + "&size=500x500&markers=" + latitude + "," + longitude + "&zoom=" + Config.Map.FINAL_ZOOM_LEVEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getMappreviewHost(Activity activity) {
|
private static String getMappreviewHost(Activity activity) {
|
||||||
|
@ -72,7 +72,7 @@ public class GeoHelper {
|
||||||
try {
|
try {
|
||||||
return URLUtil.isValidUrl(urlstring) && Patterns.WEB_URL.matcher(urlstring).matches();
|
return URLUtil.isValidUrl(urlstring) && Patterns.WEB_URL.matcher(urlstring).matches();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.d(Config.LOGTAG, "Could not use custom mappreview host and using monocles chat for mappreview " + e);
|
Log.d(Config.LOGTAG, "Could not use custom mappreview host and using blabber.im for mappreview " + e);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -110,14 +110,14 @@ public class GeoHelper {
|
||||||
final Conversational conversation = message.getConversation();
|
final Conversational conversation = message.getConversation();
|
||||||
final String label = getLabel(context, message);
|
final String label = getLabel(context, message);
|
||||||
|
|
||||||
Intent locationPluginIntent = new Intent(context, ShowLocationActivity.class);
|
final Intent locationPluginIntent = new Intent(context, ShowLocationActivity.class);
|
||||||
locationPluginIntent.putExtra("latitude", geoPoint.getLatitude());
|
locationPluginIntent.putExtra("latitude", geoPoint.getLatitude());
|
||||||
locationPluginIntent.putExtra("longitude", geoPoint.getLongitude());
|
locationPluginIntent.putExtra("longitude", geoPoint.getLongitude());
|
||||||
if (message.getStatus() != Message.STATUS_RECEIVED) {
|
if (message.getStatus() != Message.STATUS_RECEIVED) {
|
||||||
locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString());
|
locationPluginIntent.putExtra("jid", conversation.getAccount().getJid().toString());
|
||||||
locationPluginIntent.putExtra("name", context.getString(R.string.me));
|
locationPluginIntent.putExtra("name", context.getString(R.string.me));
|
||||||
} else {
|
} else {
|
||||||
Contact contact = message.getContact();
|
final Contact contact = message.getContact();
|
||||||
if (contact != null) {
|
if (contact != null) {
|
||||||
locationPluginIntent.putExtra("name", contact.getDisplayName());
|
locationPluginIntent.putExtra("name", contact.getDisplayName());
|
||||||
locationPluginIntent.putExtra("jid", contact.getJid().toString());
|
locationPluginIntent.putExtra("jid", contact.getJid().toString());
|
||||||
|
|
|
@ -98,7 +98,8 @@ public class ImStyleParser {
|
||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
} else if (c == '\n') {
|
} else if (c == '\n') {
|
||||||
return -1;
|
// ignore line breaks
|
||||||
|
//return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
return -1;
|
||||||
|
|
|
@ -19,8 +19,8 @@ public final class LocationHelper {
|
||||||
|
|
||||||
// Check whether the new location fix is newer or older
|
// Check whether the new location fix is newer or older
|
||||||
final long timeDelta = location.getTime() - prevLoc.getTime();
|
final long timeDelta = location.getTime() - prevLoc.getTime();
|
||||||
final boolean isSignificantlyNewer = timeDelta > Config.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
final boolean isSignificantlyNewer = timeDelta > Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
||||||
final boolean isSignificantlyOlder = timeDelta < -Config.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
final boolean isSignificantlyOlder = timeDelta < -Config.Map.LOCATION_FIX_SIGNIFICANT_TIME_DELTA;
|
||||||
final boolean isNewer = timeDelta > 0;
|
final boolean isNewer = timeDelta > 0;
|
||||||
|
|
||||||
if (isSignificantlyNewer) {
|
if (isSignificantlyNewer) {
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.telephony.TelephonyManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.core.content.ContextCompat;
|
||||||
|
|
||||||
|
import org.osmdroid.util.GeoPoint;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
|
||||||
|
public class LocationProvider {
|
||||||
|
|
||||||
|
public static final GeoPoint FALLBACK = new GeoPoint(0.0, 0.0);
|
||||||
|
|
||||||
|
public static String getUserCountry(final Context context) {
|
||||||
|
try {
|
||||||
|
final TelephonyManager tm = ContextCompat.getSystemService(context, TelephonyManager.class);
|
||||||
|
if (tm == null) {
|
||||||
|
return getUserCountryFallback();
|
||||||
|
}
|
||||||
|
final String simCountry = tm.getSimCountryIso();
|
||||||
|
if (simCountry != null && simCountry.length() == 2) { // SIM country code is available
|
||||||
|
return simCountry.toUpperCase(Locale.US);
|
||||||
|
} else if (tm.getPhoneType() != TelephonyManager.PHONE_TYPE_CDMA) { // device is not 3G (would be unreliable)
|
||||||
|
String networkCountry = tm.getNetworkCountryIso();
|
||||||
|
if (networkCountry != null && networkCountry.length() == 2) { // network country code is available
|
||||||
|
return networkCountry.toUpperCase(Locale.US);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return getUserCountryFallback();
|
||||||
|
} catch (final Exception e) {
|
||||||
|
return getUserCountryFallback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getUserCountryFallback() {
|
||||||
|
final Locale locale = Locale.getDefault();
|
||||||
|
return locale.getCountry();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GeoPoint getGeoPoint(final Context context) {
|
||||||
|
return getGeoPoint(context, getUserCountry(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static synchronized GeoPoint getGeoPoint(final Context context, final String country) {
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(context.getResources().openRawResource(R.raw.countries)))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
final String[] parts = line.split("\\s+", 4);
|
||||||
|
if (parts.length == 4) {
|
||||||
|
if (country.equalsIgnoreCase(parts[0])) {
|
||||||
|
try {
|
||||||
|
return new GeoPoint(Double.parseDouble(parts[1]), Double.parseDouble(parts[2]));
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
return FALLBACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (final IOException e) {
|
||||||
|
Log.d(Config.LOGTAG, "unable to parse country->geo map", e);
|
||||||
|
}
|
||||||
|
return FALLBACK;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -39,6 +39,7 @@ import eu.siacs.conversations.entities.Conversational;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.http.AesGcmURL;
|
import eu.siacs.conversations.http.AesGcmURL;
|
||||||
import eu.siacs.conversations.http.URL;
|
import eu.siacs.conversations.http.URL;
|
||||||
|
import eu.siacs.conversations.ui.util.QuoteHelper;
|
||||||
|
|
||||||
public class MessageUtils {
|
public class MessageUtils {
|
||||||
|
|
||||||
|
@ -68,8 +69,7 @@ public class MessageUtils {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final char c = line.charAt(0);
|
final char c = line.charAt(0);
|
||||||
if (c == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(line, 0)
|
if (QuoteHelper.isNestedTooDeeply(line)) {
|
||||||
|| (c == '\u00bb' && !UIHelper.isPositionFollowedByQuote(line, 0))) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (builder.length() != 0) {
|
if (builder.length() != 0) {
|
||||||
|
@ -118,6 +118,6 @@ public class MessageUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean fileWithKnownSize(Message message) {
|
public static boolean fileWithKnownSize(Message message) {
|
||||||
return message.getType() == Message.TYPE_TEXT && message.isOOb() && message.getFileParams().size > 0 && message.getFileParams().url != null;
|
return message.getType() == Message.TYPE_TEXT && message.isOOb() && message.getFileParams().size != null && message.getFileParams().size > 0 && message.getFileParams().url != null;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -21,15 +21,20 @@ import android.net.Uri;
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Transferable;
|
import eu.siacs.conversations.entities.Transferable;
|
||||||
import eu.siacs.conversations.services.ExportBackupService;
|
import eu.siacs.conversations.services.ExportBackupService;
|
||||||
|
|
||||||
|
@ -275,6 +280,8 @@ public final class MimeUtils {
|
||||||
add("image/ico", "ico");
|
add("image/ico", "ico");
|
||||||
add("image/ief", "ief");
|
add("image/ief", "ief");
|
||||||
add("image/heic", "heic");
|
add("image/heic", "heic");
|
||||||
|
add("image/heif", "heif");
|
||||||
|
add("image/avif", "avif");
|
||||||
// add ".jpg" first so it will be the default for guessExtensionFromMimeType
|
// add ".jpg" first so it will be the default for guessExtensionFromMimeType
|
||||||
add("image/jpeg", "jpg");
|
add("image/jpeg", "jpg");
|
||||||
add("image/jpeg", "jpeg");
|
add("image/jpeg", "jpeg");
|
||||||
|
@ -402,6 +409,16 @@ public final class MimeUtils {
|
||||||
applyOverrides();
|
applyOverrides();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final List<String> DOCUMENT_MIMES = Arrays.asList(
|
||||||
|
"application/pdf",
|
||||||
|
"application/vnd.oasis.opendocument.text",
|
||||||
|
"application/msword",
|
||||||
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
|
"text/x-tex",
|
||||||
|
"text/plain"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
private static void add(String mimeType, String extension) {
|
private static void add(String mimeType, String extension) {
|
||||||
// If we have an existing x -> y mapping, we do not want to
|
// If we have an existing x -> y mapping, we do not want to
|
||||||
// override it with another mapping x -> y2.
|
// override it with another mapping x -> y2.
|
||||||
|
@ -590,15 +607,19 @@ public final class MimeUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) {
|
public static String extractRelevantExtension(final String path, final boolean ignoreCryptoExtension) {
|
||||||
if (path == null || path.isEmpty()) {
|
if (Strings.isNullOrEmpty(path)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
String filename = path.substring(path.lastIndexOf('/') + 1).toLowerCase();
|
final String filenameQueryAnchor = path.substring(path.lastIndexOf('/') + 1);
|
||||||
int dotPosition = filename.lastIndexOf(".");
|
final String filenameQuery = cutBefore(filenameQueryAnchor, '#');
|
||||||
|
final String filename = cutBefore(filenameQuery, '?');
|
||||||
|
final int dotPosition = filename.lastIndexOf('.');
|
||||||
|
|
||||||
if (dotPosition != -1) {
|
if (dotPosition == -1) {
|
||||||
String extension = filename.substring(dotPosition + 1);
|
return null;
|
||||||
|
}
|
||||||
|
final String extension = filename.substring(dotPosition + 1);
|
||||||
// we want the real file extension, not the crypto one
|
// we want the real file extension, not the crypto one
|
||||||
if (ignoreCryptoExtension && Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
|
if (ignoreCryptoExtension && Transferable.VALID_CRYPTO_EXTENSIONS.contains(extension)) {
|
||||||
return extractRelevantExtension(filename.substring(0, dotPosition));
|
return extractRelevantExtension(filename.substring(0, dotPosition));
|
||||||
|
@ -606,6 +627,43 @@ public final class MimeUtils {
|
||||||
return extension;
|
return extension;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
private static String cutBefore(final String input, final char c) {
|
||||||
|
final int position = input.indexOf(c);
|
||||||
|
if (position > 0) {
|
||||||
|
return input.substring(0, position);
|
||||||
|
} else {
|
||||||
|
return input;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String getMimeTypeEmoji(Context context, String mime){
|
||||||
|
String lm;
|
||||||
|
if (mime == null) {
|
||||||
|
lm = context.getString(R.string.unknown);
|
||||||
|
} else if (mime.startsWith("audio/")) {
|
||||||
|
lm = "\uD83C\uDF99"; // studio microphone emoji
|
||||||
|
} else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) {
|
||||||
|
lm = "\uD83D\uDCC6"; // tear-off calendar emoji
|
||||||
|
} else if (mime.equals("text/x-vcard")) {
|
||||||
|
lm = "\uD83D\uDC64"; // silhouette emoji
|
||||||
|
} else if (mime.equals("application/vnd.android.package-archive")) {
|
||||||
|
lm = "\uD83D\uDCF1"; // cell phone emoji
|
||||||
|
} else if (mime.equals("application/zip") || mime.equals("application/rar")) {
|
||||||
|
lm = "\uD83D\uDDC4️"; // filing cabinet emoji
|
||||||
|
} else if (mime.equals("application/epub+zip") || mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
||||||
|
lm = "\uD83D\uDCD6"; // open book emoji
|
||||||
|
} else if (mime.equals(ExportBackupService.MIME_TYPE)) {
|
||||||
|
lm = "\uD83D\uDCBE"; // diskette emoji
|
||||||
|
} else if (DOCUMENT_MIMES.contains(mime)) {
|
||||||
|
lm = "\uD83D\uDCC4"; // page emoji
|
||||||
|
} else if (mime.startsWith("image/")) {
|
||||||
|
lm = "\uD83D\uDDBC️"; // painting emoji
|
||||||
|
} else if (mime.startsWith("video/")) {
|
||||||
|
lm = "\uD83C\uDFAC"; // clapper board emoji
|
||||||
|
} else {
|
||||||
|
lm = "\uD83D\uDCC4"; // page emoji
|
||||||
|
}
|
||||||
|
return lm;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ public final class Namespace {
|
||||||
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
||||||
public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
|
public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
|
||||||
public static final String JINGLE = "urn:xmpp:jingle:1";
|
public static final String JINGLE = "urn:xmpp:jingle:1";
|
||||||
|
public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1";
|
||||||
public static final String JINGLE_MESSAGE = "urn:xmpp:jingle-message:0";
|
public static final String JINGLE_MESSAGE = "urn:xmpp:jingle-message:0";
|
||||||
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
|
public static final String JINGLE_ENCRYPTED_TRANSPORT = "urn:xmpp:jingle:jet:0";
|
||||||
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
|
public static final String JINGLE_ENCRYPTED_TRANSPORT_OMEMO = "urn:xmpp:jingle:jet-omemo:0";
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.regex.Pattern;
|
||||||
* Commonly used regular expression patterns.
|
* Commonly used regular expression patterns.
|
||||||
*/
|
*/
|
||||||
public class Patterns {
|
public class Patterns {
|
||||||
|
|
||||||
public static final Pattern XMPP_PATTERN = Pattern
|
public static final Pattern XMPP_PATTERN = Pattern
|
||||||
.compile("xmpp\\:(?:(?:["
|
.compile("xmpp\\:(?:(?:["
|
||||||
+ Patterns.GOOD_IRI_CHAR
|
+ Patterns.GOOD_IRI_CHAR
|
||||||
|
@ -117,36 +118,6 @@ public class Patterns {
|
||||||
+ "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
|
+ "|(?:\u03b4\u03bf\u03ba\u03b9\u03bc\u03ae|\u0438\u0441\u043f\u044b\u0442\u0430\u043d\u0438\u0435|\u0440\u0444|\u0441\u0440\u0431|\u05d8\u05e2\u05e1\u05d8|\u0622\u0632\u0645\u0627\u06cc\u0634\u06cc|\u0625\u062e\u062a\u0628\u0627\u0631|\u0627\u0644\u0627\u0631\u062f\u0646|\u0627\u0644\u062c\u0632\u0627\u0626\u0631|\u0627\u0644\u0633\u0639\u0648\u062f\u064a\u0629|\u0627\u0644\u0645\u063a\u0631\u0628|\u0627\u0645\u0627\u0631\u0627\u062a|\u0628\u06be\u0627\u0631\u062a|\u062a\u0648\u0646\u0633|\u0633\u0648\u0631\u064a\u0629|\u0641\u0644\u0633\u0637\u064a\u0646|\u0642\u0637\u0631|\u0645\u0635\u0631|\u092a\u0930\u0940\u0915\u094d\u0937\u093e|\u092d\u093e\u0930\u0924|\u09ad\u09be\u09b0\u09a4|\u0a2d\u0a3e\u0a30\u0a24|\u0aad\u0abe\u0ab0\u0aa4|\u0b87\u0ba8\u0bcd\u0ba4\u0bbf\u0baf\u0bbe|\u0b87\u0bb2\u0b99\u0bcd\u0b95\u0bc8|\u0b9a\u0bbf\u0b99\u0bcd\u0b95\u0baa\u0bcd\u0baa\u0bc2\u0bb0\u0bcd|\u0baa\u0bb0\u0bbf\u0b9f\u0bcd\u0b9a\u0bc8|\u0c2d\u0c3e\u0c30\u0c24\u0c4d|\u0dbd\u0d82\u0d9a\u0dcf|\u0e44\u0e17\u0e22|\u30c6\u30b9\u30c8|\u4e2d\u56fd|\u4e2d\u570b|\u53f0\u6e7e|\u53f0\u7063|\u65b0\u52a0\u5761|\u6d4b\u8bd5|\u6e2c\u8a66|\u9999\u6e2f|\ud14c\uc2a4\ud2b8|\ud55c\uad6d|xn\\-\\-0zwm56d|xn\\-\\-11b5bs3a9aj6g|xn\\-\\-3e0b707e|xn\\-\\-45brj9c|xn\\-\\-80akhbyknj4f|xn\\-\\-90a3ac|xn\\-\\-9t4b11yi5a|xn\\-\\-clchc0ea0b2g2a9gcd|xn\\-\\-deba0ad|xn\\-\\-fiqs8s|xn\\-\\-fiqz9s|xn\\-\\-fpcrj9c3d|xn\\-\\-fzc2c9e2c|xn\\-\\-g6w251d|xn\\-\\-gecrj9c|xn\\-\\-h2brj9c|xn\\-\\-hgbk6aj7f53bba|xn\\-\\-hlcj6aya9esc7a|xn\\-\\-j6w193g|xn\\-\\-jxalpdlp|xn\\-\\-kgbechtv|xn\\-\\-kprw13d|xn\\-\\-kpry57d|xn\\-\\-lgbbat1ad8j|xn\\-\\-mgbaam7a8h|xn\\-\\-mgbayh7gpa|xn\\-\\-mgbbh1a71e|xn\\-\\-mgbc0a9azcg|xn\\-\\-mgberp4a5d4ar|xn\\-\\-o3cw4h|xn\\-\\-ogbpf8fl|xn\\-\\-p1ai|xn\\-\\-pgbs0dh|xn\\-\\-s9brj9c|xn\\-\\-wgbh1c|xn\\-\\-wgbl6a|xn\\-\\-xkc2al3hye2a|xn\\-\\-xkc2dl3a5ee0h|xn\\-\\-yfro4i67o|xn\\-\\-ygbi2ammx|xn\\-\\-zckzah|xxx)"
|
||||||
+ "|y[et]"
|
+ "|y[et]"
|
||||||
+ "|z[amw]))";
|
+ "|z[amw]))";
|
||||||
|
|
||||||
public static final Pattern EMAIL_ADDRESS
|
|
||||||
= Pattern.compile(
|
|
||||||
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
|
|
||||||
"\\@" +
|
|
||||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
|
|
||||||
"(" +
|
|
||||||
"\\." +
|
|
||||||
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
|
|
||||||
")+"
|
|
||||||
);
|
|
||||||
/**
|
|
||||||
* This pattern is intended for searching for things that look like they
|
|
||||||
* might be phone numbers in arbitrary text, not for validating whether
|
|
||||||
* something is in fact a phone number. It will miss many things that
|
|
||||||
* are legitimate phone numbers.
|
|
||||||
* <p>
|
|
||||||
* <p> The pattern matches the following:
|
|
||||||
* <ul>
|
|
||||||
* <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
|
|
||||||
* may follow.
|
|
||||||
* <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
|
|
||||||
* <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
|
|
||||||
* </ul>
|
|
||||||
*/
|
|
||||||
public static final Pattern PHONE
|
|
||||||
= Pattern.compile( // sdd = space, dot, or dash
|
|
||||||
"(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
|
|
||||||
+ "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
|
|
||||||
+ "([0-9][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
|
|
||||||
/**
|
/**
|
||||||
* Regular expression to match all IANA top-level domains.
|
* Regular expression to match all IANA top-level domains.
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -295,7 +266,7 @@ public class Patterns {
|
||||||
* IPv4-Embedded IPv6 Address (section 2 of rfc6052)
|
* IPv4-Embedded IPv6 Address (section 2 of rfc6052)
|
||||||
* IPv4-mapped IPv6 addresses (section 2.1 of rfc2765)
|
* IPv4-mapped IPv6 addresses (section 2.1 of rfc2765)
|
||||||
* IPv4-translated addresses (section 2.1 of rfc2765)
|
* IPv4-translated addresses (section 2.1 of rfc2765)
|
||||||
*
|
* <p>
|
||||||
* Taken from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/17871737#17871737
|
* Taken from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/17871737#17871737
|
||||||
*/
|
*/
|
||||||
public static final Pattern IP6_ADDRESS
|
public static final Pattern IP6_ADDRESS
|
||||||
|
@ -361,7 +332,7 @@ public class Patterns {
|
||||||
private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")";
|
private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" + ")";
|
||||||
private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
|
private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD;
|
||||||
public static final Pattern DOMAIN_NAME
|
public static final Pattern DOMAIN_NAME
|
||||||
= Pattern.compile("(" + HOST_NAME + "|" + IP6_ADDRESS + "|" + IP_ADDRESS +")");
|
= Pattern.compile("(" + HOST_NAME + "|" + IP6_ADDRESS + "|" + IP_ADDRESS + ")");
|
||||||
private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/";
|
private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/";
|
||||||
/* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */
|
/* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */
|
||||||
private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
|
private static final String WORD_BOUNDARY = "(?:\\b|$|^)";
|
||||||
|
@ -405,7 +376,7 @@ public class Patterns {
|
||||||
* Regular expression that matches domain names without a TLD
|
* Regular expression that matches domain names without a TLD
|
||||||
*/
|
*/
|
||||||
private static final String RELAXED_DOMAIN_NAME =
|
private static final String RELAXED_DOMAIN_NAME =
|
||||||
"(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")";
|
"(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" + "?)+" + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")";
|
||||||
/**
|
/**
|
||||||
* Regular expression to match strings that do not start with a supported protocol. The TLDs
|
* Regular expression to match strings that do not start with a supported protocol. The TLDs
|
||||||
* are expected to be one of the known TLDs.
|
* are expected to be one of the known TLDs.
|
||||||
|
@ -468,12 +439,35 @@ public class Patterns {
|
||||||
"(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
|
"(?:" + EMAIL_ADDRESS_LOCAL_PART + "@" + EMAIL_ADDRESS_DOMAIN + ")" +
|
||||||
WORD_BOUNDARY + ")"
|
WORD_BOUNDARY + ")"
|
||||||
);
|
);
|
||||||
|
public static final Pattern EMAIL_ADDRESS
|
||||||
|
= Pattern.compile(
|
||||||
|
"[a-zA-Z0-9\\+\\.\\_\\%\\-\\+]{1,256}" +
|
||||||
|
"\\@" +
|
||||||
|
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,64}" +
|
||||||
|
"(" +
|
||||||
|
"\\." +
|
||||||
|
"[a-zA-Z0-9][a-zA-Z0-9\\-]{0,25}" +
|
||||||
|
")+"
|
||||||
|
);
|
||||||
/**
|
/**
|
||||||
* Do not create this static utility class.
|
* This pattern is intended for searching for things that look like they
|
||||||
|
* might be phone numbers in arbitrary text, not for validating whether
|
||||||
|
* something is in fact a phone number. It will miss many things that
|
||||||
|
* are legitimate phone numbers.
|
||||||
|
*
|
||||||
|
* <p> The pattern matches the following:
|
||||||
|
* <ul>
|
||||||
|
* <li>Optionally, a + sign followed immediately by one or more digits. Spaces, dots, or dashes
|
||||||
|
* may follow.
|
||||||
|
* <li>Optionally, sets of digits in parentheses, separated by spaces, dots, or dashes.
|
||||||
|
* <li>A string starting and ending with a digit, containing digits, spaces, dots, and/or dashes.
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
private Patterns() {
|
public static final Pattern PHONE
|
||||||
}
|
= Pattern.compile( // sdd = space, dot, or dash
|
||||||
|
"(\\+[0-9]+[\\- \\.]*)?" // +<digits><sdd>*
|
||||||
|
+ "(\\([0-9]+\\)[\\- \\.]*)?" // (<digits>)<sdd>*
|
||||||
|
+ "([0-9][0-9\\- \\.]+[0-9])"); // <digit><digit|sdd>+<digit>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method to take all of the non-null matching groups in a
|
* Convenience method to take all of the non-null matching groups in a
|
||||||
|
@ -516,4 +510,10 @@ public class Patterns {
|
||||||
}
|
}
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Do not create this static utility class.
|
||||||
|
*/
|
||||||
|
private Patterns() {
|
||||||
|
}
|
||||||
}
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue