=============NEW BASELINE 7.2.4.2==============

This commit is contained in:
Moti Amar 2024-04-15 11:36:14 +03:00
parent 6203c5e51c
commit cf98df9d7c
1923 changed files with 63984 additions and 155726 deletions

View file

@ -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() }}

View file

@ -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

View file

@ -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
View file

@ -1,2 +0,0 @@
*.db
*.db.gz

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -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()

View file

@ -1,3 +0,0 @@
argparse>=1.2.1
lxml>=3.3.3
progressbar-latest>=2.4

View file

@ -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")

View file

@ -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);

Binary file not shown.

BIN
app/libs/common-release.aar Normal file

Binary file not shown.

View 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&lt;SubscriptionInfo> list = subscriptionManager.getActiveSubscriptionInfoList();" errorLine1=" List&lt;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

View file

@ -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

View file

@ -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()
}
} }

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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
}
}

View file

@ -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
} }
} }

View file

@ -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

View 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()
}
}

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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,

View file

@ -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
) )
} }

View file

@ -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

View file

@ -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)
) )

View file

@ -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)
}

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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)
}
} }

View file

@ -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()
} }

View file

@ -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()

View file

@ -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)
} }
} }
} }

View file

@ -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);
} }
} }

View file

@ -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()

View file

@ -118,7 +118,8 @@ class ConversationElementGenerator {
null, null,
null, null,
0, 0,
false false,
null
) )
val conversationMessage = ConversationMessageFactory.createWithUnresolvedData( val conversationMessage = ConversationMessageFactory.createWithUnresolvedData(

View file

@ -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()
}
} }
} }

View file

@ -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"

View file

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 87 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 174 KiB

After

Width:  |  Height:  |  Size: 176 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 121 KiB

After

Width:  |  Height:  |  Size: 120 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 122 KiB

After

Width:  |  Height:  |  Size: 127 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

After

Width:  |  Height:  |  Size: 209 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 99 KiB

After

Width:  |  Height:  |  Size: 137 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 194 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 213 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 150 KiB

After

Width:  |  Height:  |  Size: 210 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 221 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

After

Width:  |  Height:  |  Size: 202 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 298 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

After

Width:  |  Height:  |  Size: 199 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 111 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 200 KiB

After

Width:  |  Height:  |  Size: 208 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 92 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Before After
Before After

File diff suppressed because one or more lines are too long

Binary file not shown.

File diff suppressed because it is too large Load diff

View 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
}
}
}

View file

@ -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
}
}

View file

@ -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);

View file

@ -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))

View file

@ -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();
} }
} }

View file

@ -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);

View file

@ -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;
}
} }

View file

@ -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() {

View file

@ -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()
} }
} }

View file

@ -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();
} }

View file

@ -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) {

View file

@ -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();
} }

View file

@ -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();
} }
} }

View file

@ -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");
} }
} }

View file

@ -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) {

View file

@ -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();

View file

@ -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),

View file

@ -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);

View file

@ -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());
}
}

View file

@ -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) {

View file

@ -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() {

View file

@ -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()
}
} }

View file

@ -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)

View file

@ -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,

View file

@ -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) {

View file

@ -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)

View file

@ -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)
} }

View file

@ -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);
} }
} }

View file

@ -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");

View file

@ -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
)

View file

@ -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)
} }

View file

@ -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
} }
) )
} }

Some files were not shown because too many files have changed in this diff Show more