mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-18 16:57:46 +00:00
Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4f4aea22ce | ||
|
|
e0ea2bdde4 | ||
|
|
d40dc1d90b | ||
|
|
4571151e3c | ||
|
|
3e43963f67 | ||
|
|
fe71d6ac41 | ||
|
|
0514950333 | ||
|
|
a2dc781840 | ||
|
|
2c1c6fab35 | ||
|
|
3c2e428c54 | ||
|
|
8f7fe5c3ee | ||
|
|
93e9dd6425 | ||
|
|
c95f0fce6e | ||
|
|
a3c7e7e552 | ||
|
|
1e2590af49 | ||
|
|
562e608e1f | ||
|
|
417d5a2804 | ||
|
|
c0c8d2caa7 | ||
|
|
727175e4f4 | ||
|
|
577d2b13ca | ||
|
|
6ac2f922e2 | ||
|
|
98297e55c1 | ||
|
|
aa2094a2cc | ||
|
|
f8c053cc96 | ||
|
|
790f8426ac | ||
|
|
ff11609a82 | ||
|
|
94346033a8 | ||
|
|
cb1401f556 | ||
|
|
ae676d7486 | ||
|
|
2d39e43677 | ||
|
|
0ccc7e3c06 | ||
|
|
2d20ceea01 | ||
|
|
cee2702fdf | ||
|
|
6c94be70dc | ||
|
|
f24020e7b7 | ||
|
|
728f1707b6 | ||
|
|
adea15df10 | ||
|
|
be91f2396c | ||
|
|
8724d904b7 | ||
|
|
ef95479157 | ||
|
|
710cd23537 | ||
|
|
0af313a81f | ||
|
|
71be388989 | ||
|
|
db3098f633 | ||
|
|
ac197f42f2 | ||
|
|
d82882ba28 | ||
|
|
957a12875d | ||
|
|
796eb5043c | ||
|
|
4f8d86828f | ||
|
|
5370605815 | ||
|
|
d5fb71b63f | ||
|
|
2455c291d8 | ||
|
|
80ad28e9cc | ||
|
|
74552ba545 | ||
|
|
141cab1105 | ||
|
|
f012a41345 | ||
|
|
1f95df60d4 | ||
|
|
560c8c8cac | ||
|
|
7cd79f8a94 | ||
|
|
667304c81e | ||
|
|
2dd95c6ef6 | ||
|
|
29e66e1d47 | ||
|
|
5eb5af2f87 | ||
|
|
e47b62805b | ||
|
|
57adc73e95 | ||
|
|
8f4d64d37a | ||
|
|
9ce3813044 | ||
|
|
6436e2836d | ||
|
|
77c83019d0 | ||
|
|
e6dfe96569 | ||
|
|
5d515198e6 | ||
|
|
1d912c0db2 | ||
|
|
bac04dea8d | ||
|
|
3b39d13412 | ||
|
|
9838b2cf0a | ||
|
|
0ac56ca571 | ||
|
|
12321bc2f0 | ||
|
|
3a55dfa32f | ||
|
|
373972f5dc | ||
|
|
60a701f84f | ||
|
|
14f7c01fcb | ||
|
|
caf4f1a7ba | ||
|
|
eb55ac9a97 | ||
|
|
b9d8868aab | ||
|
|
bec03534ef | ||
|
|
565eab9dc1 | ||
|
|
4d229862b6 | ||
|
|
3739eb7731 | ||
|
|
ae5f9fb8ac | ||
|
|
4320a81846 | ||
|
|
9fcf40fdc4 | ||
|
|
79d6ac100c | ||
|
|
a3e3153ee3 | ||
|
|
0f525d2b07 | ||
|
|
8de3f5045b | ||
|
|
fba4ae91e3 | ||
|
|
dda68d6c95 | ||
|
|
25af25cd19 | ||
|
|
dfd5b2c225 | ||
|
|
e850d8e917 | ||
|
|
677cf725a1 | ||
|
|
e95bb9cb0f | ||
|
|
2c223a5826 | ||
|
|
bbc346bd7a | ||
|
|
cf32b93269 | ||
|
|
84f1da76ad | ||
|
|
e845fba8b3 | ||
|
|
01152ead61 | ||
|
|
198281aa47 | ||
|
|
8e8d86606b | ||
|
|
b4c2e21415 | ||
|
|
6080e1f338 | ||
|
|
6dd3fdaa55 | ||
|
|
64312f9c7f | ||
|
|
86542febf9 | ||
|
|
9da49f9f8a | ||
|
|
ce3872ce1a | ||
|
|
c466dba8c4 | ||
|
|
46d412a6c3 | ||
|
|
e2872d9af8 | ||
|
|
3474b26f61 | ||
|
|
740e934e5d | ||
|
|
61c5fc1057 | ||
|
|
7ef77bf16c | ||
|
|
aa3eb78956 | ||
|
|
cdd7b2deb9 | ||
|
|
c27300c19d | ||
|
|
8927971a19 | ||
|
|
1ced115b54 | ||
|
|
fcbd594def | ||
|
|
4b8d02fdba | ||
|
|
e10284bd13 | ||
|
|
4b5f1d64e6 | ||
|
|
b7477d287b | ||
|
|
6bab6c2454 | ||
|
|
586c45616c | ||
|
|
ccd405fdce | ||
|
|
dbf78d1b69 | ||
|
|
5f947ea2d6 | ||
|
|
73afa82147 | ||
|
|
744b79419b | ||
|
|
ce20dd97ff | ||
|
|
3983d5aca4 | ||
|
|
7b0de2d2a9 | ||
|
|
2b65482abd | ||
|
|
fe01e80af5 | ||
|
|
fc43a0d8e9 | ||
|
|
e709cdc9d5 | ||
|
|
d2d698f64e | ||
|
|
7f1e33be32 |
@@ -1,3 +1,12 @@
|
||||
---
|
||||
name: 🛠️ Bug report
|
||||
about: Let us know that something isn't working as intended
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
<!-- This is a bug report template. By following the instructions below and filling out the sections with your information, you will help the developers get all the necessary data to fix your issue.
|
||||
You can also preview your report before submitting it. You may remove sections that aren't relevant to your particular case.
|
||||
|
||||
20
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
20
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 📃Support Center
|
||||
url: https://support.signal.org/
|
||||
about: Find answers to many common questions.
|
||||
- name: ✨ Feature request
|
||||
url: https://community.signalusers.org/c/feature-requests/
|
||||
about: Missing something in Signal? Let us know.
|
||||
- name: 💬 Community support
|
||||
url: https://community.signalusers.org/c/support/
|
||||
about: Feel free to ask anything.
|
||||
- name: 📖 Developer documentation
|
||||
url: https://signal.org/docs/
|
||||
about: Official Signal developer documentation.
|
||||
- name: 📚 Translation feedback.
|
||||
url: https://community.signalusers.org/c/translation-feedback/
|
||||
about: Share feedback on translations.
|
||||
- name: ❓ Other issue?
|
||||
url: https://community.signalusers.org/
|
||||
about: Search on the community forums.
|
||||
5
.github/workflows/android.yml
vendored
5
.github/workflows/android.yml
vendored
@@ -14,16 +14,13 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: set up JDK 1.8
|
||||
uses: actions/setup-java@v1
|
||||
with:
|
||||
java-version: 1.8
|
||||
|
||||
- name: Install NDK
|
||||
run: echo "y" | sudo /usr/local/lib/android/sdk/tools/bin/sdkmanager --install "ndk;21.0.6113669" --sdk_root=${ANDROID_SDK_ROOT}
|
||||
|
||||
- name: Validate Gradle Wrapper
|
||||
uses: gradle/wrapper-validation-action@v1
|
||||
|
||||
|
||||
@@ -61,10 +61,10 @@ protobuf {
|
||||
}
|
||||
}
|
||||
|
||||
def canonicalVersionCode = 759
|
||||
def canonicalVersionName = "5.0.8"
|
||||
def canonicalVersionCode = 771
|
||||
def canonicalVersionName = "5.2.2"
|
||||
|
||||
def postFixSize = 10
|
||||
def postFixSize = 100
|
||||
def abiPostFix = ['universal' : 0,
|
||||
'armeabi-v7a' : 1,
|
||||
'arm64-v8a' : 2,
|
||||
@@ -128,7 +128,6 @@ android {
|
||||
buildConfigField "String", "ZKGROUP_SERVER_PUBLIC_PARAMS", "\"AMhf5ywVwITZMsff/eCyudZx9JDmkkkbV6PInzG4p8x3VqVJSFiMvnvlEKWuRob/1eaIetR31IYeAbm0NdOuHH8Qi+Rexi1wLlpzIo1gstHWBfZzy1+qHRV5A4TqPp15YzBPm0WSggW6PbSn+F4lf57VCnHF7p8SvzAA2ZZJPYJURt8X7bbg+H3i+PEjH9DXItNEqs2sNcug37xZQDLm7X0=\""
|
||||
buildConfigField "String[]", "LANGUAGES", "new String[]{\"" + autoResConfig().collect { s -> s.replace('-r', '_') }.join('", "') + '"}'
|
||||
buildConfigField "int", "CANONICAL_VERSION_CODE", "$canonicalVersionCode"
|
||||
buildConfigField "int", "TRACE_EVENT_MAX", "3500"
|
||||
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
|
||||
@@ -206,6 +205,12 @@ android {
|
||||
minifyEnabled true
|
||||
proguardFiles = buildTypes.debug.proguardFiles
|
||||
}
|
||||
perf {
|
||||
initWith debug
|
||||
isDefault false
|
||||
debuggable false
|
||||
matchingFallbacks = ['debug']
|
||||
}
|
||||
}
|
||||
|
||||
productFlavors {
|
||||
@@ -229,7 +234,6 @@ android {
|
||||
ext.websiteUpdateUrl = "null"
|
||||
buildConfigField "boolean", "PLAY_STORE_DISABLED", "false"
|
||||
buildConfigField "String", "NOPLAY_UPDATE_URL", "$ext.websiteUpdateUrl"
|
||||
buildConfigField "int", "TRACE_EVENT_MAX", "30_000"
|
||||
}
|
||||
|
||||
prod {
|
||||
@@ -311,8 +315,6 @@ dependencies {
|
||||
implementation "androidx.camera:camera-view:1.0.0-alpha18"
|
||||
implementation "androidx.concurrent:concurrent-futures:1.0.0"
|
||||
implementation "androidx.autofill:autofill:1.0.0"
|
||||
implementation "androidx.paging:paging-common:2.1.2"
|
||||
implementation "androidx.paging:paging-runtime:2.1.2"
|
||||
implementation 'com.google.firebase:firebase-ml-vision:24.0.3'
|
||||
implementation 'com.google.firebase:firebase-ml-vision-face-model:20.0.1'
|
||||
|
||||
@@ -335,12 +337,14 @@ dependencies {
|
||||
implementation project(':libsignal-service')
|
||||
implementation project(':paging')
|
||||
implementation project(':core-util')
|
||||
implementation project(':video')
|
||||
|
||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||
implementation 'org.whispersystems:signal-client-android:0.1.5'
|
||||
implementation 'com.google.protobuf:protobuf-javalite:3.10.0'
|
||||
implementation 'org.signal:argon2:13.1@aar'
|
||||
|
||||
implementation 'org.signal:ringrtc-android:2.8.7'
|
||||
implementation 'org.signal:ringrtc-android:2.8.9'
|
||||
|
||||
implementation "me.leolin:ShortcutBadger:1.1.16"
|
||||
implementation 'se.emilsjolander:stickylistheaders:2.7.0'
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
@@ -24,6 +25,7 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* A lot of this code is taken from {@link com.facebook.flipper.plugins.databases.impl.SqliteDatabaseDriver}
|
||||
@@ -42,8 +44,16 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
||||
try {
|
||||
Field databaseHelperField = DatabaseFactory.class.getDeclaredField("databaseHelper");
|
||||
databaseHelperField.setAccessible(true);
|
||||
SQLCipherOpenHelper sqlCipherOpenHelper = (SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext()));
|
||||
return Collections.singletonList(new Descriptor(sqlCipherOpenHelper));
|
||||
|
||||
SignalDatabase mainOpenHelper = Objects.requireNonNull((SQLCipherOpenHelper) databaseHelperField.get(DatabaseFactory.getInstance(getContext())));
|
||||
SignalDatabase keyValueOpenHelper = KeyValueDatabase.getInstance((Application) getContext());
|
||||
SignalDatabase megaphoneOpenHelper = MegaphoneDatabase.getInstance((Application) getContext());
|
||||
SignalDatabase jobManagerOpenHelper = JobDatabase.getInstance((Application) getContext());
|
||||
|
||||
return Arrays.asList(new Descriptor(mainOpenHelper),
|
||||
new Descriptor(keyValueOpenHelper),
|
||||
new Descriptor(megaphoneOpenHelper),
|
||||
new Descriptor(jobManagerOpenHelper));
|
||||
} catch (Exception e) {
|
||||
Log.i(TAG, "Unable to use reflection to access raw database.", e);
|
||||
}
|
||||
@@ -235,9 +245,9 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
||||
}
|
||||
|
||||
static class Descriptor implements DatabaseDescriptor {
|
||||
private final SQLCipherOpenHelper sqlCipherOpenHelper;
|
||||
private final SignalDatabase sqlCipherOpenHelper;
|
||||
|
||||
Descriptor(@NonNull SQLCipherOpenHelper sqlCipherOpenHelper) {
|
||||
Descriptor(@NonNull SignalDatabase sqlCipherOpenHelper) {
|
||||
this.sqlCipherOpenHelper = sqlCipherOpenHelper;
|
||||
}
|
||||
|
||||
@@ -247,11 +257,11 @@ public class FlipperSqlCipherAdapter extends DatabaseDriver<FlipperSqlCipherAdap
|
||||
}
|
||||
|
||||
public @NonNull SQLiteDatabase getReadable() {
|
||||
return sqlCipherOpenHelper.getReadableDatabase().getSqlCipherDatabase();
|
||||
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||
}
|
||||
|
||||
public @NonNull SQLiteDatabase getWritable() {
|
||||
return sqlCipherOpenHelper.getWritableDatabase().getSqlCipherDatabase();
|
||||
return sqlCipherOpenHelper.getSqlCipherDatabase();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -225,6 +225,17 @@
|
||||
<category android:name="android.intent.category.MULTIWINDOW_LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
|
||||
</activity-alias>
|
||||
|
||||
<activity android:name=".deeplinks.DeepLinkEntryActivity"
|
||||
android:noHistory="true"
|
||||
android:theme="@style/Signal.Transparent">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
@@ -241,13 +252,7 @@
|
||||
<data android:scheme="https"
|
||||
android:host="signal.group"/>
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="com.sec.minimode.icon.portrait.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
<meta-data android:name="com.sec.minimode.icon.landscape.normal"
|
||||
android:resource="@mipmap/ic_launcher" />
|
||||
|
||||
</activity-alias>
|
||||
</activity>
|
||||
|
||||
<activity android:name=".conversation.ConversationActivity"
|
||||
android:windowSoftInputMode="stateUnchanged"
|
||||
@@ -504,7 +509,6 @@
|
||||
|
||||
<activity android:name=".MainActivity"
|
||||
android:theme="@style/Theme.Signal.DayNight.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize" />
|
||||
|
||||
<activity android:name=".pin.PinRestoreActivity"
|
||||
|
||||
@@ -50,6 +50,28 @@ public final class AppInitialization {
|
||||
public static void onPostBackupRestore(@NonNull Context context) {
|
||||
Log.i(TAG, "onPostBackupRestore()");
|
||||
|
||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
SignalStore.onboarding().clearAll();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.BANDIT.getPackId(), BlessedPacks.BANDIT.getPackKey(), false));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_HANDS.getPackId(), BlessedPacks.SWOON_HANDS.getPackKey()));
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forReference(BlessedPacks.SWOON_FACES.getPackId(), BlessedPacks.SWOON_FACES.getPackKey()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary migration method that does the safest bits of {@link #onFirstEverAppLaunch(Context)}
|
||||
*/
|
||||
public static void onRepairFirstEverAppLaunch(@NonNull Context context) {
|
||||
Log.w(TAG, "onRepairFirstEverAppLaunch()");
|
||||
|
||||
InsightsOptOut.userRequestedOptOut(context);
|
||||
TextSecurePreferences.setAppMigrationVersion(context, ApplicationMigrations.CURRENT_VERSION);
|
||||
TextSecurePreferences.setJobManagerVersion(context, JobManager.CURRENT_VERSION);
|
||||
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||
TextSecurePreferences.setHasSeenStickerIntroTooltip(context, true);
|
||||
TextSecurePreferences.setPasswordDisabled(context, true);
|
||||
TextSecurePreferences.setLastExperienceVersionCode(context, Util.getCanonicalVersionCode());
|
||||
ApplicationDependencies.getMegaphoneRepository().onFirstEverAppLaunch();
|
||||
SignalStore.onFirstEverAppLaunch();
|
||||
ApplicationDependencies.getJobManager().add(StickerPackDownloadJob.forInstall(BlessedPacks.ZOZO.getPackId(), BlessedPacks.ZOZO.getPackKey(), false));
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
*/
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.os.AsyncTask;
|
||||
import android.hardware.SensorManager;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.lifecycle.DefaultLifecycleObserver;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
@@ -33,10 +33,12 @@ import com.google.android.gms.security.ProviderInstaller;
|
||||
|
||||
import org.conscrypt.Conscrypt;
|
||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||
import org.signal.core.util.ShakeDetector;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.AndroidLogger;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.logging.PersistentLogger;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.signal.glide.SignalGlideCodecs;
|
||||
import org.signal.ringrtc.CallManager;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -44,6 +46,7 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||
@@ -68,14 +71,15 @@ import org.thoughtcrime.securesms.service.LocalBackupListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.tracing.Tracer;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.SignalUncaughtExceptionHandler;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.VersionTracker;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
import org.webrtc.voiceengine.WebRtcAudioManager;
|
||||
import org.webrtc.voiceengine.WebRtcAudioUtils;
|
||||
@@ -94,7 +98,6 @@ import java.util.concurrent.TimeUnit;
|
||||
*
|
||||
* @author Moxie Marlinspike
|
||||
*/
|
||||
@Trace
|
||||
public class ApplicationContext extends MultiDexApplication implements DefaultLifecycleObserver {
|
||||
|
||||
private static final String TAG = ApplicationContext.class.getSimpleName();
|
||||
@@ -112,62 +115,80 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
@Override
|
||||
public void onCreate() {
|
||||
Tracer.getInstance().start("Application#onCreate()");
|
||||
AppStartup.getInstance().onApplicationCreate();
|
||||
|
||||
long startTime = System.currentTimeMillis();
|
||||
|
||||
if (FeatureFlags.internalUser()) {
|
||||
Tracer.getInstance().setMaxBufferSize(35_000);
|
||||
}
|
||||
|
||||
super.onCreate();
|
||||
|
||||
initializeSecurityProvider();
|
||||
initializeLogging();
|
||||
Log.i(TAG, "onCreate()");
|
||||
initializeCrashHandling();
|
||||
initializeAppDependencies();
|
||||
initializeFirstEverAppLaunch();
|
||||
initializeApplicationMigrations();
|
||||
initializeMessageRetrieval();
|
||||
initializeExpiringMessageManager();
|
||||
initializeRevealableMessageManager();
|
||||
initializeGcmCheck();
|
||||
initializeSignedPreKeyCheck();
|
||||
initializePeriodicTasks();
|
||||
initializeCircumvention();
|
||||
initializeRingRtc();
|
||||
initializePendingMessages();
|
||||
initializeBlobProvider();
|
||||
initializeCleanup();
|
||||
initializeGlideCodecs();
|
||||
AppStartup.getInstance().addBlocking("security-provider", this::initializeSecurityProvider)
|
||||
.addBlocking("logging", () -> {
|
||||
initializeLogging();
|
||||
Log.i(TAG, "onCreate()");
|
||||
})
|
||||
.addBlocking("crash-handling", this::initializeCrashHandling)
|
||||
.addBlocking("eat-db", () -> DatabaseFactory.getInstance(this))
|
||||
.addBlocking("app-dependencies", this::initializeAppDependencies)
|
||||
.addBlocking("first-launch", this::initializeFirstEverAppLaunch)
|
||||
.addBlocking("app-migrations", this::initializeApplicationMigrations)
|
||||
.addBlocking("ring-rtc", this::initializeRingRtc)
|
||||
.addBlocking("mark-registration", () -> RegistrationUtil.maybeMarkRegistrationComplete(this))
|
||||
.addBlocking("lifecycle-observer", () -> ProcessLifecycleOwner.get().getLifecycle().addObserver(this))
|
||||
.addBlocking("message-retriever", this::initializeMessageRetrieval)
|
||||
.addBlocking("dynamic-theme", () -> DynamicTheme.setDefaultDayNightMode(this))
|
||||
.addBlocking("vector-compat", () -> {
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
})
|
||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||
.addNonBlocking(this::initializeGcmCheck)
|
||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||
.addNonBlocking(this::initializePeriodicTasks)
|
||||
.addNonBlocking(this::initializeCircumvention)
|
||||
.addNonBlocking(this::initializePendingMessages)
|
||||
.addNonBlocking(this::initializeCleanup)
|
||||
.addNonBlocking(this::initializeGlideCodecs)
|
||||
.addNonBlocking(FeatureFlags::init)
|
||||
.addNonBlocking(RefreshPreKeysJob::scheduleIfNecessary)
|
||||
.addNonBlocking(StorageSyncHelper::scheduleRoutineSync)
|
||||
.addNonBlocking(() -> ApplicationDependencies.getJobManager().beginJobLoop())
|
||||
.addPostRender(this::initializeExpiringMessageManager)
|
||||
.addPostRender(this::initializeBlobProvider)
|
||||
.addPostRender(() -> NotificationChannels.create(this))
|
||||
.execute();
|
||||
|
||||
FeatureFlags.init();
|
||||
NotificationChannels.create(this);
|
||||
RefreshPreKeysJob.scheduleIfNecessary();
|
||||
StorageSyncHelper.scheduleRoutineSync();
|
||||
RegistrationUtil.maybeMarkRegistrationComplete(this);
|
||||
ProcessLifecycleOwner.get().getLifecycle().addObserver(this);
|
||||
|
||||
if (Build.VERSION.SDK_INT < 21) {
|
||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||
}
|
||||
|
||||
ApplicationDependencies.getJobManager().beginJobLoop();
|
||||
|
||||
DynamicTheme.setDefaultDayNightMode(this);
|
||||
|
||||
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
Tracer.getInstance().end("Application#onCreate()");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(@NonNull LifecycleOwner owner) {
|
||||
long startTime = System.currentTimeMillis();
|
||||
isAppVisible = true;
|
||||
Log.i(TAG, "App is now visible.");
|
||||
FeatureFlags.refreshIfNecessary();
|
||||
ApplicationDependencies.getRecipientCache().warmUp();
|
||||
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
||||
GroupV1MigrationJob.enqueueRoutineMigrationsIfNecessary(this);
|
||||
executePendingContactSync();
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
|
||||
ApplicationDependencies.getFrameRateTracker().begin();
|
||||
ApplicationDependencies.getMegaphoneRepository().onAppForegrounded();
|
||||
checkBuildExpiration();
|
||||
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
FeatureFlags.refreshIfNecessary();
|
||||
ApplicationDependencies.getRecipientCache().warmUp();
|
||||
RetrieveProfileJob.enqueueRoutineFetchIfNecessary(this);
|
||||
GroupV1MigrationJob.enqueueRoutineMigrationsIfNecessary(this);
|
||||
executePendingContactSync();
|
||||
KeyCachingService.onAppForegrounded(this);
|
||||
ApplicationDependencies.getShakeToReport().enable();
|
||||
checkBuildExpiration();
|
||||
});
|
||||
|
||||
Log.d(TAG, "onStart() took " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -177,9 +198,13 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
KeyCachingService.onAppBackgrounded(this);
|
||||
ApplicationDependencies.getMessageNotifier().clearVisibleThread();
|
||||
ApplicationDependencies.getFrameRateTracker().end();
|
||||
ApplicationDependencies.getShakeToReport().disable();
|
||||
}
|
||||
|
||||
public ExpiringMessageManager getExpiringMessageManager() {
|
||||
if (expiringMessageManager == null) {
|
||||
initializeExpiringMessageManager();
|
||||
}
|
||||
return expiringMessageManager;
|
||||
}
|
||||
|
||||
@@ -252,13 +277,16 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
|
||||
private void initializeFirstEverAppLaunch() {
|
||||
if (TextSecurePreferences.getFirstInstallVersion(this) == -1) {
|
||||
if (!SQLCipherOpenHelper.databaseFileExists(this)) {
|
||||
if (!SQLCipherOpenHelper.databaseFileExists(this) || VersionTracker.getDaysSinceFirstInstalled(this) < 365) {
|
||||
Log.i(TAG, "First ever app launch!");
|
||||
AppInitialization.onFirstEverAppLaunch(this);
|
||||
}
|
||||
|
||||
Log.i(TAG, "Setting first install version to " + BuildConfig.CANONICAL_VERSION_CODE);
|
||||
TextSecurePreferences.setFirstInstallVersion(this, BuildConfig.CANONICAL_VERSION_CODE);
|
||||
} else if (!TextSecurePreferences.isPasswordDisabled(this) && VersionTracker.getDaysSinceFirstInstalled(this) < 90) {
|
||||
Log.i(TAG, "Detected a new install that doesn't have passphrases disabled -- assuming bad initialization.");
|
||||
AppInitialization.onRepairFirstEverAppLaunch(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -333,23 +361,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
@WorkerThread
|
||||
private void initializeCircumvention() {
|
||||
AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
||||
try {
|
||||
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, t);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
if (new SignalServiceNetworkAccess(ApplicationContext.this).isCensored(ApplicationContext.this)) {
|
||||
try {
|
||||
ProviderInstaller.installIfNeeded(ApplicationContext.this);
|
||||
} catch (Throwable t) {
|
||||
Log.w(TAG, t);
|
||||
}
|
||||
};
|
||||
|
||||
task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
}
|
||||
}
|
||||
|
||||
private void executePendingContactSync() {
|
||||
@@ -370,17 +390,15 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void initializeBlobProvider() {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
BlobProvider.getInstance().onSessionStart(this);
|
||||
});
|
||||
BlobProvider.getInstance().onSessionStart(this);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void initializeCleanup() {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||
});
|
||||
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||
}
|
||||
|
||||
private void initializeGlideCodecs() {
|
||||
|
||||
@@ -39,9 +39,9 @@ import org.thoughtcrime.securesms.preferences.AppearancePreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.StoragePreferenceFragment;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||
import org.thoughtcrime.securesms.preferences.widgets.UsernamePreference;
|
||||
import org.thoughtcrime.securesms.profiles.edit.EditProfileActivity;
|
||||
@@ -65,6 +65,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener
|
||||
{
|
||||
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
||||
@@ -104,6 +105,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
initFragment(android.R.id.content, new NotificationsPreferenceFragment());
|
||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_BACKUPS_FRAGMENT, false)) {
|
||||
initFragment(android.R.id.content, new BackupsPreferenceFragment());
|
||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
|
||||
initFragment(android.R.id.content, new HelpFragment());
|
||||
} else if (icicle == null) {
|
||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
||||
} else {
|
||||
@@ -309,7 +312,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
||||
fragment = new ChatsPreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_STORAGE:
|
||||
fragment = new StoragePreferenceFragment();
|
||||
fragment = new DataAndStoragePreferenceFragment();
|
||||
break;
|
||||
case PREFERENCE_CATEGORY_DEVICES:
|
||||
Intent intent = new Intent(getActivity(), DeviceActivity.class);
|
||||
|
||||
@@ -15,6 +15,8 @@ import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.app.ActivityOptionsCompat;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.ConfigurationUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.dynamiclanguage.DynamicLanguageContextWrapper;
|
||||
@@ -31,8 +33,10 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||
logEvent("onCreate()");
|
||||
super.onCreate(savedInstanceState);
|
||||
AppStartup.getInstance().onCriticalRenderEventEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,6 +48,7 @@ public abstract class BaseActivity extends AppCompatActivity {
|
||||
@Override
|
||||
protected void onStart() {
|
||||
logEvent("onStart()");
|
||||
ApplicationDependencies.getShakeToReport().registerActivity(this);
|
||||
super.onStart();
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,10 @@ public interface BindableConversationItem extends Unbindable {
|
||||
void onVoiceNotePlay(@NonNull Uri uri, long messageId, double position);
|
||||
void onVoiceNoteSeekTo(@NonNull Uri uri, double position);
|
||||
void onGroupMigrationLearnMoreClicked(@NonNull GroupMigrationMembershipChange membershipChange);
|
||||
void onDecryptionFailedLearnMoreClicked();
|
||||
void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient);
|
||||
void onJoinGroupCallClicked();
|
||||
void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId);
|
||||
|
||||
/** @return true if handled, false if you want to let the normal url handling continue */
|
||||
boolean onUrlClicked(@NonNull String url);
|
||||
|
||||
@@ -448,8 +448,11 @@ public final class ContactSelectionListFragment extends LoggingFragment
|
||||
swipeRefresh.setVisibility(View.VISIBLE);
|
||||
reset();
|
||||
} else {
|
||||
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
||||
initializeNoContactsPermission();
|
||||
Context context = getContext();
|
||||
if (context != null) {
|
||||
Toast.makeText(getContext(), R.string.ContactSelectionListFragment_error_retrieving_contacts_check_your_network_connection, Toast.LENGTH_LONG).show();
|
||||
initializeNoContactsPermission();
|
||||
}
|
||||
}
|
||||
}
|
||||
}.execute();
|
||||
|
||||
@@ -9,6 +9,7 @@ import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.Parcelable;
|
||||
import android.view.View;
|
||||
@@ -150,7 +151,7 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
||||
startActivity((Intent)getIntent().getParcelableExtra("next_intent"));
|
||||
} else {
|
||||
// TODO [greyson] Navigation
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
startActivity(MainActivity.clearTop(this));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +159,11 @@ public class DatabaseMigrationActivity extends PassphraseRequiredActivity {
|
||||
}
|
||||
|
||||
private class ImportStateHandler extends Handler {
|
||||
|
||||
public ImportStateHandler() {
|
||||
super(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message message) {
|
||||
switch (message.what) {
|
||||
|
||||
@@ -32,9 +32,9 @@ public class DeviceAddFragment extends LoggingFragment {
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.device_add_fragment);
|
||||
this.overlay = ViewUtil.findById(this.container, R.id.overlay);
|
||||
this.scannerView = ViewUtil.findById(this.container, R.id.scanner);
|
||||
this.devicesImage = ViewUtil.findById(this.container, R.id.devices);
|
||||
this.overlay = this.container.findViewById(R.id.overlay);
|
||||
this.scannerView = this.container.findViewById(R.id.scanner);
|
||||
this.devicesImage = this.container.findViewById(R.id.devices);
|
||||
|
||||
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
this.overlay.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.devicelist.Device;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
|
||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||
|
||||
@@ -68,7 +67,7 @@ public class DeviceListFragment extends ListFragment
|
||||
|
||||
this.empty = view.findViewById(R.id.empty);
|
||||
this.progressContainer = view.findViewById(R.id.progress_container);
|
||||
this.addDeviceButton = ViewUtil.findById(view, R.id.add_device);
|
||||
this.addDeviceButton = view.findViewById(R.id.add_device);
|
||||
this.addDeviceButton.setOnClickListener(this);
|
||||
|
||||
return view;
|
||||
|
||||
@@ -35,6 +35,7 @@ import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarInviteTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture.Listener;
|
||||
@@ -93,26 +94,33 @@ public class InviteActivity extends PassphraseRequiredActivity implements Contac
|
||||
slideInAnimation = loadAnimation(R.anim.slide_from_bottom);
|
||||
slideOutAnimation = loadAnimation(R.anim.slide_to_bottom);
|
||||
|
||||
View shareButton = ViewUtil.findById(this, R.id.share_button);
|
||||
View smsButton = ViewUtil.findById(this, R.id.sms_button);
|
||||
Button smsCancelButton = ViewUtil.findById(this, R.id.cancel_sms_button);
|
||||
ContactFilterToolbar contactFilter = ViewUtil.findById(this, R.id.contact_filter);
|
||||
View shareButton = findViewById(R.id.share_button);
|
||||
Button smsButton = findViewById(R.id.sms_button);
|
||||
Button smsCancelButton = findViewById(R.id.cancel_sms_button);
|
||||
ContactFilterToolbar contactFilter = findViewById(R.id.contact_filter);
|
||||
|
||||
inviteText = ViewUtil.findById(this, R.id.invite_text);
|
||||
smsSendFrame = ViewUtil.findById(this, R.id.sms_send_frame);
|
||||
smsSendButton = ViewUtil.findById(this, R.id.send_sms_button);
|
||||
inviteText = findViewById(R.id.invite_text);
|
||||
smsSendFrame = findViewById(R.id.sms_send_frame);
|
||||
smsSendButton = findViewById(R.id.send_sms_button);
|
||||
contactsFragment = (ContactSelectionListFragment)getSupportFragmentManager().findFragmentById(R.id.contact_selection_list_fragment);
|
||||
|
||||
inviteText.setText(getString(R.string.InviteActivity_lets_switch_to_signal, getString(R.string.install_url)));
|
||||
updateSmsButtonText(contactsFragment.getSelectedContacts().size());
|
||||
|
||||
contactsFragment.setOnContactSelectedListener(this);
|
||||
shareButton.setOnClickListener(new ShareClickListener());
|
||||
smsButton.setOnClickListener(new SmsClickListener());
|
||||
smsCancelButton.setOnClickListener(new SmsCancelClickListener());
|
||||
smsSendButton.setOnClickListener(new SmsSendClickListener());
|
||||
contactFilter.setOnFilterChangedListener(new ContactFilterChangedListener());
|
||||
contactFilter.setNavigationIcon(R.drawable.ic_search_conversation_24);
|
||||
|
||||
if (Util.isDefaultSmsProvider(this)) {
|
||||
shareButton.setOnClickListener(new ShareClickListener());
|
||||
smsButton.setOnClickListener(new SmsClickListener());
|
||||
} else {
|
||||
shareButton.setVisibility(View.GONE);
|
||||
smsButton.setOnClickListener(new ShareClickListener());
|
||||
smsButton.setText(R.string.InviteActivity_share);
|
||||
}
|
||||
}
|
||||
|
||||
private Animation loadAnimation(@AnimRes int animResId) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
@@ -8,13 +9,12 @@ import android.os.Bundle;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
@Trace
|
||||
public class MainActivity extends PassphraseRequiredActivity {
|
||||
|
||||
public static final int RESULT_CONFIG_CHANGED = Activity.RESULT_FIRST_USER + 901;
|
||||
@@ -22,8 +22,19 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
private final MainNavigator navigator = new MainNavigator(this);
|
||||
|
||||
public static @NonNull Intent clearTop(@NonNull Context context) {
|
||||
Intent intent = new Intent(context, MainActivity.class);
|
||||
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
|
||||
return intent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState, boolean ready) {
|
||||
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||
super.onCreate(savedInstanceState, ready);
|
||||
setContentView(R.layout.main_activity);
|
||||
|
||||
@@ -34,6 +45,13 @@ public class MainActivity extends PassphraseRequiredActivity {
|
||||
CachedInflater.from(this).clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Intent getIntent() {
|
||||
return super.getIntent().setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |
|
||||
Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
@@ -25,7 +26,7 @@ import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationNavigationActivity;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.tracing.Tracer;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
import java.util.Locale;
|
||||
@@ -51,6 +52,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
@Override
|
||||
protected final void onCreate(Bundle savedInstanceState) {
|
||||
Tracer.getInstance().start(Log.tag(getClass()) + "#onCreate()");
|
||||
AppStartup.getInstance().onCriticalRenderEventStart();
|
||||
this.networkAccess = new SignalServiceNetworkAccess(this);
|
||||
onPreCreate();
|
||||
|
||||
@@ -63,6 +65,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
initializeClearKeyReceiver();
|
||||
onCreate(savedInstanceState, true);
|
||||
}
|
||||
|
||||
AppStartup.getInstance().onCriticalRenderEventEnd();
|
||||
Tracer.getInstance().end(Log.tag(getClass()) + "#onCreate()");
|
||||
}
|
||||
|
||||
@@ -221,15 +225,17 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||
|
||||
private Intent getConversationListIntent() {
|
||||
// TODO [greyson] Navigation
|
||||
return new Intent(this, MainActivity.class);
|
||||
return MainActivity.clearTop(this);
|
||||
}
|
||||
|
||||
private void initializeClearKeyReceiver() {
|
||||
this.clearKeyReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
Log.i(TAG, "onReceive() for clear key event");
|
||||
onMasterSecretCleared();
|
||||
Log.i(TAG, "onReceive() for clear key event. PasswordDisabled: " + TextSecurePreferences.isPasswordDisabled(context) + ", ScreenLock: " + TextSecurePreferences.isScreenLockEnabled(context));
|
||||
if (TextSecurePreferences.isScreenLockEnabled(context) || !TextSecurePreferences.isPasswordDisabled(context)) {
|
||||
onMasterSecretCleared();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ public class ShortcutLauncherActivity extends AppCompatActivity {
|
||||
if (rawId == null) {
|
||||
Toast.makeText(this, R.string.ShortcutLauncherActivity_invalid_shortcut, Toast.LENGTH_SHORT).show();
|
||||
// TODO [greyson] Navigation
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
startActivity(MainActivity.clearTop(this));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@@ -43,7 +43,7 @@ public class ShortcutLauncherActivity extends AppCompatActivity {
|
||||
Recipient recipient = Recipient.live(RecipientId.from(rawId)).get();
|
||||
// TODO [greyson] Navigation
|
||||
TaskStackBuilder backStack = TaskStackBuilder.create(this)
|
||||
.addNextIntent(new Intent(this, MainActivity.class));
|
||||
.addNextIntent(MainActivity.clearTop(this));
|
||||
|
||||
CommunicationActions.startConversation(this, recipient, null, backStack);
|
||||
finish();
|
||||
|
||||
@@ -11,8 +11,6 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class TransportOptionsAdapter extends BaseAdapter {
|
||||
@@ -55,9 +53,9 @@ public class TransportOptionsAdapter extends BaseAdapter {
|
||||
}
|
||||
|
||||
TransportOption transport = (TransportOption) getItem(position);
|
||||
ImageView imageView = ViewUtil.findById(convertView, R.id.icon);
|
||||
TextView textView = ViewUtil.findById(convertView, R.id.text);
|
||||
TextView subtextView = ViewUtil.findById(convertView, R.id.subtext);
|
||||
ImageView imageView = convertView.findViewById(R.id.icon);
|
||||
TextView textView = convertView.findViewById(R.id.text);
|
||||
TextView subtextView = convertView.findViewById(R.id.subtext);
|
||||
|
||||
imageView.getBackground().setColorFilter(transport.getBackgroundColor(), Mode.MULTIPLY);
|
||||
imageView.setImageResource(transport.getDrawable());
|
||||
|
||||
@@ -258,24 +258,24 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_display_fragment);
|
||||
this.numbersContainer = ViewUtil.findById(container, R.id.number_table);
|
||||
this.qrCode = ViewUtil.findById(container, R.id.qr_code);
|
||||
this.verified = ViewUtil.findById(container, R.id.verified_switch);
|
||||
this.qrVerified = ViewUtil.findById(container, R.id.qr_verified);
|
||||
this.description = ViewUtil.findById(container, R.id.description);
|
||||
this.tapLabel = ViewUtil.findById(container, R.id.tap_label);
|
||||
this.codes[0] = ViewUtil.findById(container, R.id.code_first);
|
||||
this.codes[1] = ViewUtil.findById(container, R.id.code_second);
|
||||
this.codes[2] = ViewUtil.findById(container, R.id.code_third);
|
||||
this.codes[3] = ViewUtil.findById(container, R.id.code_fourth);
|
||||
this.codes[4] = ViewUtil.findById(container, R.id.code_fifth);
|
||||
this.codes[5] = ViewUtil.findById(container, R.id.code_sixth);
|
||||
this.codes[6] = ViewUtil.findById(container, R.id.code_seventh);
|
||||
this.codes[7] = ViewUtil.findById(container, R.id.code_eighth);
|
||||
this.codes[8] = ViewUtil.findById(container, R.id.code_ninth);
|
||||
this.codes[9] = ViewUtil.findById(container, R.id.code_tenth);
|
||||
this.codes[10] = ViewUtil.findById(container, R.id.code_eleventh);
|
||||
this.codes[11] = ViewUtil.findById(container, R.id.code_twelth);
|
||||
this.numbersContainer = container.findViewById(R.id.number_table);
|
||||
this.qrCode = container.findViewById(R.id.qr_code);
|
||||
this.verified = container.findViewById(R.id.verified_switch);
|
||||
this.qrVerified = container.findViewById(R.id.qr_verified);
|
||||
this.description = container.findViewById(R.id.description);
|
||||
this.tapLabel = container.findViewById(R.id.tap_label);
|
||||
this.codes[0] = container.findViewById(R.id.code_first);
|
||||
this.codes[1] = container.findViewById(R.id.code_second);
|
||||
this.codes[2] = container.findViewById(R.id.code_third);
|
||||
this.codes[3] = container.findViewById(R.id.code_fourth);
|
||||
this.codes[4] = container.findViewById(R.id.code_fifth);
|
||||
this.codes[5] = container.findViewById(R.id.code_sixth);
|
||||
this.codes[6] = container.findViewById(R.id.code_seventh);
|
||||
this.codes[7] = container.findViewById(R.id.code_eighth);
|
||||
this.codes[8] = container.findViewById(R.id.code_ninth);
|
||||
this.codes[9] = container.findViewById(R.id.code_tenth);
|
||||
this.codes[10] = container.findViewById(R.id.code_eleventh);
|
||||
this.codes[11] = container.findViewById(R.id.code_twelth);
|
||||
|
||||
this.qrCode.setOnClickListener(clickListener);
|
||||
this.registerForContextMenu(numbersContainer);
|
||||
@@ -664,7 +664,7 @@ public class VerifyIdentityActivity extends PassphraseRequiredActivity implement
|
||||
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup viewGroup, Bundle bundle) {
|
||||
this.container = ViewUtil.inflate(inflater, viewGroup, R.layout.verify_scan_fragment);
|
||||
this.cameraView = ViewUtil.findById(container, R.id.scanner);
|
||||
this.cameraView = container.findViewById(R.id.scanner);
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@@ -27,7 +27,9 @@ import android.media.AudioManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.Rational;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -35,6 +37,8 @@ import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatDelegate;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.transition.Transition;
|
||||
import androidx.transition.TransitionListenerAdapter;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
@@ -58,8 +62,10 @@ import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.util.EllapsedTimeFormatter;
|
||||
import org.thoughtcrime.securesms.util.FullscreenHelper;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
import org.whispersystems.signalservice.api.messages.calls.HangupMessage;
|
||||
import org.whispersystems.signalservice.api.messages.calls.OfferMessage;
|
||||
@@ -80,6 +86,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
|
||||
private CallParticipantsListUpdatePopupWindow participantUpdateWindow;
|
||||
|
||||
private FullscreenHelper fullscreenHelper;
|
||||
private WebRtcCallView callScreen;
|
||||
private TooltipPopup videoTooltip;
|
||||
private WebRtcCallViewModel viewModel;
|
||||
@@ -100,8 +107,8 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
|
||||
requestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
setContentView(R.layout.webrtc_call_activity);
|
||||
//noinspection ConstantConditions
|
||||
getSupportActionBar().hide();
|
||||
|
||||
fullscreenHelper = new FullscreenHelper(this);
|
||||
|
||||
setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
|
||||
|
||||
@@ -141,9 +148,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
if (!viewModel.isCallingStarted()) {
|
||||
if (!viewModel.isCallStarting()) {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
|
||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
finish();
|
||||
}
|
||||
}
|
||||
@@ -156,9 +163,9 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
|
||||
EventBus.getDefault().unregister(this);
|
||||
|
||||
if (!viewModel.isCallingStarted()) {
|
||||
if (!viewModel.isCallStarting()) {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null && state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
|
||||
if (state != null && state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
||||
startService(intent);
|
||||
@@ -471,7 +478,6 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
private void handleServerFailure() {
|
||||
EventBus.getDefault().removeStickyEvent(WebRtcViewModel.class);
|
||||
callScreen.setStatus(getString(R.string.RedPhone_network_failed));
|
||||
delayedFinish();
|
||||
}
|
||||
|
||||
private void handleNoSuchUser(final @NonNull WebRtcViewModel event) {
|
||||
@@ -529,7 +535,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
.putExtra(WebRtcCallService.EXTRA_RECIPIENT_IDS, RecipientId.toSerializedList(changedRecipients));
|
||||
startService(intent);
|
||||
} else {
|
||||
startCall(state.getLocalParticipant().isVideoEnabled());
|
||||
viewModel.startCall(state.getLocalParticipant().isVideoEnabled());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -540,7 +546,7 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
public void onCanceled() {
|
||||
CallParticipantsState state = viewModel.getCallParticipantsState().getValue();
|
||||
if (state != null && state.getGroupCallState().isNotIdle()) {
|
||||
if (state.getCallState() == WebRtcViewModel.State.CALL_PRE_JOIN) {
|
||||
if (state.getCallState().isPreJoinOrNetworkUnavailable()) {
|
||||
Intent intent = new Intent(this, WebRtcCallService.class);
|
||||
intent.setAction(WebRtcCallService.ACTION_CANCEL_PRE_JOIN_CALL);
|
||||
startService(intent);
|
||||
@@ -637,6 +643,16 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showSystemUI() {
|
||||
fullscreenHelper.showSystemUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hideSystemUI() {
|
||||
fullscreenHelper.hideSystemUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput) {
|
||||
switch (audioOutput) {
|
||||
@@ -702,5 +718,10 @@ public class WebRtcCallActivity extends BaseActivity implements SafetyNumberChan
|
||||
public void onPageChanged(@NonNull CallParticipantsState.SelectedPage page) {
|
||||
viewModel.setIsViewingFocusedParticipant(page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocalPictureInPictureClicked() {
|
||||
viewModel.onLocalPictureInPictureClicked();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.backup;
|
||||
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
@@ -126,7 +127,12 @@ public class BackupDialog {
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION |
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
try {
|
||||
fragment.startActivityForResult(intent, requestCode);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(fragment.requireContext(), R.string.BackupDialog_no_file_picker_available, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
||||
dialog.dismiss();
|
||||
}))
|
||||
|
||||
@@ -74,11 +74,7 @@ public class FullBackupExporter extends FullBackupBase {
|
||||
OneTimePreKeyDatabase.TABLE_NAME,
|
||||
SessionDatabase.TABLE_NAME,
|
||||
SearchDatabase.SMS_FTS_TABLE_NAME,
|
||||
SearchDatabase.MMS_FTS_TABLE_NAME,
|
||||
JobDatabase.JOBS_TABLE_NAME,
|
||||
JobDatabase.CONSTRAINTS_TABLE_NAME,
|
||||
JobDatabase.DEPENDENCIES_TABLE_NAME,
|
||||
KeyValueDatabase.TABLE_NAME
|
||||
SearchDatabase.MMS_FTS_TABLE_NAME
|
||||
);
|
||||
|
||||
public static void export(@NonNull Context context,
|
||||
|
||||
@@ -81,8 +81,8 @@ public class ComposeText extends EmojiEditText {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
|
||||
if (!TextUtils.isEmpty(hint)) {
|
||||
if (!TextUtils.isEmpty(subHint)) {
|
||||
@@ -92,6 +92,7 @@ public class ComposeText extends EmojiEditText {
|
||||
} else {
|
||||
setHint(ellipsizeToWidth(hint));
|
||||
}
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.PorterDuff;
|
||||
import android.graphics.PorterDuffColorFilter;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.ImageView;
|
||||
@@ -20,6 +19,7 @@ import com.airbnb.lottie.LottieAnimationView;
|
||||
import com.airbnb.lottie.LottieProperty;
|
||||
import com.airbnb.lottie.model.KeyPath;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -185,20 +185,16 @@ public class ConversationItemFooter extends LinearLayout {
|
||||
ApplicationContext.getInstance(getContext()).getExpiringMessageManager().checkSchedule();
|
||||
}
|
||||
} else if (!messageRecord.isOutgoing() && !messageRecord.isMediaPending()) {
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(getContext()).getExpiringMessageManager();
|
||||
long id = messageRecord.getId();
|
||||
boolean mms = messageRecord.isMms();
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
ExpiringMessageManager expirationManager = ApplicationContext.getInstance(getContext()).getExpiringMessageManager();
|
||||
long id = messageRecord.getId();
|
||||
boolean mms = messageRecord.isMms();
|
||||
|
||||
if (mms) DatabaseFactory.getMmsDatabase(getContext()).markExpireStarted(id);
|
||||
else DatabaseFactory.getSmsDatabase(getContext()).markExpireStarted(id);
|
||||
if (mms) DatabaseFactory.getMmsDatabase(getContext()).markExpireStarted(id);
|
||||
else DatabaseFactory.getSmsDatabase(getContext()).markExpireStarted(id);
|
||||
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
return null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
expirationManager.scheduleDeletion(id, mms, messageRecord.getExpiresIn());
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.timerView.setVisibility(View.GONE);
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -91,4 +92,18 @@ public class LabeledEditText extends FrameLayout implements View.OnFocusChangeLi
|
||||
super.setEnabled(enabled);
|
||||
input.setEnabled(enabled);
|
||||
}
|
||||
|
||||
public void focusAndMoveCursorToEndAndOpenKeyboard() {
|
||||
input.requestFocus();
|
||||
|
||||
int numberLength = getText().length();
|
||||
input.setSelection(numberLength, numberLength);
|
||||
|
||||
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
imm.showSoftInput(input, InputMethodManager.SHOW_IMPLICIT);
|
||||
|
||||
if (!imm.isAcceptingText()) {
|
||||
imm.toggleSoftInput(InputMethodManager.SHOW_IMPLICIT, InputMethodManager.HIDE_IMPLICIT_ONLY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import androidx.core.view.ViewCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public final class MicrophoneRecorderView extends FrameLayout implements View.OnTouchListener {
|
||||
|
||||
@@ -55,7 +54,7 @@ public final class MicrophoneRecorderView extends FrameLayout implements View.On
|
||||
floatingRecordButton = new FloatingRecordButton(getContext(), findViewById(R.id.quick_audio_fab));
|
||||
lockDropTarget = new LockDropTarget (getContext(), findViewById(R.id.lock_drop_target));
|
||||
|
||||
View recordButton = ViewUtil.findById(this, R.id.quick_audio_toggle);
|
||||
View recordButton = findViewById(R.id.quick_audio_toggle);
|
||||
recordButton.setOnTouchListener(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.CursorRecyclerViewAdapter;
|
||||
import org.thoughtcrime.securesms.database.loaders.RecentPhotosLoader;
|
||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.LoaderCallbacks<Cursor> {
|
||||
|
||||
@@ -52,7 +51,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
||||
|
||||
inflate(context, R.layout.recent_photo_view, this);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(this, R.id.photo_list);
|
||||
this.recyclerView = findViewById(R.id.photo_list);
|
||||
this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
|
||||
this.recyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
}
|
||||
@@ -158,7 +157,7 @@ public class RecentPhotoViewRail extends FrameLayout implements LoaderManager.Lo
|
||||
RecentPhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.imageView = ViewUtil.findById(itemView, R.id.thumbnail);
|
||||
this.imageView = itemView.findViewById(R.id.thumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public final class RecyclerViewFastScroller extends LinearLayout {
|
||||
private static final int BUBBLE_ANIMATION_DURATION = 100;
|
||||
@@ -75,8 +74,8 @@ public final class RecyclerViewFastScroller extends LinearLayout {
|
||||
setClipChildren(false);
|
||||
setScrollContainer(true);
|
||||
inflate(context, R.layout.recycler_view_fast_scroller, this);
|
||||
bubble = ViewUtil.findById(this, R.id.fastscroller_bubble);
|
||||
handle = ViewUtil.findById(this, R.id.fastscroller_handle);
|
||||
bubble = findViewById(R.id.fastscroller_bubble);
|
||||
handle = findViewById(R.id.fastscroller_handle);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -21,7 +21,6 @@ import org.thoughtcrime.securesms.database.MediaDatabase;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
public class ThreadPhotoRailView extends FrameLayout {
|
||||
|
||||
@@ -41,7 +40,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
|
||||
inflate(context, R.layout.recipient_preference_photo_rail, this);
|
||||
|
||||
this.recyclerView = ViewUtil.findById(this, R.id.photo_list);
|
||||
this.recyclerView = findViewById(R.id.photo_list);
|
||||
this.recyclerView.setLayoutManager(new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false));
|
||||
this.recyclerView.setItemAnimator(new DefaultItemAnimator());
|
||||
this.recyclerView.setNestedScrollingEnabled(false);
|
||||
@@ -112,7 +111,7 @@ public class ThreadPhotoRailView extends FrameLayout {
|
||||
ThreadPhotoViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
|
||||
this.imageView = ViewUtil.findById(itemView, R.id.thumbnail);
|
||||
this.imageView = itemView.findViewById(R.id.thumbnail);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.events.PartProgressEvent;
|
||||
import org.thoughtcrime.securesms.mms.Slide;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -61,16 +60,16 @@ public final class TransferControlView extends FrameLayout {
|
||||
inflate(context, R.layout.transfer_controls_view, this);
|
||||
|
||||
setLongClickable(false);
|
||||
ViewUtil.setBackground(this, ContextCompat.getDrawable(context, R.drawable.transfer_controls_background));
|
||||
setBackground(ContextCompat.getDrawable(context, R.drawable.transfer_controls_background));
|
||||
setVisibility(GONE);
|
||||
setLayoutTransition(new LayoutTransition());
|
||||
|
||||
this.networkProgress = new HashMap<>();
|
||||
this.compresssionProgress = new HashMap<>();
|
||||
|
||||
this.progressWheel = ViewUtil.findById(this, R.id.progress_wheel);
|
||||
this.downloadDetails = ViewUtil.findById(this, R.id.download_details);
|
||||
this.downloadDetailsText = ViewUtil.findById(this, R.id.download_details_text);
|
||||
this.progressWheel = findViewById(R.id.progress_wheel);
|
||||
this.downloadDetails = findViewById(R.id.download_details);
|
||||
this.downloadDetailsText = findViewById(R.id.download_details_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -21,11 +22,9 @@ public class TypingStatusSender {
|
||||
private static final long REFRESH_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
||||
private static final long PAUSE_TYPING_TIMEOUT = TimeUnit.SECONDS.toMillis(3);
|
||||
|
||||
private final Context context;
|
||||
private final Map<Long, TimerPair> selfTypingTimers;
|
||||
|
||||
public TypingStatusSender(@NonNull Context context) {
|
||||
this.context = context;
|
||||
public TypingStatusSender() {
|
||||
this.selfTypingTimers = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
@@ -3,14 +3,11 @@ package org.thoughtcrime.securesms.components;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy;
|
||||
import com.bumptech.glide.request.target.Target;
|
||||
@@ -29,6 +26,8 @@ import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.util.BitmapDecodingException;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -83,32 +82,27 @@ public class ZoomingImageView extends FrameLayout {
|
||||
|
||||
Log.i(TAG, "Max texture size: " + maxTextureSize);
|
||||
|
||||
new AsyncTask<Void, Void, Pair<Integer, Integer>>() {
|
||||
@Override
|
||||
protected @Nullable Pair<Integer, Integer> doInBackground(Void... params) {
|
||||
if (MediaUtil.isGif(contentType)) return null;
|
||||
SimpleTask.run(ViewUtil.getActivityLifecycle(this), () -> {
|
||||
if (MediaUtil.isGif(contentType)) return null;
|
||||
|
||||
try {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
return BitmapUtil.getDimensions(inputStream);
|
||||
} catch (IOException | BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
InputStream inputStream = PartAuthority.getAttachmentStream(context, uri);
|
||||
return BitmapUtil.getDimensions(inputStream);
|
||||
} catch (IOException | BitmapDecodingException e) {
|
||||
Log.w(TAG, e);
|
||||
return null;
|
||||
}
|
||||
}, dimensions -> {
|
||||
Log.i(TAG, "Dimensions: " + (dimensions == null ? "(null)" : dimensions.first + ", " + dimensions.second));
|
||||
|
||||
protected void onPostExecute(@Nullable Pair<Integer, Integer> dimensions) {
|
||||
Log.i(TAG, "Dimensions: " + (dimensions == null ? "(null)" : dimensions.first + ", " + dimensions.second));
|
||||
|
||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||
Log.i(TAG, "Loading in standard image view...");
|
||||
setImageViewUri(glideRequests, uri);
|
||||
} else {
|
||||
Log.i(TAG, "Loading in subsampling image view...");
|
||||
setSubsamplingImageViewUri(uri);
|
||||
}
|
||||
if (dimensions == null || (dimensions.first <= maxTextureSize && dimensions.second <= maxTextureSize)) {
|
||||
Log.i(TAG, "Loading in standard image view...");
|
||||
setImageViewUri(glideRequests, uri);
|
||||
} else {
|
||||
Log.i(TAG, "Loading in subsampling image view...");
|
||||
setSubsamplingImageViewUri(uri);
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
});
|
||||
}
|
||||
|
||||
private void setImageViewUri(@NonNull GlideRequests glideRequests, @NonNull Uri uri) {
|
||||
|
||||
@@ -39,6 +39,7 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||
private final EmojiEventListener emojiEventListener;
|
||||
|
||||
private Controller controller;
|
||||
private int currentPosition;
|
||||
|
||||
public EmojiKeyboardProvider(@NonNull Context context, @Nullable EmojiEventListener emojiEventListener) {
|
||||
this.context = context;
|
||||
@@ -66,11 +67,18 @@ public class EmojiKeyboardProvider implements MediaKeyboardProvider,
|
||||
|
||||
models.add(recentModel);
|
||||
models.addAll(EmojiPages.DISPLAY_PAGES);
|
||||
|
||||
currentPosition = recentModel.getEmoji().size() > 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider) {
|
||||
presenter.present(this, emojiPagerAdapter, this, this, null, null, recentModel.getEmoji().size() > 0 ? 0 : 1);
|
||||
presenter.present(this, emojiPagerAdapter, this, this, null, null, currentPosition);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentPosition(int currentPosition) {
|
||||
this.currentPosition = currentPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -212,6 +212,7 @@ public class MediaKeyboard extends FrameLayout implements InputView,
|
||||
public void onPageSelected(int i) {
|
||||
categoryTabAdapter.setActivePosition(i);
|
||||
categoryTabs.smoothScrollToPosition(i);
|
||||
providers[providerIndex].setCurrentPosition(i);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,6 +14,7 @@ public interface MediaKeyboardProvider {
|
||||
/** @return True if the click was handled with provider-specific logic, otherwise false */
|
||||
void requestPresentation(@NonNull Presenter presenter, boolean isSoloProvider);
|
||||
void setController(@Nullable Controller controller);
|
||||
void setCurrentPosition(int currentPosition);
|
||||
|
||||
interface BackspaceObserver {
|
||||
void onBackspaceClicked();
|
||||
|
||||
@@ -5,7 +5,6 @@ import android.content.Context;
|
||||
import android.content.res.AssetManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
@@ -14,6 +13,7 @@ import org.thoughtcrime.securesms.components.emoji.EmojiPageModel;
|
||||
import org.thoughtcrime.securesms.util.ListenableFutureTask;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
@@ -56,16 +56,11 @@ public class EmojiPageBitmap {
|
||||
return null;
|
||||
};
|
||||
task = new ListenableFutureTask<>(callable);
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override protected Void doInBackground(Void... params) {
|
||||
task.run();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override protected void onPostExecute(Void aVoid) {
|
||||
task = null;
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
SimpleTask.run(() -> {
|
||||
task.run();
|
||||
return null;
|
||||
},
|
||||
unused -> task = null);
|
||||
}
|
||||
return task;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components.identity;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -12,6 +11,7 @@ import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -42,23 +42,15 @@ public class UntrustedSendDialog extends AlertDialog.Builder implements DialogIn
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
final IdentityDatabase identityDatabase = DatabaseFactory.getIdentityDatabase(getContext());
|
||||
|
||||
new AsyncTask<Void, Void, Void>() {
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
synchronized (SESSION_LOCK) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
SimpleTask.run(() -> {
|
||||
synchronized (SESSION_LOCK) {
|
||||
for (IdentityRecord identityRecord : untrustedRecords) {
|
||||
identityDatabase.setApproval(identityRecord.getRecipientId(), true);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void result) {
|
||||
resendListener.onResendMessage();
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
|
||||
return null;
|
||||
}, unused -> resendListener.onResendMessage());
|
||||
}
|
||||
|
||||
public interface ResendListener {
|
||||
|
||||
@@ -17,7 +17,6 @@ import androidx.annotation.RequiresApi;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -53,9 +52,9 @@ public class UnverifiedBannerView extends LinearLayout {
|
||||
|
||||
private void initialize() {
|
||||
LayoutInflater.from(getContext()).inflate(R.layout.unverified_banner_view, this, true);
|
||||
this.container = ViewUtil.findById(this, R.id.container);
|
||||
this.text = ViewUtil.findById(this, R.id.unverified_text);
|
||||
this.closeButton = ViewUtil.findById(this, R.id.cancel);
|
||||
this.container = findViewById(R.id.container);
|
||||
this.text = findViewById(R.id.unverified_text);
|
||||
this.closeButton = findViewById(R.id.cancel);
|
||||
}
|
||||
|
||||
public void display(@NonNull final String text,
|
||||
|
||||
@@ -18,7 +18,6 @@ import com.google.android.gms.maps.OnMapReadyCallback;
|
||||
import com.google.android.gms.maps.model.MarkerOptions;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.ListenableFuture;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||
|
||||
@@ -47,9 +46,9 @@ public class SignalMapView extends LinearLayout {
|
||||
setOrientation(LinearLayout.VERTICAL);
|
||||
LayoutInflater.from(context).inflate(R.layout.signal_map_view, this, true);
|
||||
|
||||
this.mapView = ViewUtil.findById(this, R.id.map_view);
|
||||
this.imageView = ViewUtil.findById(this, R.id.image_view);
|
||||
this.textView = ViewUtil.findById(this, R.id.address_view);
|
||||
this.mapView = findViewById(R.id.map_view);
|
||||
this.imageView = findViewById(R.id.image_view);
|
||||
this.textView = findViewById(R.id.address_view);
|
||||
}
|
||||
|
||||
public ListenableFuture<Bitmap> display(final SignalPlace place) {
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.SmsUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
|
||||
public class DefaultSmsReminder extends Reminder {
|
||||
|
||||
public DefaultSmsReminder(@NonNull Fragment fragment, short requestCode) {
|
||||
super(fragment.getString(R.string.reminder_header_sms_default_title),
|
||||
fragment.getString(R.string.reminder_header_sms_default_text));
|
||||
|
||||
final OnClickListener okListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TextSecurePreferences.setPromptedDefaultSmsProvider(fragment.requireContext(), true);
|
||||
fragment.startActivityForResult(SmsUtil.getSmsRoleIntent(fragment.requireContext()), requestCode);
|
||||
}
|
||||
};
|
||||
final OnClickListener dismissListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
TextSecurePreferences.setPromptedDefaultSmsProvider(fragment.requireContext(), true);
|
||||
}
|
||||
};
|
||||
setOkListener(okListener);
|
||||
setDismissListener(dismissListener);
|
||||
}
|
||||
|
||||
public static boolean isEligible(Context context) {
|
||||
final boolean isDefault = Util.isDefaultSmsProvider(context);
|
||||
if (isDefault) {
|
||||
TextSecurePreferences.setPromptedDefaultSmsProvider(context, false);
|
||||
}
|
||||
|
||||
return !isDefault && !TextSecurePreferences.hasPromptedDefaultSmsProvider(context);
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -78,6 +79,7 @@ public final class ReminderView extends FrameLayout {
|
||||
}
|
||||
|
||||
text.setText(reminder.getText());
|
||||
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_button_primary_text));
|
||||
|
||||
switch (reminder.getImportance()) {
|
||||
case NORMAL:
|
||||
@@ -85,6 +87,7 @@ public final class ReminderView extends FrameLayout {
|
||||
break;
|
||||
case ERROR:
|
||||
container.setBackgroundResource(R.drawable.reminder_background_error);
|
||||
text.setTextColor(ContextCompat.getColor(getContext(), R.color.signal_text_primary));
|
||||
break;
|
||||
case TERMINAL:
|
||||
container.setBackgroundResource(R.drawable.reminder_background_terminal);
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
package org.thoughtcrime.securesms.components.reminder;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.Cursor;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.InviteActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class ShareReminder extends Reminder {
|
||||
|
||||
public ShareReminder(final @NonNull Context context) {
|
||||
super(context.getString(R.string.reminder_header_share_title),
|
||||
context.getString(R.string.reminder_header_share_text));
|
||||
|
||||
setDismissListener(new OnClickListener() {
|
||||
@Override public void onClick(View v) {
|
||||
TextSecurePreferences.setPromptedShare(context, true);
|
||||
}
|
||||
});
|
||||
|
||||
setOkListener(new OnClickListener() {
|
||||
@Override public void onClick(View v) {
|
||||
TextSecurePreferences.setPromptedShare(context, true);
|
||||
context.startActivity(new Intent(context, InviteActivity.class));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static boolean isEligible(final @NonNull Context context) {
|
||||
if (!TextSecurePreferences.isPushRegistered(context) ||
|
||||
TextSecurePreferences.hasPromptedShare(context))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Cursor cursor = null;
|
||||
try {
|
||||
cursor = DatabaseFactory.getThreadDatabase(context).getConversationList();
|
||||
return cursor.getCount() >= 1;
|
||||
} finally {
|
||||
if (cursor != null) cursor.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class SystemSmsImportReminder extends Reminder {
|
||||
context.startService(intent);
|
||||
|
||||
// TODO [greyson] Navigation
|
||||
Intent nextIntent = new Intent(context, MainActivity.class);
|
||||
Intent nextIntent = MainActivity.clearTop(context);
|
||||
Intent activityIntent = new Intent(context, DatabaseMigrationActivity.class);
|
||||
activityIntent.putExtra("next_intent", nextIntent);
|
||||
context.startActivity(activityIntent);
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.media.AudioManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.RemoteException;
|
||||
import android.support.v4.media.MediaBrowserCompat;
|
||||
@@ -208,17 +209,20 @@ public class VoiceNoteMediaController implements DefaultLifecycleObserver {
|
||||
|
||||
private static class ProgressEventHandler extends Handler {
|
||||
|
||||
private final MediaControllerCompat mediaController;
|
||||
private final MutableLiveData<VoiceNotePlaybackState> voiceNotePlaybackState;
|
||||
private final MediaControllerCompat mediaController;
|
||||
private final MutableLiveData<VoiceNotePlaybackState> voiceNotePlaybackState;
|
||||
|
||||
private ProgressEventHandler(@NonNull MediaControllerCompat mediaController,
|
||||
@NonNull MutableLiveData<VoiceNotePlaybackState> voiceNotePlaybackState) {
|
||||
@NonNull MutableLiveData<VoiceNotePlaybackState> voiceNotePlaybackState)
|
||||
{
|
||||
super(Looper.getMainLooper());
|
||||
|
||||
this.mediaController = mediaController;
|
||||
this.voiceNotePlaybackState = voiceNotePlaybackState;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
public void handleMessage(@NonNull Message msg) {
|
||||
MediaMetadataCompat mediaMetadataCompat = mediaController.getMetadata();
|
||||
if (isPlayerActive(mediaController.getPlaybackState()) &&
|
||||
mediaMetadataCompat != null &&
|
||||
|
||||
@@ -64,7 +64,6 @@ final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.V
|
||||
|
||||
static class ViewHolder extends RecyclerView.ViewHolder implements CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
private final TextView textView;
|
||||
private final RadioButton radioButton;
|
||||
private final Consumer<Integer> onPressed;
|
||||
|
||||
@@ -72,16 +71,14 @@ final class AudioOutputAdapter extends RecyclerView.Adapter<AudioOutputAdapter.V
|
||||
public ViewHolder(@NonNull View itemView, @NonNull Consumer<Integer> onPressed) {
|
||||
super(itemView);
|
||||
|
||||
this.textView = itemView.findViewById(R.id.text);
|
||||
this.radioButton = itemView.findViewById(R.id.radio);
|
||||
this.onPressed = onPressed;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
void bind(@NonNull WebRtcAudioOutput audioOutput, @Nullable WebRtcAudioOutput selected) {
|
||||
textView.setText(audioOutput.getLabelRes());
|
||||
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(audioOutput.getIconRes(), 0, 0, 0);
|
||||
|
||||
radioButton.setText(audioOutput.getLabelRes());
|
||||
radioButton.setCompoundDrawablesRelativeWithIntrinsicBounds(audioOutput.getIconRes(), 0, 0, 0);
|
||||
radioButton.setOnCheckedChangeListener(null);
|
||||
radioButton.setChecked(audioOutput == selected);
|
||||
radioButton.setOnCheckedChangeListener(this);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.annimon.stream.Collectors;
|
||||
import com.annimon.stream.Stream;
|
||||
@@ -21,19 +22,19 @@ import java.util.Set;
|
||||
*/
|
||||
public final class CallParticipantListUpdate {
|
||||
|
||||
private final Set<Holder> added;
|
||||
private final Set<Holder> removed;
|
||||
private final Set<Wrapper> added;
|
||||
private final Set<Wrapper> removed;
|
||||
|
||||
CallParticipantListUpdate(@NonNull Set<Holder> added, @NonNull Set<Holder> removed) {
|
||||
CallParticipantListUpdate(@NonNull Set<Wrapper> added, @NonNull Set<Wrapper> removed) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public @NonNull Set<Holder> getAdded() {
|
||||
public @NonNull Set<Wrapper> getAdded() {
|
||||
return added;
|
||||
}
|
||||
|
||||
public @NonNull Set<Holder> getRemoved() {
|
||||
public @NonNull Set<Wrapper> getRemoved() {
|
||||
return removed;
|
||||
}
|
||||
|
||||
@@ -68,66 +69,47 @@ public final class CallParticipantListUpdate {
|
||||
public static @NonNull CallParticipantListUpdate computeDeltaUpdate(@NonNull List<CallParticipant> oldList,
|
||||
@NonNull List<CallParticipant> newList)
|
||||
{
|
||||
Set<CallParticipantId> primaries = getPrimaries(oldList, newList);
|
||||
Set<CallParticipantListUpdate.Holder> oldParticipants = Stream.of(oldList)
|
||||
Set<CallParticipantListUpdate.Wrapper> oldParticipants = Stream.of(oldList)
|
||||
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
||||
.map(p -> createHolder(p, primaries.contains(p.getCallParticipantId())))
|
||||
.map(CallParticipantListUpdate::createWrapper)
|
||||
.collect(Collectors.toSet());
|
||||
Set<CallParticipantListUpdate.Holder> newParticipants = Stream.of(newList)
|
||||
Set<CallParticipantListUpdate.Wrapper> newParticipants = Stream.of(newList)
|
||||
.filter(p -> p.getCallParticipantId().getDemuxId() != CallParticipantId.DEFAULT_ID)
|
||||
.map(p -> createHolder(p, primaries.contains(p.getCallParticipantId())))
|
||||
.map(CallParticipantListUpdate::createWrapper)
|
||||
.collect(Collectors.toSet());
|
||||
Set<CallParticipantListUpdate.Holder> added = SetUtil.difference(newParticipants, oldParticipants);
|
||||
Set<CallParticipantListUpdate.Holder> removed = SetUtil.difference(oldParticipants, newParticipants);
|
||||
Set<CallParticipantListUpdate.Wrapper> added = SetUtil.difference(newParticipants, oldParticipants);
|
||||
Set<CallParticipantListUpdate.Wrapper> removed = SetUtil.difference(oldParticipants, newParticipants);
|
||||
|
||||
return new CallParticipantListUpdate(added, removed);
|
||||
}
|
||||
|
||||
static Holder createHolder(@NonNull CallParticipant callParticipant, boolean isPrimary) {
|
||||
return new Holder(callParticipant.getCallParticipantId(), callParticipant.getRecipient(), isPrimary);
|
||||
@VisibleForTesting
|
||||
static Wrapper createWrapper(@NonNull CallParticipant callParticipant) {
|
||||
return new Wrapper(callParticipant);
|
||||
}
|
||||
|
||||
private static @NonNull Set<CallParticipantId> getPrimaries(@NonNull List<CallParticipant> oldList, @NonNull List<CallParticipant> newList) {
|
||||
return Stream.concat(Stream.of(oldList), Stream.of(newList))
|
||||
.map(CallParticipant::getCallParticipantId)
|
||||
.distinctBy(CallParticipantId::getRecipientId)
|
||||
.collect(Collectors.toSet());
|
||||
}
|
||||
static final class Wrapper {
|
||||
private final CallParticipant callParticipant;
|
||||
|
||||
static final class Holder {
|
||||
private final CallParticipantId callParticipantId;
|
||||
private final Recipient recipient;
|
||||
private final boolean isPrimary;
|
||||
|
||||
private Holder(@NonNull CallParticipantId callParticipantId, @NonNull Recipient recipient, boolean isPrimary) {
|
||||
this.callParticipantId = callParticipantId;
|
||||
this.recipient = recipient;
|
||||
this.isPrimary = isPrimary;
|
||||
private Wrapper(@NonNull CallParticipant callParticipant) {
|
||||
this.callParticipant = callParticipant;
|
||||
}
|
||||
|
||||
public @NonNull Recipient getRecipient() {
|
||||
return recipient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes whether this was the first detected instance of this recipient when generating an update. See
|
||||
* {@link CallParticipantListUpdate#computeDeltaUpdate(List, List)}
|
||||
*/
|
||||
public boolean isPrimary() {
|
||||
return isPrimary;
|
||||
public @NonNull CallParticipant getCallParticipant() {
|
||||
return callParticipant;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Holder holder = (Holder) o;
|
||||
return callParticipantId.equals(holder.callParticipantId);
|
||||
Wrapper wrapper = (Wrapper) o;
|
||||
return callParticipant.getCallParticipantId().equals(wrapper.callParticipant.getCallParticipantId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(callParticipantId);
|
||||
return Objects.hash(callParticipant.getCallParticipantId());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import org.thoughtcrime.securesms.mms.GlideApp;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.webrtc.RendererCommon;
|
||||
|
||||
@@ -49,6 +50,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
|
||||
private RecipientId recipientId;
|
||||
private boolean infoMode;
|
||||
private Runnable missingMediaKeysUpdater;
|
||||
|
||||
private AppCompatImageView backgroundAvatar;
|
||||
private AvatarImageView avatar;
|
||||
@@ -77,6 +79,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
|
||||
backgroundAvatar = findViewById(R.id.call_participant_background_avatar);
|
||||
avatar = findViewById(R.id.call_participant_item_avatar);
|
||||
pipAvatar = findViewById(R.id.call_participant_item_pip_avatar);
|
||||
@@ -102,7 +105,7 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
void setCallParticipant(@NonNull CallParticipant participant) {
|
||||
boolean participantChanged = recipientId == null || !recipientId.equals(participant.getRecipient().getId());
|
||||
recipientId = participant.getRecipient().getId();
|
||||
infoMode = participant.getRecipient().isBlocked() || (!participant.isMediaKeysReceived() && (System.currentTimeMillis() - participant.getAddedToCallTime()) > DELAY_SHOWING_MISSING_MEDIA_KEYS);
|
||||
infoMode = participant.getRecipient().isBlocked() || isMissingMediaKeys(participant);
|
||||
|
||||
if (infoMode) {
|
||||
renderer.setVisibility(View.GONE);
|
||||
@@ -149,6 +152,28 @@ public class CallParticipantView extends ConstraintLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isMissingMediaKeys(@NonNull CallParticipant participant) {
|
||||
if (missingMediaKeysUpdater != null) {
|
||||
Util.cancelRunnableOnMain(missingMediaKeysUpdater);
|
||||
missingMediaKeysUpdater = null;
|
||||
}
|
||||
|
||||
if (!participant.isMediaKeysReceived()) {
|
||||
long time = System.currentTimeMillis() - participant.getAddedToCallTime();
|
||||
if (time > DELAY_SHOWING_MISSING_MEDIA_KEYS) {
|
||||
return true;
|
||||
} else {
|
||||
missingMediaKeysUpdater = () -> {
|
||||
if (recipientId.equals(participant.getRecipient().getId())) {
|
||||
setCallParticipant(participant);
|
||||
}
|
||||
};
|
||||
Util.runOnMainDelayed(missingMediaKeysUpdater, DELAY_SHOWING_MISSING_MEDIA_KEYS - time);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setRenderInPip(boolean shouldRenderInPip) {
|
||||
if (infoMode) {
|
||||
infoMessage.setVisibility(shouldRenderInPip ? View.GONE : View.VISIBLE);
|
||||
|
||||
@@ -31,8 +31,8 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
private final AvatarImageView avatarImageView;
|
||||
private final TextView descriptionTextView;
|
||||
|
||||
private final Set<CallParticipantListUpdate.Holder> pendingAdditions = new HashSet<>();
|
||||
private final Set<CallParticipantListUpdate.Holder> pendingRemovals = new HashSet<>();
|
||||
private final Set<CallParticipantListUpdate.Wrapper> pendingAdditions = new HashSet<>();
|
||||
private final Set<CallParticipantListUpdate.Wrapper> pendingRemovals = new HashSet<>();
|
||||
|
||||
private boolean isEnabled = true;
|
||||
|
||||
@@ -112,18 +112,18 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
avatarImageView.setVisibility(recipient == null ? View.GONE : View.VISIBLE);
|
||||
}
|
||||
|
||||
private void setDescription(@NonNull Set<CallParticipantListUpdate.Holder> holders, boolean isAdded) {
|
||||
if (holders.isEmpty()) {
|
||||
private void setDescription(@NonNull Set<CallParticipantListUpdate.Wrapper> wrappers, boolean isAdded) {
|
||||
if (wrappers.isEmpty()) {
|
||||
descriptionTextView.setText("");
|
||||
} else {
|
||||
setDescriptionForRecipients(holders, isAdded);
|
||||
setDescriptionForRecipients(wrappers, isAdded);
|
||||
}
|
||||
}
|
||||
|
||||
private void setDescriptionForRecipients(@NonNull Set<CallParticipantListUpdate.Holder> recipients, boolean isAdded) {
|
||||
Iterator<CallParticipantListUpdate.Holder> iterator = recipients.iterator();
|
||||
Context context = getContentView().getContext();
|
||||
String description;
|
||||
private void setDescriptionForRecipients(@NonNull Set<CallParticipantListUpdate.Wrapper> recipients, boolean isAdded) {
|
||||
Iterator<CallParticipantListUpdate.Wrapper> iterator = recipients.iterator();
|
||||
Context context = getContentView().getContext();
|
||||
String description;
|
||||
|
||||
switch (recipients.size()) {
|
||||
case 0:
|
||||
@@ -144,22 +144,14 @@ public class CallParticipantsListUpdatePopupWindow extends PopupWindow {
|
||||
descriptionTextView.setText(description);
|
||||
}
|
||||
|
||||
private @NonNull Recipient getNextRecipient(@NonNull Iterator<CallParticipantListUpdate.Holder> holderIterator) {
|
||||
return holderIterator.next().getRecipient();
|
||||
private @NonNull Recipient getNextRecipient(@NonNull Iterator<CallParticipantListUpdate.Wrapper> wrapperIterator) {
|
||||
return wrapperIterator.next().getCallParticipant().getRecipient();
|
||||
}
|
||||
|
||||
private @NonNull String getNextDisplayName(@NonNull Iterator<CallParticipantListUpdate.Holder> holderIterator) {
|
||||
CallParticipantListUpdate.Holder holder = holderIterator.next();
|
||||
Recipient recipient = holder.getRecipient();
|
||||
private @NonNull String getNextDisplayName(@NonNull Iterator<CallParticipantListUpdate.Wrapper> wrapperIterator) {
|
||||
CallParticipantListUpdate.Wrapper wrapper = wrapperIterator.next();
|
||||
|
||||
if (recipient.isSelf()) {
|
||||
return getContentView().getContext().getString(R.string.CallParticipantsListUpdatePopupWindow__you_on_another_device);
|
||||
} else if (holder.isPrimary()) {
|
||||
return recipient.getDisplayName(getContentView().getContext());
|
||||
} else {
|
||||
return getContentView().getContext().getString(R.string.CallParticipantsListUpdatePopupWindow__s_on_another_device,
|
||||
recipient.getDisplayName(getContentView().getContext()));
|
||||
}
|
||||
return wrapper.getCallParticipant().getRecipientDisplayName(getContentView().getContext());
|
||||
}
|
||||
|
||||
private static @StringRes int getOneMemberDescriptionResourceId(boolean isAdded) {
|
||||
|
||||
@@ -6,12 +6,14 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.ComparatorCompat;
|
||||
import com.annimon.stream.OptionalLong;
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.events.CallParticipant;
|
||||
import org.thoughtcrime.securesms.events.WebRtcViewModel;
|
||||
import org.thoughtcrime.securesms.ringrtc.CameraState;
|
||||
import org.thoughtcrime.securesms.service.webrtc.collections.ParticipantCollection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
@@ -28,36 +30,36 @@ public final class CallParticipantsState {
|
||||
|
||||
public static final CallParticipantsState STARTING_STATE = new CallParticipantsState(WebRtcViewModel.State.CALL_DISCONNECTED,
|
||||
WebRtcViewModel.GroupCallState.IDLE,
|
||||
Collections.emptyList(),
|
||||
new ParticipantCollection(SMALL_GROUP_MAX),
|
||||
CallParticipant.createLocal(CameraState.UNKNOWN, new BroadcastVideoSink(null), false),
|
||||
null,
|
||||
WebRtcLocalRenderState.GONE,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
0);
|
||||
OptionalLong.empty());
|
||||
|
||||
private final WebRtcViewModel.State callState;
|
||||
private final WebRtcViewModel.GroupCallState groupCallState;
|
||||
private final List<CallParticipant> remoteParticipants;
|
||||
private final ParticipantCollection remoteParticipants;
|
||||
private final CallParticipant localParticipant;
|
||||
private final CallParticipant focusedParticipant;
|
||||
private final WebRtcLocalRenderState localRenderState;
|
||||
private final boolean isInPipMode;
|
||||
private final boolean showVideoForOutgoing;
|
||||
private final boolean isViewingFocusedParticipant;
|
||||
private final long remoteDevicesCount;
|
||||
private final OptionalLong remoteDevicesCount;
|
||||
|
||||
public CallParticipantsState(@NonNull WebRtcViewModel.State callState,
|
||||
@NonNull WebRtcViewModel.GroupCallState groupCallState,
|
||||
@NonNull List<CallParticipant> remoteParticipants,
|
||||
@NonNull ParticipantCollection remoteParticipants,
|
||||
@NonNull CallParticipant localParticipant,
|
||||
@Nullable CallParticipant focusedParticipant,
|
||||
@NonNull WebRtcLocalRenderState localRenderState,
|
||||
boolean isInPipMode,
|
||||
boolean showVideoForOutgoing,
|
||||
boolean isViewingFocusedParticipant,
|
||||
long remoteDevicesCount)
|
||||
OptionalLong remoteDevicesCount)
|
||||
{
|
||||
this.callState = callState;
|
||||
this.groupCallState = groupCallState;
|
||||
@@ -80,11 +82,7 @@ public final class CallParticipantsState {
|
||||
}
|
||||
|
||||
public @NonNull List<CallParticipant> getGridParticipants() {
|
||||
if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
|
||||
return getAllRemoteParticipants().subList(0, SMALL_GROUP_MAX);
|
||||
} else {
|
||||
return getAllRemoteParticipants();
|
||||
}
|
||||
return remoteParticipants.getGridParticipants();
|
||||
}
|
||||
|
||||
public @NonNull List<CallParticipant> getListParticipants() {
|
||||
@@ -93,14 +91,11 @@ public final class CallParticipantsState {
|
||||
if (isViewingFocusedParticipant && getAllRemoteParticipants().size() > 1) {
|
||||
listParticipants.addAll(getAllRemoteParticipants());
|
||||
listParticipants.remove(focusedParticipant);
|
||||
} else if (getAllRemoteParticipants().size() > SMALL_GROUP_MAX) {
|
||||
listParticipants.addAll(getAllRemoteParticipants().subList(SMALL_GROUP_MAX, getAllRemoteParticipants().size()));
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
listParticipants.addAll(remoteParticipants.getListParticipants());
|
||||
}
|
||||
|
||||
listParticipants.add(CallParticipant.EMPTY);
|
||||
|
||||
Collections.reverse(listParticipants);
|
||||
|
||||
return listParticipants;
|
||||
@@ -112,26 +107,26 @@ public final class CallParticipantsState {
|
||||
return context.getString(R.string.WebRtcCallView__no_one_else_is_here);
|
||||
case 1:
|
||||
if (callState == WebRtcViewModel.State.CALL_PRE_JOIN && groupCallState.isNotIdle()) {
|
||||
return context.getString(R.string.WebRtcCallView__s_is_in_this_call, remoteParticipants.get(0).getRecipient().getShortDisplayName(context));
|
||||
return context.getString(R.string.WebRtcCallView__s_is_in_this_call, remoteParticipants.get(0).getShortRecipientDisplayName(context));
|
||||
} else {
|
||||
return remoteParticipants.get(0).getRecipient().getDisplayName(context);
|
||||
return remoteParticipants.get(0).getRecipientDisplayName(context);
|
||||
}
|
||||
case 2:
|
||||
return context.getString(R.string.WebRtcCallView__s_and_s_are_in_this_call,
|
||||
remoteParticipants.get(0).getRecipient().getShortDisplayName(context),
|
||||
remoteParticipants.get(1).getRecipient().getShortDisplayName(context));
|
||||
remoteParticipants.get(0).getShortRecipientDisplayName(context),
|
||||
remoteParticipants.get(1).getShortRecipientDisplayName(context));
|
||||
default:
|
||||
int others = remoteParticipants.size() - 2;
|
||||
return context.getResources().getQuantityString(R.plurals.WebRtcCallView__s_s_and_d_others_are_in_this_call,
|
||||
others,
|
||||
remoteParticipants.get(0).getRecipient().getShortDisplayName(context),
|
||||
remoteParticipants.get(1).getRecipient().getShortDisplayName(context),
|
||||
remoteParticipants.get(0).getShortRecipientDisplayName(context),
|
||||
remoteParticipants.get(1).getShortRecipientDisplayName(context),
|
||||
others);
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull List<CallParticipant> getAllRemoteParticipants() {
|
||||
return remoteParticipants;
|
||||
return remoteParticipants.getAllParticipants();
|
||||
}
|
||||
|
||||
public @NonNull CallParticipant getLocalParticipant() {
|
||||
@@ -158,10 +153,17 @@ public final class CallParticipantsState {
|
||||
return Stream.of(getAllRemoteParticipants()).anyMatch(p -> p.getVideoSink().needsNewRequestingSize());
|
||||
}
|
||||
|
||||
public long getRemoteDevicesCount() {
|
||||
public @NonNull OptionalLong getRemoteDevicesCount() {
|
||||
return remoteDevicesCount;
|
||||
}
|
||||
|
||||
public @NonNull OptionalLong getParticipantCount() {
|
||||
boolean includeSelf = groupCallState == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
|
||||
|
||||
return remoteDevicesCount.map(l -> l + (includeSelf ? 1L : 0L))
|
||||
.or(() -> includeSelf ? OptionalLong.of(1L) : OptionalLong.empty());
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState,
|
||||
@NonNull WebRtcViewModel webRtcViewModel,
|
||||
boolean enableVideo)
|
||||
@@ -179,7 +181,8 @@ public final class CallParticipantsState {
|
||||
webRtcViewModel.getGroupState().isNotIdle(),
|
||||
webRtcViewModel.getState(),
|
||||
webRtcViewModel.getRemoteParticipants().size(),
|
||||
oldState.isViewingFocusedParticipant);
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||
|
||||
List<CallParticipant> participantsByLastSpoke = new ArrayList<>(webRtcViewModel.getRemoteParticipants());
|
||||
Collections.sort(participantsByLastSpoke, ComparatorCompat.reversed((p1, p2) -> Long.compare(p1.getLastSpoke(), p2.getLastSpoke())));
|
||||
@@ -188,7 +191,7 @@ public final class CallParticipantsState {
|
||||
|
||||
return new CallParticipantsState(webRtcViewModel.getState(),
|
||||
webRtcViewModel.getGroupState(),
|
||||
webRtcViewModel.getRemoteParticipants(),
|
||||
oldState.remoteParticipants.getNext(webRtcViewModel.getRemoteParticipants()),
|
||||
webRtcViewModel.getLocalParticipant(),
|
||||
focused,
|
||||
localRenderState,
|
||||
@@ -205,7 +208,8 @@ public final class CallParticipantsState {
|
||||
oldState.getGroupCallState().isNotIdle(),
|
||||
oldState.callState,
|
||||
oldState.getAllRemoteParticipants().size(),
|
||||
oldState.isViewingFocusedParticipant);
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||
|
||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||
|
||||
@@ -221,6 +225,28 @@ public final class CallParticipantsState {
|
||||
oldState.remoteDevicesCount);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState setExpanded(@NonNull CallParticipantsState oldState, boolean expanded) {
|
||||
WebRtcLocalRenderState localRenderState = determineLocalRenderMode(oldState.localParticipant,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.getGroupCallState().isNotIdle(),
|
||||
oldState.callState,
|
||||
oldState.getAllRemoteParticipants().size(),
|
||||
oldState.isViewingFocusedParticipant,
|
||||
expanded);
|
||||
|
||||
return new CallParticipantsState(oldState.callState,
|
||||
oldState.groupCallState,
|
||||
oldState.remoteParticipants,
|
||||
oldState.localParticipant,
|
||||
oldState.focusedParticipant,
|
||||
localRenderState,
|
||||
oldState.isInPipMode,
|
||||
oldState.showVideoForOutgoing,
|
||||
oldState.isViewingFocusedParticipant,
|
||||
oldState.remoteDevicesCount);
|
||||
}
|
||||
|
||||
public static @NonNull CallParticipantsState update(@NonNull CallParticipantsState oldState, @NonNull SelectedPage selectedPage) {
|
||||
CallParticipant focused = oldState.remoteParticipants.isEmpty() ? null : oldState.remoteParticipants.get(0);
|
||||
|
||||
@@ -230,7 +256,8 @@ public final class CallParticipantsState {
|
||||
oldState.getGroupCallState().isNotIdle(),
|
||||
oldState.callState,
|
||||
oldState.getAllRemoteParticipants().size(),
|
||||
selectedPage == SelectedPage.FOCUSED);
|
||||
selectedPage == SelectedPage.FOCUSED,
|
||||
oldState.getLocalRenderState() == WebRtcLocalRenderState.EXPANDED);
|
||||
|
||||
return new CallParticipantsState(oldState.callState,
|
||||
oldState.groupCallState,
|
||||
@@ -250,12 +277,15 @@ public final class CallParticipantsState {
|
||||
boolean isNonIdleGroupCall,
|
||||
@NonNull WebRtcViewModel.State callState,
|
||||
int numberOfRemoteParticipants,
|
||||
boolean isViewingFocusedParticipant)
|
||||
boolean isViewingFocusedParticipant,
|
||||
boolean isExpanded)
|
||||
{
|
||||
boolean displayLocal = (numberOfRemoteParticipants == 0 || !isInPip) && (isNonIdleGroupCall || localParticipant.isVideoEnabled());
|
||||
WebRtcLocalRenderState localRenderState = WebRtcLocalRenderState.GONE;
|
||||
|
||||
if (displayLocal || showVideoForOutgoing) {
|
||||
if (isExpanded && (localParticipant.isVideoEnabled() || isNonIdleGroupCall)) {
|
||||
return WebRtcLocalRenderState.EXPANDED;
|
||||
} else if (displayLocal || showVideoForOutgoing) {
|
||||
if (callState == WebRtcViewModel.State.CALL_CONNECTED) {
|
||||
if (isViewingFocusedParticipant || numberOfRemoteParticipants > 1) {
|
||||
localRenderState = WebRtcLocalRenderState.SMALLER_RECTANGLE;
|
||||
|
||||
@@ -0,0 +1,197 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
/**
|
||||
* Helps manage the expansion and shrinking of the in-app pip.
|
||||
*/
|
||||
@MainThread
|
||||
final class PictureInPictureExpansionHelper {
|
||||
|
||||
private State state = State.IS_SHRUNKEN;
|
||||
|
||||
public boolean isExpandedOrExpanding() {
|
||||
return state == State.IS_EXPANDED || state == State.IS_EXPANDING;
|
||||
}
|
||||
|
||||
public boolean isShrunkenOrShrinking() {
|
||||
return state == State.IS_SHRUNKEN || state == State.IS_SHRINKING;
|
||||
}
|
||||
|
||||
public void expand(@NonNull View toExpand, @NonNull Callback callback) {
|
||||
if (isExpandedOrExpanding()) {
|
||||
return;
|
||||
}
|
||||
|
||||
performExpandAnimation(toExpand, new Callback() {
|
||||
@Override
|
||||
public void onAnimationWillStart() {
|
||||
state = State.IS_EXPANDING;
|
||||
callback.onAnimationWillStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
callback.onPictureInPictureExpanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
callback.onPictureInPictureNotVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationHasFinished() {
|
||||
state = State.IS_EXPANDED;
|
||||
callback.onAnimationHasFinished();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void shrink(@NonNull View toExpand, @NonNull Callback callback) {
|
||||
if (isShrunkenOrShrinking()) {
|
||||
return;
|
||||
}
|
||||
|
||||
performShrinkAnimation(toExpand, new Callback() {
|
||||
@Override
|
||||
public void onAnimationWillStart() {
|
||||
state = State.IS_SHRINKING;
|
||||
callback.onAnimationWillStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
callback.onPictureInPictureExpanded();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
callback.onPictureInPictureNotVisible();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationHasFinished() {
|
||||
state = State.IS_SHRUNKEN;
|
||||
callback.onAnimationHasFinished();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void performExpandAnimation(@NonNull View target, @NonNull Callback callback) {
|
||||
ViewGroup parent = (ViewGroup) target.getParent();
|
||||
|
||||
float x = target.getX();
|
||||
float y = target.getY();
|
||||
float scaleX = parent.getMeasuredWidth() / (float) target.getMeasuredWidth();
|
||||
float scaleY = parent.getMeasuredHeight() / (float) target.getMeasuredHeight();
|
||||
float scale = Math.max(scaleX, scaleY);
|
||||
|
||||
callback.onAnimationWillStart();
|
||||
|
||||
target.animate()
|
||||
.setDuration(200)
|
||||
.x((parent.getMeasuredWidth() - target.getMeasuredWidth()) / 2f)
|
||||
.y((parent.getMeasuredHeight() - target.getMeasuredHeight()) / 2f)
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.withEndAction(() -> {
|
||||
callback.onPictureInPictureExpanded();
|
||||
target.animate()
|
||||
.setDuration(100)
|
||||
.alpha(0f)
|
||||
.withEndAction(() -> {
|
||||
callback.onPictureInPictureNotVisible();
|
||||
|
||||
target.setX(x);
|
||||
target.setY(y);
|
||||
target.setScaleX(0f);
|
||||
target.setScaleY(0f);
|
||||
target.setAlpha(1f);
|
||||
|
||||
target.animate()
|
||||
.setDuration(200)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.withEndAction(callback::onAnimationHasFinished);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void performShrinkAnimation(@NonNull View target, @NonNull Callback callback) {
|
||||
ViewGroup parent = (ViewGroup) target.getParent();
|
||||
|
||||
float x = target.getX();
|
||||
float y = target.getY();
|
||||
float scaleX = parent.getMeasuredWidth() / (float) target.getMeasuredWidth();
|
||||
float scaleY = parent.getMeasuredHeight() / (float) target.getMeasuredHeight();
|
||||
float scale = Math.max(scaleX, scaleY);
|
||||
|
||||
callback.onAnimationWillStart();
|
||||
|
||||
target.animate()
|
||||
.setDuration(200)
|
||||
.scaleX(0f)
|
||||
.scaleY(0f)
|
||||
.withEndAction(() -> {
|
||||
target.setX((parent.getMeasuredWidth() - target.getMeasuredWidth()) / 2f);
|
||||
target.setY((parent.getMeasuredHeight() - target.getMeasuredHeight()) / 2f);
|
||||
target.setAlpha(0f);
|
||||
target.setScaleX(scale);
|
||||
target.setScaleY(scale);
|
||||
|
||||
callback.onPictureInPictureNotVisible();
|
||||
|
||||
target.animate()
|
||||
.setDuration(100)
|
||||
.alpha(1f)
|
||||
.withEndAction(() -> {
|
||||
callback.onPictureInPictureExpanded();
|
||||
|
||||
target.animate()
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.x(x)
|
||||
.y(y)
|
||||
.withEndAction(callback::onAnimationHasFinished);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
enum State {
|
||||
IS_EXPANDING,
|
||||
IS_EXPANDED,
|
||||
IS_SHRINKING,
|
||||
IS_SHRUNKEN
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when an animation (shrink or expand) will begin. This happens before any animation
|
||||
* is executed.
|
||||
*/
|
||||
void onAnimationWillStart();
|
||||
|
||||
/**
|
||||
* Called when the PiP is covering the whole screen. This is when any staging / teardown of the
|
||||
* large local renderer should occur.
|
||||
*/
|
||||
void onPictureInPictureExpanded();
|
||||
|
||||
/**
|
||||
* Called when the PiP is not visible on the screen anymore. This is when any staging / teardown
|
||||
* of the pip should occur.
|
||||
*/
|
||||
void onPictureInPictureNotVisible();
|
||||
|
||||
/**
|
||||
* Called when the animation is complete. Useful for e.g. adjusting the pip's final location to
|
||||
* make sure it is respecting the screen space available.
|
||||
*/
|
||||
void onAnimationHasFinished();
|
||||
}
|
||||
}
|
||||
@@ -222,8 +222,9 @@ public class PictureInPictureGestureHelper extends GestureDetector.SimpleOnGestu
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(MotionEvent e) {
|
||||
child.performClick();
|
||||
isDragging = false;
|
||||
|
||||
child.performClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,24 @@
|
||||
package org.thoughtcrime.securesms.components.webrtc;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorInflater;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.Context;
|
||||
import android.graphics.ColorMatrix;
|
||||
import android.graphics.ColorMatrixColorFilter;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.view.animation.AlphaAnimation;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationSet;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.animation.ScaleAnimation;
|
||||
import android.view.animation.TranslateAnimation;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
@@ -95,11 +106,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private TextView participantCount;
|
||||
private Stub<FrameLayout> groupCallSpeakerHint;
|
||||
private Stub<View> groupCallFullStub;
|
||||
private View errorButton;
|
||||
private int pagerBottomMarginDp;
|
||||
private boolean controlsVisible = true;
|
||||
|
||||
private WebRtcCallParticipantsPagerAdapter pagerAdapter;
|
||||
private WebRtcCallParticipantsRecyclerAdapter recyclerAdapter;
|
||||
private PictureInPictureExpansionHelper pictureInPictureExpansionHelper;
|
||||
|
||||
|
||||
private final Set<View> incomingCallViews = new HashSet<>();
|
||||
private final Set<View> topViews = new HashSet<>();
|
||||
@@ -150,6 +164,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
callParticipantsRecycler = findViewById(R.id.call_screen_participants_recycler);
|
||||
toolbar = findViewById(R.id.call_screen_toolbar);
|
||||
startCall = findViewById(R.id.call_screen_start_call_start_call);
|
||||
errorButton = findViewById(R.id.call_screen_error_cancel);
|
||||
groupCallSpeakerHint = new Stub<>(findViewById(R.id.call_screen_group_call_speaker_hint));
|
||||
groupCallFullStub = new Stub<>(findViewById(R.id.group_call_call_full_view));
|
||||
|
||||
@@ -157,7 +172,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
View decline = findViewById(R.id.call_screen_decline_call);
|
||||
View answerLabel = findViewById(R.id.call_screen_answer_call_label);
|
||||
View declineLabel = findViewById(R.id.call_screen_decline_call_label);
|
||||
Guideline statusBarGuideline = findViewById(R.id.call_screen_status_bar_guideline);
|
||||
View cancelStartCall = findViewById(R.id.call_screen_start_call_cancel);
|
||||
|
||||
callParticipantsPager.setPageTransformer(new MarginPageTransformer(ViewUtil.dpToPx(4)));
|
||||
@@ -209,7 +223,14 @@ public class WebRtcCallView extends FrameLayout {
|
||||
answer.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallPressed));
|
||||
answerWithAudio.setOnClickListener(v -> runIfNonNull(controlsListener, ControlsListener::onAcceptCallWithVoiceOnlyPressed));
|
||||
|
||||
pictureInPictureGestureHelper = PictureInPictureGestureHelper.applyTo(smallLocalRenderFrame);
|
||||
pictureInPictureGestureHelper = PictureInPictureGestureHelper.applyTo(smallLocalRenderFrame);
|
||||
pictureInPictureExpansionHelper = new PictureInPictureExpansionHelper();
|
||||
|
||||
smallLocalRenderFrame.setOnClickListener(v -> {
|
||||
if (controlsListener != null) {
|
||||
controlsListener.onLocalPictureInPictureClicked();
|
||||
}
|
||||
});
|
||||
|
||||
startCall.setOnClickListener(v -> {
|
||||
if (controlsListener != null) {
|
||||
@@ -224,8 +245,11 @@ public class WebRtcCallView extends FrameLayout {
|
||||
largeLocalRenderNoVideoAvatar.setAlpha(0.6f);
|
||||
largeLocalRenderNoVideoAvatar.setColorFilter(new ColorMatrixColorFilter(greyScaleMatrix));
|
||||
|
||||
int statusBarHeight = ViewUtil.getStatusBarHeight(this);
|
||||
statusBarGuideline.setGuidelineBegin(statusBarHeight);
|
||||
errorButton.setOnClickListener(v -> {
|
||||
if (controlsListener != null) {
|
||||
controlsListener.onCancelStartCall();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -237,6 +261,26 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean fitSystemWindows(Rect insets) {
|
||||
Guideline statusBarGuideline = findViewById(R.id.call_screen_status_bar_guideline);
|
||||
Guideline navigationBarGuideline = findViewById(R.id.call_screen_navigation_bar_guideline);
|
||||
|
||||
statusBarGuideline.setGuidelineBegin(insets.top);
|
||||
navigationBarGuideline.setGuidelineEnd(insets.bottom);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowSystemUiVisibilityChanged(int visible) {
|
||||
if ((visible & SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0) {
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop());
|
||||
} else {
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
@@ -262,21 +306,21 @@ public class WebRtcCallView extends FrameLayout {
|
||||
pages.add(WebRtcCallParticipantsPage.forSingleParticipant(state.getFocusedParticipant(), state.isInPipMode()));
|
||||
}
|
||||
|
||||
if ((state.getGroupCallState().isNotIdle() && state.getRemoteDevicesCount() > 0) || state.getGroupCallState().isConnected()) {
|
||||
if ((state.getGroupCallState().isNotIdle() && state.getRemoteDevicesCount().orElse(0) > 0) || state.getGroupCallState().isConnected()) {
|
||||
recipientName.setText(state.getRemoteParticipantsDescription(getContext()));
|
||||
} else if (state.getGroupCallState().isNotIdle()) {
|
||||
recipientName.setText(getContext().getString(R.string.WebRtcCallView__s_group_call, Recipient.resolved(recipientId).getDisplayName(getContext())));
|
||||
}
|
||||
|
||||
if (state.getGroupCallState().isNotIdle() && participantCount != null) {
|
||||
boolean includeSelf = state.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
|
||||
|
||||
participantCount.setText(String.valueOf(state.getRemoteDevicesCount() + (includeSelf ? 1 : 0)));
|
||||
participantCount.setText(state.getParticipantCount()
|
||||
.mapToObj(String::valueOf).orElse("\u2014"));
|
||||
participantCount.setEnabled(state.getParticipantCount().isPresent());
|
||||
}
|
||||
|
||||
pagerAdapter.submitList(pages);
|
||||
recyclerAdapter.submitList(state.getListParticipants());
|
||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant());
|
||||
updateLocalCallParticipant(state.getLocalRenderState(), state.getLocalParticipant(), state.getFocusedParticipant());
|
||||
|
||||
if (state.isLargeVideoGroup() && !state.isInPipMode()) {
|
||||
layoutParticipantsForLargeCount();
|
||||
@@ -285,7 +329,7 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant) {
|
||||
public void updateLocalCallParticipant(@NonNull WebRtcLocalRenderState state, @NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
smallLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
largeLocalRender.setMirror(localCallParticipant.getCameraDirection() == CameraState.Direction.FRONT);
|
||||
|
||||
@@ -296,9 +340,18 @@ public class WebRtcCallView extends FrameLayout {
|
||||
largeLocalRender.init(localCallParticipant.getVideoSink().getEglBase());
|
||||
}
|
||||
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
smallLocalRender.setRenderInPip(true);
|
||||
videoToggle.setChecked(localCallParticipant.isVideoEnabled(), false);
|
||||
smallLocalRender.setRenderInPip(true);
|
||||
|
||||
if (state == WebRtcLocalRenderState.EXPANDED) {
|
||||
expandPip(localCallParticipant, focusedParticipant);
|
||||
return;
|
||||
} else if (state == WebRtcLocalRenderState.SMALL_RECTANGLE && pictureInPictureExpansionHelper.isExpandedOrExpanding()) {
|
||||
shrinkPip(localCallParticipant);
|
||||
return;
|
||||
} else {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
}
|
||||
|
||||
switch (state) {
|
||||
case GONE:
|
||||
@@ -425,6 +478,11 @@ public class WebRtcCallView extends FrameLayout {
|
||||
startCall.setEnabled(webRtcControls.isStartCallEnabled());
|
||||
}
|
||||
|
||||
if (webRtcControls.displayErrorControls()) {
|
||||
visibleViewSet.add(footerGradient);
|
||||
visibleViewSet.add(errorButton);
|
||||
}
|
||||
|
||||
if (webRtcControls.displayGroupCallFull()) {
|
||||
groupCallFullStub.get().setVisibility(View.VISIBLE);
|
||||
((TextView) groupCallFullStub.get().findViewById(R.id.group_call_call_full_message)).setText(webRtcControls.getGroupCallFullMessage(getContext()));
|
||||
@@ -501,6 +559,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
} else {
|
||||
cancelFadeOut();
|
||||
|
||||
if (controlsListener != null) {
|
||||
controlsListener.showSystemUI();
|
||||
}
|
||||
}
|
||||
|
||||
controls = webRtcControls;
|
||||
@@ -525,6 +587,54 @@ public class WebRtcCallView extends FrameLayout {
|
||||
}
|
||||
}
|
||||
|
||||
private void expandPip(@NonNull CallParticipant localCallParticipant, @NonNull CallParticipant focusedParticipant) {
|
||||
pictureInPictureExpansionHelper.expand(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||
@Override
|
||||
public void onAnimationWillStart() {
|
||||
largeLocalRender.attachBroadcastVideoSink(localCallParticipant.getVideoSink());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
largeLocalRenderFrame.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(focusedParticipant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationHasFinished() {
|
||||
pictureInPictureGestureHelper.adjustPip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void shrinkPip(@NonNull CallParticipant localCallParticipant) {
|
||||
pictureInPictureExpansionHelper.shrink(smallLocalRenderFrame, new PictureInPictureExpansionHelper.Callback() {
|
||||
@Override
|
||||
public void onAnimationWillStart() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureExpanded() {
|
||||
largeLocalRenderFrame.setVisibility(View.GONE);
|
||||
largeLocalRender.attachBroadcastVideoSink(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureInPictureNotVisible() {
|
||||
smallLocalRender.setCallParticipant(localCallParticipant);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationHasFinished() {
|
||||
pictureInPictureGestureHelper.adjustPip();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void animatePipToLargeRectangle() {
|
||||
ResizeAnimation animation = new ResizeAnimation(smallLocalRenderFrame, ViewUtil.dpToPx(90), ViewUtil.dpToPx(160));
|
||||
animation.setDuration(PIP_RESIZE_DURATION);
|
||||
@@ -567,12 +677,10 @@ public class WebRtcCallView extends FrameLayout {
|
||||
private void fadeOutControls() {
|
||||
fadeControls(ConstraintSet.GONE);
|
||||
controlsListener.onControlsFadeOut();
|
||||
pictureInPictureGestureHelper.clearVerticalBoundaries();
|
||||
}
|
||||
|
||||
private void fadeInControls() {
|
||||
fadeControls(ConstraintSet.VISIBLE);
|
||||
pictureInPictureGestureHelper.setVerticalBoundaries(toolbar.getBottom(), videoToggle.getTop());
|
||||
|
||||
scheduleFadeOut();
|
||||
}
|
||||
@@ -616,6 +724,15 @@ public class WebRtcCallView extends FrameLayout {
|
||||
.setDuration(TRANSITION_DURATION_MILLIS);
|
||||
|
||||
TransitionManager.endTransitions(parent);
|
||||
|
||||
if (controlsListener != null) {
|
||||
if (controlsVisible) {
|
||||
controlsListener.showSystemUI();
|
||||
} else {
|
||||
controlsListener.hideSystemUI();
|
||||
}
|
||||
}
|
||||
|
||||
TransitionManager.beginDelayedTransition(parent, transition);
|
||||
|
||||
ConstraintSet constraintSet = new ConstraintSet();
|
||||
@@ -700,6 +817,8 @@ public class WebRtcCallView extends FrameLayout {
|
||||
void onStartCall(boolean isVideoCall);
|
||||
void onCancelStartCall();
|
||||
void onControlsFadeOut();
|
||||
void showSystemUI();
|
||||
void hideSystemUI();
|
||||
void onAudioOutputChanged(@NonNull WebRtcAudioOutput audioOutput);
|
||||
void onVideoChanged(boolean isVideoEnabled);
|
||||
void onMicChanged(boolean isMicEnabled);
|
||||
@@ -710,5 +829,6 @@ public class WebRtcCallView extends FrameLayout {
|
||||
void onAcceptCallPressed();
|
||||
void onShowParticipantsList();
|
||||
void onPageChanged(@NonNull CallParticipantsState.SelectedPage page);
|
||||
void onLocalPictureInPictureClicked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
private boolean answerWithVideoAvailable = false;
|
||||
private Runnable elapsedTimeRunnable = this::handleTick;
|
||||
private boolean canEnterPipMode = false;
|
||||
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
|
||||
private boolean callingStarted = false;
|
||||
private List<CallParticipant> previousParticipantsList = Collections.emptyList();
|
||||
private boolean callStarting = false;
|
||||
|
||||
private final WebRtcCallRepository repository = new WebRtcCallRepository(ApplicationDependencies.getApplication());
|
||||
|
||||
@@ -113,8 +113,8 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
return answerWithVideoAvailable;
|
||||
}
|
||||
|
||||
public boolean isCallingStarted() {
|
||||
return callingStarted;
|
||||
public boolean isCallStarting() {
|
||||
return callStarting;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
@@ -135,13 +135,26 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
participantsState.setValue(CallParticipantsState.update(participantsState.getValue(), page));
|
||||
}
|
||||
|
||||
public void onLocalPictureInPictureClicked() {
|
||||
CallParticipantsState state = participantsState.getValue();
|
||||
if (state.getGroupCallState() != WebRtcViewModel.GroupCallState.IDLE) {
|
||||
return;
|
||||
}
|
||||
|
||||
participantsState.setValue(CallParticipantsState.setExpanded(participantsState.getValue(),
|
||||
state.getLocalRenderState() != WebRtcLocalRenderState.EXPANDED));
|
||||
}
|
||||
|
||||
public void onDismissedVideoTooltip() {
|
||||
canDisplayTooltipIfNeeded = false;
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public void updateFromWebRtcViewModel(@NonNull WebRtcViewModel webRtcViewModel, boolean enableVideo) {
|
||||
canEnterPipMode = webRtcViewModel.getState() != WebRtcViewModel.State.CALL_PRE_JOIN;
|
||||
canEnterPipMode = !webRtcViewModel.getState().isPreJoinOrNetworkUnavailable();
|
||||
if (callStarting && webRtcViewModel.getState().isPassedPreJoin()) {
|
||||
callStarting = false;
|
||||
}
|
||||
|
||||
CallParticipant localParticipant = webRtcViewModel.getLocalParticipant();
|
||||
|
||||
@@ -170,7 +183,7 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
webRtcViewModel.isBluetoothAvailable(),
|
||||
Util.hasItems(webRtcViewModel.getRemoteParticipants()),
|
||||
repository.getAudioOutput(),
|
||||
webRtcViewModel.getRemoteDevicesCount(),
|
||||
webRtcViewModel.getRemoteDevicesCount().orElse(0),
|
||||
webRtcViewModel.getParticipantLimit());
|
||||
|
||||
if (webRtcViewModel.getState() == WebRtcViewModel.State.CALL_CONNECTED && callConnectedTime == -1) {
|
||||
@@ -232,6 +245,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
case CALL_DISCONNECTED:
|
||||
callState = WebRtcControls.CallState.ENDING;
|
||||
break;
|
||||
case NETWORK_FAILURE:
|
||||
callState = WebRtcControls.CallState.ERROR;
|
||||
break;
|
||||
default:
|
||||
callState = WebRtcControls.CallState.ONGOING;
|
||||
}
|
||||
@@ -274,9 +290,9 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
private boolean shouldShowSpeakerHint(@NonNull CallParticipantsState state) {
|
||||
return !state.isInPipMode() &&
|
||||
state.getRemoteDevicesCount() > 1 &&
|
||||
state.getGroupCallState().isConnected() &&
|
||||
return !state.isInPipMode() &&
|
||||
state.getRemoteDevicesCount().orElse(0) > 1 &&
|
||||
state.getGroupCallState().isConnected() &&
|
||||
!SignalStore.tooltips().hasSeenGroupCallSpeakerView();
|
||||
}
|
||||
|
||||
@@ -309,7 +325,7 @@ public class WebRtcCallViewModel extends ViewModel {
|
||||
}
|
||||
|
||||
public void startCall(boolean isVideoCall) {
|
||||
callingStarted = true;
|
||||
callStarting = true;
|
||||
Recipient recipient = getRecipient().get();
|
||||
if (recipient.isGroup()) {
|
||||
repository.getIdentityRecords(recipient, identityRecords -> {
|
||||
|
||||
@@ -51,6 +51,10 @@ public final class WebRtcControls {
|
||||
this.participantLimit = participantLimit;
|
||||
}
|
||||
|
||||
boolean displayErrorControls() {
|
||||
return isError();
|
||||
}
|
||||
|
||||
boolean displayStartCallControls() {
|
||||
return isPreJoin();
|
||||
}
|
||||
@@ -145,6 +149,10 @@ public final class WebRtcControls {
|
||||
return audioOutput;
|
||||
}
|
||||
|
||||
private boolean isError() {
|
||||
return callState == CallState.ERROR;
|
||||
}
|
||||
|
||||
private boolean isPreJoin() {
|
||||
return callState == CallState.PRE_JOIN;
|
||||
}
|
||||
@@ -167,6 +175,7 @@ public final class WebRtcControls {
|
||||
|
||||
public enum CallState {
|
||||
NONE,
|
||||
ERROR,
|
||||
PRE_JOIN,
|
||||
INCOMING,
|
||||
OUTGOING,
|
||||
|
||||
@@ -5,5 +5,6 @@ public enum WebRtcLocalRenderState {
|
||||
SMALL_RECTANGLE,
|
||||
SMALLER_RECTANGLE,
|
||||
LARGE,
|
||||
LARGE_NO_VIDEO
|
||||
LARGE_NO_VIDEO,
|
||||
EXPANDED
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ public final class CallParticipantViewState extends RecipientMappingModel<CallPa
|
||||
|
||||
@Override
|
||||
public @NonNull String getName(@NonNull Context context) {
|
||||
return callParticipant.getRecipient().isSelf() ? context.getString(R.string.GroupMembersDialog_you)
|
||||
: super.getName(context);
|
||||
return callParticipant.getRecipientDisplayName(context);
|
||||
}
|
||||
|
||||
public int getVideoMutedVisibility() {
|
||||
@@ -36,4 +35,16 @@ public final class CallParticipantViewState extends RecipientMappingModel<CallPa
|
||||
public int getAudioMutedVisibility() {
|
||||
return callParticipant.isMicrophoneEnabled() ? View.GONE : View.VISIBLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(@NonNull CallParticipantViewState newItem) {
|
||||
return callParticipant.getCallParticipantId().equals(newItem.callParticipant.getCallParticipantId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(@NonNull CallParticipantViewState newItem) {
|
||||
return super.areContentsTheSame(newItem) &&
|
||||
callParticipant.isVideoEnabled() == newItem.callParticipant.isVideoEnabled() &&
|
||||
callParticipant.isMicrophoneEnabled() == newItem.callParticipant.isMicrophoneEnabled();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.annimon.stream.OptionalLong;
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
@@ -88,19 +89,21 @@ public class CallParticipantsListDialog extends BottomSheetDialogFragment {
|
||||
private void updateList(@NonNull CallParticipantsState callParticipantsState) {
|
||||
List<MappingModel<?>> items = new ArrayList<>();
|
||||
|
||||
boolean includeSelf = callParticipantsState.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
|
||||
boolean includeSelf = callParticipantsState.getGroupCallState() == WebRtcViewModel.GroupCallState.CONNECTED_AND_JOINED;
|
||||
OptionalLong headerCount = callParticipantsState.getParticipantCount();
|
||||
|
||||
items.add(new CallParticipantsListHeader((int) callParticipantsState.getRemoteDevicesCount() + (includeSelf ? 1 : 0)));
|
||||
headerCount.executeIfPresent(count -> {
|
||||
items.add(new CallParticipantsListHeader((int) count));
|
||||
|
||||
if (includeSelf) {
|
||||
items.add(new CallParticipantViewState(callParticipantsState.getLocalParticipant()));
|
||||
}
|
||||
if (includeSelf) {
|
||||
items.add(new CallParticipantViewState(callParticipantsState.getLocalParticipant()));
|
||||
}
|
||||
|
||||
for (CallParticipant callParticipant : callParticipantsState.getAllRemoteParticipants()) {
|
||||
items.add(new CallParticipantViewState(callParticipant));
|
||||
}
|
||||
for (CallParticipant callParticipant : callParticipantsState.getAllRemoteParticipants()) {
|
||||
items.add(new CallParticipantViewState(callParticipant));
|
||||
}
|
||||
});
|
||||
|
||||
adapter.submitList(items);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.registration.RegistrationUtil;
|
||||
import org.thoughtcrime.securesms.sms.IncomingJoinedMessage;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.ProfileUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
@@ -73,7 +72,6 @@ import java.util.concurrent.TimeoutException;
|
||||
/**
|
||||
* Manages all the stuff around determining if a user is registered or not.
|
||||
*/
|
||||
@Trace
|
||||
public class DirectoryHelper {
|
||||
|
||||
private static final String TAG = Log.tag(DirectoryHelper.class);
|
||||
@@ -254,6 +252,8 @@ public class DirectoryHelper {
|
||||
|
||||
stopwatch.split("handle-unlisted");
|
||||
|
||||
Set<RecipientId> preExistingRegisteredUsers = new HashSet<>(recipientDatabase.getRegistered());
|
||||
|
||||
recipientDatabase.bulkUpdatedRegisteredStatus(uuidMap, inactiveIds);
|
||||
|
||||
stopwatch.split("update-registered");
|
||||
@@ -267,14 +267,13 @@ public class DirectoryHelper {
|
||||
}
|
||||
|
||||
if (TextSecurePreferences.hasSuccessfullyRetrievedDirectory(context) && notifyOfNewUsers) {
|
||||
Set<RecipientId> existingSignalIds = new HashSet<>(recipientDatabase.getRegistered());
|
||||
Set<RecipientId> existingSystemIds = new HashSet<>(recipientDatabase.getSystemContacts());
|
||||
Set<RecipientId> newlyActiveIds = new HashSet<>(activeIds);
|
||||
Set<RecipientId> systemContacts = new HashSet<>(recipientDatabase.getSystemContacts());
|
||||
Set<RecipientId> newlyRegisteredSystemContacts = new HashSet<>(activeIds);
|
||||
|
||||
newlyActiveIds.removeAll(existingSignalIds);
|
||||
newlyActiveIds.retainAll(existingSystemIds);
|
||||
newlyRegisteredSystemContacts.removeAll(preExistingRegisteredUsers);
|
||||
newlyRegisteredSystemContacts.retainAll(systemContacts);
|
||||
|
||||
notifyNewUsers(context, newlyActiveIds);
|
||||
notifyNewUsers(context, newlyRegisteredSystemContacts);
|
||||
} else {
|
||||
TextSecurePreferences.setHasSuccessfullyRetrievedDirectory(context, true);
|
||||
}
|
||||
@@ -403,8 +402,11 @@ public class DirectoryHelper {
|
||||
|
||||
for (RecipientId newUser: newUsers) {
|
||||
Recipient recipient = Recipient.resolved(newUser);
|
||||
if (!SessionUtil.hasSession(context, recipient.getId()) && !recipient.isSelf()) {
|
||||
IncomingJoinedMessage message = new IncomingJoinedMessage(newUser);
|
||||
if (!SessionUtil.hasSession(context, recipient.getId()) &&
|
||||
!recipient.isSelf() &&
|
||||
recipient.hasAUserSetDisplayName(context))
|
||||
{
|
||||
IncomingJoinedMessage message = new IncomingJoinedMessage(recipient.getId());
|
||||
Optional<InsertResult> insertResult = DatabaseFactory.getSmsDatabase(context).insertMessageInbox(message);
|
||||
|
||||
if (insertResult.isPresent()) {
|
||||
|
||||
@@ -243,7 +243,6 @@ import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.stickers.StickerManagementActivity;
|
||||
import org.thoughtcrime.securesms.stickers.StickerPackInstallEvent;
|
||||
import org.thoughtcrime.securesms.stickers.StickerSearchRepository;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.BitmapUtil;
|
||||
@@ -302,7 +301,6 @@ import static org.whispersystems.libsignal.SessionCipher.SESSION_LOCK;
|
||||
* @author Moxie Marlinspike
|
||||
*
|
||||
*/
|
||||
@Trace
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationActivity extends PassphraseRequiredActivity
|
||||
implements ConversationFragment.ConversationFragmentListener,
|
||||
@@ -409,7 +407,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
if (ConversationIntents.isInvalid(getIntent())) {
|
||||
Log.w(TAG, "[onCreate] Missing recipientId!");
|
||||
// TODO [greyson] Navigation
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
startActivity(MainActivity.clearTop(this));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@@ -488,7 +486,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
if (ConversationIntents.isInvalid(intent)) {
|
||||
Log.w(TAG, "[onNewIntent] Missing recipientId!");
|
||||
// TODO [greyson] Navigation
|
||||
startActivity(new Intent(this, MainActivity.class));
|
||||
startActivity(MainActivity.clearTop(this));
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
@@ -538,6 +536,10 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
.startChain(new RequestGroupV2InfoJob(groupId))
|
||||
.then(new GroupV2UpdateSelfProfileKeyJob(groupId))
|
||||
.enqueue();
|
||||
|
||||
if (viewModel.getArgs().isFirstTimeInSelfCreatedGroup()) {
|
||||
groupViewModel.inviteFriendsOneTimeIfJustSelfInGroup(getSupportFragmentManager(), groupId);
|
||||
}
|
||||
}
|
||||
|
||||
if (groupCallViewModel != null) {
|
||||
@@ -1087,6 +1089,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return;
|
||||
}
|
||||
|
||||
final long thread = this.threadId;
|
||||
|
||||
ExpirationDialog.show(this, recipient.get().getExpireMessages(),
|
||||
expirationTime ->
|
||||
SimpleTask.run(
|
||||
@@ -1102,7 +1106,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
} else {
|
||||
DatabaseFactory.getRecipientDatabase(ConversationActivity.this).setExpireMessages(recipient.getId(), expirationTime);
|
||||
OutgoingExpirationUpdateMessage outgoingMessage = new OutgoingExpirationUpdateMessage(getRecipient(), System.currentTimeMillis(), expirationTime * 1000L);
|
||||
MessageSender.send(ConversationActivity.this, outgoingMessage, threadId, false, null);
|
||||
MessageSender.send(ConversationActivity.this, outgoingMessage, thread, false, null);
|
||||
}
|
||||
return GroupChangeResult.SUCCESS;
|
||||
},
|
||||
@@ -1882,31 +1886,31 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
private void initializeViews() {
|
||||
titleView = findViewById(R.id.conversation_title_view);
|
||||
buttonToggle = ViewUtil.findById(this, R.id.button_toggle);
|
||||
sendButton = ViewUtil.findById(this, R.id.send_button);
|
||||
attachButton = ViewUtil.findById(this, R.id.attach_button);
|
||||
composeText = ViewUtil.findById(this, R.id.embedded_text_editor);
|
||||
charactersLeft = ViewUtil.findById(this, R.id.space_left);
|
||||
buttonToggle = findViewById(R.id.button_toggle);
|
||||
sendButton = findViewById(R.id.send_button);
|
||||
attachButton = findViewById(R.id.attach_button);
|
||||
composeText = findViewById(R.id.embedded_text_editor);
|
||||
charactersLeft = findViewById(R.id.space_left);
|
||||
emojiDrawerStub = ViewUtil.findStubById(this, R.id.emoji_drawer_stub);
|
||||
attachmentKeyboardStub = ViewUtil.findStubById(this, R.id.attachment_keyboard_stub);
|
||||
unblockButton = ViewUtil.findById(this, R.id.unblock_button);
|
||||
makeDefaultSmsButton = ViewUtil.findById(this, R.id.make_default_sms_button);
|
||||
registerButton = ViewUtil.findById(this, R.id.register_button);
|
||||
container = ViewUtil.findById(this, R.id.layout_container);
|
||||
unblockButton = findViewById(R.id.unblock_button);
|
||||
makeDefaultSmsButton = findViewById(R.id.make_default_sms_button);
|
||||
registerButton = findViewById(R.id.register_button);
|
||||
container = findViewById(R.id.layout_container);
|
||||
reminderView = ViewUtil.findStubById(this, R.id.reminder_stub);
|
||||
unverifiedBannerView = ViewUtil.findStubById(this, R.id.unverified_banner_stub);
|
||||
reviewBanner = ViewUtil.findStubById(this, R.id.review_banner_stub);
|
||||
quickAttachmentToggle = ViewUtil.findById(this, R.id.quick_attachment_toggle);
|
||||
inlineAttachmentToggle = ViewUtil.findById(this, R.id.inline_attachment_container);
|
||||
inputPanel = ViewUtil.findById(this, R.id.bottom_panel);
|
||||
panelParent = ViewUtil.findById(this, R.id.conversation_activity_panel_parent);
|
||||
searchNav = ViewUtil.findById(this, R.id.conversation_search_nav);
|
||||
messageRequestBottomView = ViewUtil.findById(this, R.id.conversation_activity_message_request_bottom_bar);
|
||||
reactionOverlay = ViewUtil.findById(this, R.id.conversation_reaction_scrubber);
|
||||
quickAttachmentToggle = findViewById(R.id.quick_attachment_toggle);
|
||||
inlineAttachmentToggle = findViewById(R.id.inline_attachment_container);
|
||||
inputPanel = findViewById(R.id.bottom_panel);
|
||||
panelParent = findViewById(R.id.conversation_activity_panel_parent);
|
||||
searchNav = findViewById(R.id.conversation_search_nav);
|
||||
messageRequestBottomView = findViewById(R.id.conversation_activity_message_request_bottom_bar);
|
||||
reactionOverlay = findViewById(R.id.conversation_reaction_scrubber);
|
||||
mentionsSuggestions = ViewUtil.findStubById(this, R.id.conversation_mention_suggestions_stub);
|
||||
|
||||
ImageButton quickCameraToggle = ViewUtil.findById(this, R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = ViewUtil.findById(this, R.id.inline_attachment_button);
|
||||
ImageButton quickCameraToggle = findViewById(R.id.quick_camera_toggle);
|
||||
ImageButton inlineAttachmentButton = findViewById(R.id.inline_attachment_button);
|
||||
|
||||
noLongerMemberBanner = findViewById(R.id.conversation_no_longer_member_banner);
|
||||
requestingMemberBanner = findViewById(R.id.conversation_requesting_banner);
|
||||
@@ -1982,7 +1986,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
if (isInBubble()) {
|
||||
supportActionBar.setHomeAsUpIndicator(ContextCompat.getDrawable(this, R.drawable.ic_notification));
|
||||
toolbar.setNavigationOnClickListener(unused -> startActivity(new Intent(Intent.ACTION_MAIN).setClass(this, MainActivity.class)));
|
||||
toolbar.setNavigationOnClickListener(unused -> startActivity(MainActivity.clearTop(this)));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2675,13 +2679,14 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
}
|
||||
|
||||
private void sendMediaMessage(@NonNull MediaSendActivityResult result) {
|
||||
long thread = this.threadId;
|
||||
long expiresIn = recipient.get().getExpireMessages() * 1000L;
|
||||
QuoteModel quote = result.isViewOnce() ? null : inputPanel.getQuote().orNull();
|
||||
List<Mention> mentions = new ArrayList<>(result.getMentions());
|
||||
OutgoingMediaMessage message = new OutgoingMediaMessage(recipient.get(), new SlideDeck(), result.getBody(), System.currentTimeMillis(), -1, expiresIn, result.isViewOnce(), distributionType, quote, Collections.emptyList(), Collections.emptyList(), mentions);
|
||||
OutgoingMediaMessage secureMessage = new OutgoingSecureMediaMessage(message);
|
||||
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId);
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread);
|
||||
|
||||
inputPanel.clearQuote();
|
||||
attachmentManager.clear(glideRequests, false);
|
||||
@@ -2690,7 +2695,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
long id = fragment.stageOutgoingMessage(message);
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
long resultId = MessageSender.sendPushWithPreUploadedMedia(this, secureMessage, result.getPreUploadResults(), threadId, () -> fragment.releaseOutgoingMessage(id));
|
||||
long resultId = MessageSender.sendPushWithPreUploadedMedia(this, secureMessage, result.getPreUploadResults(), thread, () -> fragment.releaseOutgoingMessage(id));
|
||||
|
||||
int deleted = DatabaseFactory.getAttachmentDatabase(this).deleteAbandonedPreuploadedAttachments();
|
||||
Log.i(TAG, "Deleted " + deleted + " abandoned attachments.");
|
||||
@@ -2735,6 +2740,8 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return new SettableFuture<>(null);
|
||||
}
|
||||
|
||||
final long thread = this.threadId;
|
||||
|
||||
if (isSecureText && !forceSms) {
|
||||
MessageUtil.SplitResult splitMessage = MessageUtil.getSplitMessage(this, body, sendButton.getSelectedTransport().calculateCharacters(body).maxPrimaryMessageSize);
|
||||
body = splitMessage.getBody();
|
||||
@@ -2753,7 +2760,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
if (isSecureText && !forceSms) {
|
||||
outgoingMessage = new OutgoingSecureMediaMessage(outgoingMessageCandidate);
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId);
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread);
|
||||
} else {
|
||||
outgoingMessage = outgoingMessageCandidate;
|
||||
}
|
||||
@@ -2772,7 +2779,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
final long id = fragment.stageOutgoingMessage(outgoingMessage);
|
||||
|
||||
SimpleTask.run(() -> {
|
||||
return MessageSender.send(context, outgoingMessage, threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
return MessageSender.send(context, outgoingMessage, thread, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
}, result -> {
|
||||
sendComplete(result);
|
||||
future.set(null);
|
||||
@@ -2792,6 +2799,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
return;
|
||||
}
|
||||
|
||||
final long thread = this.threadId;
|
||||
final Context context = getApplicationContext();
|
||||
final String messageBody = getMessage();
|
||||
|
||||
@@ -2799,7 +2807,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
|
||||
if (isSecureText && !forceSms) {
|
||||
message = new OutgoingEncryptedMessage(recipient.get(), messageBody, expiresIn);
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(threadId);
|
||||
ApplicationDependencies.getTypingStatusSender().onTypingStopped(thread);
|
||||
} else {
|
||||
message = new OutgoingTextMessage(recipient.get(), messageBody, expiresIn, subscriptionId);
|
||||
}
|
||||
@@ -2815,7 +2823,7 @@ public class ConversationActivity extends PassphraseRequiredActivity
|
||||
new AsyncTask<OutgoingTextMessage, Void, Long>() {
|
||||
@Override
|
||||
protected Long doInBackground(OutgoingTextMessage... messages) {
|
||||
return MessageSender.send(context, messages[0], threadId, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
return MessageSender.send(context, messages[0], thread, forceSms, () -> fragment.releaseOutgoingMessage(id));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -14,7 +14,6 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -27,7 +26,6 @@ import java.util.Map;
|
||||
/**
|
||||
* Core data source for loading an individual conversation.
|
||||
*/
|
||||
@Trace
|
||||
class ConversationDataSource implements PagedDataSource<ConversationMessage> {
|
||||
|
||||
private static final String TAG = Log.tag(ConversationDataSource.class);
|
||||
|
||||
@@ -66,9 +66,11 @@ import org.signal.core.util.StreamUtil;
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.ApplicationContext;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.VerifyIdentityActivity;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.components.ConversationScrollToView;
|
||||
import org.thoughtcrime.securesms.components.ConversationTypingView;
|
||||
@@ -93,6 +95,7 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.groups.ui.migration.GroupsV1MigrationInfoBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob;
|
||||
import org.thoughtcrime.securesms.jobs.MultiDeviceViewOnceOpenJob;
|
||||
@@ -121,7 +124,6 @@ import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.stickers.StickerPackPreviewActivity;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CachedInflater;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.HtmlUtil;
|
||||
@@ -147,9 +149,9 @@ import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Trace
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
public class ConversationFragment extends LoggingFragment {
|
||||
private static final String TAG = ConversationFragment.class.getSimpleName();
|
||||
@@ -1416,10 +1418,63 @@ public class ConversationFragment extends LoggingFragment {
|
||||
GroupsV1MigrationInfoBottomSheetDialogFragment.show(requireFragmentManager(), membershipChange);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDecryptionFailedLearnMoreClicked() {
|
||||
new AlertDialog.Builder(requireContext())
|
||||
.setView(R.layout.decryption_failed_dialog)
|
||||
.setPositiveButton(android.R.string.ok, (d, w) -> {
|
||||
d.dismiss();
|
||||
})
|
||||
.setNeutralButton(R.string.ConversationFragment_contact_us, (d, w) -> {
|
||||
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
|
||||
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_HELP_FRAGMENT, true);
|
||||
|
||||
startActivity(intent);
|
||||
d.dismiss();
|
||||
})
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSafetyNumberLearnMoreClicked(@NonNull Recipient recipient) {
|
||||
if (recipient.isGroup()) {
|
||||
throw new AssertionError("Must be individual");
|
||||
}
|
||||
|
||||
AlertDialog dialog = new AlertDialog.Builder(requireContext())
|
||||
.setView(R.layout.safety_number_changed_learn_more_dialog)
|
||||
.setPositiveButton(R.string.ConversationFragment_verify, (d, w) -> {
|
||||
SimpleTask.run(getLifecycle(), () -> {
|
||||
return DatabaseFactory.getIdentityDatabase(requireContext()).getIdentity(recipient.getId());
|
||||
}, identityRecord -> {
|
||||
if (identityRecord.isPresent()) {
|
||||
startActivity(VerifyIdentityActivity.newIntent(requireContext(), identityRecord.get()));
|
||||
}});
|
||||
d.dismiss();
|
||||
})
|
||||
.setNegativeButton(R.string.ConversationFragment_not_now, (d, w) -> {
|
||||
d.dismiss();
|
||||
})
|
||||
.create();
|
||||
dialog.setOnShowListener(d -> {
|
||||
TextView title = Objects.requireNonNull(dialog.findViewById(R.id.safety_number_learn_more_title));
|
||||
TextView body = Objects.requireNonNull(dialog.findViewById(R.id.safety_number_learn_more_body));
|
||||
|
||||
title.setText(getString(R.string.ConversationFragment_your_safety_number_with_s_changed, recipient.getDisplayName(requireContext())));
|
||||
body.setText(getString(R.string.ConversationFragment_your_safety_number_with_s_changed_likey_because_they_reinstalled_signal, recipient.getDisplayName(requireContext())));
|
||||
});
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
@Override
|
||||
public void onJoinGroupCallClicked() {
|
||||
CommunicationActions.startVideoCall(requireActivity(), recipient.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInviteFriendsToGroupClicked(@NonNull GroupId.V2 groupId) {
|
||||
GroupLinkInviteFriendsBottomSheetDialogFragment.show(requireActivity().getSupportFragmentManager(), groupId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
@@ -25,12 +26,14 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupManager;
|
||||
import org.thoughtcrime.securesms.groups.GroupsV1MigrationUtil;
|
||||
import org.thoughtcrime.securesms.groups.ui.GroupChangeFailureReason;
|
||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.invite.GroupLinkInviteFriendsBottomSheetDialogFragment;
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewRecipient;
|
||||
import org.thoughtcrime.securesms.profiles.spoofing.ReviewUtil;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.util.AsynchronousCallback;
|
||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -50,6 +53,8 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||
private final LiveData<List<RecipientId>> gv1MigrationSuggestions;
|
||||
private final LiveData<Boolean> gv1MigrationReminder;
|
||||
|
||||
private boolean firstTimeInviteFriendsTriggered;
|
||||
|
||||
private ConversationGroupViewModel() {
|
||||
this.liveRecipient = new MutableLiveData<>();
|
||||
|
||||
@@ -225,6 +230,28 @@ final class ConversationGroupViewModel extends ViewModel {
|
||||
});
|
||||
}
|
||||
|
||||
void inviteFriendsOneTimeIfJustSelfInGroup(@NonNull FragmentManager supportFragmentManager, @NonNull GroupId.V2 groupId) {
|
||||
if (firstTimeInviteFriendsTriggered) {
|
||||
return;
|
||||
}
|
||||
|
||||
firstTimeInviteFriendsTriggered = true;
|
||||
|
||||
SimpleTask.run(() -> DatabaseFactory.getGroupDatabase(ApplicationDependencies.getApplication())
|
||||
.requireGroup(groupId)
|
||||
.getMembers().equals(Collections.singletonList(Recipient.self().getId())),
|
||||
justSelf -> {
|
||||
if (justSelf) {
|
||||
inviteFriends(supportFragmentManager, groupId);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
void inviteFriends(@NonNull FragmentManager supportFragmentManager, @NonNull GroupId.V2 groupId) {
|
||||
GroupLinkInviteFriendsBottomSheetDialogFragment.show(supportFragmentManager, groupId);
|
||||
}
|
||||
|
||||
static final class ReviewState {
|
||||
|
||||
private static final ReviewState EMPTY = new ReviewState(null, Recipient.UNKNOWN, 0);
|
||||
|
||||
@@ -18,15 +18,16 @@ import java.util.Objects;
|
||||
|
||||
public class ConversationIntents {
|
||||
|
||||
private static final String BUBBLE_AUTHORITY = "bubble";
|
||||
private static final String EXTRA_RECIPIENT = "recipient_id";
|
||||
private static final String EXTRA_THREAD_ID = "thread_id";
|
||||
private static final String EXTRA_TEXT = "draft_text";
|
||||
private static final String EXTRA_MEDIA = "media_list";
|
||||
private static final String EXTRA_STICKER = "sticker_extra";
|
||||
private static final String EXTRA_BORDERLESS = "borderless_extra";
|
||||
private static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
|
||||
private static final String EXTRA_STARTING_POSITION = "starting_position";
|
||||
private static final String BUBBLE_AUTHORITY = "bubble";
|
||||
private static final String EXTRA_RECIPIENT = "recipient_id";
|
||||
private static final String EXTRA_THREAD_ID = "thread_id";
|
||||
private static final String EXTRA_TEXT = "draft_text";
|
||||
private static final String EXTRA_MEDIA = "media_list";
|
||||
private static final String EXTRA_STICKER = "sticker_extra";
|
||||
private static final String EXTRA_BORDERLESS = "borderless_extra";
|
||||
private static final String EXTRA_DISTRIBUTION_TYPE = "distribution_type";
|
||||
private static final String EXTRA_STARTING_POSITION = "starting_position";
|
||||
private static final String EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP = "first_time_in_group";
|
||||
|
||||
private ConversationIntents() {
|
||||
}
|
||||
@@ -63,7 +64,8 @@ public class ConversationIntents {
|
||||
private final StickerLocator stickerLocator;
|
||||
private final boolean isBorderless;
|
||||
private final int distributionType;
|
||||
private final int startingPosition;
|
||||
private final int startingPosition;
|
||||
private final boolean firstTimeInSelfCreatedGroup;
|
||||
|
||||
static Args from(@NonNull Intent intent) {
|
||||
if (isBubbleIntent(intent)) {
|
||||
@@ -74,7 +76,8 @@ public class ConversationIntents {
|
||||
null,
|
||||
false,
|
||||
ThreadDatabase.DistributionTypes.DEFAULT,
|
||||
-1);
|
||||
-1,
|
||||
false);
|
||||
}
|
||||
|
||||
return new Args(RecipientId.from(Objects.requireNonNull(intent.getStringExtra(EXTRA_RECIPIENT))),
|
||||
@@ -84,7 +87,8 @@ public class ConversationIntents {
|
||||
intent.getParcelableExtra(EXTRA_STICKER),
|
||||
intent.getBooleanExtra(EXTRA_BORDERLESS, false),
|
||||
intent.getIntExtra(EXTRA_DISTRIBUTION_TYPE, ThreadDatabase.DistributionTypes.DEFAULT),
|
||||
intent.getIntExtra(EXTRA_STARTING_POSITION, -1));
|
||||
intent.getIntExtra(EXTRA_STARTING_POSITION, -1),
|
||||
intent.getBooleanExtra(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, false));
|
||||
}
|
||||
|
||||
private Args(@NonNull RecipientId recipientId,
|
||||
@@ -94,16 +98,18 @@ public class ConversationIntents {
|
||||
@Nullable StickerLocator stickerLocator,
|
||||
boolean isBorderless,
|
||||
int distributionType,
|
||||
int startingPosition)
|
||||
int startingPosition,
|
||||
boolean firstTimeInSelfCreatedGroup)
|
||||
{
|
||||
this.recipientId = recipientId;
|
||||
this.threadId = threadId;
|
||||
this.draftText = draftText;
|
||||
this.media = media;
|
||||
this.stickerLocator = stickerLocator;
|
||||
this.isBorderless = isBorderless;
|
||||
this.distributionType = distributionType;
|
||||
this.startingPosition = startingPosition;
|
||||
this.recipientId = recipientId;
|
||||
this.threadId = threadId;
|
||||
this.draftText = draftText;
|
||||
this.media = media;
|
||||
this.stickerLocator = stickerLocator;
|
||||
this.isBorderless = isBorderless;
|
||||
this.distributionType = distributionType;
|
||||
this.startingPosition = startingPosition;
|
||||
this.firstTimeInSelfCreatedGroup = firstTimeInSelfCreatedGroup;
|
||||
}
|
||||
|
||||
public @NonNull RecipientId getRecipientId() {
|
||||
@@ -137,6 +143,10 @@ public class ConversationIntents {
|
||||
public boolean isBorderless() {
|
||||
return isBorderless;
|
||||
}
|
||||
|
||||
public boolean isFirstTimeInSelfCreatedGroup() {
|
||||
return firstTimeInSelfCreatedGroup;
|
||||
}
|
||||
}
|
||||
|
||||
public final static class Builder {
|
||||
@@ -153,6 +163,7 @@ public class ConversationIntents {
|
||||
private int startingPosition = -1;
|
||||
private Uri dataUri;
|
||||
private String dataType;
|
||||
private boolean firstTimeInSelfCreatedGroup;
|
||||
|
||||
private Builder(@NonNull Context context,
|
||||
@NonNull RecipientId recipientId,
|
||||
@@ -212,6 +223,11 @@ public class ConversationIntents {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder firstTimeInSelfCreatedGroup() {
|
||||
this.firstTimeInSelfCreatedGroup = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public @NonNull Intent build() {
|
||||
if (stickerLocator != null && media != null) {
|
||||
throw new IllegalStateException("Cannot have both sticker and media array");
|
||||
@@ -235,6 +251,7 @@ public class ConversationIntents {
|
||||
intent.putExtra(EXTRA_DISTRIBUTION_TYPE, distributionType);
|
||||
intent.putExtra(EXTRA_STARTING_POSITION, startingPosition);
|
||||
intent.putExtra(EXTRA_BORDERLESS, isBorderless);
|
||||
intent.putExtra(EXTRA_FIRST_TIME_IN_SELF_CREATED_GROUP, firstTimeInSelfCreatedGroup);
|
||||
|
||||
if (draftText != null) {
|
||||
intent.putExtra(EXTRA_TEXT, draftText);
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.conversation;
|
||||
import android.app.Application;
|
||||
import android.database.ContentObserver;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -35,7 +36,7 @@ class ConversationStickerViewModel extends ViewModel {
|
||||
this.stickers = new MutableLiveData<>();
|
||||
this.stickersAvailable = new MutableLiveData<>();
|
||||
this.availabilityThrottler = new Throttler(500);
|
||||
this.packObserver = new ContentObserver(new Handler()) {
|
||||
this.packObserver = new ContentObserver(new Handler(Looper.getMainLooper())) {
|
||||
@Override
|
||||
public void onChange(boolean selfChange) {
|
||||
availabilityThrottler.publish(() -> repository.getStickerFeatureAvailability(stickersAvailable::postValue));
|
||||
|
||||
@@ -206,16 +206,36 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
eventListener.onGroupMigrationLearnMoreClicked(conversationMessage.getMessageRecord().getGroupV1MigrationMembershipChanges());
|
||||
}
|
||||
});
|
||||
} else if (conversationMessage.getMessageRecord().isFailedDecryptionType() &&
|
||||
(!nextMessageRecord.isPresent() || !nextMessageRecord.get().isFailedDecryptionType()))
|
||||
{
|
||||
actionButton.setText(R.string.ConversationUpdateItem_learn_more);
|
||||
actionButton.setVisibility(VISIBLE);
|
||||
actionButton.setOnClickListener(v -> {
|
||||
if (batchSelected.isEmpty() && eventListener != null) {
|
||||
eventListener.onDecryptionFailedLearnMoreClicked();
|
||||
}
|
||||
});
|
||||
} else if (conversationMessage.getMessageRecord().isIdentityUpdate()) {
|
||||
actionButton.setText(R.string.ConversationUpdateItem_learn_more);
|
||||
actionButton.setVisibility(VISIBLE);
|
||||
actionButton.setOnClickListener(v -> {
|
||||
if (batchSelected.isEmpty() && eventListener != null) {
|
||||
eventListener.onSafetyNumberLearnMoreClicked(conversationMessage.getMessageRecord().getIndividualRecipient());
|
||||
}
|
||||
});
|
||||
} else if (conversationMessage.getMessageRecord().isGroupCall()) {
|
||||
UpdateDescription updateDescription = MessageRecord.getGroupCallUpdateDescription(getContext(), conversationMessage.getMessageRecord().getBody(), true);
|
||||
Collection<UUID> uuids = updateDescription.getMentioned();
|
||||
|
||||
int text = 0;
|
||||
if (Util.hasItems(uuids)) {
|
||||
if (GroupCallUpdateDetailsUtil.parse(conversationMessage.getMessageRecord().getBody()).getIsCallFull()) {
|
||||
if (uuids.contains(TextSecurePreferences.getLocalUuid(getContext()))) {
|
||||
text = R.string.ConversationUpdateItem_return_to_call;
|
||||
} else if (GroupCallUpdateDetailsUtil.parse(conversationMessage.getMessageRecord().getBody()).getIsCallFull()) {
|
||||
text = R.string.ConversationUpdateItem_call_is_full;
|
||||
} else {
|
||||
text = uuids.contains(TextSecurePreferences.getLocalUuid(getContext())) ? R.string.ConversationUpdateItem_return_to_call : R.string.ConversationUpdateItem_join_call;
|
||||
text = R.string.ConversationUpdateItem_join_call;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,6 +251,14 @@ public final class ConversationUpdateItem extends LinearLayout
|
||||
actionButton.setVisibility(GONE);
|
||||
actionButton.setOnClickListener(null);
|
||||
}
|
||||
} else if (conversationMessage.getMessageRecord().isSelfCreatedGroup()) {
|
||||
actionButton.setText(R.string.ConversationUpdateItem_invite_friends);
|
||||
actionButton.setVisibility(VISIBLE);
|
||||
actionButton.setOnClickListener(v -> {
|
||||
if (batchSelected.isEmpty() && eventListener != null) {
|
||||
eventListener.onInviteFriendsToGroupClicked(conversationRecipient.requireGroupId().requireV2());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
actionButton.setVisibility(GONE);
|
||||
actionButton.setOnClickListener(null);
|
||||
|
||||
@@ -131,7 +131,8 @@ final class MenuState {
|
||||
messageRecord.isIdentityUpdate() ||
|
||||
messageRecord.isIdentityVerified() ||
|
||||
messageRecord.isIdentityDefault() ||
|
||||
messageRecord.isProfileChange();
|
||||
messageRecord.isProfileChange() ||
|
||||
messageRecord.isFailedDecryptionType();
|
||||
}
|
||||
|
||||
private final static class Builder {
|
||||
|
||||
@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.conversation.ui.mentions;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
@@ -32,7 +33,7 @@ public class MentionsPickerFragment extends LoggingFragment {
|
||||
private BottomSheetBehavior<View> behavior;
|
||||
private MentionsPickerViewModel viewModel;
|
||||
private final Runnable lockSheetAfterListUpdate = () -> behavior.setHideable(false);
|
||||
private final Handler handler = new Handler();
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
@Override
|
||||
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
|
||||
@@ -7,10 +7,13 @@ import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.paging.PagedListAdapter;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.ListAdapter;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.paging.PagingController;
|
||||
import org.thoughtcrime.securesms.BindableConversationListItem;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
@@ -28,7 +31,7 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
class ConversationListAdapter extends PagedListAdapter<Conversation, RecyclerView.ViewHolder> {
|
||||
class ConversationListAdapter extends ListAdapter<Conversation, RecyclerView.ViewHolder> {
|
||||
|
||||
private static final int TYPE_THREAD = 1;
|
||||
private static final int TYPE_ACTION = 2;
|
||||
@@ -46,6 +49,8 @@ class ConversationListAdapter extends PagedListAdapter<Conversation, RecyclerVie
|
||||
private boolean batchMode = false;
|
||||
private final Set<Long> typingSet = new HashSet<>();
|
||||
|
||||
private PagingController pagingController;
|
||||
|
||||
protected ConversationListAdapter(@NonNull GlideRequests glideRequests,
|
||||
@NonNull OnConversationClickListener onConversationClickListener)
|
||||
{
|
||||
@@ -156,6 +161,19 @@ class ConversationListAdapter extends PagedListAdapter<Conversation, RecyclerVie
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Conversation getItem(int position) {
|
||||
if (pagingController != null) {
|
||||
pagingController.onDataNeededAroundIndex(position);
|
||||
}
|
||||
|
||||
return super.getItem(position);
|
||||
}
|
||||
|
||||
public void setPagingController(@Nullable PagingController pagingController) {
|
||||
this.pagingController = pagingController;
|
||||
}
|
||||
|
||||
void setTypingThreads(@NonNull Set<Long> typingThreadSet) {
|
||||
this.typingSet.clear();
|
||||
this.typingSet.addAll(typingThreadSet);
|
||||
|
||||
@@ -37,19 +37,19 @@ import com.google.android.material.snackbar.Snackbar;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
|
||||
@Trace
|
||||
public class ConversationListArchiveFragment extends ConversationListFragment implements ActionMode.Callback
|
||||
{
|
||||
private RecyclerView list;
|
||||
private View emptyState;
|
||||
private Stub<View> emptyState;
|
||||
private PulsingFloatingActionButton fab;
|
||||
private PulsingFloatingActionButton cameraFab;
|
||||
private Stub<Toolbar> toolbar;
|
||||
|
||||
public static ConversationListArchiveFragment newInstance() {
|
||||
return new ConversationListArchiveFragment();
|
||||
@@ -63,26 +63,30 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
toolbar = new Stub<>(view.findViewById(R.id.toolbar_basic));
|
||||
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
|
||||
list = view.findViewById(R.id.list);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
cameraFab = view.findViewById(R.id.camera_fab);
|
||||
emptyState = view.findViewById(R.id.empty_state);
|
||||
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
|
||||
|
||||
((AppCompatActivity) requireActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true);
|
||||
Toolbar toolbar = view.findViewById(R.id.toolbar_basic);
|
||||
toolbar.setNavigationOnClickListener(v -> requireActivity().onBackPressed());
|
||||
toolbar.setTitle(R.string.AndroidManifest_archived_conversations);
|
||||
toolbar.get().setNavigationOnClickListener(v -> requireActivity().onBackPressed());
|
||||
toolbar.get().setTitle(R.string.AndroidManifest_archived_conversations);
|
||||
|
||||
fab.hide();
|
||||
cameraFab.hide();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostSubmitList() {
|
||||
protected void onPostSubmitList(int conversationCount) {
|
||||
list.setVisibility(View.VISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
|
||||
if (emptyState.resolved()) {
|
||||
emptyState.get().setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -91,8 +95,8 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getToolbarRes() {
|
||||
return R.id.toolbar_basic;
|
||||
protected @NonNull Toolbar getToolbar(@NonNull View rootView) {
|
||||
return toolbar.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -144,6 +148,11 @@ public class ConversationListArchiveFragment extends ConversationListFragment im
|
||||
}
|
||||
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
void updateEmptyState(boolean isConversationEmpty) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -7,110 +7,70 @@ import android.database.MergeCursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.paging.DataSource;
|
||||
import androidx.paging.PositionalDataSource;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.paging.PagedDataSource;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.ConversationReader;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.DatabaseObserver;
|
||||
import org.thoughtcrime.securesms.database.ThreadDatabase;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.paging.Invalidator;
|
||||
import org.thoughtcrime.securesms.util.paging.SizeFixResult;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Trace
|
||||
abstract class ConversationListDataSource extends PositionalDataSource<Conversation> {
|
||||
|
||||
public static final Executor EXECUTOR = SignalExecutors.newFixedLifoThreadExecutor("signal-conversation-list", 1, 1);
|
||||
abstract class ConversationListDataSource implements PagedDataSource<Conversation> {
|
||||
|
||||
private static final String TAG = Log.tag(ConversationListDataSource.class);
|
||||
|
||||
protected final ThreadDatabase threadDatabase;
|
||||
|
||||
protected ConversationListDataSource(@NonNull Context context, @NonNull Invalidator invalidator) {
|
||||
protected ConversationListDataSource(@NonNull Context context) {
|
||||
this.threadDatabase = DatabaseFactory.getThreadDatabase(context);
|
||||
|
||||
DatabaseObserver.Observer observer = new DatabaseObserver.Observer() {
|
||||
@Override
|
||||
public void onChanged() {
|
||||
invalidate();
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(this);
|
||||
}
|
||||
};
|
||||
|
||||
invalidator.observe(() -> {
|
||||
invalidate();
|
||||
ApplicationDependencies.getDatabaseObserver().unregisterObserver(observer);
|
||||
});
|
||||
|
||||
ApplicationDependencies.getDatabaseObserver().registerConversationListObserver(observer);
|
||||
}
|
||||
|
||||
private static ConversationListDataSource create(@NonNull Context context, @NonNull Invalidator invalidator, boolean isArchived) {
|
||||
if (!isArchived) return new UnarchivedConversationListDataSource(context, invalidator);
|
||||
else return new ArchivedConversationListDataSource(context, invalidator);
|
||||
public static ConversationListDataSource create(@NonNull Context context, boolean isArchived) {
|
||||
if (!isArchived) return new UnarchivedConversationListDataSource(context);
|
||||
else return new ArchivedConversationListDataSource(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void loadInitial(@NonNull LoadInitialParams params, @NonNull LoadInitialCallback<Conversation> callback) {
|
||||
long start = System.currentTimeMillis();
|
||||
public int size() {
|
||||
long startTime = System.currentTimeMillis();
|
||||
int count = getTotalCount();
|
||||
|
||||
List<Conversation> conversations = new ArrayList<>(params.requestedLoadSize);
|
||||
int totalCount = getTotalCount();
|
||||
int effectiveCount = params.requestedStartPosition;
|
||||
Log.d(TAG, "[size(), " + getClass().getSimpleName() + "] " + (System.currentTimeMillis() - startTime) + " ms");
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull List<Conversation> load(int start, int length, @NonNull CancellationSignal cancellationSignal) {
|
||||
Stopwatch stopwatch = new Stopwatch("load(" + start + ", " + length + "), " + getClass().getSimpleName());
|
||||
|
||||
List<Conversation> conversations = new ArrayList<>(length);
|
||||
List<Recipient> recipients = new LinkedList<>();
|
||||
|
||||
try (ConversationReader reader = new ConversationReader(getCursor(params.requestedStartPosition, params.requestedLoadSize))) {
|
||||
try (ConversationReader reader = new ConversationReader(getCursor(start, length))) {
|
||||
ThreadRecord record;
|
||||
while ((record = reader.getNext()) != null && effectiveCount < totalCount && !isInvalid()) {
|
||||
conversations.add(new Conversation(record));
|
||||
recipients.add(record.getRecipient());
|
||||
effectiveCount++;
|
||||
}
|
||||
}
|
||||
|
||||
ApplicationDependencies.getRecipientCache().addToCache(recipients);
|
||||
|
||||
if (!isInvalid()) {
|
||||
SizeFixResult<Conversation> result = SizeFixResult.ensureMultipleOfPageSize(conversations, params.requestedStartPosition, params.pageSize, totalCount);
|
||||
callback.onResult(result.getItems(), params.requestedStartPosition, result.getTotal());
|
||||
Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | start: " + params.requestedStartPosition + ", requestedSize: " + params.requestedLoadSize + ", actualSize: " + result.getItems().size() + ", totalCount: " + result.getTotal() + ", class: " + getClass().getSimpleName());
|
||||
} else {
|
||||
Log.d(TAG, "[Initial Load] " + (System.currentTimeMillis() - start) + " ms | start: " + params.requestedStartPosition + ", requestedSize: " + params.requestedLoadSize + ", totalCount: " + totalCount + ", class: " + getClass().getSimpleName() + " -- invalidated");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void loadRange(@NonNull LoadRangeParams params, @NonNull LoadRangeCallback<Conversation> callback) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
List<Conversation> conversations = new ArrayList<>(params.loadSize);
|
||||
List<Recipient> recipients = new LinkedList<>();
|
||||
|
||||
try (ConversationReader reader = new ConversationReader(getCursor(params.startPosition, params.loadSize))) {
|
||||
ThreadRecord record;
|
||||
while ((record = reader.getNext()) != null && !isInvalid()) {
|
||||
while ((record = reader.getNext()) != null && !cancellationSignal.isCanceled()) {
|
||||
conversations.add(new Conversation(record));
|
||||
recipients.add(record.getRecipient());
|
||||
}
|
||||
}
|
||||
|
||||
stopwatch.split("cursor");
|
||||
|
||||
ApplicationDependencies.getRecipientCache().addToCache(recipients);
|
||||
|
||||
callback.onResult(conversations);
|
||||
stopwatch.split("cache-recipients");
|
||||
|
||||
Log.d(TAG, "[Update] " + (System.currentTimeMillis() - start) + " ms | start: " + params.startPosition + ", size: " + params.loadSize + ", class: " + getClass().getSimpleName() + (isInvalid() ? " -- invalidated" : ""));
|
||||
stopwatch.stop(TAG);
|
||||
|
||||
return conversations;
|
||||
}
|
||||
|
||||
protected abstract int getTotalCount();
|
||||
@@ -118,8 +78,8 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
|
||||
|
||||
private static class ArchivedConversationListDataSource extends ConversationListDataSource {
|
||||
|
||||
ArchivedConversationListDataSource(@NonNull Context context, @NonNull Invalidator invalidator) {
|
||||
super(context, invalidator);
|
||||
ArchivedConversationListDataSource(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,8 +101,8 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
|
||||
private int archivedCount;
|
||||
private int unpinnedCount;
|
||||
|
||||
UnarchivedConversationListDataSource(@NonNull Context context, @NonNull Invalidator invalidator) {
|
||||
super(context, invalidator);
|
||||
UnarchivedConversationListDataSource(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -152,14 +112,27 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
|
||||
pinnedCount = threadDatabase.getPinnedConversationListCount();
|
||||
archivedCount = threadDatabase.getArchivedConversationListCount();
|
||||
unpinnedCount = unarchivedCount - pinnedCount;
|
||||
totalCount = unarchivedCount + (archivedCount != 0 ? 1 : 0) + (pinnedCount != 0 ? (unpinnedCount != 0 ? 2 : 1) : 0);
|
||||
totalCount = unarchivedCount;
|
||||
|
||||
if (archivedCount != 0) {
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
if (pinnedCount != 0) {
|
||||
if (unpinnedCount != 0) {
|
||||
totalCount += 2;
|
||||
} else {
|
||||
totalCount += 1;
|
||||
}
|
||||
}
|
||||
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Cursor getCursor(long offset, long limit) {
|
||||
List<Cursor> cursors = new ArrayList<>(5);
|
||||
List<Cursor> cursors = new ArrayList<>(5);
|
||||
long originalLimit = limit;
|
||||
|
||||
if (offset == 0 && hasPinnedHeader()) {
|
||||
MatrixCursor pinnedHeaderCursor = new MatrixCursor(ConversationReader.HEADER_COLUMN);
|
||||
@@ -183,7 +156,7 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
|
||||
Cursor unpinnedCursor = threadDatabase.getUnarchivedConversationList(false, unpinnedOffset, limit);
|
||||
cursors.add(unpinnedCursor);
|
||||
|
||||
if (offset + limit >= totalCount && hasArchivedFooter()) {
|
||||
if (offset + originalLimit >= totalCount && hasArchivedFooter()) {
|
||||
MatrixCursor archivedFooterCursor = new MatrixCursor(ConversationReader.ARCHIVED_COLUMNS);
|
||||
archivedFooterCursor.addRow(ConversationReader.createArchivedFooterRow(archivedCount));
|
||||
cursors.add(archivedFooterCursor);
|
||||
@@ -212,22 +185,4 @@ abstract class ConversationListDataSource extends PositionalDataSource<Conversat
|
||||
return archivedCount != 0;
|
||||
}
|
||||
}
|
||||
|
||||
static class Factory extends DataSource.Factory<Integer, Conversation> {
|
||||
|
||||
private final Context context;
|
||||
private final Invalidator invalidator;
|
||||
private final boolean isArchived;
|
||||
|
||||
public Factory(@NonNull Context context, @NonNull Invalidator invalidator, boolean isArchived) {
|
||||
this.context = context;
|
||||
this.invalidator = invalidator;
|
||||
this.isArchived = isArchived;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull DataSource<Integer, Conversation> create() {
|
||||
return ConversationListDataSource.create(context, invalidator, isArchived);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ import org.thoughtcrime.securesms.components.RatingManager;
|
||||
import org.thoughtcrime.securesms.components.SearchToolbar;
|
||||
import org.thoughtcrime.securesms.components.recyclerview.DeleteItemAnimator;
|
||||
import org.thoughtcrime.securesms.components.registration.PulsingFloatingActionButton;
|
||||
import org.thoughtcrime.securesms.components.reminder.DefaultSmsReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.DozeReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.OutdatedBuildReminder;
|
||||
@@ -88,8 +87,6 @@ import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.Reminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ReminderView;
|
||||
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
|
||||
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
|
||||
import org.thoughtcrime.securesms.conversation.ConversationFragment;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
@@ -117,18 +114,19 @@ import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.service.KeyCachingService;
|
||||
import org.thoughtcrime.securesms.sms.MessageSender;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.AppStartup;
|
||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.WindowUtil;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SimpleTask;
|
||||
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
|
||||
import org.thoughtcrime.securesms.util.views.Stub;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -142,7 +140,6 @@ import java.util.Set;
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
|
||||
|
||||
@Trace
|
||||
public class ConversationListFragment extends MainFragment implements ActionMode.Callback,
|
||||
ConversationListAdapter.OnConversationClickListener,
|
||||
ConversationListSearchAdapter.EventListener,
|
||||
@@ -156,21 +153,14 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
private static final int MAXIMUM_PINNED_CONVERSATIONS = 4;
|
||||
|
||||
private static final int[] EMPTY_IMAGES = new int[] { R.drawable.empty_inbox_1,
|
||||
R.drawable.empty_inbox_2,
|
||||
R.drawable.empty_inbox_3,
|
||||
R.drawable.empty_inbox_4,
|
||||
R.drawable.empty_inbox_5 };
|
||||
|
||||
private ActionMode actionMode;
|
||||
private RecyclerView list;
|
||||
private ReminderView reminderView;
|
||||
private View emptyState;
|
||||
private ImageView emptyImage;
|
||||
private Stub<ReminderView> reminderView;
|
||||
private Stub<ViewGroup> emptyState;
|
||||
private TextView searchEmptyState;
|
||||
private PulsingFloatingActionButton fab;
|
||||
private PulsingFloatingActionButton cameraFab;
|
||||
private SearchToolbar searchToolbar;
|
||||
private Stub<SearchToolbar> searchToolbar;
|
||||
private ImageView searchAction;
|
||||
private View toolbarShadow;
|
||||
private ConversationListViewModel viewModel;
|
||||
@@ -178,11 +168,13 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
private ConversationListAdapter defaultAdapter;
|
||||
private ConversationListSearchAdapter searchAdapter;
|
||||
private StickyHeaderDecoration searchAdapterDecoration;
|
||||
private ViewGroup megaphoneContainer;
|
||||
private Stub<ViewGroup> megaphoneContainer;
|
||||
private SnapToTopDataObserver snapToTopDataObserver;
|
||||
private Drawable archiveDrawable;
|
||||
private LifecycleObserver visibilityLifecycleObserver;
|
||||
|
||||
private Stopwatch startupStopwatch;
|
||||
|
||||
public static ConversationListFragment newInstance() {
|
||||
return new ConversationListFragment();
|
||||
}
|
||||
@@ -191,6 +183,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
public void onCreate(Bundle icicle) {
|
||||
super.onCreate(icicle);
|
||||
setHasOptionsMenu(true);
|
||||
startupStopwatch = new Stopwatch("startup");
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -200,28 +193,24 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
reminderView = view.findViewById(R.id.reminder);
|
||||
list = view.findViewById(R.id.list);
|
||||
fab = view.findViewById(R.id.fab);
|
||||
cameraFab = view.findViewById(R.id.camera_fab);
|
||||
emptyState = view.findViewById(R.id.empty_state);
|
||||
emptyImage = view.findViewById(R.id.empty);
|
||||
searchEmptyState = view.findViewById(R.id.search_no_results);
|
||||
searchToolbar = view.findViewById(R.id.search_toolbar);
|
||||
searchAction = view.findViewById(R.id.search_action);
|
||||
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
||||
megaphoneContainer = view.findViewById(R.id.megaphone_container);
|
||||
reminderView = new Stub<>(view.findViewById(R.id.reminder));
|
||||
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
|
||||
searchToolbar = new Stub<>(view.findViewById(R.id.search_toolbar));
|
||||
megaphoneContainer = new Stub<>(view.findViewById(R.id.megaphone_container));
|
||||
|
||||
Toolbar toolbar = view.findViewById(getToolbarRes());
|
||||
Toolbar toolbar = getToolbar(view);
|
||||
toolbar.setVisibility(View.VISIBLE);
|
||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||
|
||||
fab.show();
|
||||
cameraFab.show();
|
||||
|
||||
reminderView.setOnDismissListener(this::updateReminders);
|
||||
reminderView.setOnActionClickListener(this::onReminderAction);
|
||||
|
||||
list.setLayoutManager(new LinearLayoutManager(requireActivity()));
|
||||
list.setItemAnimator(new DeleteItemAnimator());
|
||||
list.addOnScrollListener(new ScrollListener());
|
||||
@@ -242,8 +231,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
.execute();
|
||||
});
|
||||
|
||||
initializeListAdapters();
|
||||
initializeViewModel();
|
||||
initializeListAdapters();
|
||||
initializeTypingObserver();
|
||||
initializeSearchListener();
|
||||
|
||||
@@ -265,7 +254,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
SimpleTask.run(getViewLifecycleOwner().getLifecycle(), Recipient::self, this::initializeProfileIcon);
|
||||
|
||||
if (!searchToolbar.isVisible() && list.getAdapter() != defaultAdapter) {
|
||||
if ((!searchToolbar.resolved() || !searchToolbar.get().isVisible()) && list.getAdapter() != defaultAdapter) {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
}
|
||||
@@ -331,10 +320,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
}
|
||||
|
||||
private boolean closeSearchIfOpen() {
|
||||
if (searchToolbar.isVisible() || activeAdapter == searchAdapter) {
|
||||
if ((searchToolbar.resolved() && searchToolbar.get().isVisible()) || activeAdapter == searchAdapter) {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
searchToolbar.collapse();
|
||||
searchToolbar.get().collapse();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -431,6 +420,11 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
dialogFragment.show(getChildFragmentManager(), "megaphone_dialog");
|
||||
}
|
||||
|
||||
private void initializeReminderView() {
|
||||
reminderView.get().setOnDismissListener(this::updateReminders);
|
||||
reminderView.get().setOnActionClickListener(this::onReminderAction);
|
||||
}
|
||||
|
||||
private void onReminderAction(@IdRes int reminderActionId) {
|
||||
if (reminderActionId == R.id.reminder_action_update_now) {
|
||||
PlayStoreUtil.openPlayStoreOrOurApkDownloadPage(requireContext());
|
||||
@@ -451,36 +445,36 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
private void initializeSearchListener() {
|
||||
searchAction.setOnClickListener(v -> {
|
||||
searchToolbar.display(searchAction.getX() + (searchAction.getWidth() / 2.0f),
|
||||
searchAction.getY() + (searchAction.getHeight() / 2.0f));
|
||||
});
|
||||
searchToolbar.get().display(searchAction.getX() + (searchAction.getWidth() / 2.0f),
|
||||
searchAction.getY() + (searchAction.getHeight() / 2.0f));
|
||||
|
||||
searchToolbar.setListener(new SearchToolbar.SearchListener() {
|
||||
@Override
|
||||
public void onSearchTextChange(String text) {
|
||||
String trimmed = text.trim();
|
||||
searchToolbar.get().setListener(new SearchToolbar.SearchListener() {
|
||||
@Override
|
||||
public void onSearchTextChange(String text) {
|
||||
String trimmed = text.trim();
|
||||
|
||||
viewModel.updateQuery(trimmed);
|
||||
viewModel.updateQuery(trimmed);
|
||||
|
||||
if (trimmed.length() > 0) {
|
||||
if (activeAdapter != searchAdapter) {
|
||||
setAdapter(searchAdapter);
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
list.addItemDecoration(searchAdapterDecoration);
|
||||
}
|
||||
} else {
|
||||
if (activeAdapter != defaultAdapter) {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
if (trimmed.length() > 0) {
|
||||
if (activeAdapter != searchAdapter) {
|
||||
setAdapter(searchAdapter);
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
list.addItemDecoration(searchAdapterDecoration);
|
||||
}
|
||||
} else {
|
||||
if (activeAdapter != defaultAdapter) {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSearchClosed() {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
}
|
||||
@Override
|
||||
public void onSearchClosed() {
|
||||
list.removeItemDecoration(searchAdapterDecoration);
|
||||
setAdapter(defaultAdapter);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -490,6 +484,19 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
searchAdapterDecoration = new StickyHeaderDecoration(searchAdapter, false, false);
|
||||
|
||||
setAdapter(defaultAdapter);
|
||||
|
||||
defaultAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
startupStopwatch.split("data-set");
|
||||
defaultAdapter.unregisterAdapterDataObserver(this);
|
||||
list.post(() -> {
|
||||
AppStartup.getInstance().onCriticalRenderEventEnd();
|
||||
startupStopwatch.split("first-render");
|
||||
startupStopwatch.stop(TAG);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
@@ -502,6 +509,10 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
return;
|
||||
}
|
||||
|
||||
if (adapter instanceof ConversationListAdapter) {
|
||||
((ConversationListAdapter) adapter).setPagingController(viewModel.getPagingController());
|
||||
}
|
||||
|
||||
list.setAdapter(adapter);
|
||||
|
||||
if (adapter == defaultAdapter) {
|
||||
@@ -555,20 +566,22 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
|
||||
private void onMegaphoneChanged(@Nullable Megaphone megaphone) {
|
||||
if (megaphone == null) {
|
||||
megaphoneContainer.setVisibility(View.GONE);
|
||||
megaphoneContainer.removeAllViews();
|
||||
if (megaphoneContainer.resolved()) {
|
||||
megaphoneContainer.get().setVisibility(View.GONE);
|
||||
megaphoneContainer.get().removeAllViews();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
View view = MegaphoneViewBuilder.build(requireContext(), megaphone, this);
|
||||
|
||||
megaphoneContainer.removeAllViews();
|
||||
megaphoneContainer.get().removeAllViews();
|
||||
|
||||
if (view != null) {
|
||||
megaphoneContainer.addView(view);
|
||||
megaphoneContainer.setVisibility(View.VISIBLE);
|
||||
megaphoneContainer.get().addView(view);
|
||||
megaphoneContainer.get().setVisibility(View.VISIBLE);
|
||||
} else {
|
||||
megaphoneContainer.setVisibility(View.GONE);
|
||||
megaphoneContainer.get().setVisibility(View.GONE);
|
||||
|
||||
if (megaphone.getOnVisibleListener() != null) {
|
||||
megaphone.getOnVisibleListener().onEvent(megaphone, this);
|
||||
@@ -591,14 +604,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
return Optional.of(new ServiceOutageReminder(context));
|
||||
} else if (OutdatedBuildReminder.isEligible()) {
|
||||
return Optional.of(new OutdatedBuildReminder(context));
|
||||
} else if (DefaultSmsReminder.isEligible(context)) {
|
||||
return Optional.of(new DefaultSmsReminder(this, SMS_ROLE_REQUEST_CODE));
|
||||
} else if (Util.isDefaultSmsProvider(context) && SystemSmsImportReminder.isEligible(context)) {
|
||||
return Optional.of((new SystemSmsImportReminder(context)));
|
||||
} else if (PushRegistrationReminder.isEligible(context)) {
|
||||
return Optional.of((new PushRegistrationReminder(context)));
|
||||
} else if (ShareReminder.isEligible(context)) {
|
||||
return Optional.of(new ShareReminder(context));
|
||||
} else if (DozeReminder.isEligible(context)) {
|
||||
return Optional.of(new DozeReminder(context));
|
||||
} else {
|
||||
@@ -606,9 +613,12 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
}
|
||||
}, reminder -> {
|
||||
if (reminder.isPresent() && getActivity() != null && !isRemoving()) {
|
||||
reminderView.showReminder(reminder.get());
|
||||
} else if (!reminder.isPresent()) {
|
||||
reminderView.hide();
|
||||
if (!reminderView.resolved()) {
|
||||
initializeReminderView();
|
||||
}
|
||||
reminderView.get().showReminder(reminder.get());
|
||||
} else if (reminderView.resolved() && !reminder.isPresent()) {
|
||||
reminderView.get().hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -820,33 +830,37 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
getNavigator().goToConversation(recipient.getId(), threadId, distributionType, -1);
|
||||
}
|
||||
|
||||
private void onSubmitList(@NonNull ConversationListViewModel.ConversationList conversationList) {
|
||||
if (conversationList.getConversations().isDetached()) {
|
||||
return;
|
||||
}
|
||||
|
||||
defaultAdapter.submitList(conversationList.getConversations());
|
||||
|
||||
onPostSubmitList();
|
||||
private void onSubmitList(@NonNull List<Conversation> conversationList) {
|
||||
defaultAdapter.submitList(conversationList);
|
||||
onPostSubmitList(conversationList.size());
|
||||
}
|
||||
|
||||
private void updateEmptyState(boolean isConversationEmpty) {
|
||||
void updateEmptyState(boolean isConversationEmpty) {
|
||||
if (isConversationEmpty) {
|
||||
Log.i(TAG, "Received an empty data set.");
|
||||
list.setVisibility(View.INVISIBLE);
|
||||
emptyState.setVisibility(View.VISIBLE);
|
||||
emptyImage.setImageResource(EMPTY_IMAGES[(int) (Math.random() * EMPTY_IMAGES.length)]);
|
||||
emptyState.get().setVisibility(View.VISIBLE);
|
||||
fab.startPulse(3 * 1000);
|
||||
cameraFab.startPulse(3 * 1000);
|
||||
|
||||
SignalStore.onboarding().setShowNewGroup(true);
|
||||
SignalStore.onboarding().setShowInviteFriends(true);
|
||||
} else {
|
||||
list.setVisibility(View.VISIBLE);
|
||||
emptyState.setVisibility(View.GONE);
|
||||
fab.stopPulse();
|
||||
cameraFab.stopPulse();
|
||||
|
||||
if (emptyState.resolved()) {
|
||||
emptyState.get().setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostSubmitList() {
|
||||
protected void onPostSubmitList(int conversationCount) {
|
||||
if (conversationCount >= 6 && (SignalStore.onboarding().shouldShowInviteFriends() || SignalStore.onboarding().shouldShowNewGroup())) {
|
||||
SignalStore.onboarding().clearAll();
|
||||
ApplicationDependencies.getMegaphoneRepository().markFinished(Megaphones.Event.ONBOARDING);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -976,8 +990,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
||||
}
|
||||
}
|
||||
|
||||
protected @IdRes int getToolbarRes() {
|
||||
return R.id.toolbar;
|
||||
protected Toolbar getToolbar(@NonNull View rootView) {
|
||||
return rootView.findViewById(R.id.toolbar);
|
||||
}
|
||||
|
||||
protected @PluralsRes int getArchivedSnackbarTitleRes() {
|
||||
|
||||
@@ -20,18 +20,18 @@ import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.RippleDrawable;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
@@ -64,7 +64,6 @@ import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.ExpirationUtil;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.thoughtcrime.securesms.util.SearchUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
@@ -73,7 +72,7 @@ import java.util.Set;
|
||||
|
||||
import static org.thoughtcrime.securesms.database.model.LiveUpdateMessage.recipientToStringAsync;
|
||||
|
||||
public final class ConversationListItem extends RelativeLayout
|
||||
public final class ConversationListItem extends ConstraintLayout
|
||||
implements RecipientForeverObserver,
|
||||
BindableConversationListItem,
|
||||
Unbindable,
|
||||
@@ -90,7 +89,6 @@ public final class ConversationListItem extends RelativeLayout
|
||||
private LiveRecipient recipient;
|
||||
private long threadId;
|
||||
private GlideRequests glideRequests;
|
||||
private View subjectContainer;
|
||||
private TextView subjectView;
|
||||
private TypingIndicatorView typingView;
|
||||
private FromTextView fromView;
|
||||
@@ -122,21 +120,17 @@ public final class ConversationListItem extends RelativeLayout
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.subjectContainer = findViewById(R.id.subject_container);
|
||||
this.subjectView = findViewById(R.id.subject);
|
||||
this.typingView = findViewById(R.id.typing_indicator);
|
||||
this.fromView = findViewById(R.id.from);
|
||||
this.dateView = findViewById(R.id.date);
|
||||
this.deliveryStatusIndicator = findViewById(R.id.delivery_status);
|
||||
this.alertView = findViewById(R.id.indicators_parent);
|
||||
this.contactPhotoImage = findViewById(R.id.contact_photo_image);
|
||||
this.thumbnailView = findViewById(R.id.thumbnail);
|
||||
this.archivedView = findViewById(R.id.archived);
|
||||
this.unreadIndicator = findViewById(R.id.unread_indicator);
|
||||
this.subjectView = findViewById(R.id.conversation_list_item_summary);
|
||||
this.typingView = findViewById(R.id.conversation_list_item_typing_indicator);
|
||||
this.fromView = findViewById(R.id.conversation_list_item_name);
|
||||
this.dateView = findViewById(R.id.conversation_list_item_date);
|
||||
this.deliveryStatusIndicator = findViewById(R.id.conversation_list_item_status);
|
||||
this.alertView = findViewById(R.id.conversation_list_item_alert);
|
||||
this.contactPhotoImage = findViewById(R.id.conversation_list_item_avatar);
|
||||
this.thumbnailView = findViewById(R.id.conversation_list_item_thumbnail);
|
||||
this.archivedView = findViewById(R.id.conversation_list_item_archived);
|
||||
this.unreadIndicator = findViewById(R.id.conversation_list_item_unread_indicator);
|
||||
thumbnailView.setClickable(false);
|
||||
|
||||
ViewUtil.setTextViewGravityStart(this.fromView, getContext());
|
||||
ViewUtil.setTextViewGravityStart(this.subjectView, getContext());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -158,19 +152,17 @@ public final class ConversationListItem extends RelativeLayout
|
||||
boolean batchMode,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
observeRecipient(thread.getRecipient().live());
|
||||
observeDisplayBody(null);
|
||||
setSubjectViewText(null);
|
||||
|
||||
this.selectedThreads = selectedThreads;
|
||||
this.recipient = thread.getRecipient().live();
|
||||
this.threadId = thread.getThreadId();
|
||||
this.glideRequests = glideRequests;
|
||||
this.unreadCount = thread.getUnreadCount();
|
||||
this.lastSeen = thread.getLastSeen();
|
||||
this.thread = thread;
|
||||
|
||||
this.recipient.observeForever(this);
|
||||
if (highlightSubstring != null) {
|
||||
String name = recipient.get().isSelf() ? getContext().getString(R.string.note_to_self) : recipient.get().getDisplayName(getContext());
|
||||
|
||||
@@ -215,15 +207,13 @@ public final class ConversationListItem extends RelativeLayout
|
||||
@NonNull Locale locale,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
observeRecipient(contact.live());
|
||||
observeDisplayBody(null);
|
||||
setSubjectViewText(null);
|
||||
|
||||
this.selectedThreads = Collections.emptySet();
|
||||
this.recipient = contact.live();
|
||||
this.glideRequests = glideRequests;
|
||||
|
||||
this.recipient.observeForever(this);
|
||||
|
||||
fromView.setText(contact);
|
||||
fromView.setText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), new SpannableString(fromView.getText()), highlightSubstring));
|
||||
@@ -245,16 +235,13 @@ public final class ConversationListItem extends RelativeLayout
|
||||
@NonNull Locale locale,
|
||||
@Nullable String highlightSubstring)
|
||||
{
|
||||
if (this.recipient != null) this.recipient.removeForeverObserver(this);
|
||||
observeRecipient(messageResult.conversationRecipient.live());
|
||||
observeDisplayBody(null);
|
||||
setSubjectViewText(null);
|
||||
|
||||
this.selectedThreads = Collections.emptySet();
|
||||
this.recipient = messageResult.conversationRecipient.live();
|
||||
this.glideRequests = glideRequests;
|
||||
|
||||
this.recipient.observeForever(this);
|
||||
|
||||
fromView.setText(recipient.get(), true);
|
||||
setSubjectViewText(SearchUtil.getHighlightedSpan(locale, () -> new StyleSpan(Typeface.BOLD), messageResult.bodySnippet, highlightSubstring));
|
||||
dateView.setText(DateUtils.getBriefRelativeTimeSpanString(getContext(), locale, messageResult.receivedTimestampMs));
|
||||
@@ -272,9 +259,7 @@ public final class ConversationListItem extends RelativeLayout
|
||||
@Override
|
||||
public void unbind() {
|
||||
if (this.recipient != null) {
|
||||
this.recipient.removeForeverObserver(this);
|
||||
this.recipient = null;
|
||||
|
||||
observeRecipient(null);
|
||||
setBatchMode(false);
|
||||
contactPhotoImage.setAvatar(glideRequests, null, !batchMode);
|
||||
}
|
||||
@@ -323,6 +308,18 @@ public final class ConversationListItem extends RelativeLayout
|
||||
return lastSeen;
|
||||
}
|
||||
|
||||
private void observeRecipient(@Nullable LiveRecipient newRecipient) {
|
||||
if (this.recipient != null) {
|
||||
this.recipient.removeForeverObserver(this);
|
||||
}
|
||||
|
||||
this.recipient = newRecipient;
|
||||
|
||||
if (this.recipient != null) {
|
||||
this.recipient.observeForever(this);
|
||||
}
|
||||
}
|
||||
|
||||
private void observeDisplayBody(@Nullable LiveData<SpannableString> displayBody) {
|
||||
if (this.displayBody != null) {
|
||||
this.displayBody.removeObserver(this);
|
||||
@@ -349,19 +346,8 @@ public final class ConversationListItem extends RelativeLayout
|
||||
if (thread.getSnippetUri() != null) {
|
||||
this.thumbnailView.setVisibility(View.VISIBLE);
|
||||
this.thumbnailView.setImageResource(glideRequests, thread.getSnippetUri());
|
||||
|
||||
LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectContainer .getLayoutParams();
|
||||
subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.thumbnail);
|
||||
subjectParams.addRule(RelativeLayout.START_OF, R.id.thumbnail);
|
||||
this.subjectContainer.setLayoutParams(subjectParams);
|
||||
this.post(new ThumbnailPositioner(thumbnailView, archivedView, deliveryStatusIndicator, dateView));
|
||||
} else {
|
||||
this.thumbnailView.setVisibility(View.GONE);
|
||||
|
||||
LayoutParams subjectParams = (RelativeLayout.LayoutParams)this.subjectContainer.getLayoutParams();
|
||||
subjectParams.addRule(RelativeLayout.LEFT_OF, R.id.status);
|
||||
subjectParams.addRule(RelativeLayout.START_OF, R.id.status);
|
||||
this.subjectContainer.setLayoutParams(subjectParams);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,7 +389,7 @@ public final class ConversationListItem extends RelativeLayout
|
||||
}
|
||||
|
||||
private void setRippleColor(Recipient recipient) {
|
||||
if (VERSION.SDK_INT >= 21) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
((RippleDrawable)(getBackground()).mutate())
|
||||
.setColor(ColorStateList.valueOf(recipient.getColor().toConversationColor(getContext())));
|
||||
}
|
||||
@@ -421,6 +407,11 @@ public final class ConversationListItem extends RelativeLayout
|
||||
|
||||
@Override
|
||||
public void onRecipientChanged(@NonNull Recipient recipient) {
|
||||
if (this.recipient == null || !this.recipient.getId().equals(recipient.getId())) {
|
||||
Log.w(TAG, "Bad change! Local recipient doesn't match. Ignoring. Local: " + (this.recipient == null ? "null" : this.recipient.getId()) + ", Changed: " + recipient.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
fromView.setText(recipient, unreadCount == 0);
|
||||
contactPhotoImage.setAvatar(glideRequests, recipient, !batchMode);
|
||||
setRippleColor(recipient);
|
||||
@@ -443,7 +434,8 @@ public final class ConversationListItem extends RelativeLayout
|
||||
} else if (SmsDatabase.Types.isKeyExchangeType(thread.getType())) {
|
||||
return emphasisAdded(context, context.getString(R.string.ConversationListItem_key_exchange_message), defaultTint);
|
||||
} else if (SmsDatabase.Types.isFailedDecryptType(thread.getType())) {
|
||||
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_bad_encrypted_message), defaultTint);
|
||||
UpdateDescription description = UpdateDescription.staticDescription(context.getString(R.string.ThreadRecord_chat_session_refreshed), R.drawable.ic_refresh_16);
|
||||
return emphasisAdded(context, description, defaultTint);
|
||||
} else if (SmsDatabase.Types.isNoRemoteSessionType(thread.getType())) {
|
||||
return emphasisAdded(context, context.getString(R.string.MessageDisplayHelper_message_encrypted_for_non_existing_session), defaultTint);
|
||||
} else if (SmsDatabase.Types.isEndSessionType(thread.getType())) {
|
||||
@@ -549,37 +541,4 @@ public final class ConversationListItem extends RelativeLayout
|
||||
updateTypingIndicator(typingThreads);
|
||||
}
|
||||
}
|
||||
|
||||
private static class ThumbnailPositioner implements Runnable {
|
||||
|
||||
private final View thumbnailView;
|
||||
private final View archivedView;
|
||||
private final View deliveryStatusView;
|
||||
private final View dateView;
|
||||
|
||||
ThumbnailPositioner(View thumbnailView, View archivedView, View deliveryStatusView, View dateView) {
|
||||
this.thumbnailView = thumbnailView;
|
||||
this.archivedView = archivedView;
|
||||
this.deliveryStatusView = deliveryStatusView;
|
||||
this.dateView = dateView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
LayoutParams thumbnailParams = (RelativeLayout.LayoutParams)thumbnailView.getLayoutParams();
|
||||
|
||||
if (archivedView.getVisibility() == View.VISIBLE &&
|
||||
(archivedView.getWidth() + deliveryStatusView.getWidth()) > dateView.getWidth())
|
||||
{
|
||||
thumbnailParams.addRule(RelativeLayout.LEFT_OF, R.id.status);
|
||||
thumbnailParams.addRule(RelativeLayout.START_OF, R.id.status);
|
||||
} else {
|
||||
thumbnailParams.addRule(RelativeLayout.LEFT_OF, R.id.date);
|
||||
thumbnailParams.addRule(RelativeLayout.START_OF, R.id.date);
|
||||
}
|
||||
|
||||
thumbnailView.setLayoutParams(thumbnailParams);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import android.annotation.TargetApi;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
@@ -13,12 +14,11 @@ import org.thoughtcrime.securesms.BindableConversationListItem;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConversationListItemAction extends LinearLayout implements BindableConversationListItem {
|
||||
public class ConversationListItemAction extends FrameLayout implements BindableConversationListItem {
|
||||
|
||||
private TextView description;
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ConversationListItemAction extends LinearLayout implements Bindable
|
||||
@Override
|
||||
public void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
this.description = ViewUtil.findById(this, R.id.description);
|
||||
this.description = findViewById(R.id.description);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -6,15 +6,13 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Transformations;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.paging.DataSource;
|
||||
import androidx.paging.LivePagedListBuilder;
|
||||
import androidx.paging.PagedList;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.paging.PagedData;
|
||||
import org.signal.paging.PagingConfig;
|
||||
import org.signal.paging.PagingController;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.Conversation;
|
||||
import org.thoughtcrime.securesms.conversationlist.model.SearchResult;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
@@ -26,24 +24,29 @@ import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||
import org.thoughtcrime.securesms.search.SearchRepository;
|
||||
import org.thoughtcrime.securesms.util.Debouncer;
|
||||
import org.thoughtcrime.securesms.util.Util;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.util.paging.Invalidator;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.List;
|
||||
|
||||
class ConversationListViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(ConversationListViewModel.class);
|
||||
|
||||
private final MutableLiveData<Megaphone> megaphone;
|
||||
private final MutableLiveData<SearchResult> searchResult;
|
||||
private final LiveData<ConversationList> conversationList;
|
||||
private final SearchRepository searchRepository;
|
||||
private final MegaphoneRepository megaphoneRepository;
|
||||
private final Debouncer debouncer;
|
||||
private final DatabaseObserver.Observer observer;
|
||||
private final Invalidator invalidator;
|
||||
private static boolean coldStart = true;
|
||||
|
||||
private final MutableLiveData<Megaphone> megaphone;
|
||||
private final MutableLiveData<SearchResult> searchResult;
|
||||
private final PagedData<Conversation> pagedData;
|
||||
private final LiveData<Boolean> hasNoConversations;
|
||||
private final SearchRepository searchRepository;
|
||||
private final MegaphoneRepository megaphoneRepository;
|
||||
private final Debouncer debouncer;
|
||||
private final DatabaseObserver.Observer observer;
|
||||
private final Invalidator invalidator;
|
||||
|
||||
private String lastQuery;
|
||||
private int pinnedCount;
|
||||
|
||||
private ConversationListViewModel(@NonNull Application application, @NonNull SearchRepository searchRepository, boolean isArchived) {
|
||||
this.megaphone = new MutableLiveData<>();
|
||||
@@ -52,49 +55,33 @@ class ConversationListViewModel extends ViewModel {
|
||||
this.megaphoneRepository = ApplicationDependencies.getMegaphoneRepository();
|
||||
this.debouncer = new Debouncer(300);
|
||||
this.invalidator = new Invalidator();
|
||||
this.pagedData = PagedData.create(ConversationListDataSource.create(application, isArchived),
|
||||
new PagingConfig.Builder()
|
||||
.setPageSize(15)
|
||||
.setBufferPages(2)
|
||||
.build());
|
||||
this.observer = () -> {
|
||||
if (!TextUtils.isEmpty(getLastQuery())) {
|
||||
searchRepository.query(getLastQuery(), searchResult::postValue);
|
||||
}
|
||||
pagedData.getController().onDataInvalidated();
|
||||
};
|
||||
|
||||
DataSource.Factory<Integer, Conversation> factory = new ConversationListDataSource.Factory(application, invalidator, isArchived);
|
||||
PagedList.Config config = new PagedList.Config.Builder()
|
||||
.setPageSize(15)
|
||||
.setInitialLoadSizeHint(30)
|
||||
.setEnablePlaceholders(true)
|
||||
.build();
|
||||
this.hasNoConversations = LiveDataUtil.mapAsync(pagedData.getData(), conversations -> {
|
||||
pinnedCount = DatabaseFactory.getThreadDatabase(application).getPinnedConversationListCount();
|
||||
|
||||
LiveData<PagedList<Conversation>> conversationList = new LivePagedListBuilder<>(factory, config).setFetchExecutor(ConversationListDataSource.EXECUTOR)
|
||||
.setInitialLoadKey(0)
|
||||
.build();
|
||||
if (conversations.size() > 0) {
|
||||
return false;
|
||||
} else {
|
||||
return DatabaseFactory.getThreadDatabase(application).getArchivedConversationListCount() == 0;
|
||||
}
|
||||
});
|
||||
|
||||
ApplicationDependencies.getDatabaseObserver().registerConversationListObserver(observer);
|
||||
|
||||
this.conversationList = Transformations.switchMap(conversationList, conversation -> {
|
||||
if (conversation.getDataSource().isInvalid()) {
|
||||
Log.w(TAG, "Received an invalid conversation list. Ignoring.");
|
||||
return new MutableLiveData<>();
|
||||
}
|
||||
|
||||
MutableLiveData<ConversationList> updated = new MutableLiveData<>();
|
||||
|
||||
if (isArchived) {
|
||||
updated.postValue(new ConversationList(conversation, 0, 0));
|
||||
} else {
|
||||
SignalExecutors.BOUNDED.execute(() -> {
|
||||
int archiveCount = DatabaseFactory.getThreadDatabase(application).getArchivedConversationListCount();
|
||||
int pinnedCount = DatabaseFactory.getThreadDatabase(application).getPinnedConversationListCount();
|
||||
updated.postValue(new ConversationList(conversation, archiveCount, pinnedCount));
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
|
||||
public LiveData<Boolean> hasNoConversations() {
|
||||
return Transformations.map(getConversationList(), ConversationList::isEmpty);
|
||||
return hasNoConversations;
|
||||
}
|
||||
|
||||
@NonNull LiveData<SearchResult> getSearchResult() {
|
||||
@@ -105,17 +92,26 @@ class ConversationListViewModel extends ViewModel {
|
||||
return megaphone;
|
||||
}
|
||||
|
||||
@NonNull LiveData<ConversationList> getConversationList() {
|
||||
return conversationList;
|
||||
@NonNull LiveData<List<Conversation>> getConversationList() {
|
||||
return pagedData.getData();
|
||||
}
|
||||
|
||||
@NonNull PagingController getPagingController() {
|
||||
return pagedData.getController();
|
||||
}
|
||||
|
||||
public int getPinnedCount() {
|
||||
return Objects.requireNonNull(getConversationList().getValue()).pinnedCount;
|
||||
return pinnedCount;
|
||||
}
|
||||
|
||||
void onVisible() {
|
||||
megaphoneRepository.getNextMegaphone(megaphone::postValue);
|
||||
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
|
||||
|
||||
if (!coldStart) {
|
||||
ApplicationDependencies.getDatabaseObserver().notifyConversationListListeners();
|
||||
}
|
||||
|
||||
coldStart = false;
|
||||
}
|
||||
|
||||
void onMegaphoneCompleted(@NonNull Megaphones.Event event) {
|
||||
@@ -168,32 +164,4 @@ class ConversationListViewModel extends ViewModel {
|
||||
return modelClass.cast(new ConversationListViewModel(ApplicationDependencies.getApplication(), new SearchRepository(), isArchived));
|
||||
}
|
||||
}
|
||||
|
||||
final static class ConversationList {
|
||||
private final PagedList<Conversation> conversations;
|
||||
private final int archivedCount;
|
||||
private final int pinnedCount;
|
||||
|
||||
ConversationList(PagedList<Conversation> conversations, int archivedCount, int pinnedCount) {
|
||||
this.conversations = conversations;
|
||||
this.archivedCount = archivedCount;
|
||||
this.pinnedCount = pinnedCount;
|
||||
}
|
||||
|
||||
PagedList<Conversation> getConversations() {
|
||||
return conversations;
|
||||
}
|
||||
|
||||
int getArchivedCount() {
|
||||
return archivedCount;
|
||||
}
|
||||
|
||||
public int getPinnedCount() {
|
||||
return pinnedCount;
|
||||
}
|
||||
|
||||
boolean isEmpty() {
|
||||
return conversations.isEmpty() && archivedCount == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,18 +11,30 @@ import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
import java.io.IOException;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
public class DatabaseSecretProvider {
|
||||
/**
|
||||
* It can be rather expensive to read from the keystore, so this class caches the key in memory
|
||||
* after it is created.
|
||||
*/
|
||||
public final class DatabaseSecretProvider {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static final String TAG = DatabaseSecretProvider.class.getSimpleName();
|
||||
private static volatile DatabaseSecret instance;
|
||||
|
||||
private final Context context;
|
||||
public static DatabaseSecret getOrCreateDatabaseSecret(@NonNull Context context) {
|
||||
if (instance == null) {
|
||||
synchronized (DatabaseSecretProvider.class) {
|
||||
if (instance == null) {
|
||||
instance = getOrCreate(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public DatabaseSecretProvider(@NonNull Context context) {
|
||||
this.context = context.getApplicationContext();
|
||||
return instance;
|
||||
}
|
||||
|
||||
public DatabaseSecret getOrCreateDatabaseSecret() {
|
||||
private DatabaseSecretProvider() {
|
||||
}
|
||||
|
||||
private static @NonNull DatabaseSecret getOrCreate(@NonNull Context context) {
|
||||
String unencryptedSecret = TextSecurePreferences.getDatabaseUnencryptedSecret(context);
|
||||
String encryptedSecret = TextSecurePreferences.getDatabaseEncryptedSecret(context);
|
||||
|
||||
@@ -31,12 +43,12 @@ public class DatabaseSecretProvider {
|
||||
else return createAndStoreDatabaseSecret(context);
|
||||
}
|
||||
|
||||
private DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
private static @NonNull DatabaseSecret getUnencryptedDatabaseSecret(@NonNull Context context, @NonNull String unencryptedSecret)
|
||||
{
|
||||
try {
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(unencryptedSecret);
|
||||
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
return databaseSecret;
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
@@ -51,8 +63,8 @@ public class DatabaseSecretProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
||||
private static @NonNull DatabaseSecret getEncryptedDatabaseSecret(@NonNull String serializedEncryptedSecret) {
|
||||
if (Build.VERSION.SDK_INT < 23) {
|
||||
throw new AssertionError("OS downgrade not supported. KeyStore sealed data exists on platform < M!");
|
||||
} else {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.SealedData.fromString(serializedEncryptedSecret);
|
||||
@@ -60,14 +72,14 @@ public class DatabaseSecretProvider {
|
||||
}
|
||||
}
|
||||
|
||||
private DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
private static @NonNull DatabaseSecret createAndStoreDatabaseSecret(@NonNull Context context) {
|
||||
SecureRandom random = new SecureRandom();
|
||||
byte[] secret = new byte[32];
|
||||
random.nextBytes(secret);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecret(secret);
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
KeyStoreHelper.SealedData encryptedSecret = KeyStoreHelper.seal(databaseSecret.asBytes());
|
||||
TextSecurePreferences.setDatabaseEncryptedSecret(context, encryptedSecret.serialize());
|
||||
} else {
|
||||
|
||||
@@ -82,10 +82,6 @@ public final class ProfileKeyUtil {
|
||||
return Optional.of(profileKeyOrThrow(profileKey));
|
||||
}
|
||||
|
||||
public static @NonNull Optional<ProfileKeyCredential> profileKeyCredentialOptional(@Nullable byte[] profileKey) {
|
||||
return Optional.fromNullable(profileKeyCredentialOrNull(profileKey));
|
||||
}
|
||||
|
||||
public static @NonNull ProfileKey createNew() {
|
||||
try {
|
||||
return new ProfileKey(Util.getSecretBytes(32));
|
||||
|
||||
@@ -29,4 +29,7 @@ public class SessionUtil {
|
||||
new TextSecureSessionStore(context).archiveAllSessions();
|
||||
}
|
||||
|
||||
public static void archiveSession(Context context, RecipientId recipientId, int deviceId) {
|
||||
new TextSecureSessionStore(context).archiveSession(recipientId, deviceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,6 +103,16 @@ public class TextSecureSessionStore implements SessionStore {
|
||||
}
|
||||
}
|
||||
|
||||
public void archiveSession(@NonNull RecipientId recipientId, int deviceId) {
|
||||
synchronized (FILE_LOCK) {
|
||||
SessionRecord session = DatabaseFactory.getSessionDatabase(context).load(recipientId, deviceId);
|
||||
if (session != null) {
|
||||
session.archiveCurrentState();
|
||||
DatabaseFactory.getSessionDatabase(context).store(recipientId, deviceId, session);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void archiveSiblingSessions(@NonNull SignalProtocolAddress address) {
|
||||
synchronized (FILE_LOCK) {
|
||||
if (DatabaseFactory.getRecipientDatabase(context).containsPhoneOrUuid(address.getName())) {
|
||||
|
||||
@@ -53,7 +53,6 @@ import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.stickers.StickerLocator;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.FileUtils;
|
||||
@@ -82,7 +81,6 @@ import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@Trace
|
||||
public class AttachmentDatabase extends Database {
|
||||
|
||||
private static final String TAG = AttachmentDatabase.class.getSimpleName();
|
||||
@@ -751,7 +749,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyModifyThisAttachment If false and more than one attachment shares this file, they will all up updated.
|
||||
* @param onlyModifyThisAttachment If false and more than one attachment shares this file, they will all be updated.
|
||||
* If true, then guarantees not to affect other attachments.
|
||||
*/
|
||||
public void updateAttachmentData(@NonNull DatabaseAttachment databaseAttachment,
|
||||
@@ -1032,7 +1030,7 @@ public class AttachmentDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
private File newFile() throws IOException {
|
||||
public File newFile() throws IOException {
|
||||
File partsDirectory = context.getDir(DIRECTORY, Context.MODE_PRIVATE);
|
||||
return File.createTempFile("part", ".mms", partsDirectory);
|
||||
}
|
||||
|
||||
@@ -32,13 +32,14 @@ import org.thoughtcrime.securesms.database.helpers.ClassicOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherMigrationHelper;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.migrations.LegacyMigrationJob;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||
|
||||
public class DatabaseFactory {
|
||||
|
||||
private static final Object lock = new Object();
|
||||
|
||||
private static DatabaseFactory instance;
|
||||
private static volatile DatabaseFactory instance;
|
||||
|
||||
private final SQLCipherOpenHelper databaseHelper;
|
||||
private final SmsDatabase sms;
|
||||
@@ -58,21 +59,20 @@ public class DatabaseFactory {
|
||||
private final SignedPreKeyDatabase signedPreKeyDatabase;
|
||||
private final SessionDatabase sessionDatabase;
|
||||
private final SearchDatabase searchDatabase;
|
||||
private final JobDatabase jobDatabase;
|
||||
private final StickerDatabase stickerDatabase;
|
||||
private final StorageKeyDatabase storageKeyDatabase;
|
||||
private final KeyValueDatabase keyValueDatabase;
|
||||
private final MegaphoneDatabase megaphoneDatabase;
|
||||
private final RemappedRecordsDatabase remappedRecordsDatabase;
|
||||
private final MentionDatabase mentionDatabase;
|
||||
|
||||
public static DatabaseFactory getInstance(Context context) {
|
||||
synchronized (lock) {
|
||||
if (instance == null)
|
||||
instance = new DatabaseFactory(context.getApplicationContext());
|
||||
|
||||
return instance;
|
||||
if (instance == null) {
|
||||
synchronized (lock) {
|
||||
if (instance == null) {
|
||||
instance = new DatabaseFactory(context.getApplicationContext());
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static MmsSmsDatabase getMmsSmsDatabase(Context context) {
|
||||
@@ -143,10 +143,6 @@ public class DatabaseFactory {
|
||||
return getInstance(context).searchDatabase;
|
||||
}
|
||||
|
||||
public static JobDatabase getJobDatabase(Context context) {
|
||||
return getInstance(context).jobDatabase;
|
||||
}
|
||||
|
||||
public static StickerDatabase getStickerDatabase(Context context) {
|
||||
return getInstance(context).stickerDatabase;
|
||||
}
|
||||
@@ -155,14 +151,6 @@ public class DatabaseFactory {
|
||||
return getInstance(context).storageKeyDatabase;
|
||||
}
|
||||
|
||||
public static KeyValueDatabase getKeyValueDatabase(Context context) {
|
||||
return getInstance(context).keyValueDatabase;
|
||||
}
|
||||
|
||||
public static MegaphoneDatabase getMegaphoneDatabase(Context context) {
|
||||
return getInstance(context).megaphoneDatabase;
|
||||
}
|
||||
|
||||
static RemappedRecordsDatabase getRemappedRecordsDatabase(Context context) {
|
||||
return getInstance(context).remappedRecordsDatabase;
|
||||
}
|
||||
@@ -180,6 +168,11 @@ public class DatabaseFactory {
|
||||
getInstance(context).databaseHelper.onUpgrade(database, database.getVersion(), -1);
|
||||
getInstance(context).databaseHelper.markCurrent(database);
|
||||
getInstance(context).mms.trimEntriesForExpiredMessages();
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS key_value");
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS megaphone");
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS job_spec");
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS constraint_spec");
|
||||
getInstance(context).getRawDatabase().rawExecSQL("DROP TABLE IF EXISTS dependency_spec");
|
||||
|
||||
instance.databaseHelper.close();
|
||||
instance = null;
|
||||
@@ -193,8 +186,8 @@ public class DatabaseFactory {
|
||||
private DatabaseFactory(@NonNull Context context) {
|
||||
SQLiteDatabase.loadLibs(context);
|
||||
|
||||
DatabaseSecret databaseSecret = new DatabaseSecretProvider(context).getOrCreateDatabaseSecret();
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
DatabaseSecret databaseSecret = DatabaseSecretProvider.getOrCreateDatabaseSecret(context);
|
||||
AttachmentSecret attachmentSecret = AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret();
|
||||
|
||||
this.databaseHelper = new SQLCipherOpenHelper(context, databaseSecret);
|
||||
this.sms = new SmsDatabase(context, databaseHelper);
|
||||
@@ -214,11 +207,8 @@ public class DatabaseFactory {
|
||||
this.signedPreKeyDatabase = new SignedPreKeyDatabase(context, databaseHelper);
|
||||
this.sessionDatabase = new SessionDatabase(context, databaseHelper);
|
||||
this.searchDatabase = new SearchDatabase(context, databaseHelper);
|
||||
this.jobDatabase = new JobDatabase(context, databaseHelper);
|
||||
this.stickerDatabase = new StickerDatabase(context, databaseHelper, attachmentSecret);
|
||||
this.storageKeyDatabase = new StorageKeyDatabase(context, databaseHelper);
|
||||
this.keyValueDatabase = new KeyValueDatabase(context, databaseHelper);
|
||||
this.megaphoneDatabase = new MegaphoneDatabase(context, databaseHelper);
|
||||
this.remappedRecordsDatabase = new RemappedRecordsDatabase(context, databaseHelper);
|
||||
this.mentionDatabase = new MentionDatabase(context, databaseHelper);
|
||||
}
|
||||
@@ -250,4 +240,12 @@ public class DatabaseFactory {
|
||||
public void triggerDatabaseAccess() {
|
||||
databaseHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
public SQLiteDatabase getRawDatabase() {
|
||||
return databaseHelper.getWritableDatabase().getSqlCipherDatabase();
|
||||
}
|
||||
|
||||
public boolean hasTable(String table) {
|
||||
return SqlUtil.tableExists(databaseHelper.getReadableDatabase().getSqlCipherDatabase(), table);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,11 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Trace
|
||||
public class DraftDatabase extends Database {
|
||||
|
||||
static final String TABLE_NAME = "drafts";
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.GroupMigrationMembershipChange;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SetUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
@@ -53,7 +52,6 @@ import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
@Trace
|
||||
public final class GroupDatabase extends Database {
|
||||
|
||||
private static final String TAG = Log.tag(GroupDatabase.class);
|
||||
|
||||
@@ -9,14 +9,12 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Trace
|
||||
public class GroupReceiptDatabase extends Database {
|
||||
|
||||
public static final String TABLE_NAME = "group_receipts";
|
||||
|
||||
@@ -28,7 +28,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.identity.IdentityRecordList;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.IdentityUtil;
|
||||
import org.whispersystems.libsignal.IdentityKey;
|
||||
@@ -39,7 +38,6 @@ import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Trace
|
||||
public class IdentityDatabase extends Database {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.ConstraintSpec;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.DependencySpec;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.FullSpec;
|
||||
import org.thoughtcrime.securesms.jobmanager.persistence.JobSpec;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Trace
|
||||
public class JobDatabase extends Database {
|
||||
public class JobDatabase extends SQLiteOpenHelper implements SignalDatabase {
|
||||
|
||||
public static String JOBS_TABLE_NAME = "job_spec";
|
||||
public static String CONSTRAINTS_TABLE_NAME = "constraint_spec";
|
||||
public static String DEPENDENCIES_TABLE_NAME = "dependency_spec";
|
||||
private static final String TAG = Log.tag(JobDatabase.class);
|
||||
|
||||
public static final String[] CREATE_TABLE = new String[] { Jobs.CREATE_TABLE,
|
||||
Constraints.CREATE_TABLE,
|
||||
Dependencies.CREATE_TABLE };
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "signal-jobmanager.db";
|
||||
|
||||
private static final class Jobs {
|
||||
private static final String TABLE_NAME = JOBS_TABLE_NAME;
|
||||
private static final String TABLE_NAME = "job_spec";
|
||||
private static final String ID = "_id";
|
||||
private static final String JOB_SPEC_ID = "job_spec_id";
|
||||
private static final String FACTORY_KEY = "factory_key";
|
||||
@@ -40,7 +41,6 @@ public class JobDatabase extends Database {
|
||||
private static final String RUN_ATTEMPT = "run_attempt";
|
||||
private static final String MAX_ATTEMPTS = "max_attempts";
|
||||
private static final String MAX_BACKOFF = "max_backoff";
|
||||
private static final String MAX_INSTANCES = "max_instances";
|
||||
private static final String LIFESPAN = "lifespan";
|
||||
private static final String SERIALIZED_DATA = "serialized_data";
|
||||
private static final String SERIALIZED_INPUT_DATA = "serialized_input_data";
|
||||
@@ -55,7 +55,6 @@ public class JobDatabase extends Database {
|
||||
RUN_ATTEMPT + " INTEGER, " +
|
||||
MAX_ATTEMPTS + " INTEGER, " +
|
||||
MAX_BACKOFF + " INTEGER, " +
|
||||
MAX_INSTANCES + " INTEGER, " +
|
||||
LIFESPAN + " INTEGER, " +
|
||||
SERIALIZED_DATA + " TEXT, " +
|
||||
SERIALIZED_INPUT_DATA + " TEXT DEFAULT NULL, " +
|
||||
@@ -63,7 +62,7 @@ public class JobDatabase extends Database {
|
||||
}
|
||||
|
||||
private static final class Constraints {
|
||||
private static final String TABLE_NAME = CONSTRAINTS_TABLE_NAME;
|
||||
private static final String TABLE_NAME = "constraint_spec";
|
||||
private static final String ID = "_id";
|
||||
private static final String JOB_SPEC_ID = "job_spec_id";
|
||||
private static final String FACTORY_KEY = "factory_key";
|
||||
@@ -75,7 +74,7 @@ public class JobDatabase extends Database {
|
||||
}
|
||||
|
||||
private static final class Dependencies {
|
||||
private static final String TABLE_NAME = DEPENDENCIES_TABLE_NAME;
|
||||
private static final String TABLE_NAME = "dependency_spec";
|
||||
private static final String ID = "_id";
|
||||
private static final String JOB_SPEC_ID = "job_spec_id";
|
||||
private static final String DEPENDS_ON_JOB_SPEC_ID = "depends_on_job_spec_id";
|
||||
@@ -87,8 +86,65 @@ public class JobDatabase extends Database {
|
||||
}
|
||||
|
||||
|
||||
public JobDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
private static volatile JobDatabase instance;
|
||||
|
||||
private final Application application;
|
||||
private final DatabaseSecret databaseSecret;
|
||||
|
||||
public static @NonNull JobDatabase getInstance(@NonNull Application context) {
|
||||
if (instance == null) {
|
||||
synchronized (JobDatabase.class) {
|
||||
if (instance == null) {
|
||||
instance = new JobDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public JobDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||
|
||||
this.application = application;
|
||||
this.databaseSecret = databaseSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
|
||||
db.execSQL(Jobs.CREATE_TABLE);
|
||||
db.execSQL(Constraints.CREATE_TABLE);
|
||||
db.execSQL(Dependencies.CREATE_TABLE);
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("job_spec")) {
|
||||
Log.i(TAG, "Found old job_spec table. Migrating data.");
|
||||
migrateJobSpecsFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("constraint_spec")) {
|
||||
Log.i(TAG, "Found old constraint_spec table. Migrating data.");
|
||||
migrateConstraintSpecsFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("dependency_spec")) {
|
||||
Log.i(TAG, "Found old dependency_spec table. Migrating data.");
|
||||
migrateDependencySpecsFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onOpen()");
|
||||
|
||||
dropTableIfPresent("job_spec");
|
||||
dropTableIfPresent("constraint_spec");
|
||||
dropTableIfPresent("dependency_spec");
|
||||
}
|
||||
|
||||
public synchronized void insertJobs(@NonNull List<FullSpec> fullSpecs) {
|
||||
@@ -96,7 +152,7 @@ public class JobDatabase extends Database {
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
@@ -116,7 +172,7 @@ public class JobDatabase extends Database {
|
||||
public synchronized @NonNull List<JobSpec> getAllJobSpecs() {
|
||||
List<JobSpec> jobs = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(Jobs.TABLE_NAME, null, null, null, null, null, Jobs.CREATE_TIME + ", " + Jobs.ID + " ASC")) {
|
||||
try (Cursor cursor = getReadableDatabase().query(Jobs.TABLE_NAME, null, null, null, null, null, Jobs.CREATE_TIME + ", " + Jobs.ID + " ASC")) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
jobs.add(jobSpecFromCursor(cursor));
|
||||
}
|
||||
@@ -132,7 +188,7 @@ public class JobDatabase extends Database {
|
||||
String query = Jobs.JOB_SPEC_ID + " = ?";
|
||||
String[] args = new String[]{ id };
|
||||
|
||||
databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
|
||||
getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
|
||||
}
|
||||
|
||||
public synchronized void updateJobAfterRetry(@NonNull String id, boolean isRunning, int runAttempt, long nextRunAttemptTime, @NonNull String serializedData) {
|
||||
@@ -145,14 +201,14 @@ public class JobDatabase extends Database {
|
||||
String query = Jobs.JOB_SPEC_ID + " = ?";
|
||||
String[] args = new String[]{ id };
|
||||
|
||||
databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
|
||||
getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, query, args);
|
||||
}
|
||||
|
||||
public synchronized void updateAllJobsToBePending() {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(Jobs.IS_RUNNING, 0);
|
||||
|
||||
databaseHelper.getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, null, null);
|
||||
getWritableDatabase().update(Jobs.TABLE_NAME, contentValues, null, null);
|
||||
}
|
||||
|
||||
public synchronized void updateJobs(@NonNull List<JobSpec> jobs) {
|
||||
@@ -160,7 +216,7 @@ public class JobDatabase extends Database {
|
||||
return;
|
||||
}
|
||||
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
@@ -177,7 +233,6 @@ public class JobDatabase extends Database {
|
||||
values.put(Jobs.RUN_ATTEMPT, job.getRunAttempt());
|
||||
values.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts());
|
||||
values.put(Jobs.MAX_BACKOFF, job.getMaxBackoff());
|
||||
values.put(Jobs.MAX_INSTANCES, job.getMaxInstancesForFactory());
|
||||
values.put(Jobs.LIFESPAN, job.getLifespan());
|
||||
values.put(Jobs.SERIALIZED_DATA, job.getSerializedData());
|
||||
values.put(Jobs.SERIALIZED_INPUT_DATA, job.getSerializedInputData());
|
||||
@@ -196,7 +251,7 @@ public class JobDatabase extends Database {
|
||||
}
|
||||
|
||||
public synchronized void deleteJobs(@NonNull List<String> jobIds) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
|
||||
@@ -219,7 +274,7 @@ public class JobDatabase extends Database {
|
||||
public synchronized @NonNull List<ConstraintSpec> getAllConstraintSpecs() {
|
||||
List<ConstraintSpec> constraints = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(Constraints.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
try (Cursor cursor = getReadableDatabase().query(Constraints.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
constraints.add(constraintSpecFromCursor(cursor));
|
||||
}
|
||||
@@ -231,7 +286,7 @@ public class JobDatabase extends Database {
|
||||
public synchronized @NonNull List<DependencySpec> getAllDependencySpecs() {
|
||||
List<DependencySpec> dependencies = new LinkedList<>();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(Dependencies.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
try (Cursor cursor = getReadableDatabase().query(Dependencies.TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
dependencies.add(dependencySpecFromCursor(cursor));
|
||||
}
|
||||
@@ -254,7 +309,6 @@ public class JobDatabase extends Database {
|
||||
contentValues.put(Jobs.RUN_ATTEMPT, job.getRunAttempt());
|
||||
contentValues.put(Jobs.MAX_ATTEMPTS, job.getMaxAttempts());
|
||||
contentValues.put(Jobs.MAX_BACKOFF, job.getMaxBackoff());
|
||||
contentValues.put(Jobs.MAX_INSTANCES, job.getMaxInstancesForFactory());
|
||||
contentValues.put(Jobs.LIFESPAN, job.getLifespan());
|
||||
contentValues.put(Jobs.SERIALIZED_DATA, job.getSerializedData());
|
||||
contentValues.put(Jobs.SERIALIZED_INPUT_DATA, job.getSerializedInputData());
|
||||
@@ -295,7 +349,6 @@ public class JobDatabase extends Database {
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_ATTEMPTS)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.MAX_BACKOFF)),
|
||||
cursor.getLong(cursor.getColumnIndexOrThrow(Jobs.LIFESPAN)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.MAX_INSTANCES)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_DATA)),
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(Jobs.SERIALIZED_INPUT_DATA)),
|
||||
cursor.getInt(cursor.getColumnIndexOrThrow(Jobs.IS_RUNNING)) == 1,
|
||||
@@ -313,4 +366,73 @@ public class JobDatabase extends Database {
|
||||
cursor.getString(cursor.getColumnIndexOrThrow(Dependencies.DEPENDS_ON_JOB_SPEC_ID)),
|
||||
false);
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getReadableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getWritableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
|
||||
return getWritableDatabase();
|
||||
}
|
||||
|
||||
private void dropTableIfPresent(@NonNull String table) {
|
||||
if (DatabaseFactory.getInstance(application).hasTable(table)) {
|
||||
Log.i(TAG, "Dropping original " + table + " table from the main database.");
|
||||
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE " + table);
|
||||
}
|
||||
}
|
||||
|
||||
private static void migrateJobSpecsFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM job_spec", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(Jobs.JOB_SPEC_ID, CursorUtil.requireString(cursor, "job_spec_id"));
|
||||
values.put(Jobs.FACTORY_KEY, CursorUtil.requireString(cursor, "factory_key"));
|
||||
values.put(Jobs.QUEUE_KEY, CursorUtil.requireString(cursor, "queue_key"));
|
||||
values.put(Jobs.CREATE_TIME, CursorUtil.requireLong(cursor, "create_time"));
|
||||
values.put(Jobs.NEXT_RUN_ATTEMPT_TIME, CursorUtil.requireLong(cursor, "next_run_attempt_time"));
|
||||
values.put(Jobs.RUN_ATTEMPT, CursorUtil.requireInt(cursor, "run_attempt"));
|
||||
values.put(Jobs.MAX_ATTEMPTS, CursorUtil.requireInt(cursor, "max_attempts"));
|
||||
values.put(Jobs.MAX_BACKOFF, CursorUtil.requireLong(cursor, "max_backoff"));
|
||||
values.put(Jobs.LIFESPAN, CursorUtil.requireLong(cursor, "lifespan"));
|
||||
values.put(Jobs.SERIALIZED_DATA, CursorUtil.requireString(cursor, "serialized_data"));
|
||||
values.put(Jobs.SERIALIZED_INPUT_DATA, CursorUtil.requireString(cursor, "serialized_input_data"));
|
||||
values.put(Jobs.IS_RUNNING, CursorUtil.requireInt(cursor, "is_running"));
|
||||
|
||||
newDb.insert(Jobs.TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void migrateConstraintSpecsFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM constraint_spec", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(Constraints.JOB_SPEC_ID, CursorUtil.requireString(cursor, "job_spec_id"));
|
||||
values.put(Constraints.FACTORY_KEY, CursorUtil.requireString(cursor, "factory_key"));
|
||||
|
||||
newDb.insert(Constraints.TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void migrateDependencySpecsFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM dependency_spec", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(Dependencies.JOB_SPEC_ID, CursorUtil.requireString(cursor, "job_spec_id"));
|
||||
values.put(Dependencies.DEPENDS_ON_JOB_SPEC_ID, CursorUtil.requireString(cursor, "depends_on_job_spec_id"));
|
||||
|
||||
newDb.insert(Dependencies.TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,44 +1,106 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteDatabaseHook;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.keyvalue.KeyValueDataSet;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
@Trace
|
||||
public class KeyValueDatabase extends Database {
|
||||
/**
|
||||
* Persists data for the {@link org.thoughtcrime.securesms.keyvalue.KeyValueStore}.
|
||||
*
|
||||
* This is it's own separate physical database, so it cannot do joins or queries with any other
|
||||
* tables.
|
||||
*/
|
||||
public class KeyValueDatabase extends SQLiteOpenHelper implements SignalDatabase {
|
||||
|
||||
public static final String TABLE_NAME = "key_value";
|
||||
private static final String TAG = Log.tag(KeyValueDatabase.class);
|
||||
|
||||
private static final String ID = "_id";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
private static final String TYPE = "type";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "signal-key-value.db";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
KEY + " TEXT UNIQUE, " +
|
||||
VALUE + " TEXT, " +
|
||||
TYPE + " INTEGER)";
|
||||
private static final String TABLE_NAME = "key_value";
|
||||
private static final String ID = "_id";
|
||||
private static final String KEY = "key";
|
||||
private static final String VALUE = "value";
|
||||
private static final String TYPE = "type";
|
||||
|
||||
KeyValueDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
KEY + " TEXT UNIQUE, " +
|
||||
VALUE + " TEXT, " +
|
||||
TYPE + " INTEGER)";
|
||||
|
||||
private static volatile KeyValueDatabase instance;
|
||||
|
||||
private final Application application;
|
||||
private final DatabaseSecret databaseSecret;
|
||||
|
||||
public static @NonNull KeyValueDatabase getInstance(@NonNull Application context) {
|
||||
if (instance == null) {
|
||||
synchronized (KeyValueDatabase.class) {
|
||||
if (instance == null) {
|
||||
instance = new KeyValueDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public KeyValueDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||
|
||||
this.application = application;
|
||||
this.databaseSecret = databaseSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
|
||||
db.execSQL(CREATE_TABLE);
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("key_value")) {
|
||||
Log.i(TAG, "Found old key_value table. Migrating data.");
|
||||
migrateDataFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onOpen()");
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("key_value")) {
|
||||
Log.i(TAG, "Dropping original key_value table from the main database.");
|
||||
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE key_value");
|
||||
}
|
||||
}
|
||||
|
||||
public @NonNull KeyValueDataSet getDataSet() {
|
||||
KeyValueDataSet dataSet = new KeyValueDataSet();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){
|
||||
try (Cursor cursor = getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)){
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)));
|
||||
String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
|
||||
Type type = Type.fromId(cursor.getInt(cursor.getColumnIndexOrThrow(TYPE)));
|
||||
String key = cursor.getString(cursor.getColumnIndexOrThrow(KEY));
|
||||
|
||||
switch (type) {
|
||||
case BLOB:
|
||||
@@ -67,7 +129,7 @@ public class KeyValueDatabase extends Database {
|
||||
}
|
||||
|
||||
public void writeDataSet(@NonNull KeyValueDataSet dataSet, @NonNull Collection<String> removes) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
@@ -115,6 +177,53 @@ public class KeyValueDatabase extends Database {
|
||||
}
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getReadableDatabase() {
|
||||
return getReadableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getWritableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
|
||||
return getWritableDatabase();
|
||||
}
|
||||
|
||||
private static void migrateDataFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM key_value", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
int type = CursorUtil.requireInt(cursor, "type");
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(KEY, CursorUtil.requireString(cursor, "key"));
|
||||
values.put(TYPE, type);
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
values.put(VALUE, CursorUtil.requireBlob(cursor, "value"));
|
||||
break;
|
||||
case 1:
|
||||
values.put(VALUE, CursorUtil.requireBoolean(cursor, "value"));
|
||||
break;
|
||||
case 2:
|
||||
values.put(VALUE, CursorUtil.requireFloat(cursor, "value"));
|
||||
break;
|
||||
case 3:
|
||||
values.put(VALUE, CursorUtil.requireInt(cursor, "value"));
|
||||
break;
|
||||
case 4:
|
||||
values.put(VALUE, CursorUtil.requireLong(cursor, "value"));
|
||||
break;
|
||||
case 5:
|
||||
values.put(VALUE, CursorUtil.requireString(cursor, "value"));
|
||||
break;
|
||||
}
|
||||
|
||||
newDb.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum Type {
|
||||
BLOB(0), BOOLEAN(1), FLOAT(2), INTEGER(3), LONG(4), STRING(5);
|
||||
|
||||
|
||||
@@ -10,12 +10,10 @@ import androidx.annotation.Nullable;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Trace
|
||||
public class MediaDatabase extends Database {
|
||||
|
||||
public static final int ALL_THREADS = -1;
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
import net.sqlcipher.database.SQLiteOpenHelper;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecret;
|
||||
import org.thoughtcrime.securesms.crypto.DatabaseSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.model.MegaphoneRecord;
|
||||
import org.thoughtcrime.securesms.megaphone.Megaphones.Event;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -21,13 +25,14 @@ import java.util.Set;
|
||||
/**
|
||||
* IMPORTANT: Writes should only be made through {@link org.thoughtcrime.securesms.megaphone.MegaphoneRepository}.
|
||||
*/
|
||||
@Trace
|
||||
public class MegaphoneDatabase extends Database {
|
||||
public class MegaphoneDatabase extends SQLiteOpenHelper implements SignalDatabase {
|
||||
|
||||
private static final String TAG = Log.tag(MegaphoneDatabase.class);
|
||||
|
||||
private static final String TABLE_NAME = "megaphone";
|
||||
private static final int DATABASE_VERSION = 1;
|
||||
private static final String DATABASE_NAME = "signal-megaphone.db";
|
||||
|
||||
private static final String TABLE_NAME = "megaphone";
|
||||
private static final String ID = "_id";
|
||||
private static final String EVENT = "event";
|
||||
private static final String SEEN_COUNT = "seen_count";
|
||||
@@ -35,19 +40,65 @@ public class MegaphoneDatabase extends Database {
|
||||
private static final String FIRST_VISIBLE = "first_visible";
|
||||
private static final String FINISHED = "finished";
|
||||
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
EVENT + " TEXT UNIQUE, " +
|
||||
SEEN_COUNT + " INTEGER, " +
|
||||
LAST_SEEN + " INTEGER, " +
|
||||
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
EVENT + " TEXT UNIQUE, " +
|
||||
SEEN_COUNT + " INTEGER, " +
|
||||
LAST_SEEN + " INTEGER, " +
|
||||
FIRST_VISIBLE + " INTEGER, " +
|
||||
FINISHED + " INTEGER)";
|
||||
FINISHED + " INTEGER)";
|
||||
|
||||
MegaphoneDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
private static volatile MegaphoneDatabase instance;
|
||||
|
||||
private final Application application;
|
||||
private final DatabaseSecret databaseSecret;
|
||||
|
||||
public static @NonNull MegaphoneDatabase getInstance(@NonNull Application context) {
|
||||
if (instance == null) {
|
||||
synchronized (MegaphoneDatabase.class) {
|
||||
if (instance == null) {
|
||||
instance = new MegaphoneDatabase(context, DatabaseSecretProvider.getOrCreateDatabaseSecret(context));
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public MegaphoneDatabase(@NonNull Application application, @NonNull DatabaseSecret databaseSecret) {
|
||||
super(application, DATABASE_NAME, null, DATABASE_VERSION, new SqlCipherDatabaseHook());
|
||||
|
||||
this.application = application;
|
||||
this.databaseSecret = databaseSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onCreate()");
|
||||
|
||||
db.execSQL(CREATE_TABLE);
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("megaphone")) {
|
||||
Log.i(TAG, "Found old megaphone table. Migrating data.");
|
||||
migrateDataFromPreviousDatabase(DatabaseFactory.getInstance(application).getRawDatabase(), db);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.i(TAG, "onUpgrade(" + oldVersion + ", " + newVersion + ")");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(SQLiteDatabase db) {
|
||||
Log.i(TAG, "onOpen()");
|
||||
|
||||
if (DatabaseFactory.getInstance(application).hasTable("megaphone")) {
|
||||
Log.i(TAG, "Dropping original megaphone table from the main database.");
|
||||
DatabaseFactory.getInstance(application).getRawDatabase().rawExecSQL("DROP TABLE megaphone");
|
||||
}
|
||||
}
|
||||
|
||||
public void insert(@NonNull Collection<Event> events) {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
@@ -65,14 +116,14 @@ public class MegaphoneDatabase extends Database {
|
||||
}
|
||||
|
||||
public @NonNull List<MegaphoneRecord> getAllAndDeleteMissing() {
|
||||
SQLiteDatabase db = databaseHelper.getWritableDatabase();
|
||||
SQLiteDatabase db = getWritableDatabase();
|
||||
List<MegaphoneRecord> records = new ArrayList<>();
|
||||
|
||||
db.beginTransaction();
|
||||
try {
|
||||
Set<String> missingKeys = new HashSet<>();
|
||||
|
||||
try (Cursor cursor = databaseHelper.getReadableDatabase().query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||
try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
|
||||
while (cursor != null && cursor.moveToNext()) {
|
||||
String event = cursor.getString(cursor.getColumnIndexOrThrow(EVENT));
|
||||
int seenCount = cursor.getInt(cursor.getColumnIndexOrThrow(SEEN_COUNT));
|
||||
@@ -93,7 +144,7 @@ public class MegaphoneDatabase extends Database {
|
||||
String query = EVENT + " = ?";
|
||||
String[] args = new String[]{missing};
|
||||
|
||||
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
|
||||
db.delete(TABLE_NAME, query, args);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
@@ -111,7 +162,7 @@ public class MegaphoneDatabase extends Database {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(FIRST_VISIBLE, time);
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
}
|
||||
|
||||
public void markSeen(@NonNull Event event, int seenCount, long lastSeen) {
|
||||
@@ -122,7 +173,7 @@ public class MegaphoneDatabase extends Database {
|
||||
values.put(SEEN_COUNT, seenCount);
|
||||
values.put(LAST_SEEN, lastSeen);
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
}
|
||||
|
||||
public void markFinished(@NonNull Event event) {
|
||||
@@ -132,13 +183,38 @@ public class MegaphoneDatabase extends Database {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(FINISHED, 1);
|
||||
|
||||
databaseHelper.getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
getWritableDatabase().update(TABLE_NAME, values, query, args);
|
||||
}
|
||||
|
||||
public void delete(@NonNull Event event) {
|
||||
String query = EVENT + " = ?";
|
||||
String[] args = new String[]{event.getKey()};
|
||||
|
||||
databaseHelper.getWritableDatabase().delete(TABLE_NAME, query, args);
|
||||
getWritableDatabase().delete(TABLE_NAME, query, args);
|
||||
}
|
||||
|
||||
private @NonNull SQLiteDatabase getWritableDatabase() {
|
||||
return getWritableDatabase(databaseSecret.asString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull SQLiteDatabase getSqlCipherDatabase() {
|
||||
return getWritableDatabase();
|
||||
}
|
||||
|
||||
private static void migrateDataFromPreviousDatabase(@NonNull SQLiteDatabase oldDb, @NonNull SQLiteDatabase newDb) {
|
||||
try (Cursor cursor = oldDb.rawQuery("SELECT * FROM megaphone", null)) {
|
||||
while (cursor.moveToNext()) {
|
||||
ContentValues values = new ContentValues();
|
||||
|
||||
values.put(EVENT, CursorUtil.requireString(cursor, "event"));
|
||||
values.put(SEEN_COUNT, CursorUtil.requireInt(cursor, "seen_count"));
|
||||
values.put(LAST_SEEN, CursorUtil.requireLong(cursor, "last_seen"));
|
||||
values.put(FIRST_VISIBLE, CursorUtil.requireLong(cursor, "first_visible"));
|
||||
values.put(FINISHED, CursorUtil.requireInt(cursor, "finished"));
|
||||
|
||||
newDb.insert(TABLE_NAME, null, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import com.annimon.stream.Stream;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.Mention;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
|
||||
@@ -23,7 +22,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Trace
|
||||
public class MentionDatabase extends Database {
|
||||
|
||||
static final String TABLE_NAME = "mention";
|
||||
|
||||
@@ -149,6 +149,7 @@ public abstract class MessageDatabase extends Database implements MmsSmsColumns
|
||||
public abstract Optional<InsertResult> insertMessageInbox(IncomingMediaMessage retrieved, String contentLocation, long threadId) throws MmsException;
|
||||
public abstract Pair<Long, Long> insertMessageInbox(@NonNull NotificationInd notification, int subscriptionId);
|
||||
public abstract Optional<InsertResult> insertSecureDecryptedMessageInbox(IncomingMediaMessage retrieved, long threadId) throws MmsException;
|
||||
public abstract @NonNull InsertResult insertDecryptionFailedMessage(@NonNull RecipientId recipientId, long senderDeviceId, long sentTimestamp);
|
||||
public abstract long insertMessageOutbox(long threadId, OutgoingTextMessage message, boolean forceSms, long date, InsertListener insertListener);
|
||||
public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException;
|
||||
public abstract long insertMessageOutbox(@NonNull OutgoingMediaMessage message, long threadId, boolean forceSms, int defaultReceiptStatus, @Nullable SmsDatabase.InsertListener insertListener) throws MmsException;
|
||||
|
||||
@@ -72,7 +72,6 @@ import org.thoughtcrime.securesms.revealable.ViewOnceExpirationInfo;
|
||||
import org.thoughtcrime.securesms.revealable.ViewOnceUtil;
|
||||
import org.thoughtcrime.securesms.sms.IncomingTextMessage;
|
||||
import org.thoughtcrime.securesms.sms.OutgoingTextMessage;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.thoughtcrime.securesms.util.JsonUtils;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
@@ -96,7 +95,6 @@ import java.util.UUID;
|
||||
|
||||
import static org.thoughtcrime.securesms.contactshare.Contact.Avatar;
|
||||
|
||||
@Trace
|
||||
public class MmsDatabase extends MessageDatabase {
|
||||
|
||||
private static final String TAG = MmsDatabase.class.getSimpleName();
|
||||
@@ -1385,6 +1383,11 @@ public class MmsDatabase extends MessageDatabase {
|
||||
return new Pair<>(messageId, threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull InsertResult insertDecryptionFailedMessage(@NonNull RecipientId recipientId, long senderDeviceId, long sentTimestamp) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markIncomingNotificationReceived(long threadId) {
|
||||
notifyConversationListeners(threadId);
|
||||
|
||||
@@ -32,7 +32,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.MessageRecord;
|
||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
import org.whispersystems.libsignal.util.Pair;
|
||||
|
||||
@@ -44,7 +43,6 @@ import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Trace
|
||||
public class MmsSmsDatabase extends Database {
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
|
||||
@@ -10,7 +10,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
import org.whispersystems.signalservice.api.messages.SignalServiceEnvelope;
|
||||
@@ -19,7 +18,6 @@ import org.whispersystems.signalservice.internal.util.Util;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Trace
|
||||
public class PushDatabase extends Database {
|
||||
|
||||
private static final String TAG = PushDatabase.class.getSimpleName();
|
||||
|
||||
@@ -10,11 +10,15 @@ import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.annimon.stream.Stream;
|
||||
import com.google.protobuf.ByteString;
|
||||
import com.google.protobuf.InvalidProtocolBufferException;
|
||||
|
||||
import net.sqlcipher.SQLException;
|
||||
import net.sqlcipher.database.SQLiteConstraintException;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.storageservice.protos.groups.local.DecryptedGroup;
|
||||
import org.signal.zkgroup.InvalidInputException;
|
||||
import org.signal.zkgroup.groups.GroupMasterKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKey;
|
||||
import org.signal.zkgroup.profiles.ProfileKeyCredential;
|
||||
@@ -25,6 +29,8 @@ import org.thoughtcrime.securesms.database.IdentityDatabase.IdentityRecord;
|
||||
import org.thoughtcrime.securesms.database.IdentityDatabase.VerifiedStatus;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.DeviceLastResetTime;
|
||||
import org.thoughtcrime.securesms.database.model.databaseprotos.ProfileKeyCredentialColumnData;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.groups.GroupId;
|
||||
import org.thoughtcrime.securesms.groups.v2.ProfileKeySet;
|
||||
@@ -39,7 +45,6 @@ import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.RecordUpdate;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncModels;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.Base64;
|
||||
import org.thoughtcrime.securesms.util.Bitmask;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
@@ -79,7 +84,6 @@ import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Trace
|
||||
public class RecipientDatabase extends Database {
|
||||
|
||||
private static final String TAG = RecipientDatabase.class.getSimpleName();
|
||||
@@ -126,6 +130,7 @@ public class RecipientDatabase extends Database {
|
||||
private static final String MENTION_SETTING = "mention_setting";
|
||||
private static final String STORAGE_PROTO = "storage_proto";
|
||||
private static final String LAST_GV1_MIGRATE_REMINDER = "last_gv1_migrate_reminder";
|
||||
private static final String LAST_SESSION_RESET = "last_session_reset";
|
||||
|
||||
public static final String SEARCH_PROFILE_NAME = "search_signal_profile";
|
||||
private static final String SORT_NAME = "sort_name";
|
||||
@@ -344,7 +349,8 @@ public class RecipientDatabase extends Database {
|
||||
MENTION_SETTING + " INTEGER DEFAULT " + MentionSetting.ALWAYS_NOTIFY.getId() + ", " +
|
||||
STORAGE_PROTO + " TEXT DEFAULT NULL, " +
|
||||
CAPABILITIES + " INTEGER DEFAULT 0, " +
|
||||
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0);";
|
||||
LAST_GV1_MIGRATE_REMINDER + " INTEGER DEFAULT 0, " +
|
||||
LAST_SESSION_RESET + " BLOB DEFAULT NULL);";
|
||||
|
||||
private static final String INSIGHTS_INVITEE_LIST = "SELECT " + TABLE_NAME + "." + ID +
|
||||
" FROM " + TABLE_NAME +
|
||||
@@ -1219,7 +1225,11 @@ public class RecipientDatabase extends Database {
|
||||
}
|
||||
|
||||
static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor) {
|
||||
long id = CursorUtil.requireLong(cursor, ID);
|
||||
return getRecipientSettings(context, cursor, ID);
|
||||
}
|
||||
|
||||
static @NonNull RecipientSettings getRecipientSettings(@NonNull Context context, @NonNull Cursor cursor, @NonNull String idColumnName) {
|
||||
long id = CursorUtil.requireLong(cursor, idColumnName);
|
||||
UUID uuid = UuidUtil.parseOrNull(CursorUtil.requireString(cursor, UUID));
|
||||
String username = CursorUtil.requireString(cursor, USERNAME);
|
||||
String e164 = CursorUtil.requireString(cursor, PHONE);
|
||||
@@ -1255,9 +1265,9 @@ public class RecipientDatabase extends Database {
|
||||
String storageKeyRaw = CursorUtil.requireString(cursor, STORAGE_SERVICE_ID);
|
||||
int mentionSettingId = CursorUtil.requireInt(cursor, MENTION_SETTING);
|
||||
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
byte[] profileKeyCredential = null;
|
||||
MaterialColor color;
|
||||
byte[] profileKey = null;
|
||||
ProfileKeyCredential profileKeyCredential = null;
|
||||
|
||||
try {
|
||||
color = serializedColor == null ? null : MaterialColor.fromSerialized(serializedColor);
|
||||
@@ -1276,10 +1286,17 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
if (profileKeyCredentialString != null) {
|
||||
try {
|
||||
profileKeyCredential = Base64.decode(profileKeyCredentialString);
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
profileKeyCredential = null;
|
||||
byte[] columnDataBytes = Base64.decode(profileKeyCredentialString);
|
||||
|
||||
ProfileKeyCredentialColumnData columnData = ProfileKeyCredentialColumnData.parseFrom(columnDataBytes);
|
||||
|
||||
if (Arrays.equals(columnData.getProfileKey().toByteArray(), profileKey)) {
|
||||
profileKeyCredential = new ProfileKeyCredential(columnData.getProfileKeyCredential().toByteArray());
|
||||
} else {
|
||||
Log.i(TAG, "Out of date profile key credential data ignored on read");
|
||||
}
|
||||
} catch (InvalidInputException | IOException e) {
|
||||
Log.w(TAG, "Profile key credential column data could not be read", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1499,6 +1516,30 @@ public class RecipientDatabase extends Database {
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
public void setLastSessionResetTime(@NonNull RecipientId id, DeviceLastResetTime lastResetTime) {
|
||||
ContentValues values = new ContentValues(1);
|
||||
values.put(LAST_SESSION_RESET, lastResetTime.toByteArray());
|
||||
update(id, values);
|
||||
}
|
||||
|
||||
public @NonNull DeviceLastResetTime getLastSessionResetTimes(@NonNull RecipientId id) {
|
||||
SQLiteDatabase db = databaseHelper.getReadableDatabase();
|
||||
|
||||
try (Cursor cursor = db.query(TABLE_NAME, new String[] {LAST_SESSION_RESET}, ID_WHERE, SqlUtil.buildArgs(id), null, null, null)) {
|
||||
if (cursor.moveToFirst()) {
|
||||
try {
|
||||
return DeviceLastResetTime.parseFrom(CursorUtil.requireBlob(cursor, LAST_SESSION_RESET));
|
||||
} catch (InvalidProtocolBufferException | SQLException e) {
|
||||
Log.w(TAG, e);
|
||||
return DeviceLastResetTime.newBuilder().build();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DeviceLastResetTime.newBuilder().build();
|
||||
}
|
||||
|
||||
public void setCapabilities(@NonNull RecipientId id, @NonNull SignalServiceProfile.Capabilities capabilities) {
|
||||
long value = 0;
|
||||
|
||||
@@ -1579,23 +1620,30 @@ public class RecipientDatabase extends Database {
|
||||
/**
|
||||
* Updates the profile key credential as long as the profile key matches.
|
||||
*/
|
||||
public void setProfileKeyCredential(@NonNull RecipientId id,
|
||||
@NonNull ProfileKey profileKey,
|
||||
@NonNull ProfileKeyCredential profileKeyCredential)
|
||||
public boolean setProfileKeyCredential(@NonNull RecipientId id,
|
||||
@NonNull ProfileKey profileKey,
|
||||
@NonNull ProfileKeyCredential profileKeyCredential)
|
||||
{
|
||||
String selection = ID + " = ? AND " + PROFILE_KEY + " = ?";
|
||||
String[] args = new String[]{id.serialize(), Base64.encodeBytes(profileKey.serialize())};
|
||||
ContentValues values = new ContentValues(1);
|
||||
|
||||
values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(profileKeyCredential.serialize()));
|
||||
ProfileKeyCredentialColumnData columnData = ProfileKeyCredentialColumnData.newBuilder()
|
||||
.setProfileKey(ByteString.copyFrom(profileKey.serialize()))
|
||||
.setProfileKeyCredential(ByteString.copyFrom(profileKeyCredential.serialize()))
|
||||
.build();
|
||||
|
||||
values.put(PROFILE_KEY_CREDENTIAL, Base64.encodeBytes(columnData.toByteArray()));
|
||||
|
||||
SqlUtil.Query updateQuery = SqlUtil.buildTrueUpdateQuery(selection, args, values);
|
||||
|
||||
if (update(updateQuery, values)) {
|
||||
// TODO [greyson] If we sync this in future, mark dirty
|
||||
//markDirty(id, DirtyState.UPDATE);
|
||||
boolean updated = update(updateQuery, values);
|
||||
|
||||
if (updated) {
|
||||
Recipient.live(id).refresh();
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
|
||||
private void clearProfileKeyCredential(@NonNull RecipientId id) {
|
||||
@@ -2594,7 +2642,7 @@ public class RecipientDatabase extends Database {
|
||||
|
||||
private static void updateProfileValuesForMerge(@NonNull ContentValues values, @NonNull RecipientSettings settings) {
|
||||
values.put(PROFILE_KEY, settings.getProfileKey() != null ? Base64.encodeBytes(settings.getProfileKey()) : null);
|
||||
values.put(PROFILE_KEY_CREDENTIAL, settings.getProfileKeyCredential() != null ? Base64.encodeBytes(settings.getProfileKeyCredential()) : null);
|
||||
values.putNull(PROFILE_KEY_CREDENTIAL);
|
||||
values.put(SIGNAL_PROFILE_AVATAR, settings.getProfileAvatar());
|
||||
values.put(PROFILE_GIVEN_NAME, settings.getProfileName().getGivenName());
|
||||
values.put(PROFILE_FAMILY_NAME, settings.getProfileName().getFamilyName());
|
||||
@@ -2716,7 +2764,7 @@ public class RecipientDatabase extends Database {
|
||||
private final int expireMessages;
|
||||
private final RegisteredState registered;
|
||||
private final byte[] profileKey;
|
||||
private final byte[] profileKeyCredential;
|
||||
private final ProfileKeyCredential profileKeyCredential;
|
||||
private final String systemDisplayName;
|
||||
private final String systemContactPhoto;
|
||||
private final String systemPhoneLabel;
|
||||
@@ -2755,7 +2803,7 @@ public class RecipientDatabase extends Database {
|
||||
int expireMessages,
|
||||
@NonNull RegisteredState registered,
|
||||
@Nullable byte[] profileKey,
|
||||
@Nullable byte[] profileKeyCredential,
|
||||
@Nullable ProfileKeyCredential profileKeyCredential,
|
||||
@Nullable String systemDisplayName,
|
||||
@Nullable String systemContactPhoto,
|
||||
@Nullable String systemPhoneLabel,
|
||||
@@ -2890,7 +2938,7 @@ public class RecipientDatabase extends Database {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public @Nullable byte[] getProfileKeyCredential() {
|
||||
public @Nullable ProfileKeyCredential getProfileKeyCredential() {
|
||||
return profileKeyCredential;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import net.sqlcipher.Cursor;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.CursorUtil;
|
||||
|
||||
import java.util.HashMap;
|
||||
@@ -20,7 +19,6 @@ import java.util.Map;
|
||||
/**
|
||||
* The backing datastore for {@link RemappedRecords}. See that class for more details.
|
||||
*/
|
||||
@Trace
|
||||
public class RemappedRecordsDatabase extends Database {
|
||||
|
||||
public static final String[] CREATE_TABLE = { Recipients.CREATE_TABLE,
|
||||
|
||||
@@ -3,17 +3,14 @@ package org.thoughtcrime.securesms.database;
|
||||
|
||||
import android.content.ContentValues;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.google.android.gms.vision.Tracker;
|
||||
|
||||
import net.sqlcipher.Cursor;
|
||||
import net.sqlcipher.SQLException;
|
||||
import net.sqlcipher.database.SQLiteQueryStats;
|
||||
import net.sqlcipher.database.SQLiteStatement;
|
||||
import net.sqlcipher.database.SQLiteTransactionListener;
|
||||
|
||||
import org.thoughtcrime.securesms.tracing.Tracer;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
|
||||
@@ -10,12 +10,10 @@ import com.annimon.stream.Stream;
|
||||
import net.sqlcipher.Cursor;
|
||||
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
|
||||
/**
|
||||
* Contains all databases necessary for full-text search (FTS).
|
||||
*/
|
||||
@Trace
|
||||
public class SearchDatabase extends Database {
|
||||
|
||||
public static final String SMS_FTS_TABLE_NAME = "sms_fts";
|
||||
|
||||
@@ -13,7 +13,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
||||
import org.thoughtcrime.securesms.recipients.RecipientId;
|
||||
import org.thoughtcrime.securesms.tracing.Trace;
|
||||
import org.thoughtcrime.securesms.util.SqlUtil;
|
||||
import org.whispersystems.libsignal.state.SessionRecord;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
@@ -22,7 +21,6 @@ import java.io.IOException;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
@Trace
|
||||
public class SessionDatabase extends Database {
|
||||
|
||||
private static final String TAG = SessionDatabase.class.getSimpleName();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user