=============NEW BASELINE 7.2.4.2==============
2
.github/workflows/android.yml
vendored
|
@ -32,7 +32,7 @@ jobs:
|
||||||
uses: gradle/wrapper-validation-action@v1
|
uses: gradle/wrapper-validation-action@v1
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew qa --parallel
|
run: ./gradlew qa
|
||||||
|
|
||||||
- name: Archive reports for failed build
|
- name: Archive reports for failed build
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
|
|
4
.github/workflows/diffuse.yml
vendored
|
@ -38,7 +38,7 @@ jobs:
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
if: steps.cache-base.outputs.cache-hit != 'true'
|
if: steps.cache-base.outputs.cache-hit != 'true'
|
||||||
run: ./gradlew assemblePlayProdRelease --parallel
|
run: ./gradlew assemblePlayProdRelease
|
||||||
|
|
||||||
- name: Copy base apk
|
- name: Copy base apk
|
||||||
if: steps.cache-base.outputs.cache-hit != 'true'
|
if: steps.cache-base.outputs.cache-hit != 'true'
|
||||||
|
@ -50,7 +50,7 @@ jobs:
|
||||||
clean: 'false'
|
clean: 'false'
|
||||||
|
|
||||||
- name: Build with Gradle
|
- name: Build with Gradle
|
||||||
run: ./gradlew assemblePlayProdRelease --parallel
|
run: ./gradlew assemblePlayProdRelease
|
||||||
|
|
||||||
- name: Copy PR apk
|
- name: Copy PR apk
|
||||||
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
|
run: mv app/build/outputs/apk/playProd/release/*arm64*.apk diffuse-new.apk
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Signal Android
|
# Signal Android
|
||||||
|
|
||||||
Signal is a simple, powerful, and secure messenger.
|
Signal is a simple, powerful, and secure messenger.
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ The form and manner of this distribution makes it eligible for export under the
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright 2013-2023 Signal
|
Copyright 2013-2024 Signal Messenger, LLC
|
||||||
|
|
||||||
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
|
Licensed under the GNU AGPLv3: https://www.gnu.org/licenses/agpl-3.0.html
|
||||||
|
|
||||||
|
@ -70,7 +70,7 @@ Signal – New Base-line
|
||||||
a. thoughtcrime tm
|
a. thoughtcrime tm
|
||||||
b. securesms archive
|
b. securesms archive
|
||||||
3. Replace all old package mentions vie “replace all” function (Ctrl +Shift + R)
|
3. Replace all old package mentions vie “replace all” function (Ctrl +Shift + R)
|
||||||
a. org.thoughtcrime.securesms -> org.tm.archive
|
a. org.tm.archive -> org.tm.archive
|
||||||
4. Add our archiver SDK and Common library to new folder “libs” and compile them via dependencies.
|
4. Add our archiver SDK and Common library to new folder “libs” and compile them via dependencies.
|
||||||
5. Add archiver,intune,selfauthentication folders with all archiving class with util etc. (Take them from src->main->java->org)
|
5. Add archiver,intune,selfauthentication folders with all archiving class with util etc. (Take them from src->main->java->org)
|
||||||
6. Search “ArchiveLogger.Companion.sendArchiveLog” in the current project and add all those mentions to the updated project.
|
6. Search “ArchiveLogger.Companion.sendArchiveLog” in the current project and add all those mentions to the updated project.
|
||||||
|
|
2
apntool/.gitignore
vendored
|
@ -1,2 +0,0 @@
|
||||||
*.db
|
|
||||||
*.db.gz
|
|
|
@ -1,106 +0,0 @@
|
||||||
import sys
|
|
||||||
import re
|
|
||||||
import argparse
|
|
||||||
import sqlite3
|
|
||||||
import gzip
|
|
||||||
from progressbar import ProgressBar, Counter, Timer
|
|
||||||
from lxml import etree
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog='apntool', description="""Process Android's apn xml files and drop them into an
|
|
||||||
easily queryable SQLite db. Tested up to version 9 of
|
|
||||||
their APN file.""")
|
|
||||||
parser.add_argument('-v', '--version', action='version', version='%(prog)s v1.1')
|
|
||||||
parser.add_argument('-i', '--input', help='the xml file to parse', default='apns.xml', required=False)
|
|
||||||
parser.add_argument('-o', '--output', help='the sqlite db output file', default='apns.db', required=False)
|
|
||||||
parser.add_argument('--quiet', help='do not show progress or verbose instructions', action='store_true', required=False)
|
|
||||||
parser.add_argument('--no-gzip', help="do not gzip after creation", action='store_true', required=False)
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
|
|
||||||
def normalized(target):
|
|
||||||
o2_typo = re.compile(r"02\.co\.uk")
|
|
||||||
port_typo = re.compile(r"(\d+\.\d+\.\d+\.\d+)\.(\d+)")
|
|
||||||
leading_zeros = re.compile(r"(/|\.|^)0+(\d+)")
|
|
||||||
subbed = o2_typo.sub(r'o2.co.uk', target)
|
|
||||||
subbed = port_typo.sub(r'\1:\2', subbed)
|
|
||||||
subbed = leading_zeros.sub(r'\1\2', subbed)
|
|
||||||
return subbed
|
|
||||||
|
|
||||||
try:
|
|
||||||
connection = sqlite3.connect(args.output)
|
|
||||||
cursor = connection.cursor()
|
|
||||||
cursor.execute('SELECT SQLITE_VERSION()')
|
|
||||||
version = cursor.fetchone()
|
|
||||||
if not args.quiet:
|
|
||||||
print("SQLite version: %s" % version)
|
|
||||||
print("Opening %s" % args.input)
|
|
||||||
|
|
||||||
cursor.execute("PRAGMA legacy_file_format=ON")
|
|
||||||
cursor.execute("PRAGMA journal_mode=DELETE")
|
|
||||||
cursor.execute("PRAGMA page_size=32768")
|
|
||||||
cursor.execute("VACUUM")
|
|
||||||
cursor.execute("DROP TABLE IF EXISTS apns")
|
|
||||||
cursor.execute("""CREATE TABLE apns(_id INTEGER PRIMARY KEY, mccmnc TEXT, mcc TEXT, mnc TEXT, carrier TEXT,
|
|
||||||
apn TEXT, mmsc TEXT, port INTEGER, type TEXT, protocol TEXT, bearer TEXT, roaming_protocol TEXT,
|
|
||||||
carrier_enabled INTEGER, mmsproxy TEXT, mmsport INTEGER, proxy TEXT, mvno_match_data TEXT,
|
|
||||||
mvno_type TEXT, authtype INTEGER, user TEXT, password TEXT, server TEXT)""")
|
|
||||||
|
|
||||||
apns = etree.parse(args.input)
|
|
||||||
root = apns.getroot()
|
|
||||||
pbar = None
|
|
||||||
if not args.quiet:
|
|
||||||
pbar = ProgressBar(widgets=['Processed: ', Counter(), ' apns (', Timer(), ')'], maxval=len(list(root))).start()
|
|
||||||
|
|
||||||
count = 0
|
|
||||||
for apn in root.iter("apn"):
|
|
||||||
if apn.get("mmsc") is None:
|
|
||||||
continue
|
|
||||||
sqlvars = ["?" for x in apn.attrib.keys()] + ["?"]
|
|
||||||
mccmnc = "%s%s" % (apn.get("mcc"), apn.get("mnc"))
|
|
||||||
normalized_mmsc = normalized(apn.get("mmsc"))
|
|
||||||
if normalized_mmsc != apn.get("mmsc"):
|
|
||||||
print("normalize MMSC: %s => %s" % (apn.get("mmsc"), normalized_mmsc))
|
|
||||||
apn.set("mmsc", normalized_mmsc)
|
|
||||||
|
|
||||||
if not apn.get("mmsproxy") is None:
|
|
||||||
normalized_mmsproxy = normalized(apn.get("mmsproxy"))
|
|
||||||
if normalized_mmsproxy != apn.get("mmsproxy"):
|
|
||||||
print("normalize proxy: %s => %s" % (apn.get("mmsproxy"), normalized_mmsproxy))
|
|
||||||
apn.set("mmsproxy", normalized_mmsproxy)
|
|
||||||
|
|
||||||
values = [apn.get(attrib) for attrib in apn.attrib.keys()] + [mccmnc]
|
|
||||||
keys = apn.attrib.keys() + ["mccmnc"]
|
|
||||||
|
|
||||||
cursor.execute("SELECT 1 FROM apns WHERE mccmnc = ? AND apn = ?", [mccmnc, apn.get("apn")])
|
|
||||||
if cursor.fetchone() is None:
|
|
||||||
statement = "INSERT INTO apns (%s) VALUES (%s)" % (", ".join(keys), ", ".join(sqlvars))
|
|
||||||
cursor.execute(statement, values)
|
|
||||||
|
|
||||||
count += 1
|
|
||||||
if not args.quiet:
|
|
||||||
pbar.update(count)
|
|
||||||
|
|
||||||
if not args.quiet:
|
|
||||||
pbar.finish()
|
|
||||||
connection.commit()
|
|
||||||
print("Successfully written to %s" % args.output)
|
|
||||||
|
|
||||||
if not args.no_gzip:
|
|
||||||
gzipped_file = "%s.gz" % (args.output,)
|
|
||||||
with open(args.output, 'rb') as orig:
|
|
||||||
with gzip.open(gzipped_file, 'wb') as gzipped:
|
|
||||||
gzipped.writelines(orig)
|
|
||||||
print("Successfully gzipped to %s" % gzipped_file)
|
|
||||||
|
|
||||||
if not args.quiet:
|
|
||||||
print("\nTo include this in the distribution, copy it to the project's assets/databases/ directory.")
|
|
||||||
print("If you support API 10 or lower, you must use the gzipped version to avoid corruption.")
|
|
||||||
|
|
||||||
except sqlite3.Error as e:
|
|
||||||
if connection:
|
|
||||||
connection.rollback()
|
|
||||||
print("Error: %s" % e.args[0])
|
|
||||||
sys.exit(1)
|
|
||||||
finally:
|
|
||||||
if connection:
|
|
||||||
connection.close()
|
|
|
@ -1,3 +0,0 @@
|
||||||
argparse>=1.2.1
|
|
||||||
lxml>=3.3.3
|
|
||||||
progressbar-latest>=2.4
|
|
|
@ -24,13 +24,13 @@ plugins {
|
||||||
apply(from = "static-ips.gradle.kts")
|
apply(from = "static-ips.gradle.kts")
|
||||||
|
|
||||||
//**TM_SA**//Start - Change the version code and version name upon the current version
|
//**TM_SA**//Start - Change the version code and version name upon the current version
|
||||||
val canonicalVersionCode = 1338
|
val canonicalVersionCode = 1412
|
||||||
val canonicalVersionName = "6.44.3.0"
|
val canonicalVersionName = "7.2.4.2"
|
||||||
val signal_teleMessage_version = "6.44.3.0"//Change this param in Jenkins builder and delete it.
|
val signal_teleMessage_version = "7.2.4.2"//Change this param in Jenkins builder and delete it.
|
||||||
//**TM_SA**//end
|
//**TM_SA**//end
|
||||||
|
|
||||||
/*val canonicalVersionCode = 1376
|
/*val canonicalVersionCode = 1405
|
||||||
val canonicalVersionName = "6.44.2"*/
|
val canonicalVersionName = "7.2.4"*/
|
||||||
|
|
||||||
val postFixSize = 100
|
val postFixSize = 100
|
||||||
val abiPostFix: Map<String, Int> = mapOf(
|
val abiPostFix: Map<String, Int> = mapOf(
|
||||||
|
@ -44,29 +44,25 @@ val abiPostFix: Map<String, Int> = mapOf(
|
||||||
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
|
val keystores: Map<String, Properties?> = mapOf("debug" to loadKeystoreProperties("keystore.debug.properties"))
|
||||||
|
|
||||||
val selectableVariants = listOf(
|
val selectableVariants = listOf(
|
||||||
"nightlyProdSpinner",
|
"nightlyProdTmSpinner",
|
||||||
"nightlyProdPerf",
|
"nightlyProdTmPerf",
|
||||||
"nightlyProdRelease",
|
"nightlyProdTmRelease",
|
||||||
"nightlyStagingRelease",
|
"nightlyStagingTmRelease",
|
||||||
"nightlyPnpPerf",
|
"playProdTmDebug",
|
||||||
"nightlyPnpRelease",
|
"playProdTmSpinner",
|
||||||
"playProdDebug",
|
"playProdTmCanary",
|
||||||
"playProdSpinner",
|
"playProdTmPerf",
|
||||||
"playProdCanary",
|
"playProdTmBenchmark",
|
||||||
"playProdPerf",
|
"playProdTmInstrumentation",
|
||||||
"playProdBenchmark",
|
"playProdTmRelease",
|
||||||
"playProdInstrumentation",
|
"playStagingTmDebug",
|
||||||
"playProdRelease",
|
"playStagingTmCanary",
|
||||||
"playStagingDebug",
|
"playStagingTmSpinner",
|
||||||
"playStagingCanary",
|
"playStagingTmPerf",
|
||||||
"playStagingSpinner",
|
"playStagingTmInstrumentation",
|
||||||
"playStagingPerf",
|
"playStagingTmRelease",
|
||||||
"playStagingInstrumentation",
|
"websiteProdTmSpinner",
|
||||||
"playPnpDebug",
|
"websiteProdTmRelease"
|
||||||
"playPnpSpinner",
|
|
||||||
"playStagingRelease",
|
|
||||||
"websiteProdSpinner",
|
|
||||||
"websiteProdRelease"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val signalBuildToolsVersion: String by rootProject.extra
|
val signalBuildToolsVersion: String by rootProject.extra
|
||||||
|
@ -99,14 +95,13 @@ android {
|
||||||
|
|
||||||
buildToolsVersion = signalBuildToolsVersion
|
buildToolsVersion = signalBuildToolsVersion
|
||||||
compileSdkVersion = signalCompileSdkVersion
|
compileSdkVersion = signalCompileSdkVersion
|
||||||
|
//**TM_SA**//add "ext"..
|
||||||
flavorDimensions += listOf("distribution", "environment", "ext")
|
flavorDimensions += listOf("distribution", "environment", "ext")
|
||||||
useLibrary("org.apache.http.legacy")
|
useLibrary("org.apache.http.legacy")
|
||||||
testBuildType = "instrumentation"
|
testBuildType = "instrumentation"
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = signalKotlinJvmTarget
|
jvmTarget = signalKotlinJvmTarget
|
||||||
freeCompilerArgs = listOf("-Xallow-result-return-type")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
keystores["debug"]?.let { properties ->
|
keystores["debug"]?.let { properties ->
|
||||||
|
@ -183,8 +178,8 @@ android {
|
||||||
versionCode = canonicalVersionCode * postFixSize
|
versionCode = canonicalVersionCode * postFixSize
|
||||||
versionName = canonicalVersionName
|
versionName = canonicalVersionName
|
||||||
|
|
||||||
minSdkVersion(signalMinSdkVersion)
|
minSdk = signalMinSdkVersion
|
||||||
targetSdkVersion(signalTargetSdkVersion)
|
targetSdk = signalTargetSdkVersion
|
||||||
|
|
||||||
multiDexEnabled = true
|
multiDexEnabled = true
|
||||||
|
|
||||||
|
@ -222,10 +217,9 @@ android {
|
||||||
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
|
buildConfigField("String[]", "SIGNAL_SVR2_IPS", rootProject.extra["svr2_ips"] as String)
|
||||||
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
|
buildConfigField("String", "SIGNAL_AGENT", "\"OWA\"")
|
||||||
buildConfigField("String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"")
|
buildConfigField("String", "CDSI_MRENCLAVE", "\"0f6fd79cdfdaa5b2e6337f534d3baf999318b0c462a7ac1f41297a3e4b424a57\"")
|
||||||
buildConfigField("String", "SVR2_MRENCLAVE_DEPRECATED", "\"6ee1042f9e20f880326686dd4ba50c25359f01e9f733eeba4382bca001d45094\"")
|
|
||||||
buildConfigField("String", "SVR2_MRENCLAVE", "\"a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97\"")
|
buildConfigField("String", "SVR2_MRENCLAVE", "\"a6622ad4656e1abcd0bc0ff17c229477747d2ded0495c4ebee7ed35c1789fa97\"")
|
||||||
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"")
|
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF\"")
|
||||||
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0I=\"")
|
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X36nOoGPs54XsEGzPdEV+itQNGUFEjY6X9Uv+Acuks7NpyGvCoKxGwgKgE5XyJ+nNKlyHHOLb6N1NuHyBrZrgtY/JYJHRooo5CEqYKBqdFnmbTVGEkCvJKxLnjwKWf+fEPoWeQFj5ObDjcKMZf2Jm2Ae69x+ikU5gBXsRmoF94GXTLfN0/vLt98KDPnxwAQL9j5V1jGOY8jQl6MLxEs56cwXN0dqCnImzVH3TZT1cJ8SW1BRX6qIVxEzjsSGx3yxF3suAilPMqGRp4ffyopjMD1JXiKR2RwLKzizUe5e8XyGOy9fplzhw3jVzTRyUZTRSZKkMLWcQ/gv0E4aONNqs4P+NameAZYOD12qRkxosQQP5uux6B2nRyZ7sAV54DgFyLiRcq1FvwKw2EPQdk4HDoePrO/RNUbyNddnM/mMgj4FW65xCoT1LmjrIjsv/Ggdlx46ueczhMgtBunx1/w8k8V+l8LVZ8gAT6wkU5J+DPQalQguMg12Jzug3q4TbdHiGCmD9EunCwOmsLuLJkz6EcSYXtrlDEnAM+hicw7iergYLLlMXpfTdGxJCWJmP4zqUFeTTmsmhsjGBt7NiEB/9pFFEB3pSbf4iiUukw63Eo8Aqnf4iwob6X1QviCWuc8t0LUlT9vALgh/f2DPVOOmR0RW6bgRvc7DSF20V/omg+YBw==\"")
|
||||||
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
|
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AByD873dTilmOSG0TjKrvpeaKEsUmIO8Vx9BeMmftwUs9v7ikPwM8P3OHyT0+X3EUMZrSe9VUp26Wai51Q9I8mdk0hX/yo7CeFGJyzoOqn8e/i4Ygbn5HoAyXJx5eXfIbqpc0bIxzju4H/HOQeOpt6h742qii5u/cbwOhFZCsMIbElZTaeU+BWMBQiZHIGHT5IE0qCordQKZ5iPZom0HeFa8Yq0ShuEyAl0WINBiY6xE3H/9WnvzXBbMuuk//eRxXgzO8ieCeK8FwQNxbfXqZm6Ro1cMhCOF3u7xoX83QhpN\"")
|
||||||
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
|
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AJwNSU55fsFCbgaxGRD11wO1juAs8Yr5GF8FPlGzzvdJJIKH5/4CC7ZJSOe3yL2vturVaRU2Cx0n751Vt8wkj1bozK3CBV1UokxV09GWf+hdVImLGjXGYLLhnI1J2TWEe7iWHyb553EEnRb5oxr9n3lUbNAJuRmFM7hrr0Al0F0wrDD4S8lo2mGaXe0MJCOM166F8oYRQqpFeEHfiLnxA1O8ZLh7vMdv4g9jI5phpRBTsJ5IjiJrWeP0zdIGHEssUeprDZ9OUJ14m0v61eYJMKsf59Bn+mAT2a7YfB+Don9O\"")
|
||||||
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
|
buildConfigField("String[]", "LANGUAGES", "new String[]{ ${languageList().map { "\"$it\"" }.joinToString(separator = ", ")} }")
|
||||||
|
@ -237,6 +231,7 @@ android {
|
||||||
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
|
buildConfigField("String", "GIPHY_API_KEY", "\"3o6ZsYH6U6Eri53TXy\"")
|
||||||
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
|
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/registration/generate.html\"")
|
||||||
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
|
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/challenge/generate.html\"")
|
||||||
|
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.PRODUCTION")
|
||||||
|
|
||||||
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
|
buildConfigField("String", "BUILD_DISTRIBUTION_TYPE", "\"unset\"")
|
||||||
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
|
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"unset\"")
|
||||||
|
@ -244,6 +239,7 @@ android {
|
||||||
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
|
buildConfigField("String", "BADGE_STATIC_ROOT", "\"https://updates2.signal.org/static/badges/\"")
|
||||||
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
|
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_live_6cmGZopuTsV8novGgJJW9JpC00vLIgtQ1D\"")
|
||||||
buildConfigField("boolean", "TRACING_ENABLED", "false")
|
buildConfigField("boolean", "TRACING_ENABLED", "false")
|
||||||
|
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "false")
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64")
|
||||||
|
@ -307,7 +303,7 @@ android {
|
||||||
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
|
proguardFiles(*buildTypes["debug"].proguardFiles.toTypedArray())
|
||||||
//**TM_SA**//Start
|
//**TM_SA**//Start
|
||||||
signingConfig = signingConfigs["release"]
|
signingConfig = signingConfigs["release"]
|
||||||
//**TM_SA**//End
|
//**TM_SA**//End
|
||||||
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
|
buildConfigField("String", "BUILD_VARIANT_TYPE", "\"Release\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,29 +406,21 @@ android {
|
||||||
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"")
|
buildConfigField("String", "SIGNAL_CDSI_URL", "\"https://cdsi.staging.signal.org\"")
|
||||||
buildConfigField("String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"")
|
buildConfigField("String", "SIGNAL_KEY_BACKUP_URL", "\"https://api-staging.backup.signal.org\"")
|
||||||
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\"")
|
buildConfigField("String", "SIGNAL_SVR2_URL", "\"https://svr2.staging.signal.org\"")
|
||||||
buildConfigField("String", "SVR2_MRENCLAVE_DEPRECATED", "\"a8a261420a6bb9b61aa25bf8a79e8bd20d7652531feb3381cbffd446d270be95\"")
|
|
||||||
buildConfigField("String", "SVR2_MRENCLAVE", "\"acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482\"")
|
buildConfigField("String", "SVR2_MRENCLAVE", "\"acb1973aa0bbbd14b3b4e06f145497d948fd4a98efc500fcce363b3b743ec482\"")
|
||||||
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"")
|
buildConfigField("String", "UNIDENTIFIED_SENDER_TRUST_ROOT", "\"BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx\"")
|
||||||
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCM=\"")
|
buildConfigField("String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"ABSY21VckQcbSXVNCGRYJcfWHiAMZmpTtTELcDmxgdFbtp/bWsSxZdMKzfCp8rvIs8ocCU3B37fT3r4Mi5qAemeGeR2X+/YmOGR5ofui7tD5mDQfstAI9i+4WpMtIe8KC3wU5w3Inq3uNWVmoGtpKndsNfwJrCg0Hd9zmObhypUnSkfYn2ooMOOnBpfdanRtrvetZUayDMSC5iSRcXKpdlukrpzzsCIvEwjwQlJYVPOQPj4V0F4UXXBdHSLK05uoPBCQG8G9rYIGedYsClJXnbrgGYG3eMTG5hnx4X4ntARBgELuMWWUEEfSK0mjXg+/2lPmWcTZWR9nkqgQQP0tbzuiPm74H2wMO4u1Wafe+UwyIlIT9L7KLS19Aw8r4sPrXZSSsOZ6s7M1+rTJN0bI5CKY2PX29y5Ok3jSWufIKcgKOnWoP67d5b2du2ZVJjpjfibNIHbT/cegy/sBLoFwtHogVYUewANUAXIaMPyCLRArsKhfJ5wBtTminG/PAvuBdJ70Z/bXVPf8TVsR292zQ65xwvWTejROW6AZX6aqucUjlENAErBme1YHmOSpU6tr6doJ66dPzVAWIanmO/5mgjNEDeK7DDqQdB1xd03HT2Qs2TxY3kCK8aAb/0iM0HQiXjxZ9HIgYhbtvGEnDKW5ILSUydqH/KBhW4Pb0jZWnqN/YgbWDKeJxnDbYcUob5ZY5Lt5ZCMKuaGUvCJRrCtuugSMaqjowCGRempsDdJEt+cMaalhZ6gczklJB/IbdwENW9KeVFPoFNFzhxWUIS5ML9riVYhAtE6JE5jX0xiHNVIIPthb458cfA8daR0nYfYAUKogQArm0iBezOO+mPk5vCNWI+wwkyFCqNDXz/qxl1gAntuCJtSfq9OC3NkdhQlgYQ==\"")
|
||||||
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
|
buildConfigField("String", "GENERIC_SERVER_PUBLIC_PARAMS", "\"AHILOIrFPXX9laLbalbA9+L1CXpSbM/bTJXZGZiuyK1JaI6dK5FHHWL6tWxmHKYAZTSYmElmJ5z2A5YcirjO/yfoemE03FItyaf8W1fE4p14hzb5qnrmfXUSiAIVrhaXVwIwSzH6RL/+EO8jFIjJ/YfExfJ8aBl48CKHgu1+A6kWynhttonvWWx6h7924mIzW0Czj2ROuh4LwQyZypex4GuOPW8sgIT21KNZaafgg+KbV7XM1x1tF3XA17B4uGUaDbDw2O+nR1+U5p6qHPzmJ7ggFjSN6Utu+35dS1sS0P9N\"")
|
||||||
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
|
buildConfigField("String", "BACKUP_SERVER_PUBLIC_PARAMS", "\"AHYrGb9IfugAAJiPKp+mdXUx+OL9zBolPYHYQz6GI1gWjpEu5me3zVNSvmYY4zWboZHif+HG1sDHSuvwFd0QszSwuSF4X4kRP3fJREdTZ5MCR0n55zUppTwfHRW2S4sdQ0JGz7YDQIJCufYSKh0pGNEHL6hv79Agrdnr4momr3oXdnkpVBIp3HWAQ6IbXQVSG18X36GaicI1vdT0UFmTwU2KTneluC2eyL9c5ff8PcmiS+YcLzh0OKYQXB5ZfQ06d6DiINvDQLy75zcfUOniLAj0lGJiHxGczin/RXisKSR8\"")
|
||||||
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
|
buildConfigField("String", "MOBILE_COIN_ENVIRONMENT", "\"testnet\"")
|
||||||
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
|
buildConfigField("String", "SIGNAL_CAPTCHA_URL", "\"https://signalcaptchas.org/staging/registration/generate.html\"")
|
||||||
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
|
buildConfigField("String", "RECAPTCHA_PROOF_URL", "\"https://signalcaptchas.org/staging/challenge/generate.html\"")
|
||||||
|
buildConfigField("org.signal.libsignal.net.Network.Environment", "LIBSIGNAL_NET_ENV", "org.signal.libsignal.net.Network.Environment.STAGING")
|
||||||
|
|
||||||
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
|
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Staging\"")
|
||||||
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
|
buildConfigField("String", "STRIPE_PUBLISHABLE_KEY", "\"pk_test_sngOd8FnXNkpce9nPXawKrJD00kIDngZkD\"")
|
||||||
|
buildConfigField("boolean", "MESSAGE_BACKUP_RESTORE_ENABLED", "true")
|
||||||
}
|
}
|
||||||
|
//**TM_SA**//add create..
|
||||||
create("pnp") {
|
|
||||||
dimension = "environment"
|
|
||||||
|
|
||||||
initWith(getByName("staging"))
|
|
||||||
applicationIdSuffix = ".pnp"
|
|
||||||
|
|
||||||
buildConfigField("String", "BUILD_ENVIRONMENT_TYPE", "\"Pnp\"")
|
|
||||||
}
|
|
||||||
|
|
||||||
create("tm") {
|
create("tm") {
|
||||||
dimension = "ext"
|
dimension = "ext"
|
||||||
}
|
}
|
||||||
|
@ -446,9 +434,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
applicationVariants.all {
|
applicationVariants.all {
|
||||||
val variant = this
|
outputs
|
||||||
|
|
||||||
variant.outputs
|
|
||||||
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
.map { it as com.android.build.gradle.internal.api.ApkVariantOutputImpl }
|
||||||
.forEach { output ->
|
.forEach { output ->
|
||||||
if (output.baseName.contains("nightly")) {
|
if (output.baseName.contains("nightly")) {
|
||||||
|
@ -461,10 +447,10 @@ android {
|
||||||
output.versionNameOverride = tag
|
output.versionNameOverride = tag
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-${output.versionNameOverride}.apk")
|
||||||
} else {
|
} else {
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
output.outputFileName = output.outputFileName.replace(".apk", "-${variant.versionName}.apk")
|
output.outputFileName = output.outputFileName.replace(".apk", "-$versionName.apk")
|
||||||
|
|
||||||
val abiName: String = output.getFilter("ABI") ?: "universal"
|
val abiName: String = output.getFilter("ABI") ?: "universal"
|
||||||
val postFix: Int = abiPostFix[abiName]!!
|
val postFix: Int = abiPostFix[abiName]!!
|
||||||
|
@ -478,25 +464,20 @@ android {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.variantFilter {
|
androidComponents {
|
||||||
val distribution: String = flavors[0].name
|
beforeVariants { variant ->
|
||||||
val environment: String = flavors[1].name
|
variant.enable = variant.name in selectableVariants
|
||||||
val buildType: String = buildType.name
|
|
||||||
val fullName: String = distribution + environment.capitalize() + buildType.capitalize()
|
|
||||||
|
|
||||||
if (!selectableVariants.contains(fullName)) {
|
|
||||||
ignore = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
android.buildTypes.forEach {
|
val releaseDir = "$projectDir/src/release/java"
|
||||||
val path: String = if (it.name == "release") {
|
val debugDir = "$projectDir/src/debug/java"
|
||||||
"$projectDir/src/release/java"
|
|
||||||
} else {
|
|
||||||
"$projectDir/src/debug/java"
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets.findByName(it.name)!!.java.srcDir(path)
|
android.buildTypes.configureEach {
|
||||||
|
val path = if (name == "release") releaseDir else debugDir
|
||||||
|
sourceSets.named(name) {
|
||||||
|
java.srcDir(path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -515,10 +496,8 @@ dependencies {
|
||||||
implementation(project(":donations"))
|
implementation(project(":donations"))
|
||||||
implementation(project(":contacts"))
|
implementation(project(":contacts"))
|
||||||
implementation(project(":qr"))
|
implementation(project(":qr"))
|
||||||
implementation(project(":sms-exporter"))
|
|
||||||
implementation(project(":sticky-header-grid"))
|
implementation(project(":sticky-header-grid"))
|
||||||
implementation(project(":photoview"))
|
implementation(project(":photoview"))
|
||||||
implementation(project(":glide-webp"))
|
|
||||||
implementation(project(":core-ui"))
|
implementation(project(":core-ui"))
|
||||||
|
|
||||||
implementation(libs.androidx.fragment.ktx)
|
implementation(libs.androidx.fragment.ktx)
|
||||||
|
@ -538,18 +517,22 @@ dependencies {
|
||||||
implementation(libs.androidx.exifinterface)
|
implementation(libs.androidx.exifinterface)
|
||||||
implementation(libs.androidx.compose.rxjava3)
|
implementation(libs.androidx.compose.rxjava3)
|
||||||
implementation(libs.androidx.compose.runtime.livedata)
|
implementation(libs.androidx.compose.runtime.livedata)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.constraintlayout)
|
implementation(libs.androidx.constraintlayout)
|
||||||
implementation(libs.androidx.multidex)
|
implementation(libs.androidx.multidex)
|
||||||
implementation(libs.androidx.navigation.fragment.ktx)
|
implementation(libs.androidx.navigation.fragment.ktx)
|
||||||
implementation(libs.androidx.navigation.ui.ktx)
|
implementation(libs.androidx.navigation.ui.ktx)
|
||||||
|
implementation(libs.androidx.navigation.compose)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
implementation(libs.androidx.lifecycle.viewmodel.ktx)
|
||||||
implementation(libs.androidx.lifecycle.livedata.ktx)
|
implementation(libs.androidx.lifecycle.livedata.ktx)
|
||||||
implementation(libs.androidx.lifecycle.process)
|
implementation(libs.androidx.lifecycle.process)
|
||||||
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
implementation(libs.androidx.lifecycle.viewmodel.savedstate)
|
||||||
implementation(libs.androidx.lifecycle.common.java8)
|
implementation(libs.androidx.lifecycle.common.java8)
|
||||||
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
|
implementation(libs.androidx.lifecycle.reactivestreams.ktx)
|
||||||
|
implementation(libs.androidx.activity.compose)
|
||||||
implementation(libs.androidx.camera.core)
|
implementation(libs.androidx.camera.core)
|
||||||
implementation(libs.androidx.camera.camera2)
|
implementation(libs.androidx.camera.camera2)
|
||||||
|
implementation(libs.androidx.camera.extensions)
|
||||||
implementation(libs.androidx.camera.lifecycle)
|
implementation(libs.androidx.camera.lifecycle)
|
||||||
implementation(libs.androidx.camera.view)
|
implementation(libs.androidx.camera.view)
|
||||||
implementation(libs.androidx.concurrent.futures)
|
implementation(libs.androidx.concurrent.futures)
|
||||||
|
@ -588,10 +571,6 @@ dependencies {
|
||||||
implementation(libs.android.tooltips) {
|
implementation(libs.android.tooltips) {
|
||||||
exclude(group = "com.android.support", module = "appcompat-v7")
|
exclude(group = "com.android.support", module = "appcompat-v7")
|
||||||
}
|
}
|
||||||
implementation(libs.android.smsmms) {
|
|
||||||
exclude(group = "com.squareup.okhttp", module = "okhttp")
|
|
||||||
exclude(group = "com.squareup.okhttp", module = "okhttp-urlconnection")
|
|
||||||
}
|
|
||||||
implementation(libs.stream)
|
implementation(libs.stream)
|
||||||
implementation(libs.lottie)
|
implementation(libs.lottie)
|
||||||
implementation(libs.signal.android.database.sqlcipher)
|
implementation(libs.signal.android.database.sqlcipher)
|
||||||
|
@ -656,7 +635,8 @@ dependencies {
|
||||||
androidTestUtil(testLibs.androidx.test.orchestrator)
|
androidTestUtil(testLibs.androidx.test.orchestrator)
|
||||||
|
|
||||||
//**TM_SA**//Start
|
//**TM_SA**//Start
|
||||||
implementation (libs.okhttp3)
|
// implementation (libs.okhttp3)
|
||||||
|
implementation(libs.square.okhttp3)
|
||||||
implementation (libs.okhttpUrlconnection)
|
implementation (libs.okhttpUrlconnection)
|
||||||
implementation (libs.loggingInterceptor)
|
implementation (libs.loggingInterceptor)
|
||||||
implementation (libs.retrofit2)
|
implementation (libs.retrofit2)
|
||||||
|
@ -667,13 +647,15 @@ dependencies {
|
||||||
implementation (libs.commonsLang3)
|
implementation (libs.commonsLang3)
|
||||||
implementation (libs.commonsIo)
|
implementation (libs.commonsIo)
|
||||||
implementation (libs.commonsText)
|
implementation (libs.commonsText)
|
||||||
|
implementation (libs.adapterRxjava)
|
||||||
|
|
||||||
implementation (group = "commons-io", name = "commons-io", version = "2.6") //For test copy file
|
implementation (group = "commons-io", name = "commons-io", version = "2.6") //For test copy file
|
||||||
debugImplementation(files("libs/androidcopysdk-signal-debug.aar"))
|
debugImplementation(files("libs/androidcopysdk-signal-debug.aar"))
|
||||||
releaseImplementation(files("libs/androidcopysdk-signal-release.aar"))
|
releaseImplementation(files("libs/androidcopysdk-signal-release.aar"))
|
||||||
debugImplementation(files("libs/authenticatorsdk-signal-debug.aar"))
|
debugImplementation(files("libs/authenticatorsdk-signal-debug.aar"))
|
||||||
releaseImplementation(files("libs/authenticatorsdk-signal-release.aar"))
|
releaseImplementation(files("libs/authenticatorsdk-signal-release.aar"))
|
||||||
implementation(files("libs/common-debug.aar"))
|
debugImplementation(files("libs/common-debug.aar"))
|
||||||
|
releaseImplementation(files("libs/common-release.aar"))
|
||||||
// Include the MAM SDK
|
// Include the MAM SDK
|
||||||
implementation("com.arthenica:mobile-ffmpeg-full:4.4.LTS")
|
implementation("com.arthenica:mobile-ffmpeg-full:4.4.LTS")
|
||||||
implementation (files("MAMSDK/Microsoft.Intune.MAM.SDK.aar"))
|
implementation (files("MAMSDK/Microsoft.Intune.MAM.SDK.aar"))
|
||||||
|
@ -691,7 +673,6 @@ fun assertIsGitRepo() {
|
||||||
|
|
||||||
fun getLastCommitTimestamp(): String {
|
fun getLastCommitTimestamp(): String {
|
||||||
assertIsGitRepo()
|
assertIsGitRepo()
|
||||||
|
|
||||||
ByteArrayOutputStream().use { os ->
|
ByteArrayOutputStream().use { os ->
|
||||||
exec {
|
exec {
|
||||||
executable = "git"
|
executable = "git"
|
||||||
|
@ -717,7 +698,6 @@ fun getGitHash(): String {
|
||||||
|
|
||||||
fun getCurrentGitTag(): String? {
|
fun getCurrentGitTag(): String? {
|
||||||
assertIsGitRepo()
|
assertIsGitRepo()
|
||||||
|
|
||||||
val stdout = ByteArrayOutputStream()
|
val stdout = ByteArrayOutputStream()
|
||||||
exec {
|
exec {
|
||||||
commandLine = listOf("git", "tag", "--points-at", "HEAD")
|
commandLine = listOf("git", "tag", "--points-at", "HEAD")
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
#include <linux/memfd.h>
|
#include <linux/memfd.h>
|
||||||
#include <syscall.h>
|
#include <syscall.h>
|
||||||
//**TM_SA**//Change the package name to be our name.
|
//**TM_SA**//Change the package name to be our name.
|
||||||
jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
|
jint JNICALL Java_tm_archive_securesms_util_FileUtils_getFileDescriptorOwner
|
||||||
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
|
(JNIEnv *env, jclass clazz, jobject fileDescriptor)
|
||||||
{
|
{
|
||||||
jclass fdClass = env->GetObjectClass(fileDescriptor);
|
jclass fdClass = env->GetObjectClass(fileDescriptor);
|
||||||
|
@ -31,9 +31,8 @@ jint JNICALL Java_org_tm_archive_util_FileUtils_getFileDescriptorOwner
|
||||||
|
|
||||||
return stat_struct.st_uid;
|
return stat_struct.st_uid;
|
||||||
}
|
}
|
||||||
|
|
||||||
//**TM_SA**//Change the package name to be our name.
|
//**TM_SA**//Change the package name to be our name.
|
||||||
JNIEXPORT jint JNICALL Java_org_tm_archive_util_FileUtils_createMemoryFileDescriptor
|
JNIEXPORT jint JNICALL Java_tm_archive_securesms_util_FileUtils_createMemoryFileDescriptor
|
||||||
(JNIEnv *env, jclass clazz, jstring jname)
|
(JNIEnv *env, jclass clazz, jstring jname)
|
||||||
{
|
{
|
||||||
const char *name = env->GetStringUTFChars(jname, NULL);
|
const char *name = env->GetStringUTFChars(jname, NULL);
|
||||||
|
|
BIN
app/libs/common-release.aar
Normal file
|
@ -6,12 +6,11 @@
|
||||||
message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
|
message="Call requires permission which may be rejected by user: code should explicitly check to see if permission is available (with `checkPermission`) or explicitly handle a potential `SecurityException`"
|
||||||
errorLine1=" List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
|
errorLine1=" List<SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();"
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
<!--//**TM_SA**//Start-->
|
<!--//**TM_SA**//change path name-->
|
||||||
<location
|
<location
|
||||||
file="src/main/java/org/tm/archive/util/dualsim/SubscriptionManagerCompat.java"
|
file="src/main/java/org/tm/archive/util/dualsim/SubscriptionManagerCompat.java"
|
||||||
line="101"
|
line="101"
|
||||||
column="35"/>
|
column="35"/>
|
||||||
<!--//**TM_SA**//End-->
|
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
|
|
|
@ -2,7 +2,9 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
-keepattributes SourceFile,LineNumberTable
|
-keepattributes SourceFile,LineNumberTable
|
||||||
-keep class org.whispersystems.** { *; }
|
-keep class org.whispersystems.** { *; }
|
||||||
|
-keep class org.signal.libsignal.net.** { *; }
|
||||||
-keep class org.signal.libsignal.protocol.** { *; }
|
-keep class org.signal.libsignal.protocol.** { *; }
|
||||||
|
-keep class org.signal.libsignal.usernames.** { *; }
|
||||||
-keep class org.tm.archive.** { *; }
|
-keep class org.tm.archive.** { *; }
|
||||||
-keep class org.signal.donations.json.** { *; }
|
-keep class org.signal.donations.json.** { *; }
|
||||||
-keepclassmembers class ** {
|
-keepclassmembers class ** {
|
||||||
|
@ -19,4 +21,46 @@
|
||||||
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
|
-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
|
||||||
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
-dontwarn org.slf4j.impl.StaticLoggerBinder
|
||||||
-dontwarn sun.net.spi.nameservice.NameService
|
-dontwarn sun.net.spi.nameservice.NameService
|
||||||
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
|
-dontwarn sun.net.spi.nameservice.NameServiceDescriptor
|
||||||
|
|
||||||
|
#TM_SA start
|
||||||
|
-keepclassmembers class com.tm.authenticatorsdk.** {
|
||||||
|
public <init>();
|
||||||
|
}
|
||||||
|
-keepclassmembers class com.tm.androidcopysdk.** {
|
||||||
|
public <init>();
|
||||||
|
}
|
||||||
|
|
||||||
|
-dontwarn com.github.underscore.lodash.$
|
||||||
|
-dontwarn com.google.crypto.tink.subtle.Ed25519Sign$KeyPair
|
||||||
|
-dontwarn com.google.crypto.tink.subtle.Ed25519Sign
|
||||||
|
-dontwarn com.google.crypto.tink.subtle.Ed25519Verify
|
||||||
|
-dontwarn com.google.crypto.tink.subtle.X25519
|
||||||
|
-dontwarn dalvik.system.VMStack
|
||||||
|
-dontwarn java.lang.ProcessHandle
|
||||||
|
-dontwarn java.lang.management.ManagementFactory
|
||||||
|
-dontwarn java.lang.management.RuntimeMXBean
|
||||||
|
-dontwarn javax.naming.InitialContext
|
||||||
|
-dontwarn javax.naming.NameNotFoundException
|
||||||
|
-dontwarn javax.naming.NamingException
|
||||||
|
-dontwarn org.bouncycastle.asn1.ASN1Encodable
|
||||||
|
-dontwarn org.bouncycastle.asn1.pkcs.PrivateKeyInfo
|
||||||
|
-dontwarn org.bouncycastle.asn1.x509.AlgorithmIdentifier
|
||||||
|
-dontwarn org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||||
|
-dontwarn org.bouncycastle.cert.X509CertificateHolder
|
||||||
|
-dontwarn org.bouncycastle.cert.jcajce.JcaX509CertificateHolder
|
||||||
|
-dontwarn org.bouncycastle.crypto.BlockCipher
|
||||||
|
-dontwarn org.bouncycastle.crypto.CipherParameters
|
||||||
|
-dontwarn org.bouncycastle.crypto.InvalidCipherTextException
|
||||||
|
-dontwarn org.bouncycastle.crypto.engines.AESEngine
|
||||||
|
-dontwarn org.bouncycastle.crypto.modes.GCMBlockCipher
|
||||||
|
-dontwarn org.bouncycastle.crypto.params.AEADParameters
|
||||||
|
-dontwarn org.bouncycastle.crypto.params.KeyParameter
|
||||||
|
-dontwarn org.bouncycastle.jce.provider.BouncyCastleProvider
|
||||||
|
-dontwarn org.bouncycastle.openssl.PEMException
|
||||||
|
-dontwarn org.bouncycastle.openssl.PEMKeyPair
|
||||||
|
-dontwarn org.bouncycastle.openssl.PEMParser
|
||||||
|
-dontwarn org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter
|
||||||
|
-dontwarn rx.android.schedulers.AndroidSchedulers
|
||||||
|
-dontwarn sun.reflect.Reflection
|
||||||
|
#TM_SA end
|
||||||
|
|
|
@ -35,4 +35,18 @@ class SignalInstrumentationApplicationContext : ApplicationContext() {
|
||||||
LogDatabase.getInstance(this).logs.trimToSize()
|
LogDatabase.getInstance(this).logs.trimToSize()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun beginJobLoop() = Unit
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Some of the jobs can interfere with some of the instrumentation tests.
|
||||||
|
*
|
||||||
|
* For example, we may try to create a release channel recipient while doing
|
||||||
|
* an import/backup test.
|
||||||
|
*
|
||||||
|
* This can be used to start the job loop if needed for tests that rely on it.
|
||||||
|
*/
|
||||||
|
fun beginJobLoopForTests() {
|
||||||
|
super.beginJobLoop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -255,7 +255,7 @@ class BackupTest {
|
||||||
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
|
SignalStore.donationsValues().setSubscriber(Subscriber(SubscriberId.generate(), "USD"))
|
||||||
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
|
SignalStore.donationsValues().setDisplayBadgesOnProfile(false)
|
||||||
|
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberListingMode = PhoneNumberPrivacyValues.PhoneNumberListingMode.UNLISTED
|
SignalStore.phoneNumberPrivacy().phoneNumberDiscoverabilityMode = PhoneNumberPrivacyValues.PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
|
||||||
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
|
SignalStore.phoneNumberPrivacy().phoneNumberSharingMode = PhoneNumberPrivacyValues.PhoneNumberSharingMode.NOBODY
|
||||||
|
|
||||||
SignalStore.settings().isLinkPreviewsEnabled = false
|
SignalStore.settings().isLinkPreviewsEnabled = false
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.tm.archive.backup.v2
|
||||||
|
|
||||||
|
import org.tm.archive.crypto.ProfileKeyUtil
|
||||||
|
import org.whispersystems.signalservice.api.util.toByteArray
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
object TestRecipientUtils {
|
||||||
|
|
||||||
|
private var upperGenAci = 13131313L
|
||||||
|
private var lowerGenAci = 0L
|
||||||
|
|
||||||
|
private var upperGenPni = 12121212L
|
||||||
|
private var lowerGenPni = 0L
|
||||||
|
|
||||||
|
private var groupMasterKeyRandom = Random(12345)
|
||||||
|
|
||||||
|
fun generateProfileKey(): ByteArray {
|
||||||
|
return ProfileKeyUtil.createNew().serialize()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextPni(): ByteArray {
|
||||||
|
synchronized(this) {
|
||||||
|
lowerGenPni++
|
||||||
|
var uuid = UUID(upperGenPni, lowerGenPni)
|
||||||
|
return uuid.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun nextAci(): ByteArray {
|
||||||
|
synchronized(this) {
|
||||||
|
lowerGenAci++
|
||||||
|
var uuid = UUID(upperGenAci, lowerGenAci)
|
||||||
|
return uuid.toByteArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateGroupMasterKey(): ByteArray {
|
||||||
|
val masterKey = ByteArray(32)
|
||||||
|
groupMasterKeyRandom.nextBytes(masterKey)
|
||||||
|
return masterKey
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ package org.tm.archive.conversation.v2.items
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
|
@ -29,7 +30,6 @@ import org.tm.archive.groups.GroupId
|
||||||
import org.tm.archive.groups.GroupMigrationMembershipChange
|
import org.tm.archive.groups.GroupMigrationMembershipChange
|
||||||
import org.tm.archive.linkpreview.LinkPreview
|
import org.tm.archive.linkpreview.LinkPreview
|
||||||
import org.tm.archive.mediapreview.MediaIntentFactory
|
import org.tm.archive.mediapreview.MediaIntentFactory
|
||||||
import org.tm.archive.mms.GlideRequests
|
|
||||||
import org.tm.archive.recipients.Recipient
|
import org.tm.archive.recipients.Recipient
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.tm.archive.stickers.StickerLocator
|
import org.tm.archive.stickers.StickerLocator
|
||||||
|
@ -209,8 +209,9 @@ class V2ConversationItemShapeTest {
|
||||||
override val selectedItems: Set<MultiselectPart> = emptySet()
|
override val selectedItems: Set<MultiselectPart> = emptySet()
|
||||||
override val isMessageRequestAccepted: Boolean = true
|
override val isMessageRequestAccepted: Boolean = true
|
||||||
override val searchQuery: String? = null
|
override val searchQuery: String? = null
|
||||||
override val glideRequests: GlideRequests = mockk()
|
override val requestManager: RequestManager = mockk()
|
||||||
override val isParentInScroll: Boolean = false
|
override val isParentInScroll: Boolean = false
|
||||||
|
override fun getChatColorsData(): ChatColorsDrawable.ChatColorsData = ChatColorsDrawable.ChatColorsData(null, null)
|
||||||
|
|
||||||
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
override fun onStartExpirationTimeout(messageRecord: MessageRecord) = Unit
|
||||||
|
|
||||||
|
@ -321,5 +322,11 @@ class V2ConversationItemShapeTest {
|
||||||
override fun onItemClick(item: MultiselectPart?) = Unit
|
override fun onItemClick(item: MultiselectPart?) = Unit
|
||||||
|
|
||||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
|
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) = Unit
|
||||||
|
|
||||||
|
override fun onShowSafetyTips(forGroup: Boolean) = Unit
|
||||||
|
|
||||||
|
override fun onReportSpamLearnMoreClicked() = Unit
|
||||||
|
|
||||||
|
override fun onMessageRequestAcceptOptionsClicked() = Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,18 +51,16 @@ class AttachmentTableTest {
|
||||||
|
|
||||||
SignalDatabase.attachments.updateAttachmentData(
|
SignalDatabase.attachments.updateAttachmentData(
|
||||||
attachment,
|
attachment,
|
||||||
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
|
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
|
||||||
false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SignalDatabase.attachments.updateAttachmentData(
|
SignalDatabase.attachments.updateAttachmentData(
|
||||||
attachment2,
|
attachment2,
|
||||||
createMediaStream(byteArrayOf(1, 2, 3)),
|
createMediaStream(byteArrayOf(1, 2, 3))
|
||||||
false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
|
||||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
|
||||||
|
|
||||||
assertNotEquals(attachment1Info, attachment2Info)
|
assertNotEquals(attachment1Info, attachment2Info)
|
||||||
}
|
}
|
||||||
|
@ -79,18 +77,16 @@ class AttachmentTableTest {
|
||||||
|
|
||||||
SignalDatabase.attachments.updateAttachmentData(
|
SignalDatabase.attachments.updateAttachmentData(
|
||||||
attachment,
|
attachment,
|
||||||
createMediaStream(byteArrayOf(1, 2, 3, 4, 5)),
|
createMediaStream(byteArrayOf(1, 2, 3, 4, 5))
|
||||||
true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
SignalDatabase.attachments.updateAttachmentData(
|
SignalDatabase.attachments.updateAttachmentData(
|
||||||
attachment2,
|
attachment2,
|
||||||
createMediaStream(byteArrayOf(1, 2, 3, 4)),
|
createMediaStream(byteArrayOf(1, 2, 3, 4))
|
||||||
true
|
|
||||||
)
|
)
|
||||||
|
|
||||||
val attachment1Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment.attachmentId, AttachmentTable.DATA_FILE)
|
val attachment1Info = SignalDatabase.attachments.getDataFileInfo(attachment.attachmentId)
|
||||||
val attachment2Info = SignalDatabase.attachments.getAttachmentDataFileInfo(attachment2.attachmentId, AttachmentTable.DATA_FILE)
|
val attachment2Info = SignalDatabase.attachments.getDataFileInfo(attachment2.attachmentId)
|
||||||
|
|
||||||
assertNotEquals(attachment1Info, attachment2Info)
|
assertNotEquals(attachment1Info, attachment2Info)
|
||||||
}
|
}
|
||||||
|
@ -121,15 +117,14 @@ class AttachmentTableTest {
|
||||||
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
|
val highDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(highQualityPreUpload)
|
||||||
|
|
||||||
// WHEN
|
// WHEN
|
||||||
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData), false)
|
SignalDatabase.attachments.updateAttachmentData(standardDatabaseAttachment, createMediaStream(compressedData))
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
val previousInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(previousDatabaseAttachmentId, AttachmentTable.DATA_FILE)!!
|
val previousInfo = SignalDatabase.attachments.getDataFileInfo(previousDatabaseAttachmentId)!!
|
||||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
|
||||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
|
||||||
|
|
||||||
assertNotEquals(standardInfo, highInfo)
|
assertNotEquals(standardInfo, highInfo)
|
||||||
standardInfo.file assertIs previousInfo.file
|
|
||||||
highInfo.file assertIsNot standardInfo.file
|
highInfo.file assertIsNot standardInfo.file
|
||||||
highInfo.file.exists() assertIs true
|
highInfo.file.exists() assertIs true
|
||||||
}
|
}
|
||||||
|
@ -158,9 +153,9 @@ class AttachmentTableTest {
|
||||||
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
val secondHighDatabaseAttachment = SignalDatabase.attachments.insertAttachmentForPreUpload(secondHighQualityPreUpload)
|
||||||
|
|
||||||
// THEN
|
// THEN
|
||||||
val standardInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(standardDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
val standardInfo = SignalDatabase.attachments.getDataFileInfo(standardDatabaseAttachment.attachmentId)!!
|
||||||
val highInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(highDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
val highInfo = SignalDatabase.attachments.getDataFileInfo(highDatabaseAttachment.attachmentId)!!
|
||||||
val secondHighInfo = SignalDatabase.attachments.getAttachmentDataFileInfo(secondHighDatabaseAttachment.attachmentId, AttachmentTable.DATA_FILE)!!
|
val secondHighInfo = SignalDatabase.attachments.getDataFileInfo(secondHighDatabaseAttachment.attachmentId)!!
|
||||||
|
|
||||||
highInfo.file assertIsNot standardInfo.file
|
highInfo.file assertIsNot standardInfo.file
|
||||||
secondHighInfo.file assertIs highInfo.file
|
secondHighInfo.file assertIs highInfo.file
|
||||||
|
|
|
@ -0,0 +1,804 @@
|
||||||
|
package org.tm.archive.database
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import org.junit.Assert.assertArrayEquals
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Assert.assertFalse
|
||||||
|
import org.junit.Assert.assertNotEquals
|
||||||
|
import org.junit.Assert.assertNotNull
|
||||||
|
import org.junit.Assert.assertNull
|
||||||
|
import org.junit.Assert.assertTrue
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.signal.core.util.Base64
|
||||||
|
import org.signal.core.util.update
|
||||||
|
import org.tm.archive.attachments.AttachmentId
|
||||||
|
import org.tm.archive.attachments.PointerAttachment
|
||||||
|
import org.tm.archive.database.AttachmentTable.TransformProperties
|
||||||
|
import org.tm.archive.keyvalue.SignalStore
|
||||||
|
import org.tm.archive.mms.MediaStream
|
||||||
|
import org.tm.archive.mms.OutgoingMessage
|
||||||
|
import org.tm.archive.mms.QuoteModel
|
||||||
|
import org.tm.archive.mms.SentMediaQuality
|
||||||
|
import org.tm.archive.providers.BlobProvider
|
||||||
|
import org.tm.archive.recipients.Recipient
|
||||||
|
import org.tm.archive.util.MediaUtil
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId
|
||||||
|
import java.io.File
|
||||||
|
import java.util.UUID
|
||||||
|
import kotlin.random.Random
|
||||||
|
import kotlin.time.Duration.Companion.days
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collection of [AttachmentTable] tests focused around deduping logic.
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class AttachmentTableTest_deduping {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val DATA_A = byteArrayOf(1, 2, 3)
|
||||||
|
val DATA_A_COMPRESSED = byteArrayOf(4, 5, 6)
|
||||||
|
val DATA_A_HASH = byteArrayOf(1, 1, 1)
|
||||||
|
|
||||||
|
val DATA_B = byteArrayOf(7, 8, 9)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
SignalStore.account().setAci(ServiceId.ACI.from(UUID.randomUUID()))
|
||||||
|
SignalStore.account().setPni(ServiceId.PNI.from(UUID.randomUUID()))
|
||||||
|
SignalStore.account().setE164("+15558675309")
|
||||||
|
|
||||||
|
SignalDatabase.attachments.deleteAllAttachments()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates two different files with different data. Should not dedupe.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun differentFiles() {
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_B)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts files with identical data but with transform properties that make them incompatible. Should not dedupe.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun identicalFiles_incompatibleTransforms() {
|
||||||
|
// Non-matching qualities
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-matching video trim flag
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties())
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-matching video trim start time
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-matching video trim end time
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 1))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 0, videoTrimEndTimeUs = 2))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-matching mp4 fast start
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(mp4FastStart = true))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(mp4FastStart = false))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts files with identical data and compatible transform properties. Should dedupe.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun identicalFiles_compatibleTransforms() {
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, false)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, false)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, false)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
val id2 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, false)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Walks through various scenarios where files are compressed and uploaded.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun compressionAndUploads() {
|
||||||
|
// Matches after the first is compressed, skip transform properly set
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Matches after the first is uploaded, skip transform and ending hash properly set
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mimics sending two files at once. Ensures all fields are kept in sync as we compress and upload.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, false)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
|
||||||
|
upload(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-use the upload when uploaded recently
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not re-use old uploads
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis() - 100.days.inWholeMilliseconds)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
|
||||||
|
assertDoesNotHaveRemoteFields(id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This isn't so much "desirable behavior" as it is documenting how things work.
|
||||||
|
// If an attachment is compressed but not uploaded yet, it will have a DATA_HASH_START that doesn't match the actual file content.
|
||||||
|
// This means that if we insert a new attachment with data that matches the compressed data, we won't find a match.
|
||||||
|
// This is ok because we don't allow forwarding unsent messages, so the chances of the user somehow sending a file that matches data we compressed are very low.
|
||||||
|
// What *is* more common is that the user may send DATA_A again, and in this case we will still catch the dedupe (which is already tested above).
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents what would happen if you forward an already-send compressed attachment. We should match, skip transform, and skip upload.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents what would happen if you edited a video, sent it, then forwarded it. We should match, skip transform, and skip upload.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents what would happen if you edited a video, sent it, then forwarded it, but *edited the forwarded video*. We should not dedupe.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(videoTrim = true, videoTrimStartTimeUs = 1, videoTrimEndTimeUs = 2))
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id2, false)
|
||||||
|
assertDoesNotHaveRemoteFields(id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents what would happen if you sent an image using standard quality, then forwarded it using high quality.
|
||||||
|
// Since you're forwarding, it doesn't matter if the new thing has a higher quality, we should still match and skip transform.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This represents what would happen if you sent an image using high quality, then forwarded it using standard quality.
|
||||||
|
// Since you're forwarding, it doesn't matter if the new thing has a lower quality, we should still match and skip transform.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, TransformProperties(sentMediaQuality = SentMediaQuality.HIGH.code))
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1, uploadTimestamp = System.currentTimeMillis())
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A_COMPRESSED, TransformProperties(sentMediaQuality = SentMediaQuality.STANDARD.code))
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertSkipTransform(id1, true)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure that files marked as unhashable are all updated together
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
upload(id1)
|
||||||
|
upload(id2)
|
||||||
|
clearHashes(id1)
|
||||||
|
clearHashes(id2)
|
||||||
|
|
||||||
|
val file = dataFile(id1)
|
||||||
|
SignalDatabase.attachments.markDataFileAsUnhashable(file)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
|
||||||
|
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id1)!!
|
||||||
|
assertTrue(dataFileInfo.hashEnd!!.startsWith("UNHASHABLE-"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Various deletion scenarios to ensure that duped files don't deleted while there's still references.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun deletions() {
|
||||||
|
// Delete original then dupe
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
val dataFile = dataFile(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
|
||||||
|
delete(id1)
|
||||||
|
|
||||||
|
assertDeleted(id1)
|
||||||
|
assertRowAndFileExists(id2)
|
||||||
|
assertTrue(dataFile.exists())
|
||||||
|
|
||||||
|
delete(id2)
|
||||||
|
|
||||||
|
assertDeleted(id2)
|
||||||
|
assertFalse(dataFile.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete dupe then original
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
val dataFile = dataFile(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
|
||||||
|
delete(id2)
|
||||||
|
assertDeleted(id2)
|
||||||
|
assertRowAndFileExists(id1)
|
||||||
|
assertTrue(dataFile.exists())
|
||||||
|
|
||||||
|
delete(id1)
|
||||||
|
assertDeleted(id1)
|
||||||
|
assertFalse(dataFile.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete original after it was compressed
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
|
||||||
|
delete(id1)
|
||||||
|
|
||||||
|
assertDeleted(id1)
|
||||||
|
assertRowAndFileExists(id2)
|
||||||
|
assertSkipTransform(id2, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Quotes are weak references and should not prevent us from deleting the file
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertQuote(id1)
|
||||||
|
|
||||||
|
val dataFile = dataFile(id1)
|
||||||
|
|
||||||
|
delete(id1)
|
||||||
|
assertDeleted(id1)
|
||||||
|
assertRowExists(id2)
|
||||||
|
assertFalse(dataFile.exists())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun quotes() {
|
||||||
|
// Basic quote deduping
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertQuote(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Making sure remote fields carry
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertQuote(id1)
|
||||||
|
upload(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashStartMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Making sure things work for quotes of videos, which have trickier transform properties
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A, transformProperties = TransformProperties.forVideoTrim(1, 2))
|
||||||
|
compress(id1, DATA_A_COMPRESSED)
|
||||||
|
upload(id1)
|
||||||
|
|
||||||
|
val id2 = insertQuote(id1)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertRemoteFieldsMatch(id1, id2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Suite of tests around the migration where we hash all of the attachments and potentially dedupe them.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
fun migration() {
|
||||||
|
// Verifying that getUnhashedDataFile only returns if there's actually missing hashes
|
||||||
|
test {
|
||||||
|
val id = insertWithData(DATA_A)
|
||||||
|
upload(id)
|
||||||
|
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifying that getUnhashedDataFile finds the missing hash
|
||||||
|
test {
|
||||||
|
val id = insertWithData(DATA_A)
|
||||||
|
upload(id)
|
||||||
|
clearHashes(id)
|
||||||
|
assertNotNull(SignalDatabase.attachments.getUnhashedDataFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verifying that getUnhashedDataFile doesn't return if the file isn't done downloading
|
||||||
|
test {
|
||||||
|
val id = insertWithData(DATA_A)
|
||||||
|
upload(id)
|
||||||
|
setTransferState(id, AttachmentTable.TRANSFER_PROGRESS_PENDING)
|
||||||
|
clearHashes(id)
|
||||||
|
assertNull(SignalDatabase.attachments.getUnhashedDataFile())
|
||||||
|
}
|
||||||
|
|
||||||
|
// If two attachments share the same file, when we backfill the hash, make sure both get their hashes set
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
upload(id1)
|
||||||
|
upload(id2)
|
||||||
|
|
||||||
|
clearHashes(id1)
|
||||||
|
clearHashes(id2)
|
||||||
|
|
||||||
|
val file = dataFile(id1)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file, DATA_A_HASH)
|
||||||
|
|
||||||
|
assertDataHashEnd(id1, DATA_A_HASH)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a situation where two different attachments have the same data but wrote to different files, and verifies the migration dedupes it
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
upload(id1)
|
||||||
|
clearHashes(id1)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
upload(id2)
|
||||||
|
clearHashes(id2)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
|
||||||
|
val file1 = dataFile(id1)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
|
||||||
|
|
||||||
|
assertDataHashEnd(id1, DATA_A_HASH)
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
|
||||||
|
val file2 = dataFile(id2)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertFalse(file2.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We've got three files now with the same data, with two of them sharing a file. We want to make sure *both* entries that share the same file get deduped.
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
upload(id1)
|
||||||
|
clearHashes(id1)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
val id3 = insertWithData(DATA_A)
|
||||||
|
upload(id2)
|
||||||
|
upload(id3)
|
||||||
|
clearHashes(id2)
|
||||||
|
clearHashes(id3)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertDataFilesAreTheSame(id2, id3)
|
||||||
|
|
||||||
|
val file1 = dataFile(id1)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
|
||||||
|
assertDataHashEnd(id1, DATA_A_HASH)
|
||||||
|
|
||||||
|
val file2 = dataFile(id2)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
|
||||||
|
|
||||||
|
assertDataFilesAreTheSame(id1, id2)
|
||||||
|
assertDataHashEndMatches(id1, id2)
|
||||||
|
assertDataHashEndMatches(id2, id3)
|
||||||
|
assertFalse(file2.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't want to mess with files that are still downloading, so this makes sure that even if data matches, we don't dedupe and don't delete the file
|
||||||
|
test {
|
||||||
|
val id1 = insertWithData(DATA_A)
|
||||||
|
upload(id1)
|
||||||
|
clearHashes(id1)
|
||||||
|
|
||||||
|
val id2 = insertWithData(DATA_A)
|
||||||
|
// *not* uploaded
|
||||||
|
clearHashes(id2)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
|
||||||
|
val file1 = dataFile(id1)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file1, DATA_A_HASH)
|
||||||
|
assertDataHashEnd(id1, DATA_A_HASH)
|
||||||
|
|
||||||
|
val file2 = dataFile(id2)
|
||||||
|
SignalDatabase.attachments.setHashForDataFile(file2, DATA_A_HASH)
|
||||||
|
|
||||||
|
assertDataFilesAreDifferent(id1, id2)
|
||||||
|
assertTrue(file2.exists())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestContext {
|
||||||
|
fun insertWithData(data: ByteArray, transformProperties: TransformProperties = TransformProperties.empty()): AttachmentId {
|
||||||
|
val uri = BlobProvider.getInstance().forData(data).createForSingleSessionInMemory()
|
||||||
|
|
||||||
|
val attachment = UriAttachmentBuilder.build(
|
||||||
|
id = Random.nextLong(),
|
||||||
|
uri = uri,
|
||||||
|
contentType = MediaUtil.IMAGE_JPEG,
|
||||||
|
transformProperties = transformProperties
|
||||||
|
)
|
||||||
|
|
||||||
|
return SignalDatabase.attachments.insertAttachmentForPreUpload(attachment).attachmentId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun insertQuote(attachmentId: AttachmentId): AttachmentId {
|
||||||
|
val originalAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
|
||||||
|
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(Recipient.self())
|
||||||
|
val messageId = SignalDatabase.messages.insertMessageOutbox(
|
||||||
|
message = OutgoingMessage(
|
||||||
|
threadRecipient = Recipient.self(),
|
||||||
|
sentTimeMillis = System.currentTimeMillis(),
|
||||||
|
body = "some text",
|
||||||
|
outgoingQuote = QuoteModel(
|
||||||
|
id = 123,
|
||||||
|
author = Recipient.self().id,
|
||||||
|
text = "Some quote text",
|
||||||
|
isOriginalMissing = false,
|
||||||
|
attachments = listOf(originalAttachment),
|
||||||
|
mentions = emptyList(),
|
||||||
|
type = QuoteModel.Type.NORMAL,
|
||||||
|
bodyRanges = null
|
||||||
|
)
|
||||||
|
),
|
||||||
|
threadId = threadId,
|
||||||
|
forceSms = false,
|
||||||
|
insertListener = null
|
||||||
|
)
|
||||||
|
|
||||||
|
val attachments = SignalDatabase.attachments.getAttachmentsForMessage(messageId)
|
||||||
|
return attachments[0].attachmentId
|
||||||
|
}
|
||||||
|
|
||||||
|
fun compress(attachmentId: AttachmentId, newData: ByteArray, mp4FastStart: Boolean = false) {
|
||||||
|
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
|
||||||
|
SignalDatabase.attachments.updateAttachmentData(databaseAttachment, newData.asMediaStream())
|
||||||
|
SignalDatabase.attachments.markAttachmentAsTransformed(attachmentId, withFastStart = mp4FastStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun upload(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()) {
|
||||||
|
SignalDatabase.attachments.finalizeAttachmentAfterUpload(attachmentId, createPointerAttachment(attachmentId, uploadTimestamp), uploadTimestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun delete(attachmentId: AttachmentId) {
|
||||||
|
SignalDatabase.attachments.deleteAttachment(attachmentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun dataFile(attachmentId: AttachmentId): File {
|
||||||
|
return SignalDatabase.attachments.getDataFileInfo(attachmentId)!!.file
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setTransferState(attachmentId: AttachmentId, transferState: Int) {
|
||||||
|
// messageId doesn't actually matter -- that's for notifying listeners
|
||||||
|
SignalDatabase.attachments.setTransferState(messageId = -1, attachmentId = attachmentId, transferState = transferState)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearHashes(id: AttachmentId) {
|
||||||
|
SignalDatabase.attachments.writableDatabase
|
||||||
|
.update(AttachmentTable.TABLE_NAME)
|
||||||
|
.values(
|
||||||
|
AttachmentTable.DATA_HASH_START to null,
|
||||||
|
AttachmentTable.DATA_HASH_END to null
|
||||||
|
)
|
||||||
|
.where("${AttachmentTable.ID} = ?", id)
|
||||||
|
.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDeleted(attachmentId: AttachmentId) {
|
||||||
|
assertNull("$attachmentId exists, but it shouldn't!", SignalDatabase.attachments.getAttachment(attachmentId))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertRowAndFileExists(attachmentId: AttachmentId) {
|
||||||
|
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
|
||||||
|
assertNotNull("$attachmentId does not exist!", databaseAttachment)
|
||||||
|
|
||||||
|
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(attachmentId)
|
||||||
|
assertTrue("The file for $attachmentId does not exist!", dataFileInfo!!.file.exists())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertRowExists(attachmentId: AttachmentId) {
|
||||||
|
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)
|
||||||
|
assertNotNull("$attachmentId does not exist!", databaseAttachment)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDataFilesAreTheSame(lhs: AttachmentId, rhs: AttachmentId) {
|
||||||
|
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
|
||||||
|
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
|
||||||
|
|
||||||
|
assert(lhsInfo.file.exists())
|
||||||
|
assert(rhsInfo.file.exists())
|
||||||
|
|
||||||
|
assertEquals(lhsInfo.file, rhsInfo.file)
|
||||||
|
assertEquals(lhsInfo.length, rhsInfo.length)
|
||||||
|
assertArrayEquals(lhsInfo.random, rhsInfo.random)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDataFilesAreDifferent(lhs: AttachmentId, rhs: AttachmentId) {
|
||||||
|
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
|
||||||
|
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
|
||||||
|
|
||||||
|
assert(lhsInfo.file.exists())
|
||||||
|
assert(rhsInfo.file.exists())
|
||||||
|
|
||||||
|
assertNotEquals(lhsInfo.file, rhsInfo.file)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDataHashStartMatches(lhs: AttachmentId, rhs: AttachmentId) {
|
||||||
|
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
|
||||||
|
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
|
||||||
|
|
||||||
|
assertNotNull(lhsInfo.hashStart)
|
||||||
|
assertEquals("DATA_HASH_START's did not match!", lhsInfo.hashStart, rhsInfo.hashStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDataHashEndMatches(lhs: AttachmentId, rhs: AttachmentId) {
|
||||||
|
val lhsInfo = SignalDatabase.attachments.getDataFileInfo(lhs)!!
|
||||||
|
val rhsInfo = SignalDatabase.attachments.getDataFileInfo(rhs)!!
|
||||||
|
|
||||||
|
assertNotNull(lhsInfo.hashEnd)
|
||||||
|
assertEquals("DATA_HASH_END's did not match!", lhsInfo.hashEnd, rhsInfo.hashEnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDataHashEnd(id: AttachmentId, byteArray: ByteArray) {
|
||||||
|
val dataFileInfo = SignalDatabase.attachments.getDataFileInfo(id)!!
|
||||||
|
assertArrayEquals(byteArray, Base64.decode(dataFileInfo.hashEnd!!))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertRemoteFieldsMatch(lhs: AttachmentId, rhs: AttachmentId) {
|
||||||
|
val lhsAttachment = SignalDatabase.attachments.getAttachment(lhs)!!
|
||||||
|
val rhsAttachment = SignalDatabase.attachments.getAttachment(rhs)!!
|
||||||
|
|
||||||
|
assertEquals(lhsAttachment.remoteLocation, rhsAttachment.remoteLocation)
|
||||||
|
assertEquals(lhsAttachment.remoteKey, rhsAttachment.remoteKey)
|
||||||
|
assertArrayEquals(lhsAttachment.remoteDigest, rhsAttachment.remoteDigest)
|
||||||
|
assertArrayEquals(lhsAttachment.incrementalDigest, rhsAttachment.incrementalDigest)
|
||||||
|
assertEquals(lhsAttachment.incrementalMacChunkSize, rhsAttachment.incrementalMacChunkSize)
|
||||||
|
assertEquals(lhsAttachment.cdnNumber, rhsAttachment.cdnNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertDoesNotHaveRemoteFields(attachmentId: AttachmentId) {
|
||||||
|
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
|
||||||
|
assertEquals(0, databaseAttachment.uploadTimestamp)
|
||||||
|
assertNull(databaseAttachment.remoteLocation)
|
||||||
|
assertNull(databaseAttachment.remoteDigest)
|
||||||
|
assertNull(databaseAttachment.remoteKey)
|
||||||
|
assertEquals(0, databaseAttachment.cdnNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun assertSkipTransform(attachmentId: AttachmentId, state: Boolean) {
|
||||||
|
val transformProperties = SignalDatabase.attachments.getTransformProperties(attachmentId)!!
|
||||||
|
assertEquals("Incorrect skipTransform!", transformProperties.skipTransform, state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ByteArray.asMediaStream(): MediaStream {
|
||||||
|
return MediaStream(this.inputStream(), MediaUtil.IMAGE_JPEG, 2, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPointerAttachment(attachmentId: AttachmentId, uploadTimestamp: Long = System.currentTimeMillis()): PointerAttachment {
|
||||||
|
val location = "somewhere-${Random.nextLong()}"
|
||||||
|
val key = "somekey-${Random.nextLong()}"
|
||||||
|
val digest = Random.nextBytes(32)
|
||||||
|
val incrementalDigest = Random.nextBytes(16)
|
||||||
|
|
||||||
|
val databaseAttachment = SignalDatabase.attachments.getAttachment(attachmentId)!!
|
||||||
|
|
||||||
|
return PointerAttachment(
|
||||||
|
"image/jpeg",
|
||||||
|
AttachmentTable.TRANSFER_PROGRESS_DONE,
|
||||||
|
databaseAttachment.size, // size
|
||||||
|
null,
|
||||||
|
3, // cdnNumber
|
||||||
|
location,
|
||||||
|
key,
|
||||||
|
digest,
|
||||||
|
incrementalDigest,
|
||||||
|
5, // incrementalMacChunkSize
|
||||||
|
null,
|
||||||
|
databaseAttachment.voiceNote,
|
||||||
|
databaseAttachment.borderless,
|
||||||
|
databaseAttachment.videoGif,
|
||||||
|
databaseAttachment.width,
|
||||||
|
databaseAttachment.height,
|
||||||
|
uploadTimestamp,
|
||||||
|
databaseAttachment.caption,
|
||||||
|
databaseAttachment.stickerLocator,
|
||||||
|
databaseAttachment.blurHash
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun test(content: TestContext.() -> Unit) {
|
||||||
|
SignalDatabase.attachments.deleteAllAttachments()
|
||||||
|
val context = TestContext()
|
||||||
|
context.content()
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import org.junit.Assert.assertTrue
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.signal.core.util.readToList
|
import org.signal.core.util.readToList
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.withinTransaction
|
import org.signal.core.util.withinTransaction
|
||||||
|
@ -33,8 +33,8 @@ class GroupTableTest {
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
groupTable = SignalDatabase.groups
|
groupTable = SignalDatabase.groups
|
||||||
|
|
||||||
groupTable.writableDatabase.delete(GroupTable.TABLE_NAME).run()
|
groupTable.writableDatabase.deleteAll(GroupTable.TABLE_NAME)
|
||||||
groupTable.writableDatabase.delete(GroupTable.MembershipTable.TABLE_NAME).run()
|
groupTable.writableDatabase.deleteAll(GroupTable.MembershipTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -10,7 +10,7 @@ import org.signal.core.util.forEach
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.requireNonNullString
|
import org.signal.core.util.requireNonNullString
|
||||||
import org.signal.core.util.select
|
import org.signal.core.util.select
|
||||||
import org.signal.core.util.update
|
import org.signal.core.util.updateAll
|
||||||
import org.tm.archive.crash.CrashConfig
|
import org.tm.archive.crash.CrashConfig
|
||||||
import org.tm.archive.dependencies.ApplicationDependencies
|
import org.tm.archive.dependencies.ApplicationDependencies
|
||||||
import org.tm.archive.testing.assertIs
|
import org.tm.archive.testing.assertIs
|
||||||
|
@ -220,7 +220,7 @@ class LogDatabaseTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
db.writableDatabase
|
db.writableDatabase
|
||||||
.update(LogDatabase.CrashTable.TABLE_NAME)
|
.updateAll(LogDatabase.CrashTable.TABLE_NAME)
|
||||||
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
.values(LogDatabase.CrashTable.LAST_PROMPTED_AT to currentTime)
|
||||||
.run()
|
.run()
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,6 @@ import org.signal.core.util.CursorUtil
|
||||||
import org.tm.archive.profiles.ProfileName
|
import org.tm.archive.profiles.ProfileName
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.tm.archive.testing.SignalActivityRule
|
import org.tm.archive.testing.SignalActivityRule
|
||||||
import org.tm.archive.util.FeatureFlags
|
|
||||||
import org.tm.archive.util.FeatureFlagsAccessor
|
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
@ -59,7 +57,7 @@ class RecipientTableTest {
|
||||||
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
SignalDatabase.recipients.setProfileName(hiddenRecipient, ProfileName.fromParts("Hidden", "Person"))
|
||||||
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
SignalDatabase.recipients.markHidden(hiddenRecipient)
|
||||||
|
|
||||||
val results = SignalDatabase.recipients.querySignalContacts("Hidden", false)!!
|
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Hidden", false))!!
|
||||||
|
|
||||||
assertEquals(0, results.count)
|
assertEquals(0, results.count)
|
||||||
}
|
}
|
||||||
|
@ -130,7 +128,7 @@ class RecipientTableTest {
|
||||||
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
|
SignalDatabase.recipients.setProfileName(blockedRecipient, ProfileName.fromParts("Blocked", "Person"))
|
||||||
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
|
SignalDatabase.recipients.setBlocked(blockedRecipient, true)
|
||||||
|
|
||||||
val results = SignalDatabase.recipients.querySignalContacts("Blocked", false)!!
|
val results = SignalDatabase.recipients.querySignalContacts(RecipientTable.ContactSearchQuery("Blocked", false))!!
|
||||||
|
|
||||||
assertEquals(0, results.count)
|
assertEquals(0, results.count)
|
||||||
}
|
}
|
||||||
|
@ -167,8 +165,6 @@ class RecipientTableTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() {
|
fun givenARecipientWithPniAndAci_whenIMarkItUnregistered_thenIExpectItToBeSplit() {
|
||||||
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
|
|
||||||
|
|
||||||
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
|
|
||||||
SignalDatabase.recipients.markUnregistered(mainId)
|
SignalDatabase.recipients.markUnregistered(mainId)
|
||||||
|
@ -185,12 +181,10 @@ class RecipientTableTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() {
|
fun givenARecipientWithPniAndAci_whenISplitItForStorageSync_thenIExpectItToBeSplit() {
|
||||||
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
|
|
||||||
|
|
||||||
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
val mainId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
|
val mainRecord = SignalDatabase.recipients.getRecord(mainId)
|
||||||
|
|
||||||
SignalDatabase.recipients.splitForStorageSync(mainRecord.storageId!!)
|
SignalDatabase.recipients.splitForStorageSyncIfNecessary(mainRecord.aci!!)
|
||||||
|
|
||||||
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import org.signal.core.util.Base64
|
||||||
import org.signal.core.util.SqlUtil
|
import org.signal.core.util.SqlUtil
|
||||||
import org.signal.core.util.exists
|
import org.signal.core.util.exists
|
||||||
import org.signal.core.util.orNull
|
import org.signal.core.util.orNull
|
||||||
|
import org.signal.core.util.readToSingleBoolean
|
||||||
import org.signal.core.util.requireLong
|
import org.signal.core.util.requireLong
|
||||||
import org.signal.core.util.requireNonNullString
|
import org.signal.core.util.requireNonNullString
|
||||||
import org.signal.core.util.select
|
import org.signal.core.util.select
|
||||||
|
@ -40,8 +41,6 @@ import org.tm.archive.mms.IncomingMessage
|
||||||
import org.tm.archive.notifications.profiles.NotificationProfile
|
import org.tm.archive.notifications.profiles.NotificationProfile
|
||||||
import org.tm.archive.recipients.Recipient
|
import org.tm.archive.recipients.Recipient
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.tm.archive.util.FeatureFlags
|
|
||||||
import org.tm.archive.util.FeatureFlagsAccessor
|
|
||||||
import org.tm.archive.util.Util
|
import org.tm.archive.util.Util
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
|
@ -57,7 +56,6 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
SignalStore.account().setE164(E164_SELF)
|
SignalStore.account().setE164(E164_SELF)
|
||||||
SignalStore.account().setAci(ACI_SELF)
|
SignalStore.account().setAci(ACI_SELF)
|
||||||
SignalStore.account().setPni(PNI_SELF)
|
SignalStore.account().setPni(PNI_SELF)
|
||||||
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -109,6 +107,18 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
val record = SignalDatabase.recipients.getRecord(id)
|
val record = SignalDatabase.recipients.getRecord(id)
|
||||||
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("e164+pni+aci insert, pni verified") {
|
||||||
|
val id = process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||||
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
expectPniVerified()
|
||||||
|
|
||||||
|
val record = SignalDatabase.recipients.getRecord(id)
|
||||||
|
assertEquals(RecipientTable.RegisteredState.REGISTERED, record.registered)
|
||||||
|
|
||||||
|
process(E164_A, PNI_A, ACI_A, pniVerified = false)
|
||||||
|
expectPniVerified()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -164,6 +174,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
expectNoSessionSwitchoverEvent()
|
expectNoSessionSwitchoverEvent()
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("no match, all fields") {
|
test("no match, all fields") {
|
||||||
|
@ -225,6 +236,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
given(E164_A, PNI_A, null, pniSession = true)
|
given(E164_A, PNI_A, null, pniSession = true)
|
||||||
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
process(E164_A, PNI_A, ACI_A, pniVerified = true)
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("e164 and aci matches, all provided, new pni") {
|
test("e164 and aci matches, all provided, new pni") {
|
||||||
|
@ -694,6 +707,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
|
|
||||||
expectDeleted()
|
expectDeleted()
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("merge, e164+pni & aci, pni session, pni verified") {
|
test("merge, e164+pni & aci, pni session, pni verified") {
|
||||||
|
@ -706,6 +721,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
expect(E164_A, PNI_A, ACI_A)
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
expectThreadMergeEvent(E164_A)
|
expectThreadMergeEvent(E164_A)
|
||||||
|
expectPniVerified()
|
||||||
}
|
}
|
||||||
|
|
||||||
test("merge, e164+pni & e164+pni+aci, change number") {
|
test("merge, e164+pni & e164+pni+aci, change number") {
|
||||||
|
@ -760,6 +776,18 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
expectThreadMergeEvent(E164_A)
|
expectThreadMergeEvent(E164_A)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("merge, e164+pni & e164+aci, pni+aci provided, change number") {
|
||||||
|
given(E164_A, PNI_A, null)
|
||||||
|
given(E164_B, null, ACI_A)
|
||||||
|
|
||||||
|
process(null, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
|
||||||
|
expectThreadMergeEvent(E164_A)
|
||||||
|
expectChangeNumberEvent()
|
||||||
|
}
|
||||||
|
|
||||||
test("merge, e164 + pni reassigned, aci abandoned") {
|
test("merge, e164 + pni reassigned, aci abandoned") {
|
||||||
given(E164_A, PNI_A, ACI_A)
|
given(E164_A, PNI_A, ACI_A)
|
||||||
given(E164_B, PNI_B, ACI_B)
|
given(E164_B, PNI_B, ACI_B)
|
||||||
|
@ -772,6 +800,17 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
expectChangeNumberEvent()
|
expectChangeNumberEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test("merge, e164 follows pni+aci") {
|
||||||
|
given(E164_A, PNI_A, null)
|
||||||
|
given(null, null, ACI_A)
|
||||||
|
|
||||||
|
process(null, PNI_A, ACI_A, pniVerified = true)
|
||||||
|
|
||||||
|
expect(E164_A, PNI_A, ACI_A)
|
||||||
|
expectThreadMergeEvent(E164_A)
|
||||||
|
expectPniVerified()
|
||||||
|
}
|
||||||
|
|
||||||
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
|
test("local user, local e164 and aci provided, changeSelf=false, leave e164 alone") {
|
||||||
given(E164_SELF, null, ACI_SELF)
|
given(E164_SELF, null, ACI_SELF)
|
||||||
given(null, null, ACI_A)
|
given(null, null, ACI_A)
|
||||||
|
@ -874,8 +913,8 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
|
|
||||||
// Thread validation
|
// Thread validation
|
||||||
assertEquals(threadIdAci, retrievedThreadId)
|
assertEquals(threadIdAci, retrievedThreadId)
|
||||||
Assert.assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
|
assertNull(SignalDatabase.threads.getThreadIdFor(recipientIdE164))
|
||||||
Assert.assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
|
assertNull(SignalDatabase.threads.getThreadRecord(threadIdE164))
|
||||||
|
|
||||||
// SMS validation
|
// SMS validation
|
||||||
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
|
val sms1: MessageRecord = SignalDatabase.messages.getMessageRecord(smsId1)!!
|
||||||
|
@ -919,10 +958,10 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
|
|
||||||
// Identity validation
|
// Identity validation
|
||||||
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
|
assertEquals(identityKeyAci, SignalDatabase.identities.getIdentityStoreRecord(ACI_A.toString())!!.identityKey)
|
||||||
Assert.assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
|
assertNull(SignalDatabase.identities.getIdentityStoreRecord(E164_A))
|
||||||
|
|
||||||
// Session validation
|
// Session validation
|
||||||
Assert.assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
|
assertNotNull(SignalDatabase.sessions.load(ACI_SELF, SignalProtocolAddress(ACI_A.toString(), 1)))
|
||||||
|
|
||||||
// Reaction validation
|
// Reaction validation
|
||||||
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
|
val reactionsSms: List<ReactionRecord> = SignalDatabase.reactions.getReactions(MessageId(smsId1))
|
||||||
|
@ -1037,6 +1076,10 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
if (!test.sessionSwitchoverExpected) {
|
if (!test.sessionSwitchoverExpected) {
|
||||||
test.expectNoSessionSwitchoverEvent()
|
test.expectNoSessionSwitchoverEvent()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!test.pniVerifiedExpected) {
|
||||||
|
test.expectPniNotVerified()
|
||||||
|
}
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
if (e.javaClass != exception) {
|
if (e.javaClass != exception) {
|
||||||
val error = java.lang.AssertionError("[$name] ${e.message}")
|
val error = java.lang.AssertionError("[$name] ${e.message}")
|
||||||
|
@ -1056,6 +1099,7 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
var changeNumberExpected = false
|
var changeNumberExpected = false
|
||||||
var threadMergeExpected = false
|
var threadMergeExpected = false
|
||||||
var sessionSwitchoverExpected = false
|
var sessionSwitchoverExpected = false
|
||||||
|
var pniVerifiedExpected = false
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Need to delete these first to prevent foreign key crash
|
// Need to delete these first to prevent foreign key crash
|
||||||
|
@ -1207,6 +1251,24 @@ class RecipientTableTest_getAndPossiblyMerge {
|
||||||
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
|
assertNull("Unexpected thread merge event!", getLatestThreadMergeEvent(outputRecipientId))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun expectPniVerified() {
|
||||||
|
assertTrue("Expected PNI to be verified!", isPniVerified(outputRecipientId))
|
||||||
|
pniVerifiedExpected = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expectPniNotVerified() {
|
||||||
|
assertFalse("Expected PNI to be not be verified!", isPniVerified(outputRecipientId))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPniVerified(recipientId: RecipientId): Boolean {
|
||||||
|
return SignalDatabase.rawDatabase
|
||||||
|
.select(RecipientTable.PNI_SIGNATURE_VERIFIED)
|
||||||
|
.from(RecipientTable.TABLE_NAME)
|
||||||
|
.where("${RecipientTable.ID} = ?", recipientId)
|
||||||
|
.run()
|
||||||
|
.readToSingleBoolean(false)
|
||||||
|
}
|
||||||
|
|
||||||
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
private fun insert(e164: String?, pni: PNI?, aci: ACI?): RecipientId {
|
||||||
val id: Long = SignalDatabase.rawDatabase.insert(
|
val id: Long = SignalDatabase.rawDatabase.insert(
|
||||||
RecipientTable.TABLE_NAME,
|
RecipientTable.TABLE_NAME,
|
||||||
|
|
|
@ -290,7 +290,8 @@ class SmsDatabaseTest_collapseJoinRequestEventsIfPossible {
|
||||||
from = sender,
|
from = sender,
|
||||||
timestamp = wallClock,
|
timestamp = wallClock,
|
||||||
groupId = groupId,
|
groupId = groupId,
|
||||||
groupContext = groupContext
|
groupContext = groupContext,
|
||||||
|
serverGuid = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,7 @@ import java.util.Optional
|
||||||
*
|
*
|
||||||
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
|
* Handles setting up a mock web server for API calls, and provides mockable versions of [SignalServiceNetworkAccess].
|
||||||
*/
|
*/
|
||||||
class InstrumentationApplicationDependencyProvider(application: Application, default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
|
class InstrumentationApplicationDependencyProvider(val application: Application, private val default: ApplicationDependencyProvider) : ApplicationDependencies.Provider by default {
|
||||||
|
|
||||||
private val serviceTrustStore: TrustStore
|
private val serviceTrustStore: TrustStore
|
||||||
private val uncensoredConfiguration: SignalServiceConfiguration
|
private val uncensoredConfiguration: SignalServiceConfiguration
|
||||||
|
|
|
@ -52,7 +52,7 @@ class MessageContentProcessor__recipientStatusTest {
|
||||||
processor.process(
|
processor.process(
|
||||||
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
||||||
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
|
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0])),
|
||||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
|
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
|
||||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
|
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -64,7 +64,7 @@ class MessageContentProcessor__recipientStatusTest {
|
||||||
processor.process(
|
processor.process(
|
||||||
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
envelope = MessageContentFuzzer.envelope(envelopeTimestamp),
|
||||||
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
|
content = MessageContentFuzzer.syncSentTextMessage(initialTextMessage, deliveredTo = listOf(harness.others[0], harness.others[1]), recipientUpdate = true),
|
||||||
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId),
|
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, groupId = groupId),
|
||||||
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
|
serverDeliveredTimestamp = MessageContentFuzzer.fuzzServerDeliveredTimestamp(envelopeTimestamp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,225 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2024 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.tm.archive.messages
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockkStatic
|
||||||
|
import io.mockk.slot
|
||||||
|
import io.mockk.unmockkStatic
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.tm.archive.database.SignalDatabase
|
||||||
|
import org.tm.archive.jobs.ThreadUpdateJob
|
||||||
|
import org.tm.archive.recipients.RecipientId
|
||||||
|
import org.tm.archive.testing.GroupTestingUtils
|
||||||
|
import org.tm.archive.testing.MessageContentFuzzer
|
||||||
|
import org.tm.archive.testing.SignalActivityRule
|
||||||
|
import org.tm.archive.testing.assertIs
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Suppress("ClassName")
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SyncMessageProcessorTest_readSyncs {
|
||||||
|
|
||||||
|
@get:Rule
|
||||||
|
val harness = SignalActivityRule(createGroup = true)
|
||||||
|
|
||||||
|
private lateinit var alice: RecipientId
|
||||||
|
private lateinit var bob: RecipientId
|
||||||
|
private lateinit var group: GroupTestingUtils.TestGroupInfo
|
||||||
|
private lateinit var processor: MessageContentProcessor
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setUp() {
|
||||||
|
alice = harness.others[0]
|
||||||
|
bob = harness.others[1]
|
||||||
|
group = harness.group!!
|
||||||
|
|
||||||
|
processor = MessageContentProcessor(harness.context)
|
||||||
|
|
||||||
|
val threadIdSlot = slot<Long>()
|
||||||
|
mockkStatic(ThreadUpdateJob::class)
|
||||||
|
every { ThreadUpdateJob.enqueue(capture(threadIdSlot)) } answers {
|
||||||
|
SignalDatabase.threads.update(threadIdSlot.captured, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
unmockkStatic(ThreadUpdateJob::class)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleSynchronizeReadMessage() {
|
||||||
|
val messageHelper = MessageHelper()
|
||||||
|
|
||||||
|
val message1Timestamp = messageHelper.incomingText().timestamp
|
||||||
|
val message2Timestamp = messageHelper.incomingText().timestamp
|
||||||
|
|
||||||
|
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
|
||||||
|
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 2
|
||||||
|
|
||||||
|
messageHelper.syncReadMessage(alice to message1Timestamp, alice to message2Timestamp)
|
||||||
|
|
||||||
|
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleSynchronizeReadMessageMissingTimestamp() {
|
||||||
|
val messageHelper = MessageHelper()
|
||||||
|
|
||||||
|
messageHelper.incomingText().timestamp
|
||||||
|
val message2Timestamp = messageHelper.incomingText().timestamp
|
||||||
|
|
||||||
|
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
|
||||||
|
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 2
|
||||||
|
|
||||||
|
messageHelper.syncReadMessage(alice to message2Timestamp)
|
||||||
|
|
||||||
|
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleSynchronizeReadWithEdits() {
|
||||||
|
val messageHelper = MessageHelper()
|
||||||
|
|
||||||
|
val message1Timestamp = messageHelper.incomingText().timestamp
|
||||||
|
messageHelper.syncReadMessage(alice to message1Timestamp)
|
||||||
|
|
||||||
|
val editMessage1Timestamp1 = messageHelper.incomingEditText(message1Timestamp).timestamp
|
||||||
|
val editMessage1Timestamp2 = messageHelper.incomingEditText(editMessage1Timestamp1).timestamp
|
||||||
|
|
||||||
|
val message2Timestamp = messageHelper.incomingMedia().timestamp
|
||||||
|
|
||||||
|
val threadId = SignalDatabase.threads.getThreadIdFor(alice)!!
|
||||||
|
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 2
|
||||||
|
|
||||||
|
messageHelper.syncReadMessage(alice to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
|
||||||
|
|
||||||
|
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 0
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun handleSynchronizeReadWithEditsInGroup() {
|
||||||
|
val messageHelper = MessageHelper()
|
||||||
|
|
||||||
|
val message1Timestamp = messageHelper.incomingText(sender = alice, destination = group.recipientId).timestamp
|
||||||
|
|
||||||
|
messageHelper.syncReadMessage(alice to message1Timestamp)
|
||||||
|
|
||||||
|
val editMessage1Timestamp1 = messageHelper.incomingEditText(targetTimestamp = message1Timestamp, sender = alice, destination = group.recipientId).timestamp
|
||||||
|
val editMessage1Timestamp2 = messageHelper.incomingEditText(targetTimestamp = editMessage1Timestamp1, sender = alice, destination = group.recipientId).timestamp
|
||||||
|
|
||||||
|
val message2Timestamp = messageHelper.incomingMedia(sender = bob, destination = group.recipientId).timestamp
|
||||||
|
|
||||||
|
val threadId = SignalDatabase.threads.getThreadIdFor(group.recipientId)!!
|
||||||
|
var threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 2
|
||||||
|
|
||||||
|
messageHelper.syncReadMessage(bob to message2Timestamp, alice to editMessage1Timestamp1, alice to editMessage1Timestamp2)
|
||||||
|
|
||||||
|
threadRecord = SignalDatabase.threads.getThreadRecord(threadId)!!
|
||||||
|
threadRecord.unreadCount assertIs 0
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class MessageHelper(var startTime: Long = System.currentTimeMillis()) {
|
||||||
|
|
||||||
|
fun incomingText(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
|
||||||
|
startTime += 1000
|
||||||
|
|
||||||
|
val messageData = MessageData(timestamp = startTime)
|
||||||
|
|
||||||
|
processor.process(
|
||||||
|
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
|
||||||
|
content = MessageContentFuzzer.fuzzTextMessage(
|
||||||
|
sentTimestamp = messageData.timestamp,
|
||||||
|
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
|
||||||
|
),
|
||||||
|
metadata = MessageContentFuzzer.envelopeMetadata(
|
||||||
|
source = sender,
|
||||||
|
destination = harness.self.id,
|
||||||
|
groupId = if (destination == group.recipientId) group.groupId else null
|
||||||
|
),
|
||||||
|
serverDeliveredTimestamp = messageData.timestamp + 10
|
||||||
|
)
|
||||||
|
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun incomingMedia(sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
|
||||||
|
startTime += 1000
|
||||||
|
|
||||||
|
val messageData = MessageData(timestamp = startTime)
|
||||||
|
|
||||||
|
processor.process(
|
||||||
|
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
|
||||||
|
content = MessageContentFuzzer.fuzzStickerMediaMessage(
|
||||||
|
sentTimestamp = messageData.timestamp,
|
||||||
|
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
|
||||||
|
),
|
||||||
|
metadata = MessageContentFuzzer.envelopeMetadata(
|
||||||
|
source = sender,
|
||||||
|
destination = harness.self.id,
|
||||||
|
groupId = if (destination == group.recipientId) group.groupId else null
|
||||||
|
),
|
||||||
|
serverDeliveredTimestamp = messageData.timestamp + 10
|
||||||
|
)
|
||||||
|
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun incomingEditText(targetTimestamp: Long = System.currentTimeMillis(), sender: RecipientId = alice, destination: RecipientId = harness.self.id): MessageData {
|
||||||
|
startTime += 1000
|
||||||
|
|
||||||
|
val messageData = MessageData(timestamp = startTime)
|
||||||
|
|
||||||
|
processor.process(
|
||||||
|
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
|
||||||
|
content = MessageContentFuzzer.editTextMessage(
|
||||||
|
targetTimestamp = targetTimestamp,
|
||||||
|
editedDataMessage = MessageContentFuzzer.fuzzTextMessage(
|
||||||
|
sentTimestamp = messageData.timestamp,
|
||||||
|
groupContextV2 = if (destination == group.recipientId) group.groupV2Context else null
|
||||||
|
).dataMessage!!
|
||||||
|
),
|
||||||
|
metadata = MessageContentFuzzer.envelopeMetadata(
|
||||||
|
source = sender,
|
||||||
|
destination = harness.self.id,
|
||||||
|
groupId = if (destination == group.recipientId) group.groupId else null
|
||||||
|
),
|
||||||
|
serverDeliveredTimestamp = messageData.timestamp + 10
|
||||||
|
)
|
||||||
|
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncReadMessage(vararg reads: Pair<RecipientId, Long>): MessageData {
|
||||||
|
startTime += 1000
|
||||||
|
val messageData = MessageData(timestamp = startTime)
|
||||||
|
|
||||||
|
processor.process(
|
||||||
|
envelope = MessageContentFuzzer.envelope(messageData.timestamp, serverGuid = messageData.serverGuid),
|
||||||
|
content = MessageContentFuzzer.syncReadsMessage(reads.toList()),
|
||||||
|
metadata = MessageContentFuzzer.envelopeMetadata(harness.self.id, harness.self.id, sourceDeviceId = 2),
|
||||||
|
serverDeliveredTimestamp = messageData.timestamp + 10
|
||||||
|
)
|
||||||
|
|
||||||
|
return messageData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class MessageData(val serverGuid: UUID = UUID.randomUUID(), val timestamp: Long)
|
||||||
|
}
|
|
@ -57,27 +57,10 @@ class UsernameEditFragmentTest {
|
||||||
InstrumentationApplicationDependencyProvider.clearHandlers()
|
InstrumentationApplicationDependencyProvider.clearHandlers()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testUsernameCreationInRegistration() {
|
|
||||||
val scenario = createScenario(true)
|
|
||||||
|
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
|
||||||
|
|
||||||
onView(withId(R.id.toolbar)).check { view, noViewFoundException ->
|
|
||||||
noViewFoundException.assertIsNull()
|
|
||||||
val toolbar = view as Toolbar
|
|
||||||
|
|
||||||
toolbar.navigationIcon.assertIsNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
onView(withText(R.string.UsernameEditFragment__add_a_username)).check(matches(isDisplayed()))
|
|
||||||
onView(withContentDescription(R.string.load_more_header__loading)).check(matches(withEffectiveVisibility(ViewMatchers.Visibility.GONE)))
|
|
||||||
}
|
|
||||||
|
|
||||||
@Ignore("Flakey espresso test.")
|
@Ignore("Flakey espresso test.")
|
||||||
@Test
|
@Test
|
||||||
fun testUsernameCreationOutsideOfRegistration() {
|
fun testUsernameCreationOutsideOfRegistration() {
|
||||||
val scenario = createScenario()
|
val scenario = createScenario(UsernameEditMode.NORMAL)
|
||||||
|
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
|
@ -108,7 +91,7 @@ class UsernameEditFragmentTest {
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
val scenario = createScenario(isInRegistration = true)
|
val scenario = createScenario(UsernameEditMode.NORMAL)
|
||||||
scenario.moveToState(Lifecycle.State.RESUMED)
|
scenario.moveToState(Lifecycle.State.RESUMED)
|
||||||
|
|
||||||
onView(withId(R.id.username_text)).perform(typeText(nickname))
|
onView(withId(R.id.username_text)).perform(typeText(nickname))
|
||||||
|
@ -132,8 +115,8 @@ class UsernameEditFragmentTest {
|
||||||
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
|
onView(withId(R.id.username_done_button)).check(matches(isNotEnabled()))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createScenario(isInRegistration: Boolean = false): FragmentScenario<UsernameEditFragment> {
|
private fun createScenario(mode: UsernameEditMode = UsernameEditMode.NORMAL): FragmentScenario<UsernameEditFragment> {
|
||||||
val fragmentArgs = UsernameEditFragmentArgs.Builder().setIsInRegistration(isInRegistration).build().toBundle()
|
val fragmentArgs = UsernameEditFragmentArgs.Builder().setMode(mode).build().toBundle()
|
||||||
return launchFragmentInContainer(
|
return launchFragmentInContainer(
|
||||||
fragmentArgs = fragmentArgs,
|
fragmentArgs = fragmentArgs,
|
||||||
themeResId = R.style.Signal_DayNight_NoActionBar
|
themeResId = R.style.Signal_DayNight_NoActionBar
|
||||||
|
|
|
@ -12,8 +12,6 @@ import org.tm.archive.database.RecipientTable
|
||||||
import org.tm.archive.database.SignalDatabase
|
import org.tm.archive.database.SignalDatabase
|
||||||
import org.tm.archive.keyvalue.SignalStore
|
import org.tm.archive.keyvalue.SignalStore
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.tm.archive.util.FeatureFlags
|
|
||||||
import org.tm.archive.util.FeatureFlagsAccessor
|
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||||
|
@ -29,11 +27,10 @@ class ContactRecordProcessorTest {
|
||||||
SignalStore.account().setE164(E164_SELF)
|
SignalStore.account().setE164(E164_SELF)
|
||||||
SignalStore.account().setAci(ACI_SELF)
|
SignalStore.account().setAci(ACI_SELF)
|
||||||
SignalStore.account().setPni(PNI_SELF)
|
SignalStore.account().setPni(PNI_SELF)
|
||||||
FeatureFlagsAccessor.forceValue(FeatureFlags.PHONE_NUMBER_PRIVACY, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun process_splitContact_normalSplit() {
|
fun process_splitContact_normalSplit_twoRecords() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
setStorageId(originalId, STORAGE_ID_A)
|
setStorageId(originalId, STORAGE_ID_A)
|
||||||
|
@ -69,6 +66,35 @@ class ContactRecordProcessorTest {
|
||||||
assertNotEquals(byAci, byE164)
|
assertNotEquals(byAci, byE164)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun process_splitContact_normalSplit_oneRecord() {
|
||||||
|
// GIVEN
|
||||||
|
val originalId = SignalDatabase.recipients.getAndPossiblyMerge(ACI_A, PNI_A, E164_A)
|
||||||
|
setStorageId(originalId, STORAGE_ID_A)
|
||||||
|
|
||||||
|
val remote = buildRecord(
|
||||||
|
STORAGE_ID_B,
|
||||||
|
ContactRecord(
|
||||||
|
aci = ACI_A.toString(),
|
||||||
|
unregisteredAtTimestamp = 100
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// WHEN
|
||||||
|
val subject = ContactRecordProcessor()
|
||||||
|
subject.process(listOf(remote), StorageSyncHelper.KEY_GENERATOR)
|
||||||
|
|
||||||
|
// THEN
|
||||||
|
val byAci: RecipientId = SignalDatabase.recipients.getByAci(ACI_A).get()
|
||||||
|
|
||||||
|
val byE164: RecipientId = SignalDatabase.recipients.getByE164(E164_A).get()
|
||||||
|
val byPni: RecipientId = SignalDatabase.recipients.getByPni(PNI_A).get()
|
||||||
|
|
||||||
|
assertEquals(originalId, byAci)
|
||||||
|
assertEquals(byE164, byPni)
|
||||||
|
assertNotEquals(byAci, byE164)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
|
fun process_splitContact_doNotSplitIfAciRecordIsRegistered() {
|
||||||
// GIVEN
|
// GIVEN
|
||||||
|
|
|
@ -40,7 +40,7 @@ class AliceClient(val serviceId: ServiceId, val e164: String, val trustRoot: ECK
|
||||||
ApplicationDependencies.getIncomingMessageObserver()
|
ApplicationDependencies.getIncomingMessageObserver()
|
||||||
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
|
.processEnvelope(bufferedStore, envelope, serverDeliveredTimestamp)
|
||||||
?.mapNotNull { it.run() }
|
?.mapNotNull { it.run() }
|
||||||
?.forEach { ApplicationDependencies.getJobManager().add(it) }
|
?.forEach { it.enqueue() }
|
||||||
|
|
||||||
bufferedStore.flushToDisk()
|
bufferedStore.flushToDisk()
|
||||||
val end = System.currentTimeMillis()
|
val end = System.currentTimeMillis()
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package org.tm.archive.testing
|
package org.tm.archive.testing
|
||||||
|
|
||||||
|
import okio.ByteString.Companion.toByteString
|
||||||
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
import org.signal.storageservice.protos.groups.Member
|
import org.signal.storageservice.protos.groups.Member
|
||||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
import org.signal.storageservice.protos.groups.local.DecryptedGroup
|
||||||
|
@ -9,6 +10,7 @@ import org.tm.archive.groups.GroupId
|
||||||
import org.tm.archive.recipients.Recipient
|
import org.tm.archive.recipients.Recipient
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
|
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,5 +48,8 @@ object GroupTestingUtils {
|
||||||
return member(aci = requireAci())
|
return member(aci = requireAci())
|
||||||
}
|
}
|
||||||
|
|
||||||
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId)
|
data class TestGroupInfo(val groupId: GroupId.V2, val masterKey: GroupMasterKey, val recipientId: RecipientId) {
|
||||||
|
val groupV2Context: GroupContextV2
|
||||||
|
get() = GroupContextV2(masterKey = masterKey.serialize().toByteString(), revision = 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,6 +12,7 @@ import org.whispersystems.signalservice.internal.push.AttachmentPointer
|
||||||
import org.whispersystems.signalservice.internal.push.BodyRange
|
import org.whispersystems.signalservice.internal.push.BodyRange
|
||||||
import org.whispersystems.signalservice.internal.push.Content
|
import org.whispersystems.signalservice.internal.push.Content
|
||||||
import org.whispersystems.signalservice.internal.push.DataMessage
|
import org.whispersystems.signalservice.internal.push.DataMessage
|
||||||
|
import org.whispersystems.signalservice.internal.push.EditMessage
|
||||||
import org.whispersystems.signalservice.internal.push.Envelope
|
import org.whispersystems.signalservice.internal.push.Envelope
|
||||||
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
import org.whispersystems.signalservice.internal.push.GroupContextV2
|
||||||
import org.whispersystems.signalservice.internal.push.SyncMessage
|
import org.whispersystems.signalservice.internal.push.SyncMessage
|
||||||
|
@ -33,22 +34,22 @@ object MessageContentFuzzer {
|
||||||
/**
|
/**
|
||||||
* Create an [Envelope].
|
* Create an [Envelope].
|
||||||
*/
|
*/
|
||||||
fun envelope(timestamp: Long): Envelope {
|
fun envelope(timestamp: Long, serverGuid: UUID = UUID.randomUUID()): Envelope {
|
||||||
return Envelope.Builder()
|
return Envelope.Builder()
|
||||||
.timestamp(timestamp)
|
.timestamp(timestamp)
|
||||||
.serverTimestamp(timestamp + 5)
|
.serverTimestamp(timestamp + 5)
|
||||||
.serverGuid(UUID.randomUUID().toString())
|
.serverGuid(serverGuid.toString())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create metadata to match an [Envelope].
|
* Create metadata to match an [Envelope].
|
||||||
*/
|
*/
|
||||||
fun envelopeMetadata(source: RecipientId, destination: RecipientId, groupId: GroupId.V2? = null): EnvelopeMetadata {
|
fun envelopeMetadata(source: RecipientId, destination: RecipientId, sourceDeviceId: Int = 1, groupId: GroupId.V2? = null): EnvelopeMetadata {
|
||||||
return EnvelopeMetadata(
|
return EnvelopeMetadata(
|
||||||
sourceServiceId = Recipient.resolved(source).requireServiceId(),
|
sourceServiceId = Recipient.resolved(source).requireServiceId(),
|
||||||
sourceE164 = null,
|
sourceE164 = null,
|
||||||
sourceDeviceId = 1,
|
sourceDeviceId = sourceDeviceId,
|
||||||
sealedSender = true,
|
sealedSender = true,
|
||||||
groupId = groupId?.decodedId,
|
groupId = groupId?.decodedId,
|
||||||
destinationServiceId = Recipient.resolved(destination).requireServiceId()
|
destinationServiceId = Recipient.resolved(destination).requireServiceId()
|
||||||
|
@ -60,10 +61,11 @@ object MessageContentFuzzer {
|
||||||
* - An expire timer value
|
* - An expire timer value
|
||||||
* - Bold style body ranges
|
* - Bold style body ranges
|
||||||
*/
|
*/
|
||||||
fun fuzzTextMessage(groupContextV2: GroupContextV2? = null): Content {
|
fun fuzzTextMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
|
||||||
return Content.Builder()
|
return Content.Builder()
|
||||||
.dataMessage(
|
.dataMessage(
|
||||||
DataMessage.Builder().buildWith {
|
DataMessage.Builder().buildWith {
|
||||||
|
timestamp = sentTimestamp
|
||||||
body = string()
|
body = string()
|
||||||
if (random.nextBoolean()) {
|
if (random.nextBoolean()) {
|
||||||
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
|
expireTimer = random.nextInt(0..28.days.inWholeSeconds.toInt())
|
||||||
|
@ -87,6 +89,20 @@ object MessageContentFuzzer {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an edit message.
|
||||||
|
*/
|
||||||
|
fun editTextMessage(targetTimestamp: Long, editedDataMessage: DataMessage): Content {
|
||||||
|
return Content.Builder()
|
||||||
|
.editMessage(
|
||||||
|
EditMessage.Builder().buildWith {
|
||||||
|
targetSentTimestamp = targetTimestamp
|
||||||
|
dataMessage = editedDataMessage
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a sync sent text message for the given [DataMessage].
|
* Create a sync sent text message for the given [DataMessage].
|
||||||
*/
|
*/
|
||||||
|
@ -116,6 +132,24 @@ object MessageContentFuzzer {
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a sync reads message for the given [RecipientId] and message timestamp pairings.
|
||||||
|
*/
|
||||||
|
fun syncReadsMessage(timestamps: List<Pair<RecipientId, Long>>): Content {
|
||||||
|
return Content
|
||||||
|
.Builder()
|
||||||
|
.syncMessage(
|
||||||
|
SyncMessage.Builder().buildWith {
|
||||||
|
read = timestamps.map { (senderId, timestamp) ->
|
||||||
|
SyncMessage.Read.Builder().buildWith {
|
||||||
|
this.senderAci = Recipient.resolved(senderId).requireAci().toString()
|
||||||
|
this.timestamp = timestamp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
).build()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a random media message that may be:
|
* Create a random media message that may be:
|
||||||
* - A text body
|
* - A text body
|
||||||
|
@ -184,22 +218,21 @@ object MessageContentFuzzer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a random media message that can never contain a text body. It may be:
|
* Create a random media message that contains a sticker.
|
||||||
* - A sticker
|
|
||||||
*/
|
*/
|
||||||
fun fuzzMediaMessageNoText(previousMessages: List<TestMessage> = emptyList()): Content {
|
fun fuzzStickerMediaMessage(sentTimestamp: Long? = null, groupContextV2: GroupContextV2? = null): Content {
|
||||||
return Content.Builder()
|
return Content.Builder()
|
||||||
.dataMessage(
|
.dataMessage(
|
||||||
DataMessage.Builder().buildWith {
|
DataMessage.Builder().buildWith {
|
||||||
if (random.nextFloat() < 0.9) {
|
timestamp = sentTimestamp
|
||||||
sticker = DataMessage.Sticker.Builder().buildWith {
|
sticker = DataMessage.Sticker.Builder().buildWith {
|
||||||
packId = byteString(length = 24)
|
packId = byteString(length = 24)
|
||||||
packKey = byteString(length = 128)
|
packKey = byteString(length = 128)
|
||||||
stickerId = random.nextInt()
|
stickerId = random.nextInt()
|
||||||
data_ = attachmentPointer()
|
data_ = attachmentPointer()
|
||||||
emoji = emojis.random(random)
|
emoji = emojis.random(random)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
groupV2 = groupContextV2
|
||||||
}
|
}
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ class SignalActivityRule(private val othersCount: Int = 4, private val createGro
|
||||||
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
||||||
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
||||||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
|
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true))
|
||||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||||
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
||||||
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
|
|
|
@ -2,7 +2,9 @@ package org.tm.archive.testing
|
||||||
|
|
||||||
import org.junit.rules.TestWatcher
|
import org.junit.rules.TestWatcher
|
||||||
import org.junit.runner.Description
|
import org.junit.runner.Description
|
||||||
|
import org.signal.core.util.deleteAll
|
||||||
import org.tm.archive.database.SignalDatabase
|
import org.tm.archive.database.SignalDatabase
|
||||||
|
import org.tm.archive.database.ThreadTable
|
||||||
import org.tm.archive.keyvalue.SignalStore
|
import org.tm.archive.keyvalue.SignalStore
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
|
@ -34,7 +36,8 @@ class SignalDatabaseRule(
|
||||||
|
|
||||||
private fun deleteAllThreads() {
|
private fun deleteAllThreads() {
|
||||||
if (deleteAllThreadsOnEachRun) {
|
if (deleteAllThreadsOnEachRun) {
|
||||||
SignalDatabase.threads.clearForTests()
|
SignalDatabase.threads.deleteAllConversations()
|
||||||
|
SignalDatabase.rawDatabase.deleteAll(ThreadTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,6 @@ package org.tm.archive.util;
|
||||||
public final class FeatureFlagsAccessor {
|
public final class FeatureFlagsAccessor {
|
||||||
|
|
||||||
public static void forceValue(String key, Object value) {
|
public static void forceValue(String key, Object value) {
|
||||||
FeatureFlags.FORCED_VALUES.put(FeatureFlags.PHONE_NUMBER_PRIVACY, true);
|
FeatureFlags.FORCED_VALUES.put(key, value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -100,7 +100,7 @@ object TestUsers {
|
||||||
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
val recipientId = RecipientId.from(SignalServiceAddress(aci, "+15555551%03d".format(i)))
|
||||||
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
SignalDatabase.recipients.setProfileName(recipientId, ProfileName.fromParts("Buddy", "#$i"))
|
||||||
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
SignalDatabase.recipients.setProfileKeyIfAbsent(recipientId, ProfileKeyUtil.createNew())
|
||||||
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true, true, true, true, true, true, true))
|
SignalDatabase.recipients.setCapabilities(recipientId, SignalServiceProfile.Capabilities(true, true, true))
|
||||||
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
SignalDatabase.recipients.setProfileSharing(recipientId, true)
|
||||||
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
SignalDatabase.recipients.markRegistered(recipientId, aci)
|
||||||
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
val otherIdentity = IdentityKeyUtil.generateIdentityKeyPair()
|
||||||
|
|
|
@ -118,7 +118,8 @@ class ConversationElementGenerator {
|
||||||
null,
|
null,
|
||||||
null,
|
null,
|
||||||
0,
|
0,
|
||||||
false
|
false,
|
||||||
|
null
|
||||||
)
|
)
|
||||||
|
|
||||||
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(
|
||||||
|
|
|
@ -13,6 +13,7 @@ import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.navigation.navGraphViewModels
|
import androidx.navigation.navGraphViewModels
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
|
||||||
import io.reactivex.rxjava3.kotlin.subscribeBy
|
import io.reactivex.rxjava3.kotlin.subscribeBy
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable
|
import org.signal.core.util.concurrent.LifecycleDisposable
|
||||||
|
@ -33,6 +34,7 @@ import org.tm.archive.conversation.colors.Colorizer
|
||||||
import org.tm.archive.conversation.colors.RecyclerViewColorizer
|
import org.tm.archive.conversation.colors.RecyclerViewColorizer
|
||||||
import org.tm.archive.conversation.mutiselect.MultiselectPart
|
import org.tm.archive.conversation.mutiselect.MultiselectPart
|
||||||
import org.tm.archive.conversation.v2.ConversationAdapterV2
|
import org.tm.archive.conversation.v2.ConversationAdapterV2
|
||||||
|
import org.tm.archive.conversation.v2.items.ChatColorsDrawable
|
||||||
import org.tm.archive.database.model.InMemoryMessageRecord
|
import org.tm.archive.database.model.InMemoryMessageRecord
|
||||||
import org.tm.archive.database.model.MessageRecord
|
import org.tm.archive.database.model.MessageRecord
|
||||||
import org.tm.archive.database.model.MmsMessageRecord
|
import org.tm.archive.database.model.MmsMessageRecord
|
||||||
|
@ -41,7 +43,6 @@ import org.tm.archive.groups.GroupId
|
||||||
import org.tm.archive.groups.GroupMigrationMembershipChange
|
import org.tm.archive.groups.GroupMigrationMembershipChange
|
||||||
import org.tm.archive.linkpreview.LinkPreview
|
import org.tm.archive.linkpreview.LinkPreview
|
||||||
import org.tm.archive.mediapreview.MediaIntentFactory
|
import org.tm.archive.mediapreview.MediaIntentFactory
|
||||||
import org.tm.archive.mms.GlideApp
|
|
||||||
import org.tm.archive.recipients.Recipient
|
import org.tm.archive.recipients.Recipient
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.tm.archive.stickers.StickerLocator
|
import org.tm.archive.stickers.StickerLocator
|
||||||
|
@ -61,11 +62,12 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
val adapter = ConversationAdapterV2(
|
val adapter = ConversationAdapterV2(
|
||||||
lifecycleOwner = viewLifecycleOwner,
|
lifecycleOwner = viewLifecycleOwner,
|
||||||
glideRequests = GlideApp.with(this),
|
requestManager = Glide.with(this),
|
||||||
clickListener = ClickListener(),
|
clickListener = ClickListener(),
|
||||||
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
hasWallpaper = springboardViewModel.hasWallpaper.value,
|
||||||
colorizer = Colorizer(),
|
colorizer = Colorizer(),
|
||||||
startExpirationTimeout = {}
|
startExpirationTimeout = {},
|
||||||
|
chatColorsDataProvider = { ChatColorsDrawable.ChatColorsData(null, null) }
|
||||||
)
|
)
|
||||||
|
|
||||||
if (springboardViewModel.hasWallpaper.value) {
|
if (springboardViewModel.hasWallpaper.value) {
|
||||||
|
@ -296,5 +298,17 @@ class InternalConversationTestFragment : Fragment(R.layout.conversation_test_fra
|
||||||
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
override fun onItemLongClick(itemView: View?, item: MultiselectPart?) {
|
||||||
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onShowSafetyTips(forGroup: Boolean) {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onReportSpamLearnMoreClicked() {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onMessageRequestAcceptOptionsClicked() {
|
||||||
|
Toast.makeText(requireContext(), "Can't touch this.", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
tools:replace="android:usesCleartextTraffic"
|
tools:replace="android:usesCleartextTraffic"
|
||||||
|
|
|
@ -22,17 +22,10 @@
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
|
||||||
<uses-permission android:name="org.tm.archive.ACCESS_SECRETS"/>
|
<uses-permission android:name="org.tm.archive.ACCESS_SECRETS"/>
|
||||||
<uses-permission android:name="android.permission.READ_PROFILE"/>
|
<uses-permission android:name="android.permission.READ_PROFILE"/>
|
||||||
<uses-permission android:name="android.permission.BROADCAST_WAP_PUSH"
|
|
||||||
tools:ignore="ProtectedPermissions"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
<uses-permission android:name="android.permission.READ_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.RECEIVE_MMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.SEND_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.WRITE_SMS"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
|
||||||
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||||
|
|
||||||
|
@ -50,16 +43,10 @@
|
||||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
<uses-permission android:name="android.permission.READ_CALL_STATE"/>
|
||||||
|
|
||||||
<!-- For sending/receiving events -->
|
|
||||||
<uses-permission android:name="android.permission.WRITE_CALENDAR"/>
|
|
||||||
<uses-permission android:name="android.permission.READ_CALENDAR"/>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Normal -->
|
<!-- Normal -->
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
@ -76,17 +63,14 @@
|
||||||
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
|
<uses-permission android:name="android.permission.INSTALL_SHORTCUT"/>
|
||||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
|
||||||
|
|
||||||
<!-- For fixing MMS -->
|
<!-- For device transfer -->
|
||||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||||
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
|
||||||
<!-- Set image as wallpaper -->
|
|
||||||
<uses-permission android:name="android.permission.SET_WALLPAPER"/>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
<uses-permission android:name="android.permission.BROADCAST_STICKY" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
|
||||||
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
|
||||||
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
<uses-permission android:name="android.permission.RAISED_THREAD_PRIORITY" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
|
||||||
|
@ -100,7 +84,6 @@
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||||
|
|
||||||
|
|
||||||
<application android:name=".ApplicationContext"
|
<application android:name=".ApplicationContext"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
@ -108,10 +91,10 @@
|
||||||
android:resizeableActivity="true"
|
android:resizeableActivity="true"
|
||||||
android:fullBackupOnly="false"
|
android:fullBackupOnly="false"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
tools:replace="android:allowBackup,android:supportsRtl"
|
|
||||||
android:backupAgent=".absbackup.SignalBackupAgent"
|
android:backupAgent=".absbackup.SignalBackupAgent"
|
||||||
android:theme="@style/TextSecure.LightTheme"
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
android:largeHeap="true">
|
android:largeHeap="true">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.gms.wallet.api.enabled"
|
android:name="com.google.android.gms.wallet.api.enabled"
|
||||||
android:value="true" />
|
android:value="true" />
|
||||||
|
@ -161,12 +144,6 @@
|
||||||
android:value=".MainActivity" />
|
android:value=".MainActivity" />
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".PromptMmsActivity"
|
|
||||||
android:label="Configure MMS Settings"
|
|
||||||
android:windowSoftInputMode="stateUnchanged"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<activity android:name=".DeviceProvisioningActivity"
|
<activity android:name=".DeviceProvisioningActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
|
@ -186,10 +163,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".preferences.MmsPreferencesActivity"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
<activity android:name=".sharing.interstitial.ShareInterstitialActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:windowSoftInputMode="adjustResize"
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
@ -694,7 +667,12 @@
|
||||||
|
|
||||||
<activity android:name=".NewConversationActivity"
|
<activity android:name=".NewConversationActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:windowSoftInputMode="stateAlwaysVisible"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity android:name=".recipients.ui.findby.FindByActivity"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
@ -726,6 +704,7 @@
|
||||||
android:theme="@style/TextSecure.DarkNoActionBar"
|
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||||
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
android:windowSoftInputMode="stateAlwaysHidden|adjustNothing"
|
||||||
android:launchMode="singleTop"
|
android:launchMode="singleTop"
|
||||||
|
android:screenOrientation="portrait"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
@ -769,6 +748,13 @@
|
||||||
android:windowSoftInputMode="stateAlwaysHidden"
|
android:windowSoftInputMode="stateAlwaysHidden"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".backup.v2.ui.MessageBackupsFlowActivity"
|
||||||
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:exported="false"
|
||||||
|
android:theme="@style/Signal.DayNight.NoActionBar"
|
||||||
|
android:windowSoftInputMode="adjustResize" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".stories.settings.StorySettingsActivity"
|
android:name=".stories.settings.StorySettingsActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
@ -940,12 +926,12 @@
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name="org.tm.archive.webrtc.VoiceCallShare"
|
<activity android:name="org.tm.archive.webrtc.VoiceCallShare"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:excludeFromRecents="true"
|
android:excludeFromRecents="true"
|
||||||
android:permission="android.permission.CALL_PHONE"
|
android:permission="android.permission.CALL_PHONE"
|
||||||
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
android:theme="@style/NoAnimation.Theme.BlackScreen"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
||||||
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW" />
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
@ -976,9 +962,8 @@
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".profiles.username.AddAUsernameActivity"
|
<activity android:name=".backup.v2.ui.MessageBackupsTestRestoreActivity"
|
||||||
android:theme="@style/Signal.DayNight.NoActionBar"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".profiles.manage.EditProfileActivity"
|
<activity android:name=".profiles.manage.EditProfileActivity"
|
||||||
|
@ -986,6 +971,11 @@
|
||||||
android:windowSoftInputMode="stateVisible|adjustResize"
|
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity android:name=".nicknames.NicknameActivity"
|
||||||
|
android:theme="@style/TextSecure.LightTheme"
|
||||||
|
android:windowSoftInputMode="stateVisible|adjustResize"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".payments.preferences.PaymentsActivity"
|
android:name=".payments.preferences.PaymentsActivity"
|
||||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||||
|
@ -1073,13 +1063,6 @@
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".megaphone.SmsExportMegaphoneActivity"
|
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
<activity android:name=".ratelimit.RecaptchaProofActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize|uiMode"
|
||||||
|
@ -1096,15 +1079,18 @@
|
||||||
android:theme="@style/Theme.Signal.WallpaperCropper"
|
android:theme="@style/Theme.Signal.WallpaperCropper"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".reactions.edit.EditReactionsActivity"
|
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrImageSelectionActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/TextSecure.DarkNoActionBar"
|
||||||
|
android:exported="false"/>
|
||||||
|
|
||||||
|
<activity android:name=".components.settings.app.usernamelinks.main.UsernameQrScannerActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<activity android:name=".exporter.flow.SmsExportActivity"
|
<activity android:name=".reactions.edit.EditReactionsActivity"
|
||||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||||
android:launchMode="singleTask"
|
|
||||||
android:screenOrientation="portrait"
|
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
|
@ -1113,12 +1099,6 @@
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<service
|
|
||||||
android:enabled="true"
|
|
||||||
android:name=".exporter.SignalSmsExportService"
|
|
||||||
android:foregroundServiceType="dataSync"
|
|
||||||
android:exported="false"/>
|
|
||||||
|
|
||||||
<service
|
<service
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:name=".service.webrtc.WebRtcCallService"
|
android:name=".service.webrtc.WebRtcCallService"
|
||||||
|
@ -1165,19 +1145,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
<service android:name=".service.QuickResponseService"
|
|
||||||
android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
|
|
||||||
android:exported="true" >
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<data android:scheme="sms" />
|
|
||||||
<data android:scheme="smsto" />
|
|
||||||
<data android:scheme="mms" />
|
|
||||||
<data android:scheme="mmsto" />
|
|
||||||
</intent-filter>
|
|
||||||
</service>
|
|
||||||
|
|
||||||
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
|
<service android:name=".service.AccountAuthenticatorService" android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.accounts.AccountAuthenticator" />
|
<action android:name="android.accounts.AccountAuthenticator" />
|
||||||
|
@ -1215,13 +1182,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver android:name=".service.SmsDeliveryListener"
|
|
||||||
android:exported="true">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="org.tm.archive.services.MESSAGE_SENT"/>
|
|
||||||
</intent-filter>
|
|
||||||
</receiver>
|
|
||||||
|
|
||||||
<receiver android:name=".notifications.MarkReadReceiver"
|
<receiver android:name=".notifications.MarkReadReceiver"
|
||||||
android:enabled="true"
|
android:enabled="true"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
@ -1281,11 +1241,6 @@
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
android:grantUriPermissions="true" />
|
android:grantUriPermissions="true" />
|
||||||
|
|
||||||
<provider android:name=".providers.MmsBodyProvider"
|
|
||||||
android:grantUriPermissions="true"
|
|
||||||
android:exported="false"
|
|
||||||
android:authorities="${applicationId}.mms" />
|
|
||||||
|
|
||||||
<provider android:name="androidx.core.content.FileProvider"
|
<provider android:name="androidx.core.content.FileProvider"
|
||||||
android:authorities="${applicationId}.fileprovider"
|
android:authorities="${applicationId}.fileprovider"
|
||||||
android:exported="false"
|
android:exported="false"
|
||||||
|
@ -1397,6 +1352,16 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<service android:name="org.tm.archive.service.webrtc.ActiveCallManager$ActiveCallForegroundService" android:exported="false" />
|
||||||
|
<receiver android:name="org.tm.archive.service.webrtc.ActiveCallManager$ActiveCallServiceReceiver" android:exported="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.tm.archive.service.webrtc.ActiveCallAction.DENY"/>
|
||||||
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="org.tm.archive.service.webrtc.ActiveCallAction.HANGUP"/>
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
<uses-library android:name="org.apache.http.legacy" android:required="false"/>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 87 KiB |
Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 67 KiB |
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 65 KiB |
Before Width: | Height: | Size: 126 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 174 KiB After Width: | Height: | Size: 176 KiB |
Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 120 KiB |
Before Width: | Height: | Size: 122 KiB After Width: | Height: | Size: 127 KiB |
Before Width: | Height: | Size: 176 KiB After Width: | Height: | Size: 209 KiB |
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 137 KiB |
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 213 KiB |
Before Width: | Height: | Size: 150 KiB After Width: | Height: | Size: 210 KiB |
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 221 KiB |
Before Width: | Height: | Size: 137 KiB After Width: | Height: | Size: 202 KiB |
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 298 KiB |
Before Width: | Height: | Size: 87 KiB After Width: | Height: | Size: 199 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 111 KiB |
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 208 KiB |
Before Width: | Height: | Size: 92 KiB After Width: | Height: | Size: 100 KiB |
BIN
app/src/main/assets/fonts/SignalSymbols-Bold.otf
Normal file
|
@ -1,63 +0,0 @@
|
||||||
package org.selfAuthentication
|
|
||||||
|
|
||||||
import android.app.Activity
|
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.view.View
|
|
||||||
import com.tm.androidcopysdk.AndroidCopySDK
|
|
||||||
import com.tm.androidcopysdk.ISendLogCallback
|
|
||||||
import com.tm.androidcopysdk.utils.PrefManager
|
|
||||||
import org.archiver.ArchivePreferenceConstants
|
|
||||||
import org.tm.archive.R
|
|
||||||
|
|
||||||
|
|
||||||
class SelfAuthenticationDialogBuilder : ISendLogCallback{
|
|
||||||
lateinit var mLogsSentContext : Activity
|
|
||||||
lateinit var mProgressDialog : View
|
|
||||||
|
|
||||||
fun doSendLogsClicked(context: Activity, view : View) {
|
|
||||||
mLogsSentContext = context
|
|
||||||
val builder = AlertDialog.Builder(context)
|
|
||||||
mProgressDialog = view
|
|
||||||
|
|
||||||
builder.setTitle(R.string.not_activated_user_dialog_title)
|
|
||||||
builder.setMessage(context.getString(R.string.not_activated_user_dialog_message))
|
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.DebugSendLogs) { dialog, which ->
|
|
||||||
|
|
||||||
// mProgressDialog.show()
|
|
||||||
mProgressDialog.visibility = View.VISIBLE
|
|
||||||
AndroidCopySDK.getInstance(context).sentLogs(
|
|
||||||
context,
|
|
||||||
this,
|
|
||||||
PrefManager.getStringPref(context, ArchivePreferenceConstants.PREF_KEY_DEVICE_PHONE_NUMBER, ""),
|
|
||||||
"Signal Archiver logs",
|
|
||||||
PrefManager.getStringPref(context, ArchivePreferenceConstants.PREF_KEY_DEVICE_NAME, ""),
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
ArchivePreferenceConstants.GENERATE_TOK_NAME,
|
|
||||||
ArchivePreferenceConstants.GENERATE_TOK_PASS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
builder.setNegativeButton(R.string.OK, null)
|
|
||||||
builder.show()
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendLogFailure() {
|
|
||||||
if(::mProgressDialog.isInitialized) {
|
|
||||||
// mProgressDialog.dismiss()
|
|
||||||
mProgressDialog.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendLogSucceed() {
|
|
||||||
if(::mProgressDialog.isInitialized) {
|
|
||||||
// mProgressDialog.dismiss()
|
|
||||||
mProgressDialog.visibility = View.GONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package org.archive.selfAuthentication
|
|
||||||
|
|
||||||
class SelfAuthenticatorConstants {
|
|
||||||
|
|
||||||
companion object{
|
|
||||||
|
|
||||||
val selfVersion = "1.1.1"
|
|
||||||
|
|
||||||
val selfAuthenticationSucceed = "selfAuthenticationSucceed"
|
|
||||||
val selfAuthenticationFailed = "selfAuthenticationFailed"
|
|
||||||
var isAuthenticationProcessOpened = false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -24,6 +24,7 @@ import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
import androidx.multidex.MultiDexApplication;
|
import androidx.multidex.MultiDexApplication;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.google.android.gms.security.ProviderInstaller;
|
import com.google.android.gms.security.ProviderInstaller;
|
||||||
|
|
||||||
import org.archiver.FCMConnector;
|
import org.archiver.FCMConnector;
|
||||||
|
@ -56,9 +57,11 @@ import org.tm.archive.jobs.CheckServiceReachabilityJob;
|
||||||
import org.tm.archive.jobs.DownloadLatestEmojiDataJob;
|
import org.tm.archive.jobs.DownloadLatestEmojiDataJob;
|
||||||
import org.tm.archive.jobs.EmojiSearchIndexDownloadJob;
|
import org.tm.archive.jobs.EmojiSearchIndexDownloadJob;
|
||||||
import org.tm.archive.jobs.ExternalLaunchDonationJob;
|
import org.tm.archive.jobs.ExternalLaunchDonationJob;
|
||||||
|
import org.tm.archive.jobs.FcmRefreshJob;
|
||||||
import org.tm.archive.jobs.FontDownloaderJob;
|
import org.tm.archive.jobs.FontDownloaderJob;
|
||||||
import org.tm.archive.jobs.GroupRingCleanupJob;
|
import org.tm.archive.jobs.GroupRingCleanupJob;
|
||||||
import org.tm.archive.jobs.GroupV2UpdateSelfProfileKeyJob;
|
import org.tm.archive.jobs.GroupV2UpdateSelfProfileKeyJob;
|
||||||
|
import org.tm.archive.jobs.LinkedDeviceInactiveCheckJob;
|
||||||
import org.tm.archive.jobs.MultiDeviceContactUpdateJob;
|
import org.tm.archive.jobs.MultiDeviceContactUpdateJob;
|
||||||
import org.tm.archive.jobs.PnpInitializeDevicesJob;
|
import org.tm.archive.jobs.PnpInitializeDevicesJob;
|
||||||
import org.tm.archive.jobs.PreKeysSyncJob;
|
import org.tm.archive.jobs.PreKeysSyncJob;
|
||||||
|
@ -74,7 +77,6 @@ import org.tm.archive.logging.CustomSignalProtocolLogger;
|
||||||
import org.tm.archive.logging.PersistentLogger;
|
import org.tm.archive.logging.PersistentLogger;
|
||||||
import org.tm.archive.messageprocessingalarm.RoutineMessageFetchReceiver;
|
import org.tm.archive.messageprocessingalarm.RoutineMessageFetchReceiver;
|
||||||
import org.tm.archive.migrations.ApplicationMigrations;
|
import org.tm.archive.migrations.ApplicationMigrations;
|
||||||
import org.tm.archive.mms.GlideApp;
|
|
||||||
import org.tm.archive.mms.SignalGlideComponents;
|
import org.tm.archive.mms.SignalGlideComponents;
|
||||||
import org.tm.archive.mms.SignalGlideModule;
|
import org.tm.archive.mms.SignalGlideModule;
|
||||||
import org.tm.archive.providers.BlobProvider;
|
import org.tm.archive.providers.BlobProvider;
|
||||||
|
@ -127,6 +129,10 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
|
|
||||||
private static final String TAG = Log.tag(ApplicationContext.class);
|
private static final String TAG = Log.tag(ApplicationContext.class);
|
||||||
|
|
||||||
|
/* public static ApplicationContext getInstance(Context context) {
|
||||||
|
return (ApplicationContext)context.getApplicationContext();
|
||||||
|
}*///*TM_SA*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
Tracer.getInstance().start("Application#onCreate()");
|
Tracer.getInstance().start("Application#onCreate()");
|
||||||
|
@ -174,7 +180,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||||
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
.addBlocking("glide", () -> SignalGlideModule.setRegisterGlideComponents(new SignalGlideComponents()))
|
||||||
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
|
.addNonBlocking(() -> RegistrationUtil.maybeMarkRegistrationComplete())
|
||||||
.addNonBlocking(() -> GlideApp.get(this))
|
.addNonBlocking(() -> Glide.get(this))
|
||||||
.addNonBlocking(this::cleanAvatarStorage)
|
.addNonBlocking(this::cleanAvatarStorage)
|
||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
.addNonBlocking(this::initializePendingRetryReceiptManager)
|
||||||
|
@ -186,7 +192,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
.addNonBlocking(this::initializeCleanup)
|
.addNonBlocking(this::initializeCleanup)
|
||||||
.addNonBlocking(this::initializeGlideCodecs)
|
.addNonBlocking(this::initializeGlideCodecs)
|
||||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
.addNonBlocking(this::beginJobLoop)
|
||||||
.addNonBlocking(EmojiSource::refresh)
|
.addNonBlocking(EmojiSource::refresh)
|
||||||
.addNonBlocking(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this))
|
.addNonBlocking(() -> ApplicationDependencies.getGiphyMp4Cache().onAppStart(this))
|
||||||
.addNonBlocking(this::ensureProfileUploaded)
|
.addNonBlocking(this::ensureProfileUploaded)
|
||||||
|
@ -194,7 +200,6 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
.addPostRender(() -> ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary())
|
.addPostRender(() -> ApplicationDependencies.getDeletedCallEventManager().scheduleIfNecessary())
|
||||||
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
.addPostRender(() -> RateLimitUtil.retryAllRateLimitedMessages(this))
|
||||||
.addPostRender(this::initializeExpiringMessageManager)
|
.addPostRender(this::initializeExpiringMessageManager)
|
||||||
.addPostRender(() -> SignalStore.settings().setDefaultSms(Util.isDefaultSmsProvider(this)))
|
|
||||||
.addPostRender(this::initializeTrimThreadsByDateManager)
|
.addPostRender(this::initializeTrimThreadsByDateManager)
|
||||||
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
|
.addPostRender(RefreshSvrCredentialsJob::enqueueIfNecessary)
|
||||||
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
.addPostRender(() -> DownloadLatestEmojiDataJob.scheduleIfNecessary(this))
|
||||||
|
@ -212,6 +217,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
.addPostRender(() -> ApplicationDependencies.getRecipientCache().warmUp())
|
||||||
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
|
.addPostRender(AccountConsistencyWorkerJob::enqueueIfNecessary)
|
||||||
.addPostRender(GroupRingCleanupJob::enqueue)
|
.addPostRender(GroupRingCleanupJob::enqueue)
|
||||||
|
.addPostRender(LinkedDeviceInactiveCheckJob::enqueueIfNecessary)
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||||
|
@ -270,7 +276,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
public void checkBuildExpiration() {
|
public void checkBuildExpiration() {
|
||||||
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
if (Util.getTimeUntilBuildExpiry() <= 0 && !SignalStore.misc().isClientDeprecated()) {
|
||||||
Log.w(TAG, "Build expired!");
|
Log.w(TAG, "Build expired!");
|
||||||
SignalStore.misc().markClientDeprecated();
|
SignalStore.misc().setClientDeprecated(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -304,7 +310,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
protected void initializeLogging() {
|
protected void initializeLogging() {
|
||||||
Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), new PersistentLogger(this)); // TM_SA
|
Log.initialize(FeatureFlags::internalUser, new AndroidLogger(), new PersistentLogger(this));
|
||||||
|
|
||||||
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
SignalProtocolLoggerProvider.setProvider(new CustomSignalProtocolLogger());
|
||||||
|
|
||||||
|
@ -463,6 +469,11 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
protected void beginJobLoop() {
|
||||||
|
ApplicationDependencies.getJobManager().beginJobLoop();
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
private void initializeBlobProvider() {
|
private void initializeBlobProvider() {
|
||||||
BlobProvider.getInstance().initialize(this);
|
BlobProvider.getInstance().initialize(this);
|
||||||
|
|
|
@ -18,6 +18,7 @@ import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.core.app.ActivityOptionsCompat;
|
import androidx.core.app.ActivityOptionsCompat;
|
||||||
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
|
||||||
|
|
||||||
|
import com.bumptech.glide.Glide;
|
||||||
import com.bumptech.glide.load.DataSource;
|
import com.bumptech.glide.load.DataSource;
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||||
import com.bumptech.glide.load.engine.GlideException;
|
import com.bumptech.glide.load.engine.GlideException;
|
||||||
|
@ -32,7 +33,6 @@ import org.tm.archive.contacts.avatars.ContactPhoto;
|
||||||
import org.tm.archive.contacts.avatars.FallbackContactPhoto;
|
import org.tm.archive.contacts.avatars.FallbackContactPhoto;
|
||||||
import org.tm.archive.contacts.avatars.ProfileContactPhoto;
|
import org.tm.archive.contacts.avatars.ProfileContactPhoto;
|
||||||
import org.tm.archive.contacts.avatars.ResourceContactPhoto;
|
import org.tm.archive.contacts.avatars.ResourceContactPhoto;
|
||||||
import org.tm.archive.mms.GlideApp;
|
|
||||||
import org.tm.archive.recipients.Recipient;
|
import org.tm.archive.recipients.Recipient;
|
||||||
import org.tm.archive.recipients.RecipientId;
|
import org.tm.archive.recipients.RecipientId;
|
||||||
import org.tm.archive.util.FullscreenHelper;
|
import org.tm.archive.util.FullscreenHelper;
|
||||||
|
@ -96,7 +96,7 @@ public final class AvatarPreviewActivity extends PassphraseRequiredActivity {
|
||||||
|
|
||||||
Resources resources = this.getResources();
|
Resources resources = this.getResources();
|
||||||
|
|
||||||
GlideApp.with(this)
|
Glide.with(this)
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
.load(contactPhoto)
|
.load(contactPhoto)
|
||||||
.fallback(fallbackPhoto.asCallCard(this))
|
.fallback(fallbackPhoto.asCallCard(this))
|
||||||
|
|
|
@ -8,6 +8,8 @@ import androidx.annotation.Nullable;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
import androidx.lifecycle.Observer;
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.signal.ringrtc.CallLinkRootKey;
|
import org.signal.ringrtc.CallLinkRootKey;
|
||||||
import org.tm.archive.components.voice.VoiceNotePlaybackState;
|
import org.tm.archive.components.voice.VoiceNotePlaybackState;
|
||||||
import org.tm.archive.contactshare.Contact;
|
import org.tm.archive.contactshare.Contact;
|
||||||
|
@ -26,7 +28,6 @@ import org.tm.archive.groups.GroupId;
|
||||||
import org.tm.archive.groups.GroupMigrationMembershipChange;
|
import org.tm.archive.groups.GroupMigrationMembershipChange;
|
||||||
import org.tm.archive.linkpreview.LinkPreview;
|
import org.tm.archive.linkpreview.LinkPreview;
|
||||||
import org.tm.archive.mediapreview.MediaIntentFactory;
|
import org.tm.archive.mediapreview.MediaIntentFactory;
|
||||||
import org.tm.archive.mms.GlideRequests;
|
|
||||||
import org.tm.archive.recipients.Recipient;
|
import org.tm.archive.recipients.Recipient;
|
||||||
import org.tm.archive.recipients.RecipientId;
|
import org.tm.archive.recipients.RecipientId;
|
||||||
import org.tm.archive.stickers.StickerLocator;
|
import org.tm.archive.stickers.StickerLocator;
|
||||||
|
@ -41,7 +42,7 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||||
@NonNull ConversationMessage messageRecord,
|
@NonNull ConversationMessage messageRecord,
|
||||||
@NonNull Optional<MessageRecord> previousMessageRecord,
|
@NonNull Optional<MessageRecord> previousMessageRecord,
|
||||||
@NonNull Optional<MessageRecord> nextMessageRecord,
|
@NonNull Optional<MessageRecord> nextMessageRecord,
|
||||||
@NonNull GlideRequests glideRequests,
|
@NonNull RequestManager requestManager,
|
||||||
@NonNull Locale locale,
|
@NonNull Locale locale,
|
||||||
@NonNull Set<MultiselectPart> batchSelected,
|
@NonNull Set<MultiselectPart> batchSelected,
|
||||||
@NonNull Recipient recipients,
|
@NonNull Recipient recipients,
|
||||||
|
@ -122,5 +123,8 @@ public interface BindableConversationItem extends Unbindable, GiphyMp4Playable,
|
||||||
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
|
void onEditedIndicatorClicked(@NonNull MessageRecord messageRecord);
|
||||||
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
|
void onShowGroupDescriptionClicked(@NonNull String groupName, @NonNull String description, boolean shouldLinkifyWebLinks);
|
||||||
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
|
void onJoinCallLink(@NonNull CallLinkRootKey callLinkRootKey);
|
||||||
|
void onShowSafetyTips(boolean forGroup);
|
||||||
|
void onReportSpamLearnMoreClicked();
|
||||||
|
void onMessageRequestAcceptOptionsClicked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,9 +3,10 @@ package org.tm.archive;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
import androidx.lifecycle.LifecycleOwner;
|
||||||
|
|
||||||
|
import com.bumptech.glide.RequestManager;
|
||||||
|
|
||||||
import org.tm.archive.conversationlist.model.ConversationSet;
|
import org.tm.archive.conversationlist.model.ConversationSet;
|
||||||
import org.tm.archive.database.model.ThreadRecord;
|
import org.tm.archive.database.model.ThreadRecord;
|
||||||
import org.tm.archive.mms.GlideRequests;
|
|
||||||
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -14,7 +15,7 @@ public interface BindableConversationListItem extends Unbindable {
|
||||||
|
|
||||||
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
void bind(@NonNull LifecycleOwner lifecycleOwner,
|
||||||
@NonNull ThreadRecord thread,
|
@NonNull ThreadRecord thread,
|
||||||
@NonNull GlideRequests glideRequests, @NonNull Locale locale,
|
@NonNull RequestManager requestManager, @NonNull Locale locale,
|
||||||
@NonNull Set<Long> typingThreads,
|
@NonNull Set<Long> typingThreads,
|
||||||
@NonNull ConversationSet selectedConversations);
|
@NonNull ConversationSet selectedConversations);
|
||||||
|
|
||||||
|
|
|
@ -11,16 +11,36 @@ import androidx.lifecycle.Lifecycle;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
import org.tm.archive.database.SignalDatabase;
|
|
||||||
import org.tm.archive.recipients.Recipient;
|
|
||||||
import org.signal.core.util.concurrent.SimpleTask;
|
import org.signal.core.util.concurrent.SimpleTask;
|
||||||
|
import org.signal.storageservice.protos.groups.local.DecryptedPendingMember;
|
||||||
|
import org.tm.archive.database.SignalDatabase;
|
||||||
|
import org.tm.archive.database.model.GroupRecord;
|
||||||
|
import org.tm.archive.recipients.Recipient;
|
||||||
|
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil;
|
||||||
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import okio.ByteString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
* This should be used whenever we want to prompt the user to block/unblock a recipient.
|
||||||
*/
|
*/
|
||||||
public final class BlockUnblockDialog {
|
public final class BlockUnblockDialog {
|
||||||
|
|
||||||
private BlockUnblockDialog() { }
|
private BlockUnblockDialog() {}
|
||||||
|
|
||||||
|
public static void showReportSpamFor(@NonNull Context context,
|
||||||
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onReportSpam,
|
||||||
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
|
{
|
||||||
|
SimpleTask.run(lifecycle,
|
||||||
|
() -> buildReportSpamFor(context, recipient, onReportSpam, onBlockAndReportSpam),
|
||||||
|
AlertDialog.Builder::show);
|
||||||
|
}
|
||||||
|
|
||||||
public static void showBlockFor(@NonNull Context context,
|
public static void showBlockFor(@NonNull Context context,
|
||||||
@NonNull Lifecycle lifecycle,
|
@NonNull Lifecycle lifecycle,
|
||||||
|
@ -137,4 +157,37 @@ public final class BlockUnblockDialog {
|
||||||
|
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@WorkerThread
|
||||||
|
private static AlertDialog.Builder buildReportSpamFor(@NonNull Context context,
|
||||||
|
@NonNull Recipient recipient,
|
||||||
|
@NonNull Runnable onReportSpam,
|
||||||
|
@Nullable Runnable onBlockAndReportSpam)
|
||||||
|
{
|
||||||
|
recipient = recipient.resolve();
|
||||||
|
|
||||||
|
AlertDialog.Builder builder = new MaterialAlertDialogBuilder(context)
|
||||||
|
.setTitle(R.string.BlockUnblockDialog_report_spam_title)
|
||||||
|
.setPositiveButton(R.string.BlockUnblockDialog_report_spam, (d, w) -> onReportSpam.run());
|
||||||
|
|
||||||
|
if (onBlockAndReportSpam != null) {
|
||||||
|
builder.setNeutralButton(android.R.string.cancel, null)
|
||||||
|
.setNegativeButton(R.string.BlockUnblockDialog_report_spam_and_block, (d, w) -> onBlockAndReportSpam.run());
|
||||||
|
} else {
|
||||||
|
builder.setNegativeButton(android.R.string.cancel, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (recipient.isGroup()) {
|
||||||
|
Recipient adder = SignalDatabase.groups().getGroupInviter(recipient.requireGroupId());
|
||||||
|
if (adder != null) {
|
||||||
|
builder.setMessage(context.getString(R.string.BlockUnblockDialog_report_spam_group_named_adder, adder.getDisplayName(context)));
|
||||||
|
} else {
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_report_spam_group_unknown_adder);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
builder.setMessage(R.string.BlockUnblockDialog_report_spam_description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,14 +24,17 @@ import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
|
|
||||||
|
import org.signal.core.util.DimensionUnit;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.tm.archive.components.ContactFilterView;
|
import org.tm.archive.components.ContactFilterView;
|
||||||
import org.tm.archive.contacts.ContactSelectionDisplayMode;
|
import org.tm.archive.contacts.ContactSelectionDisplayMode;
|
||||||
import org.tm.archive.contacts.sync.ContactDiscovery;
|
import org.tm.archive.contacts.sync.ContactDiscovery;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.recipients.RecipientId;
|
import org.tm.archive.recipients.RecipientId;
|
||||||
|
import org.tm.archive.util.DisplayMetricsUtil;
|
||||||
import org.tm.archive.util.DynamicNoActionBarTheme;
|
import org.tm.archive.util.DynamicNoActionBarTheme;
|
||||||
import org.tm.archive.util.DynamicTheme;
|
import org.tm.archive.util.DynamicTheme;
|
||||||
|
import org.tm.archive.util.FeatureFlags;
|
||||||
import org.tm.archive.util.ServiceUtil;
|
import org.tm.archive.util.ServiceUtil;
|
||||||
import org.tm.archive.util.Util;
|
import org.tm.archive.util.Util;
|
||||||
|
|
||||||
|
@ -70,8 +73,7 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle icicle, boolean ready) {
|
protected void onCreate(Bundle icicle, boolean ready) {
|
||||||
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
if (!getIntent().hasExtra(ContactSelectionListFragment.DISPLAY_MODE)) {
|
||||||
boolean includeSms = Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
|
int displayMode = ContactSelectionDisplayMode.FLAG_PUSH | ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_SELF;
|
||||||
int displayMode = includeSms ? ContactSelectionDisplayMode.FLAG_ALL : ContactSelectionDisplayMode.FLAG_PUSH | ContactSelectionDisplayMode.FLAG_ACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_INACTIVE_GROUPS | ContactSelectionDisplayMode.FLAG_SELF;
|
|
||||||
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
getIntent().putExtra(ContactSelectionListFragment.DISPLAY_MODE, displayMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -99,6 +101,10 @@ public abstract class ContactSelectionActivity extends PassphraseRequiredActivit
|
||||||
|
|
||||||
private void initializeContactFilterView() {
|
private void initializeContactFilterView() {
|
||||||
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
this.contactFilterView = findViewById(R.id.contact_filter_edit_text);
|
||||||
|
|
||||||
|
if (getResources().getDisplayMetrics().heightPixels >= DimensionUnit.DP.toPixels(600)) {
|
||||||
|
this.contactFilterView.focusAndShowKeyboard();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeToolbar() {
|
private void initializeToolbar() {
|
||||||
|
|
|
@ -27,6 +27,8 @@ class ContactSelectionListAdapter(
|
||||||
registerFactory(RefreshContactsModel::class.java, LayoutFactory({ RefreshContactsViewHolder(it, onClickCallbacks::onRefreshContactsClicked) }, R.layout.contact_selection_refresh_action_item))
|
registerFactory(RefreshContactsModel::class.java, LayoutFactory({ RefreshContactsViewHolder(it, onClickCallbacks::onRefreshContactsClicked) }, R.layout.contact_selection_refresh_action_item))
|
||||||
registerFactory(MoreHeaderModel::class.java, LayoutFactory({ MoreHeaderViewHolder(it) }, R.layout.contact_search_section_header))
|
registerFactory(MoreHeaderModel::class.java, LayoutFactory({ MoreHeaderViewHolder(it) }, R.layout.contact_search_section_header))
|
||||||
registerFactory(EmptyModel::class.java, LayoutFactory({ EmptyViewHolder(it) }, R.layout.contact_selection_empty_state))
|
registerFactory(EmptyModel::class.java, LayoutFactory({ EmptyViewHolder(it) }, R.layout.contact_selection_empty_state))
|
||||||
|
registerFactory(FindByUsernameModel::class.java, LayoutFactory({ FindByUsernameViewHolder(it, onClickCallbacks::onFindByUsernameClicked) }, R.layout.contact_selection_find_by_username_item))
|
||||||
|
registerFactory(FindByPhoneNumberModel::class.java, LayoutFactory({ FindByPhoneNumberViewHolder(it, onClickCallbacks::onFindByPhoneNumberClicked) }, R.layout.contact_selection_find_by_phone_number_item))
|
||||||
}
|
}
|
||||||
|
|
||||||
class NewGroupModel : MappingModel<NewGroupModel> {
|
class NewGroupModel : MappingModel<NewGroupModel> {
|
||||||
|
@ -44,6 +46,16 @@ class ContactSelectionListAdapter(
|
||||||
override fun areContentsTheSame(newItem: RefreshContactsModel): Boolean = true
|
override fun areContentsTheSame(newItem: RefreshContactsModel): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class FindByUsernameModel : MappingModel<FindByUsernameModel> {
|
||||||
|
override fun areItemsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
|
override fun areContentsTheSame(newItem: FindByUsernameModel): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
class FindByPhoneNumberModel : MappingModel<FindByPhoneNumberModel> {
|
||||||
|
override fun areItemsTheSame(newItem: FindByPhoneNumberModel): Boolean = true
|
||||||
|
override fun areContentsTheSame(newItem: FindByPhoneNumberModel): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
class MoreHeaderModel : MappingModel<MoreHeaderModel> {
|
class MoreHeaderModel : MappingModel<MoreHeaderModel> {
|
||||||
override fun areItemsTheSame(newItem: MoreHeaderModel): Boolean = true
|
override fun areItemsTheSame(newItem: MoreHeaderModel): Boolean = true
|
||||||
|
|
||||||
|
@ -92,13 +104,33 @@ class ContactSelectionListAdapter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class FindByPhoneNumberViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<FindByPhoneNumberModel>(itemView) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener { onClickListener() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(model: FindByPhoneNumberModel) = Unit
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FindByUsernameViewHolder(itemView: View, onClickListener: () -> Unit) : MappingViewHolder<FindByUsernameModel>(itemView) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.setOnClickListener { onClickListener() }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun bind(model: FindByUsernameModel) = Unit
|
||||||
|
}
|
||||||
|
|
||||||
class ArbitraryRepository : org.tm.archive.contacts.paged.ArbitraryRepository {
|
class ArbitraryRepository : org.tm.archive.contacts.paged.ArbitraryRepository {
|
||||||
|
|
||||||
enum class ArbitraryRow(val code: String) {
|
enum class ArbitraryRow(val code: String) {
|
||||||
NEW_GROUP("new-group"),
|
NEW_GROUP("new-group"),
|
||||||
INVITE_TO_SIGNAL("invite-to-signal"),
|
INVITE_TO_SIGNAL("invite-to-signal"),
|
||||||
MORE_HEADING("more-heading"),
|
MORE_HEADING("more-heading"),
|
||||||
REFRESH_CONTACTS("refresh-contacts");
|
REFRESH_CONTACTS("refresh-contacts"),
|
||||||
|
FIND_BY_USERNAME("find-by-username"),
|
||||||
|
FIND_BY_PHONE_NUMBER("find-by-phone-number");
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fromCode(code: String) = values().first { it.code == code }
|
fun fromCode(code: String) = values().first { it.code == code }
|
||||||
|
@ -120,6 +152,8 @@ class ContactSelectionListAdapter(
|
||||||
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
ArbitraryRow.INVITE_TO_SIGNAL -> InviteToSignalModel()
|
||||||
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
ArbitraryRow.MORE_HEADING -> MoreHeaderModel()
|
||||||
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
ArbitraryRow.REFRESH_CONTACTS -> RefreshContactsModel()
|
||||||
|
ArbitraryRow.FIND_BY_PHONE_NUMBER -> FindByPhoneNumberModel()
|
||||||
|
ArbitraryRow.FIND_BY_USERNAME -> FindByUsernameModel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,5 +162,7 @@ class ContactSelectionListAdapter(
|
||||||
fun onNewGroupClicked()
|
fun onNewGroupClicked()
|
||||||
fun onInviteToSignalClicked()
|
fun onInviteToSignalClicked()
|
||||||
fun onRefreshContactsClicked()
|
fun onRefreshContactsClicked()
|
||||||
|
fun onFindByPhoneNumberClicked()
|
||||||
|
fun onFindByUsernameClicked()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,6 +76,7 @@ import org.tm.archive.profiles.manage.UsernameRepository.UsernameAciFetchResult;
|
||||||
import org.tm.archive.recipients.Recipient;
|
import org.tm.archive.recipients.Recipient;
|
||||||
import org.tm.archive.recipients.RecipientId;
|
import org.tm.archive.recipients.RecipientId;
|
||||||
import org.tm.archive.util.CommunicationActions;
|
import org.tm.archive.util.CommunicationActions;
|
||||||
|
import org.tm.archive.util.FeatureFlags;
|
||||||
import org.tm.archive.util.TextSecurePreferences;
|
import org.tm.archive.util.TextSecurePreferences;
|
||||||
import org.tm.archive.util.UsernameUtil;
|
import org.tm.archive.util.UsernameUtil;
|
||||||
import org.tm.archive.util.ViewUtil;
|
import org.tm.archive.util.ViewUtil;
|
||||||
|
@ -141,6 +142,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
private ContactSearchMediator contactSearchMediator;
|
private ContactSearchMediator contactSearchMediator;
|
||||||
|
|
||||||
@Nullable private NewConversationCallback newConversationCallback;
|
@Nullable private NewConversationCallback newConversationCallback;
|
||||||
|
@Nullable private FindByCallback findByCallback;
|
||||||
@Nullable private NewCallCallback newCallCallback;
|
@Nullable private NewCallCallback newCallCallback;
|
||||||
@Nullable private ScrollCallback scrollCallback;
|
@Nullable private ScrollCallback scrollCallback;
|
||||||
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
@Nullable private OnItemLongClickListener onItemLongClickListener;
|
||||||
|
@ -161,6 +163,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
newConversationCallback = (NewConversationCallback) context;
|
newConversationCallback = (NewConversationCallback) context;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context instanceof FindByCallback) {
|
||||||
|
findByCallback = (FindByCallback) context;
|
||||||
|
}
|
||||||
|
|
||||||
if (context instanceof NewCallCallback) {
|
if (context instanceof NewCallCallback) {
|
||||||
newCallCallback = (NewCallCallback) context;
|
newCallCallback = (NewCallCallback) context;
|
||||||
}
|
}
|
||||||
|
@ -379,6 +385,16 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
newConversationCallback.onNewGroup(false);
|
newConversationCallback.onNewGroup(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByPhoneNumberClicked() {
|
||||||
|
findByCallback.onFindByPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByUsernameClicked() {
|
||||||
|
findByCallback.onFindByUsername();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onInviteToSignalClicked() {
|
public void onInviteToSignalClicked() {
|
||||||
if (newConversationCallback != null) {
|
if (newConversationCallback != null) {
|
||||||
|
@ -660,6 +676,10 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void addRecipientToSelectionIfAble(@NonNull RecipientId recipientId) {
|
||||||
|
listClickListener.onItemClick(new ContactSearchKey.RecipientSearchKey(recipientId, false));
|
||||||
|
}
|
||||||
|
|
||||||
private class ListClickListener {
|
private class ListClickListener {
|
||||||
public void onItemClick(ContactSearchKey contact) {
|
public void onItemClick(ContactSearchKey contact) {
|
||||||
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
boolean isUnknown = contact instanceof ContactSearchKey.UnknownRecipientKey;
|
||||||
|
@ -870,10 +890,15 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
return ContactSearchConfiguration.build(builder -> {
|
return ContactSearchConfiguration.build(builder -> {
|
||||||
builder.setQuery(contactSearchState.getQuery());
|
builder.setQuery(contactSearchState.getQuery());
|
||||||
|
|
||||||
if (newConversationCallback != null) {
|
if (newConversationCallback != null && !hasQuery) {
|
||||||
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.NEW_GROUP.getCode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (findByCallback != null && !hasQuery) {
|
||||||
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_BY_USERNAME.getCode());
|
||||||
|
builder.arbitrary(ContactSelectionListAdapter.ArbitraryRepository.ArbitraryRow.FIND_BY_PHONE_NUMBER.getCode());
|
||||||
|
}
|
||||||
|
|
||||||
if (transportType != null) {
|
if (transportType != null) {
|
||||||
if (!hasQuery && includeRecents) {
|
if (!hasQuery && includeRecents) {
|
||||||
builder.addSection(new ContactSearchConfiguration.Section.Recents(
|
builder.addSection(new ContactSearchConfiguration.Section.Recents(
|
||||||
|
@ -888,12 +913,14 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean hideHeader = newCallCallback != null || (newConversationCallback != null && !hasQuery);
|
||||||
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
|
builder.addSection(new ContactSearchConfiguration.Section.Individuals(
|
||||||
includeSelf,
|
includeSelf,
|
||||||
transportType,
|
transportType,
|
||||||
newCallCallback == null,
|
!hideHeader,
|
||||||
null,
|
null,
|
||||||
!hideLetterHeaders()
|
!hideLetterHeaders(),
|
||||||
|
newConversationCallback != null ? ContactSearchSortOrder.RECENCY : ContactSearchSortOrder.NATURAL
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -919,7 +946,7 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
builder.username(newRowMode);
|
builder.username(newRowMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newCallCallback != null || newConversationCallback != null) {
|
if ((newCallCallback != null || newConversationCallback != null) && !hasQuery) {
|
||||||
addMoreSection(builder);
|
addMoreSection(builder);
|
||||||
builder.withEmptyState(emptyBuilder -> {
|
builder.withEmptyState(emptyBuilder -> {
|
||||||
emptyBuilder.addSection(ContactSearchConfiguration.Section.Empty.INSTANCE);
|
emptyBuilder.addSection(ContactSearchConfiguration.Section.Empty.INSTANCE);
|
||||||
|
@ -1011,6 +1038,12 @@ public final class ContactSelectionListFragment extends LoggingFragment {
|
||||||
void onNewGroup(boolean forceV1);
|
void onNewGroup(boolean forceV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface FindByCallback {
|
||||||
|
void onFindByUsername();
|
||||||
|
|
||||||
|
void onFindByPhoneNumber();
|
||||||
|
}
|
||||||
|
|
||||||
public interface NewCallCallback {
|
public interface NewCallCallback {
|
||||||
void onInvite();
|
void onInvite();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.tm.archive;
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
@ -28,6 +29,7 @@ import org.signal.libsignal.zkgroup.profiles.ProfileKey;
|
||||||
import org.signal.qr.kitkat.ScanListener;
|
import org.signal.qr.kitkat.ScanListener;
|
||||||
import org.tm.archive.crypto.ProfileKeyUtil;
|
import org.tm.archive.crypto.ProfileKeyUtil;
|
||||||
import org.tm.archive.dependencies.ApplicationDependencies;
|
import org.tm.archive.dependencies.ApplicationDependencies;
|
||||||
|
import org.tm.archive.jobs.LinkedDeviceInactiveCheckJob;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.permissions.Permissions;
|
import org.tm.archive.permissions.Permissions;
|
||||||
import org.signal.core.util.Base64;
|
import org.signal.core.util.Base64;
|
||||||
|
@ -48,6 +50,8 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
|
|
||||||
private static final String TAG = Log.tag(DeviceActivity.class);
|
private static final String TAG = Log.tag(DeviceActivity.class);
|
||||||
|
|
||||||
|
private static final String EXTRA_DIRECT_TO_SCANNER = "add";
|
||||||
|
|
||||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||||
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
|
||||||
|
|
||||||
|
@ -56,6 +60,13 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
private DeviceLinkFragment deviceLinkFragment;
|
private DeviceLinkFragment deviceLinkFragment;
|
||||||
private MenuItem cameraSwitchItem = null;
|
private MenuItem cameraSwitchItem = null;
|
||||||
|
|
||||||
|
|
||||||
|
public static Intent getIntentForScanner(Context context) {
|
||||||
|
Intent intent = new Intent(context, DeviceActivity.class);
|
||||||
|
intent.putExtra(EXTRA_DIRECT_TO_SCANNER, true);
|
||||||
|
return intent;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPreCreate() {
|
public void onPreCreate() {
|
||||||
dynamicTheme.onCreate(this);
|
dynamicTheme.onCreate(this);
|
||||||
|
@ -79,7 +90,7 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
this.deviceListFragment.setAddDeviceButtonListener(this);
|
this.deviceListFragment.setAddDeviceButtonListener(this);
|
||||||
this.deviceAddFragment.setScanListener(this);
|
this.deviceAddFragment.setScanListener(this);
|
||||||
|
|
||||||
if (getIntent().getBooleanExtra("add", false)) {
|
if (getIntent().getBooleanExtra(EXTRA_DIRECT_TO_SCANNER, false)) {
|
||||||
initFragment(R.id.fragment_container, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceAddFragment, dynamicLanguage.getCurrentLocale());
|
||||||
} else {
|
} else {
|
||||||
initFragment(R.id.fragment_container, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
initFragment(R.id.fragment_container, deviceListFragment, dynamicLanguage.getCurrentLocale());
|
||||||
|
@ -221,6 +232,8 @@ public class DeviceActivity extends PassphraseRequiredActivity
|
||||||
protected void onPostExecute(Integer result) {
|
protected void onPostExecute(Integer result) {
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
|
|
||||||
|
LinkedDeviceInactiveCheckJob.enqueue();
|
||||||
|
|
||||||
Context context = DeviceActivity.this;
|
Context context = DeviceActivity.this;
|
||||||
|
|
||||||
switch (result) {
|
switch (result) {
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.signal.core.util.logging.Log;
|
||||||
import org.tm.archive.database.loaders.DeviceListLoader;
|
import org.tm.archive.database.loaders.DeviceListLoader;
|
||||||
import org.tm.archive.dependencies.ApplicationDependencies;
|
import org.tm.archive.dependencies.ApplicationDependencies;
|
||||||
import org.tm.archive.devicelist.Device;
|
import org.tm.archive.devicelist.Device;
|
||||||
|
import org.tm.archive.jobs.LinkedDeviceInactiveCheckJob;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.util.TextSecurePreferences;
|
import org.tm.archive.util.TextSecurePreferences;
|
||||||
import org.tm.archive.util.task.ProgressDialogAsyncTask;
|
import org.tm.archive.util.task.ProgressDialogAsyncTask;
|
||||||
|
@ -166,6 +167,7 @@ public class DeviceListFragment extends ListFragment
|
||||||
super.onPostExecute(result);
|
super.onPostExecute(result);
|
||||||
if (result) {
|
if (result) {
|
||||||
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
|
||||||
|
LinkedDeviceInactiveCheckJob.enqueue();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,7 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
|
.setTitle(getString(R.string.DeviceProvisioningActivity_link_a_signal_device))
|
||||||
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
|
.setMessage(getString(R.string.DeviceProvisioningActivity_it_looks_like_youre_trying_to_link_a_signal_device_using_a_3rd_party_scanner))
|
||||||
.setPositiveButton(R.string.DeviceProvisioningActivity_continue, (dialog1, which) -> {
|
.setPositiveButton(R.string.DeviceProvisioningActivity_continue, (dialog1, which) -> {
|
||||||
Intent intent = new Intent(DeviceProvisioningActivity.this, DeviceActivity.class);
|
startActivity(DeviceActivity.getIntentForScanner(this));
|
||||||
intent.putExtra("add", true);
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
finish();
|
||||||
})
|
})
|
||||||
.setNegativeButton(android.R.string.cancel, (dialog12, which) -> {
|
.setNegativeButton(android.R.string.cancel, (dialog12, which) -> {
|
||||||
|
@ -38,7 +36,6 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActivity {
|
||||||
.setOnDismissListener(dialog13 -> finish())
|
.setOnDismissListener(dialog13 -> finish())
|
||||||
.create();
|
.create();
|
||||||
|
|
||||||
dialog.setIcon(getResources().getDrawable(R.drawable.ic_launcher_foreground));
|
|
||||||
dialog.show();
|
dialog.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,6 @@ public final class GroupMembersDialog {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void contactClick(@NonNull Recipient recipient) {
|
private void contactClick(@NonNull Recipient recipient) {
|
||||||
RecipientBottomSheetDialogFragment.create(recipient.getId(), groupRecipient.requireGroupId())
|
RecipientBottomSheetDialogFragment.show(fragmentActivity.getSupportFragmentManager(), recipient.getId(), groupRecipient.requireGroupId());
|
||||||
.show(fragmentActivity.getSupportFragmentManager(), "BOTTOM");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,16 @@ import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.AnimRes;
|
import androidx.annotation.AnimRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.appcompat.widget.Toolbar;
|
import androidx.appcompat.widget.Toolbar;
|
||||||
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
import androidx.interpolator.view.animation.FastOutSlowInInterpolator;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
|
||||||
|
import org.signal.core.util.concurrent.ListenableFuture.Listener;
|
||||||
import org.tm.archive.components.ContactFilterView;
|
import org.tm.archive.components.ContactFilterView;
|
||||||
import org.tm.archive.components.ContactFilterView.OnFilterChangedListener;
|
import org.tm.archive.components.ContactFilterView.OnFilterChangedListener;
|
||||||
import org.tm.archive.contacts.ContactSelectionDisplayMode;
|
import org.tm.archive.contacts.ContactSelectionDisplayMode;
|
||||||
import org.tm.archive.contacts.SelectedContact;
|
import org.tm.archive.contacts.SelectedContact;
|
||||||
import org.tm.archive.database.SignalDatabase;
|
|
||||||
import org.tm.archive.groups.SelectionLimits;
|
import org.tm.archive.groups.SelectionLimits;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.mms.OutgoingMessage;
|
import org.tm.archive.mms.OutgoingMessage;
|
||||||
|
@ -38,7 +37,6 @@ import org.tm.archive.util.DynamicNoActionBarInviteTheme;
|
||||||
import org.tm.archive.util.DynamicTheme;
|
import org.tm.archive.util.DynamicTheme;
|
||||||
import org.tm.archive.util.Util;
|
import org.tm.archive.util.Util;
|
||||||
import org.tm.archive.util.ViewUtil;
|
import org.tm.archive.util.ViewUtil;
|
||||||
import org.tm.archive.util.concurrent.ListenableFuture.Listener;
|
|
||||||
import org.tm.archive.util.task.ProgressDialogAsyncTask;
|
import org.tm.archive.util.task.ProgressDialogAsyncTask;
|
||||||
import org.tm.archive.util.text.AfterTextChanged;
|
import org.tm.archive.util.text.AfterTextChanged;
|
||||||
|
|
||||||
|
@ -121,14 +119,9 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||||
|
|
||||||
if (Util.isDefaultSmsProvider(this) && SignalStore.misc().getSmsExportPhase().isSmsSupported()) {
|
smsButton.setVisibility(View.GONE);
|
||||||
shareButton.setOnClickListener(new ShareClickListener());
|
shareText.setText(R.string.InviteActivity_share);
|
||||||
smsButton.setOnClickListener(new SmsClickListener());
|
shareButton.setOnClickListener(new ShareClickListener());
|
||||||
} else {
|
|
||||||
smsButton.setVisibility(View.GONE);
|
|
||||||
shareText.setText(R.string.InviteActivity_share);
|
|
||||||
shareButton.setOnClickListener(new ShareClickListener());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Animation loadAnimation(@AnimRes int animResId) {
|
private Animation loadAnimation(@AnimRes int animResId) {
|
||||||
|
@ -202,13 +195,6 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SmsClickListener implements OnClickListener {
|
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
ViewUtil.animateIn(smsSendFrame, slideInAnimation);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SmsCancelClickListener implements OnClickListener {
|
private class SmsCancelClickListener implements OnClickListener {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
|
|
|
@ -2,33 +2,20 @@ package org.tm.archive;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.Dialog;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewTreeObserver;
|
import android.view.ViewTreeObserver;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
import com.tm.androidcopysdk.network.appSettings.UpdateEvent;
|
|
||||||
import com.tm.androidcopysdk.network.appSettings.WorkerIntentService;
|
|
||||||
import com.tm.androidcopysdk.utils.PrefManager;
|
|
||||||
import com.tm.logger.Log;
|
|
||||||
|
|
||||||
import org.archiver.ArchivePreferenceConstants;
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
|
||||||
import org.greenrobot.eventbus.Subscribe;
|
|
||||||
import org.greenrobot.eventbus.ThreadMode;
|
|
||||||
import org.selfAuthentication.SelfAuthenticatorManager;
|
|
||||||
import org.signal.core.util.concurrent.LifecycleDisposable;
|
import org.signal.core.util.concurrent.LifecycleDisposable;
|
||||||
import org.signal.donations.StripeApi;
|
import org.signal.donations.StripeApi;
|
||||||
import org.tm.archive.components.DebugLogsPromptDialogFragment;
|
import org.tm.archive.components.DebugLogsPromptDialogFragment;
|
||||||
|
@ -37,7 +24,6 @@ import org.tm.archive.components.settings.app.AppSettingsActivity;
|
||||||
import org.tm.archive.components.voice.VoiceNoteMediaController;
|
import org.tm.archive.components.voice.VoiceNoteMediaController;
|
||||||
import org.tm.archive.components.voice.VoiceNoteMediaControllerOwner;
|
import org.tm.archive.components.voice.VoiceNoteMediaControllerOwner;
|
||||||
import org.tm.archive.conversationlist.RelinkDevicesReminderBottomSheetFragment;
|
import org.tm.archive.conversationlist.RelinkDevicesReminderBottomSheetFragment;
|
||||||
import org.tm.archive.dependencies.ApplicationDependencies;
|
|
||||||
import org.tm.archive.devicetransfer.olddevice.OldDeviceExitActivity;
|
import org.tm.archive.devicetransfer.olddevice.OldDeviceExitActivity;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.net.DeviceTransferBlockingInterceptor;
|
import org.tm.archive.net.DeviceTransferBlockingInterceptor;
|
||||||
|
@ -63,7 +49,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||||
private ConversationListTabsViewModel conversationListTabsViewModel;
|
private ConversationListTabsViewModel conversationListTabsViewModel;
|
||||||
private VitalsViewModel vitalsViewModel;
|
private VitalsViewModel vitalsViewModel;
|
||||||
|
|
||||||
|
|
||||||
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
|
private final LifecycleDisposable lifecycleDisposable = new LifecycleDisposable();
|
||||||
|
|
||||||
private boolean onFirstRender = false;
|
private boolean onFirstRender = false;
|
||||||
|
@ -122,8 +107,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
private void presentVitalsState(VitalsViewModel.State state) {
|
private void presentVitalsState(VitalsViewModel.State state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
|
@ -169,7 +152,7 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||||
.setMessage(R.string.OldDeviceTransferLockedDialog__your_signal_account_has_been_transferred_to_your_new_device)
|
.setMessage(R.string.OldDeviceTransferLockedDialog__your_signal_account_has_been_transferred_to_your_new_device)
|
||||||
.setPositiveButton(R.string.OldDeviceTransferLockedDialog__done, (d, w) -> OldDeviceExitActivity.exit(this))
|
.setPositiveButton(R.string.OldDeviceTransferLockedDialog__done, (d, w) -> OldDeviceExitActivity.exit(this))
|
||||||
.setNegativeButton(R.string.OldDeviceTransferLockedDialog__cancel_and_activate_this_device, (d, w) -> {
|
.setNegativeButton(R.string.OldDeviceTransferLockedDialog__cancel_and_activate_this_device, (d, w) -> {
|
||||||
SignalStore.misc().clearOldDeviceTransferLocked();
|
SignalStore.misc().setOldDeviceTransferLocked(false);
|
||||||
DeviceTransferBlockingInterceptor.getInstance().unblockNetwork();
|
DeviceTransferBlockingInterceptor.getInstance().unblockNetwork();
|
||||||
})
|
})
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
@ -186,7 +169,6 @@ public class MainActivity extends PassphraseRequiredActivity implements VoiceNot
|
||||||
vitalsViewModel.checkSlowNotificationHeuristics();
|
vitalsViewModel.checkSlowNotificationHeuristics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onStop() {
|
protected void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
|
|
|
@ -50,6 +50,9 @@ import org.tm.archive.groups.ui.creategroup.CreateGroupActivity;
|
||||||
import org.tm.archive.keyvalue.SignalStore;
|
import org.tm.archive.keyvalue.SignalStore;
|
||||||
import org.tm.archive.recipients.Recipient;
|
import org.tm.archive.recipients.Recipient;
|
||||||
import org.tm.archive.recipients.RecipientId;
|
import org.tm.archive.recipients.RecipientId;
|
||||||
|
import org.tm.archive.recipients.RecipientRepository;
|
||||||
|
import org.tm.archive.recipients.ui.findby.FindByActivity;
|
||||||
|
import org.tm.archive.recipients.ui.findby.FindByMode;
|
||||||
import org.tm.archive.util.CommunicationActions;
|
import org.tm.archive.util.CommunicationActions;
|
||||||
import org.tm.archive.util.views.SimpleProgressDialog;
|
import org.tm.archive.util.views.SimpleProgressDialog;
|
||||||
|
|
||||||
|
@ -70,14 +73,16 @@ import io.reactivex.rxjava3.disposables.Disposable;
|
||||||
* @author Moxie Marlinspike
|
* @author Moxie Marlinspike
|
||||||
*/
|
*/
|
||||||
public class NewConversationActivity extends ContactSelectionActivity
|
public class NewConversationActivity extends ContactSelectionActivity
|
||||||
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener
|
implements ContactSelectionListFragment.NewConversationCallback, ContactSelectionListFragment.OnItemLongClickListener, ContactSelectionListFragment.FindByCallback
|
||||||
{
|
{
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = Log.tag(NewConversationActivity.class);
|
private static final String TAG = Log.tag(NewConversationActivity.class);
|
||||||
|
|
||||||
private ContactsManagementViewModel viewModel;
|
private ContactsManagementViewModel viewModel;
|
||||||
private ActivityResultLauncher<Intent> contactLauncher;
|
private ActivityResultLauncher<Intent> contactLauncher;
|
||||||
|
private ActivityResultLauncher<Intent> createGroupLauncher;
|
||||||
|
private ActivityResultLauncher<FindByMode> findByLauncher;
|
||||||
|
|
||||||
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
private final LifecycleDisposable disposables = new LifecycleDisposable();
|
||||||
|
|
||||||
|
@ -99,13 +104,23 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
findByLauncher = registerForActivityResult(new FindByActivity.Contract(), result -> {
|
||||||
|
if (result != null) {
|
||||||
|
launch(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
createGroupLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> {
|
||||||
|
if (result.getResultCode() == RESULT_OK) {
|
||||||
|
finish();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
viewModel = new ViewModelProvider(this, factory).get(ContactsManagementViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
|
public void onBeforeContactSelected(boolean isFromUnknownSearchKey, @NonNull Optional<RecipientId> recipientId, String number, @NonNull Consumer<Boolean> callback) {
|
||||||
boolean smsSupported = SignalStore.misc().getSmsExportPhase().allowSmsFeatures();
|
|
||||||
|
|
||||||
if (recipientId.isPresent()) {
|
if (recipientId.isPresent()) {
|
||||||
launch(Recipient.resolved(recipientId.get()));
|
launch(Recipient.resolved(recipientId.get()));
|
||||||
} else {
|
} else {
|
||||||
|
@ -116,33 +131,19 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
|
|
||||||
AlertDialog progress = SimpleProgressDialog.show(this);
|
AlertDialog progress = SimpleProgressDialog.show(this);
|
||||||
|
|
||||||
SimpleTask.run(getLifecycle(), () -> {
|
SimpleTask.run(getLifecycle(), () -> RecipientRepository.lookupNewE164(this, number), result -> {
|
||||||
Recipient resolved = Recipient.external(this, number);
|
|
||||||
|
|
||||||
if (!resolved.isRegistered() || !resolved.hasServiceId()) {
|
|
||||||
Log.i(TAG, "[onContactSelected] Not registered or no UUID. Doing a directory refresh.");
|
|
||||||
try {
|
|
||||||
ContactDiscovery.refresh(this, resolved, false, TimeUnit.SECONDS.toMillis(10));
|
|
||||||
resolved = Recipient.resolved(resolved.getId());
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.w(TAG, "[onContactSelected] Failed to refresh directory for new contact.");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return resolved;
|
|
||||||
}, resolved -> {
|
|
||||||
progress.dismiss();
|
progress.dismiss();
|
||||||
|
|
||||||
if (resolved != null) {
|
if (result instanceof RecipientRepository.LookupResult.Success) {
|
||||||
if (smsSupported || resolved.isRegistered() && resolved.hasServiceId()) {
|
Recipient resolved = Recipient.resolved(((RecipientRepository.LookupResult.Success) result).getRecipientId());
|
||||||
|
if (resolved.isRegistered() && resolved.hasServiceId()) {
|
||||||
launch(resolved);
|
launch(resolved);
|
||||||
} else {
|
|
||||||
new MaterialAlertDialogBuilder(this)
|
|
||||||
.setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, resolved.getDisplayName(this)))
|
|
||||||
.setPositiveButton(android.R.string.ok, null)
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
|
} else if (result instanceof RecipientRepository.LookupResult.NotFound || result instanceof RecipientRepository.LookupResult.InvalidEntry) {
|
||||||
|
new MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(getString(R.string.NewConversationActivity__s_is_not_a_signal_user, number))
|
||||||
|
.setPositiveButton(android.R.string.ok, null)
|
||||||
|
.show();
|
||||||
} else {
|
} else {
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again)
|
.setMessage(R.string.NetworkFailure__network_error_check_your_connection_and_try_again)
|
||||||
|
@ -150,8 +151,6 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else if (smsSupported) {
|
|
||||||
launch(Recipient.external(this, number));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -163,7 +162,12 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launch(Recipient recipient) {
|
private void launch(Recipient recipient) {
|
||||||
Disposable disposable = ConversationIntents.createBuilder(this, recipient.getId(), -1L)
|
launch(recipient.getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void launch(RecipientId recipientId) {
|
||||||
|
Disposable disposable = ConversationIntents.createBuilder(this, recipientId, -1L)
|
||||||
.map(builder -> builder
|
.map(builder -> builder
|
||||||
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
.withDraftText(getIntent().getStringExtra(Intent.EXTRA_TEXT))
|
||||||
.withDataUri(getIntent().getData())
|
.withDataUri(getIntent().getData())
|
||||||
|
@ -206,7 +210,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleCreateGroup() {
|
private void handleCreateGroup() {
|
||||||
startActivity(CreateGroupActivity.newIntent(this));
|
createGroupLauncher.launch(CreateGroupActivity.newIntent(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleInvite() {
|
private void handleInvite() {
|
||||||
|
@ -231,7 +235,17 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
@Override
|
@Override
|
||||||
public void onNewGroup(boolean forceV1) {
|
public void onNewGroup(boolean forceV1) {
|
||||||
handleCreateGroup();
|
handleCreateGroup();
|
||||||
finish();
|
// finish();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByUsername() {
|
||||||
|
findByLauncher.launch(FindByMode.USERNAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFindByPhoneNumber() {
|
||||||
|
findByLauncher.launch(FindByMode.PHONE_NUMBER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -286,7 +300,7 @@ public class NewConversationActivity extends ContactSelectionActivity
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (recipient.isRegistered() || (SignalStore.misc().getSmsExportPhase().allowSmsFeatures())) {
|
if (recipient.isRegistered()) {
|
||||||
return new ActionItem(
|
return new ActionItem(
|
||||||
R.drawable.ic_phone_right_24,
|
R.drawable.ic_phone_right_24,
|
||||||
getString(R.string.NewConversationActivity__audio_call),
|
getString(R.string.NewConversationActivity__audio_call),
|
||||||
|
|
|
@ -374,7 +374,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {
|
||||||
Log.i(TAG, "onAuthenticationSucceeded");
|
Log.i(TAG, "onAuthenticationSucceeded");
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_check_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.symbol_check_white_48);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.green_500), PorterDuff.Mode.SRC_IN);
|
||||||
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
fingerprintPrompt.animate().setInterpolator(new BounceInterpolator()).scaleX(1.1f).scaleY(1.1f).setDuration(500).setListener(new AnimationCompleteListener() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -388,7 +388,7 @@ public class PassphrasePromptActivity extends PassphraseActivity {
|
||||||
public void onAuthenticationFailed() {
|
public void onAuthenticationFailed() {
|
||||||
Log.w(TAG, "onAuthenticationFailed()");
|
Log.w(TAG, "onAuthenticationFailed()");
|
||||||
|
|
||||||
fingerprintPrompt.setImageResource(R.drawable.ic_close_white_48dp);
|
fingerprintPrompt.setImageResource(R.drawable.symbol_x_white_48);
|
||||||
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
fingerprintPrompt.getBackground().setColorFilter(getResources().getColor(R.color.red_500), PorterDuff.Mode.SRC_IN);
|
||||||
|
|
||||||
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
|
TranslateAnimation shake = new TranslateAnimation(0, 30, 0, 0);
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
package org.tm.archive;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.widget.Button;
|
|
||||||
|
|
||||||
import org.tm.archive.preferences.MmsPreferencesActivity;
|
|
||||||
|
|
||||||
public class PromptMmsActivity extends PassphraseRequiredActivity {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle bundle, boolean ready) {
|
|
||||||
setContentView(R.layout.prompt_apn_activity);
|
|
||||||
initializeResources();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void initializeResources() {
|
|
||||||
Button okButton = findViewById(R.id.ok_button);
|
|
||||||
Button cancelButton = findViewById(R.id.cancel_button);
|
|
||||||
|
|
||||||
okButton.setOnClickListener(v -> {
|
|
||||||
Intent intent = new Intent(PromptMmsActivity.this, MmsPreferencesActivity.class);
|
|
||||||
intent.putExtras(PromptMmsActivity.this.getIntent().getExtras());
|
|
||||||
startActivity(intent);
|
|
||||||
finish();
|
|
||||||
});
|
|
||||||
|
|
||||||
cancelButton.setOnClickListener(v -> finish());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -28,7 +28,7 @@ import java.util.Objects;
|
||||||
* screen lock.
|
* screen lock.
|
||||||
*/
|
*/
|
||||||
public abstract class SignalBaseActivity extends AppCompatActivity {//*TM_SA*/change BaseActivity to SignalBaseActivity
|
public abstract class SignalBaseActivity extends AppCompatActivity {//*TM_SA*/change BaseActivity to SignalBaseActivity
|
||||||
private static final String TAG = Log.tag(SignalBaseActivity.class);
|
private static final String TAG = Log.tag(BaseActivity.class);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
|
|
|
@ -137,6 +137,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
public static final String EXTRA_ENABLE_VIDEO_IF_AVAILABLE = WebRtcCallActivity.class.getCanonicalName() + ".ENABLE_VIDEO_IF_AVAILABLE";
|
||||||
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
|
public static final String EXTRA_STARTED_FROM_FULLSCREEN = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_FULLSCREEN";
|
||||||
public static final String EXTRA_STARTED_FROM_CALL_LINK = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_CALL_LINK";
|
public static final String EXTRA_STARTED_FROM_CALL_LINK = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_CALL_LINK";
|
||||||
|
public static final String EXTRA_LAUNCH_IN_PIP = WebRtcCallActivity.class.getCanonicalName() + ".STARTED_FROM_CALL_LINK";
|
||||||
|
|
||||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||||
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
|
private CallStateUpdatePopupWindow callStateUpdatePopupWindow;
|
||||||
|
@ -159,6 +160,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
private LifecycleDisposable lifecycleDisposable;
|
private LifecycleDisposable lifecycleDisposable;
|
||||||
private long lastCallLinkDisconnectDialogShowTime;
|
private long lastCallLinkDisconnectDialogShowTime;
|
||||||
private ControlsAndInfoController controlsAndInfo;
|
private ControlsAndInfoController controlsAndInfo;
|
||||||
|
private boolean enterPipOnResume;
|
||||||
|
private long lastProcessedIntentTimestamp;
|
||||||
|
|
||||||
private Disposable ephemeralStateDisposable = Disposable.empty();
|
private Disposable ephemeralStateDisposable = Disposable.empty();
|
||||||
|
|
||||||
|
@ -264,6 +267,11 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
}
|
}
|
||||||
}, TimeUnit.SECONDS.toMillis(1));
|
}, TimeUnit.SECONDS.toMillis(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (enterPipOnResume) {
|
||||||
|
enterPipOnResume = false;
|
||||||
|
enterPipModeIfPossible();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -299,10 +307,16 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
requestNewSizesThrottle.clear();
|
requestNewSizesThrottle.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplicationDependencies.getSignalCallManager().setEnableVideo(false);
|
||||||
|
|
||||||
if (!viewModel.isCallStarting()) {
|
if (!viewModel.isCallStarting()) {
|
||||||
CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot();
|
CallParticipantsState state = viewModel.getCallParticipantsStateSnapshot();
|
||||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
if (state != null) {
|
||||||
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||||
|
ApplicationDependencies.getSignalCallManager().cancelPreJoin();
|
||||||
|
} else if (state.getCallState().getInOngoingCall() && isInPipMode()) {
|
||||||
|
ApplicationDependencies.getSignalCallManager().relaunchPipOnForeground();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -361,6 +375,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
Log.d(TAG, "Intent: Action: " + intent.getAction());
|
Log.d(TAG, "Intent: Action: " + intent.getAction());
|
||||||
Log.d(TAG, "Intent: EXTRA_STARTED_FROM_FULLSCREEN: " + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false));
|
Log.d(TAG, "Intent: EXTRA_STARTED_FROM_FULLSCREEN: " + intent.getBooleanExtra(EXTRA_STARTED_FROM_FULLSCREEN, false));
|
||||||
Log.d(TAG, "Intent: EXTRA_ENABLE_VIDEO_IF_AVAILABLE: " + intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false));
|
Log.d(TAG, "Intent: EXTRA_ENABLE_VIDEO_IF_AVAILABLE: " + intent.getBooleanExtra(EXTRA_ENABLE_VIDEO_IF_AVAILABLE, false));
|
||||||
|
Log.d(TAG, "Intent: EXTRA_LAUNCH_IN_PIP: " + intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processIntent(@NonNull Intent intent) {
|
private void processIntent(@NonNull Intent intent) {
|
||||||
|
@ -373,6 +388,12 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
} else if (END_CALL_ACTION.equals(intent.getAction())) {
|
} else if (END_CALL_ACTION.equals(intent.getAction())) {
|
||||||
handleEndCall();
|
handleEndCall();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (System.currentTimeMillis() - lastProcessedIntentTimestamp > TimeUnit.SECONDS.toMillis(1)) {
|
||||||
|
enterPipOnResume = intent.getBooleanExtra(EXTRA_LAUNCH_IN_PIP, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastProcessedIntentTimestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializePendingParticipantFragmentListener() {
|
private void initializePendingParticipantFragmentListener() {
|
||||||
|
@ -765,7 +786,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
if (isFinishing()) return; // XXX Stuart added this check above, not sure why, so I'm repeating in ignorance. - moxie
|
||||||
new MaterialAlertDialogBuilder(this)
|
new MaterialAlertDialogBuilder(this)
|
||||||
.setTitle(R.string.RedPhone_number_not_registered)
|
.setTitle(R.string.RedPhone_number_not_registered)
|
||||||
.setIcon(R.drawable.ic_warning)
|
.setIcon(R.drawable.symbol_error_triangle_fill_24)
|
||||||
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
.setMessage(R.string.RedPhone_the_number_you_dialed_does_not_support_secure_voice)
|
||||||
.setCancelable(true)
|
.setCancelable(true)
|
||||||
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
.setPositiveButton(R.string.RedPhone_got_it, (d, w) -> handleTerminate(event.getRecipient(), HangupMessage.Type.NORMAL))
|
||||||
|
@ -851,8 +872,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSystemPipEnabledAndAvailable() {
|
private boolean isSystemPipEnabledAndAvailable() {
|
||||||
return Build.VERSION.SDK_INT >= 26 &&
|
return Build.VERSION.SDK_INT >= 26 && getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
|
||||||
getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void delayedFinish() {
|
private void delayedFinish() {
|
||||||
|
|
|
@ -3,13 +3,14 @@ package org.tm.archive.attachments
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
import org.signal.core.util.DatabaseId
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
data class AttachmentId(
|
data class AttachmentId(
|
||||||
@JsonProperty("rowId")
|
@JsonProperty("rowId")
|
||||||
@JvmField
|
@JvmField
|
||||||
val id: Long
|
val id: Long
|
||||||
) : Parcelable {
|
) : Parcelable, DatabaseId {
|
||||||
|
|
||||||
val isValid: Boolean
|
val isValid: Boolean
|
||||||
get() = id >= 0
|
get() = id >= 0
|
||||||
|
@ -17,4 +18,8 @@ data class AttachmentId(
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return "AttachmentId::$id"
|
return "AttachmentId::$id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun serialize(): String {
|
||||||
|
return id.toString()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,9 @@ class DatabaseAttachment : Attachment {
|
||||||
@JvmField
|
@JvmField
|
||||||
val hasData: Boolean
|
val hasData: Boolean
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val dataHash: String?
|
||||||
|
|
||||||
private val hasThumbnail: Boolean
|
private val hasThumbnail: Boolean
|
||||||
val displayOrder: Int
|
val displayOrder: Int
|
||||||
|
|
||||||
|
@ -53,7 +56,8 @@ class DatabaseAttachment : Attachment {
|
||||||
audioHash: AudioHash?,
|
audioHash: AudioHash?,
|
||||||
transformProperties: TransformProperties?,
|
transformProperties: TransformProperties?,
|
||||||
displayOrder: Int,
|
displayOrder: Int,
|
||||||
uploadTimestamp: Long
|
uploadTimestamp: Long,
|
||||||
|
dataHash: String?
|
||||||
) : super(
|
) : super(
|
||||||
contentType = contentType!!,
|
contentType = contentType!!,
|
||||||
transferState = transferProgress,
|
transferState = transferProgress,
|
||||||
|
@ -81,6 +85,7 @@ class DatabaseAttachment : Attachment {
|
||||||
this.attachmentId = attachmentId
|
this.attachmentId = attachmentId
|
||||||
this.mmsId = mmsId
|
this.mmsId = mmsId
|
||||||
this.hasData = hasData
|
this.hasData = hasData
|
||||||
|
this.dataHash = dataHash
|
||||||
this.hasThumbnail = hasThumbnail
|
this.hasThumbnail = hasThumbnail
|
||||||
this.displayOrder = displayOrder
|
this.displayOrder = displayOrder
|
||||||
}
|
}
|
||||||
|
@ -88,6 +93,7 @@ class DatabaseAttachment : Attachment {
|
||||||
constructor(parcel: Parcel) : super(parcel) {
|
constructor(parcel: Parcel) : super(parcel) {
|
||||||
attachmentId = ParcelCompat.readParcelable(parcel, AttachmentId::class.java.classLoader, AttachmentId::class.java)!!
|
attachmentId = ParcelCompat.readParcelable(parcel, AttachmentId::class.java.classLoader, AttachmentId::class.java)!!
|
||||||
hasData = ParcelUtil.readBoolean(parcel)
|
hasData = ParcelUtil.readBoolean(parcel)
|
||||||
|
dataHash = parcel.readString()
|
||||||
hasThumbnail = ParcelUtil.readBoolean(parcel)
|
hasThumbnail = ParcelUtil.readBoolean(parcel)
|
||||||
mmsId = parcel.readLong()
|
mmsId = parcel.readLong()
|
||||||
displayOrder = parcel.readInt()
|
displayOrder = parcel.readInt()
|
||||||
|
@ -97,6 +103,7 @@ class DatabaseAttachment : Attachment {
|
||||||
super.writeToParcel(dest, flags)
|
super.writeToParcel(dest, flags)
|
||||||
dest.writeParcelable(attachmentId, 0)
|
dest.writeParcelable(attachmentId, 0)
|
||||||
ParcelUtil.writeBoolean(dest, hasData)
|
ParcelUtil.writeBoolean(dest, hasData)
|
||||||
|
dest.writeString(dataHash)
|
||||||
ParcelUtil.writeBoolean(dest, hasThumbnail)
|
ParcelUtil.writeBoolean(dest, hasThumbnail)
|
||||||
dest.writeLong(mmsId)
|
dest.writeLong(mmsId)
|
||||||
dest.writeInt(displayOrder)
|
dest.writeInt(displayOrder)
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.tm.archive.attachments
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
|
import androidx.annotation.VisibleForTesting
|
||||||
import org.signal.core.util.Base64.encodeWithPadding
|
import org.signal.core.util.Base64.encodeWithPadding
|
||||||
import org.tm.archive.blurhash.BlurHash
|
import org.tm.archive.blurhash.BlurHash
|
||||||
import org.tm.archive.database.AttachmentTable
|
import org.tm.archive.database.AttachmentTable
|
||||||
|
@ -14,7 +15,8 @@ import org.whispersystems.signalservice.internal.push.DataMessage
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
class PointerAttachment : Attachment {
|
class PointerAttachment : Attachment {
|
||||||
private constructor(
|
@VisibleForTesting
|
||||||
|
constructor(
|
||||||
contentType: String,
|
contentType: String,
|
||||||
transferState: Int,
|
transferState: Int,
|
||||||
size: Long,
|
size: Long,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.tm.archive.media.DecryptableUriMediaInput;
|
import org.tm.archive.media.DecryptableUriMediaInput;
|
||||||
import org.tm.archive.media.MediaInput;
|
import org.tm.archive.video.interfaces.MediaInput;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
@ -57,12 +57,6 @@ public final class AudioWaveFormGenerator {
|
||||||
throw new IOException("Mime not audio");
|
throw new IOException("Mime not audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
//**TM_SA**//S
|
|
||||||
if(mime.equals("audio/ac3")){
|
|
||||||
throw new IOException("Ac3 Audio type not supported");
|
|
||||||
}
|
|
||||||
//**TM_SA**//E
|
|
||||||
|
|
||||||
MediaCodec codec = MediaCodec.createDecoderByType(mime);
|
MediaCodec codec = MediaCodec.createDecoderByType(mime);
|
||||||
|
|
||||||
if (totalDurationUs == 0) {
|
if (totalDurationUs == 0) {
|
||||||
|
|
|
@ -8,12 +8,12 @@ import android.widget.TextView
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
import androidx.appcompat.content.res.AppCompatResources
|
||||||
import androidx.core.view.setPadding
|
import androidx.core.view.setPadding
|
||||||
import com.airbnb.lottie.SimpleColorFilter
|
import com.airbnb.lottie.SimpleColorFilter
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import org.tm.archive.R
|
import org.tm.archive.R
|
||||||
import org.tm.archive.avatar.Avatar
|
import org.tm.archive.avatar.Avatar
|
||||||
import org.tm.archive.avatar.AvatarRenderer
|
import org.tm.archive.avatar.AvatarRenderer
|
||||||
import org.tm.archive.avatar.Avatars
|
import org.tm.archive.avatar.Avatars
|
||||||
import org.tm.archive.mms.DecryptableStreamUriLoader
|
import org.tm.archive.mms.DecryptableStreamUriLoader
|
||||||
import org.tm.archive.mms.GlideApp
|
|
||||||
import org.tm.archive.util.adapter.mapping.LayoutFactory
|
import org.tm.archive.util.adapter.mapping.LayoutFactory
|
||||||
import org.tm.archive.util.adapter.mapping.MappingAdapter
|
import org.tm.archive.util.adapter.mapping.MappingAdapter
|
||||||
import org.tm.archive.util.adapter.mapping.MappingModel
|
import org.tm.archive.util.adapter.mapping.MappingModel
|
||||||
|
@ -132,12 +132,12 @@ object AvatarPickerItem {
|
||||||
}
|
}
|
||||||
is Avatar.Photo -> {
|
is Avatar.Photo -> {
|
||||||
textView.visible = false
|
textView.visible = false
|
||||||
GlideApp.with(imageView).load(DecryptableStreamUriLoader.DecryptableUri(model.avatar.uri)).into(imageView)
|
Glide.with(imageView).load(DecryptableStreamUriLoader.DecryptableUri(model.avatar.uri)).into(imageView)
|
||||||
}
|
}
|
||||||
is Avatar.Resource -> {
|
is Avatar.Resource -> {
|
||||||
imageView.setPadding((imageView.width * 0.2).toInt())
|
imageView.setPadding((imageView.width * 0.2).toInt())
|
||||||
textView.visible = false
|
textView.visible = false
|
||||||
GlideApp.with(imageView).clear(imageView)
|
Glide.with(imageView).clear(imageView)
|
||||||
imageView.setImageResource(model.avatar.resourceId)
|
imageView.setImageResource(model.avatar.resourceId)
|
||||||
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
|
imageView.colorFilter = SimpleColorFilter(model.avatar.color.foregroundColor)
|
||||||
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
imageView.background.colorFilter = SimpleColorFilter(model.avatar.color.backgroundColor)
|
||||||
|
|
|
@ -5,10 +5,10 @@ import android.util.AttributeSet
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import androidx.core.content.res.use
|
import androidx.core.content.res.use
|
||||||
|
import com.bumptech.glide.RequestManager
|
||||||
import org.tm.archive.R
|
import org.tm.archive.R
|
||||||
import org.tm.archive.components.AvatarImageView
|
import org.tm.archive.components.AvatarImageView
|
||||||
import org.tm.archive.database.model.StoryViewState
|
import org.tm.archive.database.model.StoryViewState
|
||||||
import org.tm.archive.mms.GlideRequests
|
|
||||||
import org.tm.archive.recipients.Recipient
|
import org.tm.archive.recipients.Recipient
|
||||||
import org.tm.archive.stories.Stories
|
import org.tm.archive.stories.Stories
|
||||||
import org.tm.archive.util.visible
|
import org.tm.archive.util.visible
|
||||||
|
@ -76,7 +76,7 @@ class AvatarView @JvmOverloads constructor(
|
||||||
/**
|
/**
|
||||||
* Displays Note-to-Self
|
* Displays Note-to-Self
|
||||||
*/
|
*/
|
||||||
fun displayChatAvatar(requestManager: GlideRequests, recipient: Recipient, isQuickContactEnabled: Boolean) {
|
fun displayChatAvatar(requestManager: RequestManager, recipient: Recipient, isQuickContactEnabled: Boolean) {
|
||||||
avatar.setAvatar(requestManager, recipient, isQuickContactEnabled)
|
avatar.setAvatar(requestManager, recipient, isQuickContactEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -160,7 +160,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||||
for (String table : tables) {
|
for (String table : tables) {
|
||||||
throwIfCanceled(cancellationSignal);
|
throwIfCanceled(cancellationSignal);
|
||||||
if (table.equals(MessageTable.TABLE_NAME)) {
|
if (table.equals(MessageTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isNonExpiringMessage(cursor), null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isNonExpiringMessage(input, cursor), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(ReactionTable.TABLE_NAME)) {
|
} else if (table.equals(ReactionTable.TABLE_NAME)) {
|
||||||
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, CursorUtil.requireLong(cursor, ReactionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
count = exportTable(table, input, outputStream, cursor -> isForNonExpiringMessage(input, CursorUtil.requireLong(cursor, ReactionTable.MESSAGE_ID)), null, count, estimatedCount, cancellationSignal);
|
||||||
} else if (table.equals(MentionTable.TABLE_NAME)) {
|
} else if (table.equals(MentionTable.TABLE_NAME)) {
|
||||||
|
@ -579,25 +579,34 @@ public class FullBackupExporter extends FullBackupBase {
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isNonExpiringMessage(@NonNull Cursor cursor) {
|
private static boolean isNonExpiringMessage(@NonNull SQLiteDatabase db, @NonNull Cursor cursor) {
|
||||||
long expiresIn = CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN);
|
long id = CursorUtil.requireLong(cursor, MessageTable.ID);
|
||||||
boolean viewOnce = CursorUtil.requireBoolean(cursor, MessageTable.VIEW_ONCE);
|
long expireStarted = CursorUtil.requireLong(cursor, MessageTable.EXPIRE_STARTED);
|
||||||
|
long expiresIn = CursorUtil.requireLong(cursor, MessageTable.EXPIRES_IN);
|
||||||
|
long latestRevisionId = CursorUtil.requireLong(cursor, MessageTable.LATEST_REVISION_ID);
|
||||||
|
|
||||||
if (expiresIn == 0 && !viewOnce) {
|
long expiresAt = expireStarted + expiresIn;
|
||||||
return true;
|
long timeRemaining = expiresAt - System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (latestRevisionId > 0 && latestRevisionId != id ) {
|
||||||
|
return isForNonExpiringMessage(db, latestRevisionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return expiresIn > EXPIRATION_BACKUP_THRESHOLD;
|
if (expireStarted > 0 && timeRemaining <= EXPIRATION_BACKUP_THRESHOLD) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long messageId) {
|
private static boolean isForNonExpiringMessage(@NonNull SQLiteDatabase db, long messageId) {
|
||||||
String[] columns = new String[] { MessageTable.EXPIRES_IN, MessageTable.VIEW_ONCE };
|
String[] columns = new String[] { MessageTable.ID, MessageTable.EXPIRE_STARTED, MessageTable.EXPIRES_IN, MessageTable.LATEST_REVISION_ID };
|
||||||
String where = MessageTable.ID + " = ?";
|
String where = MessageTable.ID + " = ?";
|
||||||
String[] args = SqlUtil.buildArgs(messageId);
|
String[] args = SqlUtil.buildArgs(messageId);
|
||||||
|
|
||||||
try (Cursor mmsCursor = db.query(MessageTable.TABLE_NAME, columns, where, args, null, null, null)) {
|
try (Cursor mmsCursor = db.query(MessageTable.TABLE_NAME, columns, where, args, null, null, null)) {
|
||||||
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
if (mmsCursor != null && mmsCursor.moveToFirst()) {
|
||||||
return isNonExpiringMessage(mmsCursor);
|
return isNonExpiringMessage(db, mmsCursor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -155,11 +155,9 @@ public class FullBackupImporter extends FullBackupBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void processVersion(@NonNull SQLiteDatabase db, DatabaseVersion version) throws IOException {
|
private static void processVersion(@NonNull SQLiteDatabase db, DatabaseVersion version) throws IOException {
|
||||||
//**TM_SA**//s
|
if (version.version == null || version.version > db.getVersion()) {
|
||||||
/*if (version.version == null || version.version > db.getVersion()) {
|
|
||||||
throw new DatabaseDowngradeException(db.getVersion(), version.version != null ? version.version : -1);
|
throw new DatabaseDowngradeException(db.getVersion(), version.version != null ? version.version : -1);
|
||||||
}*/
|
}
|
||||||
//**TM_SA**//s
|
|
||||||
|
|
||||||
db.setVersion(version.version);
|
db.setVersion(version.version);
|
||||||
}
|
}
|
||||||
|
@ -196,7 +194,7 @@ public class FullBackupImporter extends FullBackupBase {
|
||||||
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
private static void processAttachment(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret, @NonNull SQLiteDatabase db, @NonNull Attachment attachment, BackupRecordInputStream inputStream)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
File dataFile = AttachmentTable.newFile(context);
|
File dataFile = AttachmentTable.newDataFile(context);
|
||||||
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
Pair<byte[], OutputStream> output = ModernEncryptingPartOutputStream.createFor(attachmentSecret, dataFile, false);
|
||||||
boolean isLegacyTable = SqlUtil.tableExists(db, "part");
|
boolean isLegacyTable = SqlUtil.tableExists(db, "part");
|
||||||
|
|
||||||
|
|
|
@ -5,10 +5,16 @@
|
||||||
|
|
||||||
package org.tm.archive.backup.v2
|
package org.tm.archive.backup.v2
|
||||||
|
|
||||||
|
import org.signal.core.util.Base64
|
||||||
import org.signal.core.util.EventTimer
|
import org.signal.core.util.EventTimer
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
import org.signal.core.util.withinTransaction
|
import org.signal.core.util.withinTransaction
|
||||||
|
import org.signal.libsignal.messagebackup.MessageBackup
|
||||||
|
import org.signal.libsignal.messagebackup.MessageBackup.ValidationResult
|
||||||
|
import org.signal.libsignal.messagebackup.MessageBackupKey
|
||||||
|
import org.signal.libsignal.protocol.ServiceId.Aci
|
||||||
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
import org.signal.libsignal.zkgroup.profiles.ProfileKey
|
||||||
|
import org.tm.archive.attachments.DatabaseAttachment
|
||||||
import org.tm.archive.backup.v2.database.ChatItemImportInserter
|
import org.tm.archive.backup.v2.database.ChatItemImportInserter
|
||||||
import org.tm.archive.backup.v2.database.clearAllDataForBackupRestore
|
import org.tm.archive.backup.v2.database.clearAllDataForBackupRestore
|
||||||
import org.tm.archive.backup.v2.processor.AccountDataProcessor
|
import org.tm.archive.backup.v2.processor.AccountDataProcessor
|
||||||
|
@ -16,6 +22,7 @@ import org.tm.archive.backup.v2.processor.CallLogBackupProcessor
|
||||||
import org.tm.archive.backup.v2.processor.ChatBackupProcessor
|
import org.tm.archive.backup.v2.processor.ChatBackupProcessor
|
||||||
import org.tm.archive.backup.v2.processor.ChatItemBackupProcessor
|
import org.tm.archive.backup.v2.processor.ChatItemBackupProcessor
|
||||||
import org.tm.archive.backup.v2.processor.RecipientBackupProcessor
|
import org.tm.archive.backup.v2.processor.RecipientBackupProcessor
|
||||||
|
import org.tm.archive.backup.v2.proto.BackupInfo
|
||||||
import org.tm.archive.backup.v2.stream.BackupExportWriter
|
import org.tm.archive.backup.v2.stream.BackupExportWriter
|
||||||
import org.tm.archive.backup.v2.stream.EncryptedBackupReader
|
import org.tm.archive.backup.v2.stream.EncryptedBackupReader
|
||||||
import org.tm.archive.backup.v2.stream.EncryptedBackupWriter
|
import org.tm.archive.backup.v2.stream.EncryptedBackupWriter
|
||||||
|
@ -23,13 +30,22 @@ import org.tm.archive.backup.v2.stream.PlainTextBackupReader
|
||||||
import org.tm.archive.backup.v2.stream.PlainTextBackupWriter
|
import org.tm.archive.backup.v2.stream.PlainTextBackupWriter
|
||||||
import org.tm.archive.database.SignalDatabase
|
import org.tm.archive.database.SignalDatabase
|
||||||
import org.tm.archive.dependencies.ApplicationDependencies
|
import org.tm.archive.dependencies.ApplicationDependencies
|
||||||
|
import org.tm.archive.groups.GroupId
|
||||||
|
import org.tm.archive.jobs.RequestGroupV2InfoJob
|
||||||
import org.tm.archive.keyvalue.SignalStore
|
import org.tm.archive.keyvalue.SignalStore
|
||||||
import org.tm.archive.recipients.RecipientId
|
import org.tm.archive.recipients.RecipientId
|
||||||
import org.whispersystems.signalservice.api.NetworkResult
|
import org.whispersystems.signalservice.api.NetworkResult
|
||||||
import org.whispersystems.signalservice.api.archive.ArchiveGetBackupInfoResponse
|
import org.whispersystems.signalservice.api.archive.ArchiveGetMediaItemsResponse
|
||||||
|
import org.whispersystems.signalservice.api.archive.ArchiveMediaRequest
|
||||||
|
import org.whispersystems.signalservice.api.archive.ArchiveMediaResponse
|
||||||
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
import org.whispersystems.signalservice.api.archive.ArchiveServiceCredential
|
||||||
|
import org.whispersystems.signalservice.api.archive.BatchArchiveMediaResponse
|
||||||
|
import org.whispersystems.signalservice.api.archive.DeleteArchivedMediaRequest
|
||||||
|
import org.whispersystems.signalservice.api.backup.BackupKey
|
||||||
|
import org.whispersystems.signalservice.api.crypto.AttachmentCipherStreamUtil
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
import org.whispersystems.signalservice.api.push.ServiceId.ACI
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
import org.whispersystems.signalservice.api.push.ServiceId.PNI
|
||||||
|
import org.whispersystems.signalservice.internal.crypto.PaddingInputStream
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
@ -37,6 +53,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||||
object BackupRepository {
|
object BackupRepository {
|
||||||
|
|
||||||
private val TAG = Log.tag(BackupRepository::class.java)
|
private val TAG = Log.tag(BackupRepository::class.java)
|
||||||
|
private const val VERSION = 1L
|
||||||
|
|
||||||
fun export(plaintext: Boolean = false): ByteArray {
|
fun export(plaintext: Boolean = false): ByteArray {
|
||||||
val eventTimer = EventTimer()
|
val eventTimer = EventTimer()
|
||||||
|
@ -53,7 +70,15 @@ object BackupRepository {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val exportState = ExportState(System.currentTimeMillis())
|
||||||
|
|
||||||
writer.use {
|
writer.use {
|
||||||
|
writer.write(
|
||||||
|
BackupInfo(
|
||||||
|
version = VERSION,
|
||||||
|
backupTimeMs = exportState.backupTime
|
||||||
|
)
|
||||||
|
)
|
||||||
// Note: Without a transaction, we may export inconsistent state. But because we have a transaction,
|
// Note: Without a transaction, we may export inconsistent state. But because we have a transaction,
|
||||||
// writes from other threads are blocked. This is something to think more about.
|
// writes from other threads are blocked. This is something to think more about.
|
||||||
SignalDatabase.rawDatabase.withinTransaction {
|
SignalDatabase.rawDatabase.withinTransaction {
|
||||||
|
@ -62,12 +87,12 @@ object BackupRepository {
|
||||||
eventTimer.emit("account")
|
eventTimer.emit("account")
|
||||||
}
|
}
|
||||||
|
|
||||||
RecipientBackupProcessor.export {
|
RecipientBackupProcessor.export(exportState) {
|
||||||
writer.write(it)
|
writer.write(it)
|
||||||
eventTimer.emit("recipient")
|
eventTimer.emit("recipient")
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatBackupProcessor.export { frame ->
|
ChatBackupProcessor.export(exportState) { frame ->
|
||||||
writer.write(frame)
|
writer.write(frame)
|
||||||
eventTimer.emit("thread")
|
eventTimer.emit("thread")
|
||||||
}
|
}
|
||||||
|
@ -77,7 +102,7 @@ object BackupRepository {
|
||||||
eventTimer.emit("call")
|
eventTimer.emit("call")
|
||||||
}
|
}
|
||||||
|
|
||||||
ChatItemBackupProcessor.export { frame ->
|
ChatItemBackupProcessor.export(exportState) { frame ->
|
||||||
writer.write(frame)
|
writer.write(frame)
|
||||||
eventTimer.emit("message")
|
eventTimer.emit("message")
|
||||||
}
|
}
|
||||||
|
@ -89,6 +114,13 @@ object BackupRepository {
|
||||||
return outputStream.toByteArray()
|
return outputStream.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun validate(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData): ValidationResult {
|
||||||
|
val masterKey = SignalStore.svr().getOrCreateMasterKey()
|
||||||
|
val key = MessageBackupKey(masterKey.serialize(), Aci.parseFromBinary(selfData.aci.toByteArray()))
|
||||||
|
|
||||||
|
return MessageBackup.validate(key, MessageBackup.Purpose.REMOTE_BACKUP, inputStreamFactory, length)
|
||||||
|
}
|
||||||
|
|
||||||
fun import(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData, plaintext: Boolean = false) {
|
fun import(length: Long, inputStreamFactory: () -> InputStream, selfData: SelfData, plaintext: Boolean = false) {
|
||||||
val eventTimer = EventTimer()
|
val eventTimer = EventTimer()
|
||||||
|
|
||||||
|
@ -103,6 +135,15 @@ object BackupRepository {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val header = frameReader.getHeader()
|
||||||
|
if (header == null) {
|
||||||
|
Log.e(TAG, "Backup is missing header!")
|
||||||
|
return
|
||||||
|
} else if (header.version > VERSION) {
|
||||||
|
Log.e(TAG, "Backup version is newer than we understand: ${header.version}")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// Note: Without a transaction, bad imports could lead to lost data. But because we have a transaction,
|
// Note: Without a transaction, bad imports could lead to lost data. But because we have a transaction,
|
||||||
// writes from other threads are blocked. This is something to think more about.
|
// writes from other threads are blocked. This is something to think more about.
|
||||||
SignalDatabase.rawDatabase.withinTransaction {
|
SignalDatabase.rawDatabase.withinTransaction {
|
||||||
|
@ -118,6 +159,7 @@ object BackupRepository {
|
||||||
SignalDatabase.recipients.setProfileKey(selfId, selfData.profileKey)
|
SignalDatabase.recipients.setProfileKey(selfId, selfData.profileKey)
|
||||||
SignalDatabase.recipients.setProfileSharing(selfId, true)
|
SignalDatabase.recipients.setProfileSharing(selfId, true)
|
||||||
|
|
||||||
|
eventTimer.emit("setup")
|
||||||
val backupState = BackupState()
|
val backupState = BackupState()
|
||||||
val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(backupState)
|
val chatItemInserter: ChatItemImportInserter = ChatItemBackupProcessor.beginImport(backupState)
|
||||||
|
|
||||||
|
@ -162,13 +204,21 @@ object BackupRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val groups = SignalDatabase.groups.getGroups()
|
||||||
|
while (groups.hasNext()) {
|
||||||
|
val group = groups.next()
|
||||||
|
if (group.id.isV2) {
|
||||||
|
ApplicationDependencies.getJobManager().add(RequestGroupV2InfoJob(group.id as GroupId.V2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Log.d(TAG, "import() ${eventTimer.stop().summary}")
|
Log.d(TAG, "import() ${eventTimer.stop().summary}")
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an object with details about the remote backup state.
|
* Returns an object with details about the remote backup state.
|
||||||
*/
|
*/
|
||||||
fun getRemoteBackupState(): NetworkResult<ArchiveGetBackupInfoResponse> {
|
fun getRemoteBackupState(): NetworkResult<BackupMetadata> {
|
||||||
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
|
@ -182,6 +232,18 @@ object BackupRepository {
|
||||||
}
|
}
|
||||||
.then { credential ->
|
.then { credential ->
|
||||||
api.getBackupInfo(backupKey, credential)
|
api.getBackupInfo(backupKey, credential)
|
||||||
|
.map { it to credential }
|
||||||
|
}
|
||||||
|
.then { pair ->
|
||||||
|
val (info, credential) = pair
|
||||||
|
api.debugGetUploadedMediaItemMetadata(backupKey, credential)
|
||||||
|
.also { Log.i(TAG, "MediaItemMetadataResult: $it") }
|
||||||
|
.map { mediaObjects ->
|
||||||
|
BackupMetadata(
|
||||||
|
usedSpace = info.usedSpace ?: 0,
|
||||||
|
mediaCount = mediaObjects.size.toLong()
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,6 +281,77 @@ object BackupRepository {
|
||||||
.also { Log.i(TAG, "OverallResult: $it") } is NetworkResult.Success
|
.also { Log.i(TAG, "OverallResult: $it") } is NetworkResult.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an object with details about the remote backup state.
|
||||||
|
*/
|
||||||
|
fun debugGetArchivedMediaState(): NetworkResult<List<ArchiveGetMediaItemsResponse.StoredMediaObject>> {
|
||||||
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
|
return api
|
||||||
|
.triggerBackupIdReservation(backupKey)
|
||||||
|
.then { getAuthCredential() }
|
||||||
|
.then { credential ->
|
||||||
|
api.debugGetUploadedMediaItemMetadata(backupKey, credential)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun archiveMedia(attachment: DatabaseAttachment): NetworkResult<ArchiveMediaResponse> {
|
||||||
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
|
return api
|
||||||
|
.triggerBackupIdReservation(backupKey)
|
||||||
|
.then { getAuthCredential() }
|
||||||
|
.then { credential ->
|
||||||
|
api.archiveAttachmentMedia(
|
||||||
|
backupKey = backupKey,
|
||||||
|
serviceCredential = credential,
|
||||||
|
item = attachment.toArchiveMediaRequest(backupKey)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.also { Log.i(TAG, "backupMediaResult: $it") }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun archiveMedia(attachments: List<DatabaseAttachment>): NetworkResult<BatchArchiveMediaResponse> {
|
||||||
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
|
return api
|
||||||
|
.triggerBackupIdReservation(backupKey)
|
||||||
|
.then { getAuthCredential() }
|
||||||
|
.then { credential ->
|
||||||
|
api.archiveAttachmentMedia(
|
||||||
|
backupKey = backupKey,
|
||||||
|
serviceCredential = credential,
|
||||||
|
items = attachments.map { it.toArchiveMediaRequest(backupKey) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.also { Log.i(TAG, "backupMediaResult: $it") }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteArchivedMedia(attachments: List<DatabaseAttachment>): NetworkResult<Unit> {
|
||||||
|
val api = ApplicationDependencies.getSignalServiceAccountManager().archiveApi
|
||||||
|
val backupKey = SignalStore.svr().getOrCreateMasterKey().deriveBackupKey()
|
||||||
|
|
||||||
|
val mediaToDelete = attachments.map {
|
||||||
|
DeleteArchivedMediaRequest.ArchivedMediaObject(
|
||||||
|
cdn = 3, // TODO [cody] store and reuse backup cdn returned from copy/move call
|
||||||
|
mediaId = backupKey.deriveMediaId(Base64.decode(it.dataHash!!)).toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAuthCredential()
|
||||||
|
.then { credential ->
|
||||||
|
api.deleteArchivedMedia(
|
||||||
|
backupKey = backupKey,
|
||||||
|
serviceCredential = credential,
|
||||||
|
mediaToDelete = mediaToDelete
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.also { Log.i(TAG, "deleteBackupMediaResult: $it") }
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves an auth credential, preferring a cached value if available.
|
* Retrieves an auth credential, preferring a cached value if available.
|
||||||
*/
|
*/
|
||||||
|
@ -246,6 +379,26 @@ object BackupRepository {
|
||||||
val e164: String,
|
val e164: String,
|
||||||
val profileKey: ProfileKey
|
val profileKey: ProfileKey
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun DatabaseAttachment.toArchiveMediaRequest(backupKey: BackupKey): ArchiveMediaRequest {
|
||||||
|
val mediaSecrets = backupKey.deriveMediaSecrets(Base64.decode(dataHash!!))
|
||||||
|
return ArchiveMediaRequest(
|
||||||
|
sourceAttachment = ArchiveMediaRequest.SourceAttachment(
|
||||||
|
cdn = cdnNumber,
|
||||||
|
key = remoteLocation!!
|
||||||
|
),
|
||||||
|
objectLength = AttachmentCipherStreamUtil.getCiphertextLength(PaddingInputStream.getPaddedSize(size)).toInt(),
|
||||||
|
mediaId = mediaSecrets.id.toString(),
|
||||||
|
hmacKey = Base64.encodeWithPadding(mediaSecrets.macKey),
|
||||||
|
encryptionKey = Base64.encodeWithPadding(mediaSecrets.cipherKey),
|
||||||
|
iv = Base64.encodeWithPadding(mediaSecrets.iv)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExportState(val backupTime: Long) {
|
||||||
|
val recipientIds = HashSet<Long>()
|
||||||
|
val threadIds = HashSet<Long>()
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackupState {
|
class BackupState {
|
||||||
|
@ -255,3 +408,8 @@ class BackupState {
|
||||||
val chatIdToBackupRecipientId = HashMap<Long, Long>()
|
val chatIdToBackupRecipientId = HashMap<Long, Long>()
|
||||||
val callIdToType = HashMap<Long, Long>()
|
val callIdToType = HashMap<Long, Long>()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class BackupMetadata(
|
||||||
|
val usedSpace: Long,
|
||||||
|
val mediaCount: Long
|
||||||
|
)
|
||||||
|
|
|
@ -5,9 +5,9 @@
|
||||||
|
|
||||||
package org.tm.archive.backup.v2.database
|
package org.tm.archive.backup.v2.database
|
||||||
|
|
||||||
import org.signal.core.util.delete
|
import org.signal.core.util.deleteAll
|
||||||
import org.tm.archive.database.AttachmentTable
|
import org.tm.archive.database.AttachmentTable
|
||||||
|
|
||||||
fun AttachmentTable.clearAllDataForBackupRestore() {
|
fun AttachmentTable.clearAllDataForBackupRestore() {
|
||||||
writableDatabase.delete(AttachmentTable.TABLE_NAME).run()
|
writableDatabase.deleteAll(AttachmentTable.TABLE_NAME)
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,17 +39,12 @@ fun CallTable.restoreCallLogFromBackup(call: BackupCall, backupState: BackupStat
|
||||||
Call.Type.UNKNOWN_TYPE -> return
|
Call.Type.UNKNOWN_TYPE -> return
|
||||||
}
|
}
|
||||||
|
|
||||||
val event = when (call.event) {
|
val event = when (call.state) {
|
||||||
Call.Event.DELETE -> CallTable.Event.DELETE
|
Call.State.MISSED -> CallTable.Event.MISSED
|
||||||
Call.Event.JOINED -> CallTable.Event.JOINED
|
Call.State.COMPLETED -> CallTable.Event.ACCEPTED
|
||||||
Call.Event.GENERIC_GROUP_CALL -> CallTable.Event.GENERIC_GROUP_CALL
|
Call.State.DECLINED_BY_USER -> CallTable.Event.DECLINED
|
||||||
Call.Event.DECLINED -> CallTable.Event.DECLINED
|
Call.State.DECLINED_BY_NOTIFICATION_PROFILE -> CallTable.Event.MISSED_NOTIFICATION_PROFILE
|
||||||
Call.Event.ACCEPTED -> CallTable.Event.ACCEPTED
|
Call.State.UNKNOWN_EVENT -> return
|
||||||
Call.Event.MISSED -> CallTable.Event.MISSED
|
|
||||||
Call.Event.OUTGOING_RING -> CallTable.Event.OUTGOING_RING
|
|
||||||
Call.Event.OUTGOING -> CallTable.Event.ONGOING
|
|
||||||
Call.Event.NOT_ACCEPTED -> CallTable.Event.NOT_ACCEPTED
|
|
||||||
Call.Event.UNKNOWN_EVENT -> return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val direction = if (call.outgoing) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING
|
val direction = if (call.outgoing) CallTable.Direction.OUTGOING else CallTable.Direction.INCOMING
|
||||||
|
@ -62,7 +57,8 @@ fun CallTable.restoreCallLogFromBackup(call: BackupCall, backupState: BackupStat
|
||||||
CallTable.TYPE to CallTable.Type.serialize(type),
|
CallTable.TYPE to CallTable.Type.serialize(type),
|
||||||
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
|
CallTable.DIRECTION to CallTable.Direction.serialize(direction),
|
||||||
CallTable.EVENT to CallTable.Event.serialize(event),
|
CallTable.EVENT to CallTable.Event.serialize(event),
|
||||||
CallTable.TIMESTAMP to call.timestamp
|
CallTable.TIMESTAMP to call.timestamp,
|
||||||
|
CallTable.RINGER to if (call.ringerRecipientId != null) backupState.backupToLocalRecipientId[call.ringerRecipientId]?.toLong() else null
|
||||||
)
|
)
|
||||||
|
|
||||||
writableDatabase.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
writableDatabase.insert(CallTable.TABLE_NAME, SQLiteDatabase.CONFLICT_IGNORE, values)
|
||||||
|
@ -102,18 +98,18 @@ class CallLogIterator(private val cursor: Cursor) : Iterator<BackupCall?>, Close
|
||||||
},
|
},
|
||||||
timestamp = cursor.requireLong(CallTable.TIMESTAMP),
|
timestamp = cursor.requireLong(CallTable.TIMESTAMP),
|
||||||
ringerRecipientId = if (cursor.isNull(CallTable.RINGER)) null else cursor.requireLong(CallTable.RINGER),
|
ringerRecipientId = if (cursor.isNull(CallTable.RINGER)) null else cursor.requireLong(CallTable.RINGER),
|
||||||
event = when (event) {
|
state = when (event) {
|
||||||
CallTable.Event.ONGOING -> Call.Event.OUTGOING
|
CallTable.Event.ONGOING -> Call.State.COMPLETED
|
||||||
CallTable.Event.OUTGOING_RING -> Call.Event.OUTGOING_RING
|
CallTable.Event.OUTGOING_RING -> Call.State.COMPLETED
|
||||||
CallTable.Event.ACCEPTED -> Call.Event.ACCEPTED
|
CallTable.Event.ACCEPTED -> Call.State.COMPLETED
|
||||||
CallTable.Event.DECLINED -> Call.Event.DECLINED
|
CallTable.Event.DECLINED -> Call.State.DECLINED_BY_USER
|
||||||
CallTable.Event.GENERIC_GROUP_CALL -> Call.Event.GENERIC_GROUP_CALL
|
CallTable.Event.GENERIC_GROUP_CALL -> Call.State.COMPLETED
|
||||||
CallTable.Event.JOINED -> Call.Event.JOINED
|
CallTable.Event.JOINED -> Call.State.COMPLETED
|
||||||
CallTable.Event.MISSED,
|
CallTable.Event.MISSED -> Call.State.MISSED
|
||||||
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> Call.Event.MISSED
|
CallTable.Event.MISSED_NOTIFICATION_PROFILE -> Call.State.DECLINED_BY_NOTIFICATION_PROFILE
|
||||||
CallTable.Event.DELETE -> Call.Event.DELETE
|
CallTable.Event.DELETE -> Call.State.COMPLETED
|
||||||
CallTable.Event.RINGING -> Call.Event.UNKNOWN_EVENT
|
CallTable.Event.RINGING -> Call.State.MISSED
|
||||||
CallTable.Event.NOT_ACCEPTED -> Call.Event.NOT_ACCEPTED
|
CallTable.Event.NOT_ACCEPTED -> Call.State.MISSED
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|