mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-05-08 09:18:39 +01:00
Add UI support for configuring a proxy.
This commit is contained in:
@@ -254,6 +254,14 @@
|
|||||||
<data android:scheme="https"
|
<data android:scheme="https"
|
||||||
android:host="signal.group"/>
|
android:host="signal.group"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="https"
|
||||||
|
android:host="signal.tube" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
<activity android:name=".conversation.ConversationActivity"
|
<activity android:name=".conversation.ConversationActivity"
|
||||||
@@ -349,7 +357,8 @@
|
|||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||||
|
|
||||||
<activity android:name=".ApplicationPreferencesActivity"
|
<activity android:name=".ApplicationPreferencesActivity"
|
||||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
|
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
<category android:name="android.intent.category.NOTIFICATION_PREFERENCES" />
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
package org.thoughtcrime.securesms;
|
package org.thoughtcrime.securesms;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.hardware.SensorManager;
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -33,7 +32,6 @@ import com.google.android.gms.security.ProviderInstaller;
|
|||||||
|
|
||||||
import org.conscrypt.Conscrypt;
|
import org.conscrypt.Conscrypt;
|
||||||
import org.signal.aesgcmprovider.AesGcmProvider;
|
import org.signal.aesgcmprovider.AesGcmProvider;
|
||||||
import org.signal.core.util.ShakeDetector;
|
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.AndroidLogger;
|
import org.signal.core.util.logging.AndroidLogger;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
@@ -46,7 +44,6 @@ import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper;
|
|||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
|
||||||
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
import org.thoughtcrime.securesms.gcm.FcmJobService;
|
||||||
import org.thoughtcrime.securesms.jobmanager.JobManager;
|
|
||||||
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
|
||||||
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
|
||||||
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
import org.thoughtcrime.securesms.jobs.GroupV1MigrationJob;
|
||||||
@@ -71,7 +68,6 @@ import org.thoughtcrime.securesms.service.LocalBackupListener;
|
|||||||
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
import org.thoughtcrime.securesms.service.RotateSenderCertificateListener;
|
||||||
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
import org.thoughtcrime.securesms.service.RotateSignedPreKeyListener;
|
||||||
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
import org.thoughtcrime.securesms.service.UpdateApkRefreshListener;
|
||||||
import org.thoughtcrime.securesms.shakereport.ShakeToReport;
|
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||||
import org.thoughtcrime.securesms.util.AppStartup;
|
import org.thoughtcrime.securesms.util.AppStartup;
|
||||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||||
@@ -145,6 +141,12 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
.addBlocking("proxy-init", () -> {
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
Log.w(TAG, "Proxy detected. Enabling Conscrypt.setUseEngineSocketByDefault()");
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(true);
|
||||||
|
}
|
||||||
|
})
|
||||||
.addNonBlocking(this::initializeRevealableMessageManager)
|
.addNonBlocking(this::initializeRevealableMessageManager)
|
||||||
.addNonBlocking(this::initializeGcmCheck)
|
.addNonBlocking(this::initializeGcmCheck)
|
||||||
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
.addNonBlocking(this::initializeSignedPreKeyCheck)
|
||||||
@@ -272,7 +274,7 @@ public class ApplicationContext extends MultiDexApplication implements DefaultLi
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void initializeAppDependencies() {
|
private void initializeAppDependencies() {
|
||||||
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this, new SignalServiceNetworkAccess(this)));
|
ApplicationDependencies.init(this, new ApplicationDependencyProvider(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initializeFirstEverAppLaunch() {
|
private void initializeFirstEverAppLaunch() {
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import org.thoughtcrime.securesms.preferences.BackupsPreferenceFragment;
|
|||||||
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.ChatsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.CorrectedPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.DataAndStoragePreferenceFragment;
|
||||||
|
import org.thoughtcrime.securesms.preferences.EditProxyFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.NotificationsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
import org.thoughtcrime.securesms.preferences.SmsMmsPreferenceFragment;
|
||||||
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
import org.thoughtcrime.securesms.preferences.widgets.ProfilePreference;
|
||||||
@@ -67,6 +68,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
{
|
{
|
||||||
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
public static final String LAUNCH_TO_BACKUPS_FRAGMENT = "launch.to.backups.fragment";
|
||||||
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
public static final String LAUNCH_TO_HELP_FRAGMENT = "launch.to.help.fragment";
|
||||||
|
public static final String LAUNCH_TO_PROXY_FRAGMENT = "launch.to.proxy.fragment";
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
@SuppressWarnings("unused")
|
||||||
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
private static final String TAG = ApplicationPreferencesActivity.class.getSimpleName();
|
||||||
@@ -108,6 +110,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActivity
|
|||||||
initFragment(android.R.id.content, new BackupsPreferenceFragment());
|
initFragment(android.R.id.content, new BackupsPreferenceFragment());
|
||||||
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_HELP_FRAGMENT, false)) {
|
||||||
initFragment(android.R.id.content, new HelpFragment());
|
initFragment(android.R.id.content, new HelpFragment());
|
||||||
|
} else if (getIntent() != null && getIntent().getBooleanExtra(LAUNCH_TO_PROXY_FRAGMENT, false)) {
|
||||||
|
initFragment(android.R.id.content, EditProxyFragment.newInstance());
|
||||||
} else if (icicle == null) {
|
} else if (icicle == null) {
|
||||||
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
initFragment(android.R.id.content, new ApplicationPreferenceFragment());
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
navigator.onCreate(savedInstanceState);
|
navigator.onCreate(savedInstanceState);
|
||||||
|
|
||||||
handleGroupLinkInIntent(getIntent());
|
handleGroupLinkInIntent(getIntent());
|
||||||
|
handleProxyInIntent(getIntent());
|
||||||
|
|
||||||
CachedInflater.from(this).clear();
|
CachedInflater.from(this).clear();
|
||||||
}
|
}
|
||||||
@@ -56,6 +57,7 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
protected void onNewIntent(Intent intent) {
|
protected void onNewIntent(Intent intent) {
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
handleGroupLinkInIntent(intent);
|
handleGroupLinkInIntent(intent);
|
||||||
|
handleProxyInIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -95,4 +97,11 @@ public class MainActivity extends PassphraseRequiredActivity {
|
|||||||
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
CommunicationActions.handlePotentialGroupLinkUrl(this, data.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleProxyInIntent(Intent intent) {
|
||||||
|
Uri data = intent.getData();
|
||||||
|
if (data != null) {
|
||||||
|
CommunicationActions.handlePotentialProxyLinkUrl(this, data.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -131,6 +131,7 @@ import org.thoughtcrime.securesms.util.HtmlUtil;
|
|||||||
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
import org.thoughtcrime.securesms.util.RemoteDeleteUtil;
|
||||||
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
import org.thoughtcrime.securesms.util.SaveAttachmentTask;
|
||||||
import org.thoughtcrime.securesms.util.SetUtil;
|
import org.thoughtcrime.securesms.util.SetUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.SignalProxyUtil;
|
||||||
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||||
@@ -336,6 +337,7 @@ public class ConversationFragment extends LoggingFragment {
|
|||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
initializeTypingObserver();
|
initializeTypingObserver();
|
||||||
|
SignalProxyUtil.startListeningToWebsocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -1429,7 +1431,8 @@ public class ConversationFragment extends LoggingFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onUrlClicked(@NonNull String url) {
|
public boolean onUrlClicked(@NonNull String url) {
|
||||||
return CommunicationActions.handlePotentialGroupLinkUrl(requireActivity(), url);
|
return CommunicationActions.handlePotentialGroupLinkUrl(requireActivity(), url) ||
|
||||||
|
CommunicationActions.handlePotentialProxyLinkUrl(requireActivity(), url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
+38
@@ -72,6 +72,7 @@ import org.greenrobot.eventbus.Subscribe;
|
|||||||
import org.greenrobot.eventbus.ThreadMode;
|
import org.greenrobot.eventbus.ThreadMode;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||||
import org.thoughtcrime.securesms.MainFragment;
|
import org.thoughtcrime.securesms.MainFragment;
|
||||||
import org.thoughtcrime.securesms.MainNavigator;
|
import org.thoughtcrime.securesms.MainNavigator;
|
||||||
import org.thoughtcrime.securesms.NewConversationActivity;
|
import org.thoughtcrime.securesms.NewConversationActivity;
|
||||||
@@ -108,6 +109,7 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneActionController;
|
|||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneViewBuilder;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||||
import org.thoughtcrime.securesms.mms.GlideApp;
|
import org.thoughtcrime.securesms.mms.GlideApp;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
@@ -118,6 +120,7 @@ import org.thoughtcrime.securesms.util.AppStartup;
|
|||||||
import org.thoughtcrime.securesms.util.AvatarUtil;
|
import org.thoughtcrime.securesms.util.AvatarUtil;
|
||||||
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
import org.thoughtcrime.securesms.util.PlayStoreUtil;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.SignalProxyUtil;
|
||||||
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
import org.thoughtcrime.securesms.util.SnapToTopDataObserver;
|
||||||
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
import org.thoughtcrime.securesms.util.StickyHeaderDecoration;
|
||||||
import org.thoughtcrime.securesms.util.Stopwatch;
|
import org.thoughtcrime.securesms.util.Stopwatch;
|
||||||
@@ -161,6 +164,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
private PulsingFloatingActionButton fab;
|
private PulsingFloatingActionButton fab;
|
||||||
private PulsingFloatingActionButton cameraFab;
|
private PulsingFloatingActionButton cameraFab;
|
||||||
private Stub<SearchToolbar> searchToolbar;
|
private Stub<SearchToolbar> searchToolbar;
|
||||||
|
private ImageView proxyStatus;
|
||||||
private ImageView searchAction;
|
private ImageView searchAction;
|
||||||
private View toolbarShadow;
|
private View toolbarShadow;
|
||||||
private ConversationListViewModel viewModel;
|
private ConversationListViewModel viewModel;
|
||||||
@@ -199,6 +203,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
searchEmptyState = view.findViewById(R.id.search_no_results);
|
searchEmptyState = view.findViewById(R.id.search_no_results);
|
||||||
searchAction = view.findViewById(R.id.search_action);
|
searchAction = view.findViewById(R.id.search_action);
|
||||||
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
toolbarShadow = view.findViewById(R.id.conversation_list_toolbar_shadow);
|
||||||
|
proxyStatus = view.findViewById(R.id.conversation_list_proxy_status);
|
||||||
reminderView = new Stub<>(view.findViewById(R.id.reminder));
|
reminderView = new Stub<>(view.findViewById(R.id.reminder));
|
||||||
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
|
emptyState = new Stub<>(view.findViewById(R.id.empty_state));
|
||||||
searchToolbar = new Stub<>(view.findViewById(R.id.search_toolbar));
|
searchToolbar = new Stub<>(view.findViewById(R.id.search_toolbar));
|
||||||
@@ -208,6 +213,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
toolbar.setVisibility(View.VISIBLE);
|
toolbar.setVisibility(View.VISIBLE);
|
||||||
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
|
||||||
|
|
||||||
|
proxyStatus.setOnClickListener(v -> onProxyStatusClicked());
|
||||||
|
|
||||||
fab.show();
|
fab.show();
|
||||||
cameraFab.show();
|
cameraFab.show();
|
||||||
|
|
||||||
@@ -262,6 +269,8 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
if (activeAdapter != null) {
|
if (activeAdapter != null) {
|
||||||
activeAdapter.notifyDataSetChanged();
|
activeAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SignalProxyUtil.startListeningToWebsocket();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -543,6 +552,7 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
viewModel.getMegaphone().observe(getViewLifecycleOwner(), this::onMegaphoneChanged);
|
viewModel.getMegaphone().observe(getViewLifecycleOwner(), this::onMegaphoneChanged);
|
||||||
viewModel.getConversationList().observe(getViewLifecycleOwner(), this::onSubmitList);
|
viewModel.getConversationList().observe(getViewLifecycleOwner(), this::onSubmitList);
|
||||||
viewModel.hasNoConversations().observe(getViewLifecycleOwner(), this::updateEmptyState);
|
viewModel.hasNoConversations().observe(getViewLifecycleOwner(), this::updateEmptyState);
|
||||||
|
viewModel.getPipeState().observe(getViewLifecycleOwner(), this::updateProxyStatus);
|
||||||
|
|
||||||
visibilityLifecycleObserver = new DefaultLifecycleObserver() {
|
visibilityLifecycleObserver = new DefaultLifecycleObserver() {
|
||||||
@Override
|
@Override
|
||||||
@@ -856,6 +866,34 @@ public class ConversationListFragment extends MainFragment implements ActionMode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateProxyStatus(@NonNull PipeConnectivityListener.State state) {
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
proxyStatus.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case CONNECTING:
|
||||||
|
case DISCONNECTED:
|
||||||
|
proxyStatus.setImageResource(R.drawable.ic_proxy_connecting_24);
|
||||||
|
break;
|
||||||
|
case CONNECTED:
|
||||||
|
proxyStatus.setImageResource(R.drawable.ic_proxy_connected_24);
|
||||||
|
break;
|
||||||
|
case FAILURE:
|
||||||
|
proxyStatus.setImageResource(R.drawable.ic_proxy_failed_24);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
proxyStatus.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onProxyStatusClicked() {
|
||||||
|
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
|
||||||
|
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_PROXY_FRAGMENT, true);
|
||||||
|
|
||||||
|
startActivity(intent);
|
||||||
|
}
|
||||||
|
|
||||||
protected void onPostSubmitList(int conversationCount) {
|
protected void onPostSubmitList(int conversationCount) {
|
||||||
if (conversationCount >= 6 && (SignalStore.onboarding().shouldShowInviteFriends() || SignalStore.onboarding().shouldShowNewGroup())) {
|
if (conversationCount >= 6 && (SignalStore.onboarding().shouldShowInviteFriends() || SignalStore.onboarding().shouldShowNewGroup())) {
|
||||||
SignalStore.onboarding().clearAll();
|
SignalStore.onboarding().clearAll();
|
||||||
|
|||||||
+5
@@ -21,6 +21,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
|||||||
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
import org.thoughtcrime.securesms.megaphone.Megaphone;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
||||||
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
import org.thoughtcrime.securesms.megaphone.Megaphones;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
import org.thoughtcrime.securesms.search.SearchRepository;
|
import org.thoughtcrime.securesms.search.SearchRepository;
|
||||||
import org.thoughtcrime.securesms.util.Debouncer;
|
import org.thoughtcrime.securesms.util.Debouncer;
|
||||||
import org.thoughtcrime.securesms.util.Util;
|
import org.thoughtcrime.securesms.util.Util;
|
||||||
@@ -100,6 +101,10 @@ class ConversationListViewModel extends ViewModel {
|
|||||||
return pagedData.getController();
|
return pagedData.getController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<PipeConnectivityListener.State> getPipeState() {
|
||||||
|
return ApplicationDependencies.getPipeListener().getState();
|
||||||
|
}
|
||||||
|
|
||||||
public int getPinnedCount() {
|
public int getPinnedCount() {
|
||||||
return pinnedCount;
|
return pinnedCount;
|
||||||
}
|
}
|
||||||
|
|||||||
+28
-3
@@ -18,6 +18,7 @@ import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
|||||||
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
import org.thoughtcrime.securesms.recipients.LiveRecipientCache;
|
||||||
@@ -79,9 +80,9 @@ public class ApplicationDependencies {
|
|||||||
throw new IllegalStateException("Already initialized!");
|
throw new IllegalStateException("Already initialized!");
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationDependencies.application = application;
|
ApplicationDependencies.application = application;
|
||||||
ApplicationDependencies.provider = provider;
|
ApplicationDependencies.provider = provider;
|
||||||
ApplicationDependencies.messageNotifier = provider.provideMessageNotifier();
|
ApplicationDependencies.messageNotifier = provider.provideMessageNotifier();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +90,10 @@ public class ApplicationDependencies {
|
|||||||
return application;
|
return application;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull PipeConnectivityListener getPipeListener() {
|
||||||
|
return provider.providePipeListener();
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull SignalServiceAccountManager getSignalServiceAccountManager() {
|
public static @NonNull SignalServiceAccountManager getSignalServiceAccountManager() {
|
||||||
if (accountManager == null) {
|
if (accountManager == null) {
|
||||||
synchronized (LOCK) {
|
synchronized (LOCK) {
|
||||||
@@ -179,6 +184,25 @@ public class ApplicationDependencies {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void resetNetworkConnectionsAfterProxyChange() {
|
||||||
|
synchronized (LOCK) {
|
||||||
|
getPipeListener().reset();
|
||||||
|
|
||||||
|
if (incomingMessageObserver != null) {
|
||||||
|
incomingMessageObserver.terminate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageSender != null) {
|
||||||
|
messageSender.cancelInFlightRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
incomingMessageObserver = null;
|
||||||
|
messageReceiver = null;
|
||||||
|
accountManager = null;
|
||||||
|
messageSender = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull SignalServiceNetworkAccess getSignalServiceNetworkAccess() {
|
public static @NonNull SignalServiceNetworkAccess getSignalServiceNetworkAccess() {
|
||||||
return provider.provideSignalServiceNetworkAccess();
|
return provider.provideSignalServiceNetworkAccess();
|
||||||
}
|
}
|
||||||
@@ -336,6 +360,7 @@ public class ApplicationDependencies {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public interface Provider {
|
public interface Provider {
|
||||||
|
@NonNull PipeConnectivityListener providePipeListener();
|
||||||
@NonNull GroupsV2Operations provideGroupsV2Operations();
|
@NonNull GroupsV2Operations provideGroupsV2Operations();
|
||||||
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager();
|
@NonNull SignalServiceAccountManager provideSignalServiceAccountManager();
|
||||||
@NonNull SignalServiceMessageSender provideSignalServiceMessageSender();
|
@NonNull SignalServiceMessageSender provideSignalServiceMessageSender();
|
||||||
|
|||||||
+23
-37
@@ -4,6 +4,7 @@ import android.app.Application;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import org.greenrobot.eventbus.EventBus;
|
import org.greenrobot.eventbus.EventBus;
|
||||||
import org.signal.core.util.concurrent.SignalExecutors;
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
@@ -35,10 +36,12 @@ import org.thoughtcrime.securesms.jobs.PushProcessMessageJob;
|
|||||||
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
|
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.ReactionSendJob;
|
import org.thoughtcrime.securesms.jobs.ReactionSendJob;
|
||||||
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
import org.thoughtcrime.securesms.jobs.TypingSendJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
import org.thoughtcrime.securesms.megaphone.MegaphoneRepository;
|
||||||
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
import org.thoughtcrime.securesms.messages.BackgroundMessageRetriever;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
import org.thoughtcrime.securesms.messages.IncomingMessageObserver;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
import org.thoughtcrime.securesms.notifications.DefaultMessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
import org.thoughtcrime.securesms.notifications.MessageNotifier;
|
||||||
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
|
import org.thoughtcrime.securesms.notifications.OptimizedMessageNotifier;
|
||||||
@@ -53,12 +56,14 @@ import org.thoughtcrime.securesms.util.EarlyMessageCache;
|
|||||||
import org.thoughtcrime.securesms.util.FeatureFlags;
|
import org.thoughtcrime.securesms.util.FeatureFlags;
|
||||||
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
import org.thoughtcrime.securesms.util.FrameRateTracker;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.thoughtcrime.securesms.util.concurrent.SettableFuture;
|
||||||
import org.whispersystems.libsignal.util.guava.Optional;
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
|
||||||
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
import org.whispersystems.signalservice.api.SignalServiceMessageSender;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
import org.whispersystems.signalservice.api.groupsv2.ClientZkOperations;
|
||||||
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
import org.whispersystems.signalservice.api.groupsv2.GroupsV2Operations;
|
||||||
|
import org.whispersystems.signalservice.api.push.exceptions.AuthorizationFailedException;
|
||||||
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
import org.whispersystems.signalservice.api.util.CredentialsProvider;
|
||||||
import org.whispersystems.signalservice.api.util.SleepTimer;
|
import org.whispersystems.signalservice.api.util.SleepTimer;
|
||||||
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
import org.whispersystems.signalservice.api.util.UptimeSleepTimer;
|
||||||
@@ -66,6 +71,8 @@ import org.whispersystems.signalservice.api.websocket.ConnectivityListener;
|
|||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies.
|
* Implementation of {@link ApplicationDependencies.Provider} that provides real app dependencies.
|
||||||
*/
|
*/
|
||||||
@@ -73,16 +80,21 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
|
|
||||||
private static final String TAG = Log.tag(ApplicationDependencyProvider.class);
|
private static final String TAG = Log.tag(ApplicationDependencyProvider.class);
|
||||||
|
|
||||||
private final Application context;
|
private final Application context;
|
||||||
private final SignalServiceNetworkAccess networkAccess;
|
private final PipeConnectivityListener pipeListener;
|
||||||
|
|
||||||
public ApplicationDependencyProvider(@NonNull Application context, @NonNull SignalServiceNetworkAccess networkAccess) {
|
public ApplicationDependencyProvider(@NonNull Application context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.networkAccess = networkAccess;
|
this.pipeListener = new PipeConnectivityListener(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull ClientZkOperations provideClientZkOperations() {
|
private @NonNull ClientZkOperations provideClientZkOperations() {
|
||||||
return ClientZkOperations.create(networkAccess.getConfiguration(context));
|
return ClientZkOperations.create(provideSignalServiceNetworkAccess().getConfiguration(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NonNull PipeConnectivityListener providePipeListener() {
|
||||||
|
return pipeListener;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -92,7 +104,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager() {
|
public @NonNull SignalServiceAccountManager provideSignalServiceAccountManager() {
|
||||||
return new SignalServiceAccountManager(networkAccess.getConfiguration(context),
|
return new SignalServiceAccountManager(provideSignalServiceNetworkAccess().getConfiguration(context),
|
||||||
new DynamicCredentialsProvider(context),
|
new DynamicCredentialsProvider(context),
|
||||||
BuildConfig.SIGNAL_AGENT,
|
BuildConfig.SIGNAL_AGENT,
|
||||||
provideGroupsV2Operations(),
|
provideGroupsV2Operations(),
|
||||||
@@ -101,7 +113,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender() {
|
public @NonNull SignalServiceMessageSender provideSignalServiceMessageSender() {
|
||||||
return new SignalServiceMessageSender(networkAccess.getConfiguration(context),
|
return new SignalServiceMessageSender(provideSignalServiceNetworkAccess().getConfiguration(context),
|
||||||
new DynamicCredentialsProvider(context),
|
new DynamicCredentialsProvider(context),
|
||||||
new SignalProtocolStoreImpl(context),
|
new SignalProtocolStoreImpl(context),
|
||||||
BuildConfig.SIGNAL_AGENT,
|
BuildConfig.SIGNAL_AGENT,
|
||||||
@@ -119,10 +131,10 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver() {
|
public @NonNull SignalServiceMessageReceiver provideSignalServiceMessageReceiver() {
|
||||||
SleepTimer sleepTimer = TextSecurePreferences.isFcmDisabled(context) ? new AlarmSleepTimer(context)
|
SleepTimer sleepTimer = TextSecurePreferences.isFcmDisabled(context) ? new AlarmSleepTimer(context)
|
||||||
: new UptimeSleepTimer();
|
: new UptimeSleepTimer();
|
||||||
return new SignalServiceMessageReceiver(networkAccess.getConfiguration(context),
|
return new SignalServiceMessageReceiver(provideSignalServiceNetworkAccess().getConfiguration(context),
|
||||||
new DynamicCredentialsProvider(context),
|
new DynamicCredentialsProvider(context),
|
||||||
BuildConfig.SIGNAL_AGENT,
|
BuildConfig.SIGNAL_AGENT,
|
||||||
new PipeConnectivityListener(),
|
pipeListener,
|
||||||
sleepTimer,
|
sleepTimer,
|
||||||
provideClientZkOperations().getProfileOperations(),
|
provideClientZkOperations().getProfileOperations(),
|
||||||
FeatureFlags.okHttpAutomaticRetry());
|
FeatureFlags.okHttpAutomaticRetry());
|
||||||
@@ -130,7 +142,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess() {
|
public @NonNull SignalServiceNetworkAccess provideSignalServiceNetworkAccess() {
|
||||||
return networkAccess;
|
return new SignalServiceNetworkAccess(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -240,30 +252,4 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
|
|||||||
return TextSecurePreferences.getSignalingKey(context);
|
return TextSecurePreferences.getSignalingKey(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class PipeConnectivityListener implements ConnectivityListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnected() {
|
|
||||||
Log.i(TAG, "onConnected()");
|
|
||||||
TextSecurePreferences.setUnauthorizedReceived(context, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnecting() {
|
|
||||||
Log.i(TAG, "onConnecting()");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDisconnected() {
|
|
||||||
Log.w(TAG, "onDisconnected()");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onAuthenticationFailure() {
|
|
||||||
Log.w(TAG, "onAuthenticationFailure()");
|
|
||||||
TextSecurePreferences.setUnauthorizedReceived(context, true);
|
|
||||||
EventBus.getDefault().post(new ReminderUpdateEvent());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,70 @@
|
|||||||
|
package org.thoughtcrime.securesms.keyvalue;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||||
|
|
||||||
|
public final class ProxyValues extends SignalStoreValues {
|
||||||
|
|
||||||
|
private static final String KEY_PROXY_ENABLED = "proxy.enabled";
|
||||||
|
private static final String KEY_HOST = "proxy.host";
|
||||||
|
private static final String KEY_PORT = "proxy.port";
|
||||||
|
|
||||||
|
ProxyValues(@NonNull KeyValueStore store) {
|
||||||
|
super(store);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void onFirstEverAppLaunch() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void enableProxy(@NonNull SignalProxy proxy) {
|
||||||
|
getStore().beginWrite()
|
||||||
|
.putBoolean(KEY_PROXY_ENABLED, true)
|
||||||
|
.putString(KEY_HOST, proxy.getHost())
|
||||||
|
.putInteger(KEY_PORT, proxy.getPort())
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disables the proxy, but does not clear out the last-chosen host.
|
||||||
|
*/
|
||||||
|
public void disableProxy() {
|
||||||
|
putBoolean(KEY_PROXY_ENABLED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isProxyEnabled() {
|
||||||
|
return getBoolean(KEY_PROXY_ENABLED, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the proxy. This does not *enable* the proxy. This is because the user may want to set a
|
||||||
|
* proxy and then enabled it and disable it at will.
|
||||||
|
*/
|
||||||
|
public void setProxy(@Nullable SignalProxy proxy) {
|
||||||
|
if (proxy != null) {
|
||||||
|
getStore().beginWrite()
|
||||||
|
.putString(KEY_HOST, proxy.getHost())
|
||||||
|
.putInteger(KEY_PORT, proxy.getPort())
|
||||||
|
.apply();
|
||||||
|
} else {
|
||||||
|
getStore().beginWrite()
|
||||||
|
.remove(KEY_HOST)
|
||||||
|
.remove(KEY_PORT)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable SignalProxy getProxy() {
|
||||||
|
String host = getString(KEY_HOST, null);
|
||||||
|
int port = getInteger(KEY_PORT, 0);
|
||||||
|
|
||||||
|
if (host != null) {
|
||||||
|
return new SignalProxy(host, port);
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,7 @@ public final class SignalStore {
|
|||||||
private final PhoneNumberPrivacyValues phoneNumberPrivacyValues;
|
private final PhoneNumberPrivacyValues phoneNumberPrivacyValues;
|
||||||
private final OnboardingValues onboardingValues;
|
private final OnboardingValues onboardingValues;
|
||||||
private final WallpaperValues wallpaperValues;
|
private final WallpaperValues wallpaperValues;
|
||||||
|
private final ProxyValues proxyValues;
|
||||||
|
|
||||||
private SignalStore() {
|
private SignalStore() {
|
||||||
this.store = new KeyValueStore(ApplicationDependencies.getApplication());
|
this.store = new KeyValueStore(ApplicationDependencies.getApplication());
|
||||||
@@ -48,6 +49,7 @@ public final class SignalStore {
|
|||||||
this.phoneNumberPrivacyValues = new PhoneNumberPrivacyValues(store);
|
this.phoneNumberPrivacyValues = new PhoneNumberPrivacyValues(store);
|
||||||
this.onboardingValues = new OnboardingValues(store);
|
this.onboardingValues = new OnboardingValues(store);
|
||||||
this.wallpaperValues = new WallpaperValues(store);
|
this.wallpaperValues = new WallpaperValues(store);
|
||||||
|
this.proxyValues = new ProxyValues(store);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onFirstEverAppLaunch() {
|
public static void onFirstEverAppLaunch() {
|
||||||
@@ -65,6 +67,7 @@ public final class SignalStore {
|
|||||||
phoneNumberPrivacy().onFirstEverAppLaunch();
|
phoneNumberPrivacy().onFirstEverAppLaunch();
|
||||||
onboarding().onFirstEverAppLaunch();
|
onboarding().onFirstEverAppLaunch();
|
||||||
wallpaper().onFirstEverAppLaunch();
|
wallpaper().onFirstEverAppLaunch();
|
||||||
|
proxy().onFirstEverAppLaunch();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static @NonNull KbsValues kbsValues() {
|
public static @NonNull KbsValues kbsValues() {
|
||||||
@@ -127,6 +130,10 @@ public final class SignalStore {
|
|||||||
return INSTANCE.wallpaperValues;
|
return INSTANCE.wallpaperValues;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static @NonNull ProxyValues proxy() {
|
||||||
|
return INSTANCE.proxyValues;
|
||||||
|
}
|
||||||
|
|
||||||
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
public static @NonNull GroupsV2AuthorizationSignalStoreCache groupsV2AuthorizationCache() {
|
||||||
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
return new GroupsV2AuthorizationSignalStoreCache(getStore());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import org.thoughtcrime.securesms.R;
|
|||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
||||||
import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob;
|
import org.thoughtcrime.securesms.jobs.PushDecryptDrainedJob;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor.Processor;
|
import org.thoughtcrime.securesms.messages.IncomingMessageProcessor.Processor;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
import org.thoughtcrime.securesms.push.SignalServiceNetworkAccess;
|
||||||
@@ -56,6 +57,7 @@ public class IncomingMessageObserver {
|
|||||||
|
|
||||||
private volatile boolean networkDrained;
|
private volatile boolean networkDrained;
|
||||||
private volatile boolean decryptionDrained;
|
private volatile boolean decryptionDrained;
|
||||||
|
private volatile boolean terminated;
|
||||||
|
|
||||||
public IncomingMessageObserver(@NonNull Application context) {
|
public IncomingMessageObserver(@NonNull Application context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@@ -138,9 +140,10 @@ public class IncomingMessageObserver {
|
|||||||
boolean websocketRegistered = TextSecurePreferences.isWebsocketRegistered(context);
|
boolean websocketRegistered = TextSecurePreferences.isWebsocketRegistered(context);
|
||||||
boolean isGcmDisabled = TextSecurePreferences.isFcmDisabled(context);
|
boolean isGcmDisabled = TextSecurePreferences.isFcmDisabled(context);
|
||||||
boolean hasNetwork = NetworkConstraint.isMet(context);
|
boolean hasNetwork = NetworkConstraint.isMet(context);
|
||||||
|
boolean hasProxy = SignalStore.proxy().isProxyEnabled();
|
||||||
|
|
||||||
Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Censored: %s, Registered: %s, Websocket Registered: %s",
|
Log.d(TAG, String.format("Network: %s, Foreground: %s, FCM: %s, Censored: %s, Registered: %s, Websocket Registered: %s, Proxy: %s",
|
||||||
hasNetwork, appVisible, !isGcmDisabled, networkAccess.isCensored(context), registered, websocketRegistered));
|
hasNetwork, appVisible, !isGcmDisabled, networkAccess.isCensored(context), registered, websocketRegistered, hasProxy));
|
||||||
|
|
||||||
return registered &&
|
return registered &&
|
||||||
websocketRegistered &&
|
websocketRegistered &&
|
||||||
@@ -157,10 +160,19 @@ public class IncomingMessageObserver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void terminate() {
|
||||||
|
Log.w(TAG, "Beginning termination.");
|
||||||
|
terminated = true;
|
||||||
|
shutdown(pipe, unidentifiedPipe);
|
||||||
|
}
|
||||||
|
|
||||||
private void shutdown(@Nullable SignalServiceMessagePipe pipe, @Nullable SignalServiceMessagePipe unidentifiedPipe) {
|
private void shutdown(@Nullable SignalServiceMessagePipe pipe, @Nullable SignalServiceMessagePipe unidentifiedPipe) {
|
||||||
try {
|
try {
|
||||||
if (pipe != null) {
|
if (pipe != null) {
|
||||||
|
Log.w(TAG, "Shutting down normal pipe.");
|
||||||
pipe.shutdown();
|
pipe.shutdown();
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No need to shutdown normal pipe, it doesn't exist.");
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.w(TAG, "Closing normal pipe failed!", t);
|
Log.w(TAG, "Closing normal pipe failed!", t);
|
||||||
@@ -168,7 +180,10 @@ public class IncomingMessageObserver {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (unidentifiedPipe != null) {
|
if (unidentifiedPipe != null) {
|
||||||
|
Log.w(TAG, "Shutting down unidentified pipe.");
|
||||||
unidentifiedPipe.shutdown();
|
unidentifiedPipe.shutdown();
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "No need to shutdown unidentified pipe, it doesn't exist.");
|
||||||
}
|
}
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
Log.w(TAG, "Closing unidentified pipe failed!", t);
|
Log.w(TAG, "Closing unidentified pipe failed!", t);
|
||||||
@@ -187,12 +202,13 @@ public class IncomingMessageObserver {
|
|||||||
|
|
||||||
MessageRetrievalThread() {
|
MessageRetrievalThread() {
|
||||||
super("MessageRetrievalService");
|
super("MessageRetrievalService");
|
||||||
|
Log.i(TAG, "Initializing! (" + this.hashCode() + ")");
|
||||||
setUncaughtExceptionHandler(this);
|
setUncaughtExceptionHandler(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
while (true) {
|
while (!terminated) {
|
||||||
Log.i(TAG, "Waiting for websocket state change....");
|
Log.i(TAG, "Waiting for websocket state change....");
|
||||||
waitForConnectionNecessary();
|
waitForConnectionNecessary();
|
||||||
|
|
||||||
@@ -236,6 +252,8 @@ public class IncomingMessageObserver {
|
|||||||
|
|
||||||
Log.i(TAG, "Looping...");
|
Log.i(TAG, "Looping...");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "Terminated! (" + this.hashCode() + ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package org.thoughtcrime.securesms.net;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.util.DefaultValueLiveData;
|
||||||
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
import org.whispersystems.signalservice.api.websocket.ConnectivityListener;
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Our standard listener for reacting to the state of the websocket. Translates the state into a
|
||||||
|
* LiveData for observation.
|
||||||
|
*/
|
||||||
|
public class PipeConnectivityListener implements ConnectivityListener {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(PipeConnectivityListener.class);
|
||||||
|
|
||||||
|
private final Application application;
|
||||||
|
private final DefaultValueLiveData<State> state;
|
||||||
|
|
||||||
|
public PipeConnectivityListener(@NonNull Application application) {
|
||||||
|
this.application = application;
|
||||||
|
this.state = new DefaultValueLiveData<>(State.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnected() {
|
||||||
|
Log.i(TAG, "onConnected()");
|
||||||
|
TextSecurePreferences.setUnauthorizedReceived(application, false);
|
||||||
|
state.postValue(State.CONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onConnecting() {
|
||||||
|
Log.i(TAG, "onConnecting()");
|
||||||
|
state.postValue(State.CONNECTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisconnected() {
|
||||||
|
Log.w(TAG, "onDisconnected()");
|
||||||
|
state.postValue(State.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAuthenticationFailure() {
|
||||||
|
Log.w(TAG, "onAuthenticationFailure()");
|
||||||
|
TextSecurePreferences.setUnauthorizedReceived(application, true);
|
||||||
|
EventBus.getDefault().post(new ReminderUpdateEvent());
|
||||||
|
state.postValue(State.FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onGenericFailure(Response response, Throwable throwable) {
|
||||||
|
Log.w(TAG, "onGenericFailure() Response: " + response, throwable);
|
||||||
|
state.postValue(State.FAILURE);
|
||||||
|
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
Log.w(TAG, "Encountered an error while we had a proxy set! Terminating the connection to prevent retry spam.");
|
||||||
|
ApplicationDependencies.getIncomingMessageObserver().terminate();
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset() {
|
||||||
|
state.postValue(State.DISCONNECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull DefaultValueLiveData<State> getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum State {
|
||||||
|
DISCONNECTED, CONNECTING, CONNECTED, FAILURE
|
||||||
|
}
|
||||||
|
}
|
||||||
+8
@@ -25,6 +25,7 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
|||||||
|
|
||||||
private static final String TAG = Log.tag(DataAndStoragePreferenceFragment.class);
|
private static final String TAG = Log.tag(DataAndStoragePreferenceFragment.class);
|
||||||
private static final String MANAGE_STORAGE_KEY = "pref_data_manage";
|
private static final String MANAGE_STORAGE_KEY = "pref_data_manage";
|
||||||
|
private static final String USE_PROXY_KEY = "pref_use_proxy";
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
@@ -52,6 +53,12 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
|||||||
viewModel.getStorageBreakdown()
|
viewModel.getStorageBreakdown()
|
||||||
.observe(requireActivity(),
|
.observe(requireActivity(),
|
||||||
breakdown -> manageStorage.setSummary(Util.getPrettyFileSize(breakdown.getTotalSize())));
|
breakdown -> manageStorage.setSummary(Util.getPrettyFileSize(breakdown.getTotalSize())));
|
||||||
|
|
||||||
|
|
||||||
|
findPreference(USE_PROXY_KEY).setOnPreferenceClickListener(unused -> {
|
||||||
|
requireApplicationPreferencesActivity().pushFragment(EditProxyFragment.newInstance());
|
||||||
|
return false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -65,6 +72,7 @@ public class DataAndStoragePreferenceFragment extends ListSummaryPreferenceFragm
|
|||||||
requireApplicationPreferencesActivity().getSupportActionBar().setTitle(R.string.preferences__data_and_storage);
|
requireApplicationPreferencesActivity().getSupportActionBar().setTitle(R.string.preferences__data_and_storage);
|
||||||
setMediaDownloadSummaries();
|
setMediaDownloadSummaries();
|
||||||
ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(requireActivity()).refreshStorageBreakdown(requireContext());
|
ApplicationPreferencesViewModel.getApplicationPreferencesViewModel(requireActivity()).refreshStorageBreakdown(requireContext());
|
||||||
|
findPreference(USE_PROXY_KEY).setSummary(SignalStore.proxy().isProxyEnabled() ? R.string.preferences_on : R.string.preferences_off);
|
||||||
}
|
}
|
||||||
|
|
||||||
private @NonNull ApplicationPreferencesActivity requireApplicationPreferencesActivity() {
|
private @NonNull ApplicationPreferencesActivity requireApplicationPreferencesActivity() {
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package org.thoughtcrime.securesms.preferences;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.appcompat.widget.SwitchCompat;
|
||||||
|
import androidx.core.app.ShareCompat;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
import androidx.navigation.Navigation;
|
||||||
|
|
||||||
|
import com.dd.CircularProgressButton;
|
||||||
|
|
||||||
|
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
|
import org.whispersystems.libsignal.util.guava.Optional;
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||||
|
|
||||||
|
public class EditProxyFragment extends Fragment {
|
||||||
|
|
||||||
|
private SwitchCompat proxySwitch;
|
||||||
|
private EditText proxyText;
|
||||||
|
private TextView proxyTitle;
|
||||||
|
private TextView proxyStatus;
|
||||||
|
private View shareButton;
|
||||||
|
private CircularProgressButton saveButton;
|
||||||
|
private EditProxyViewModel viewModel;
|
||||||
|
|
||||||
|
public static EditProxyFragment newInstance() {
|
||||||
|
return new EditProxyFragment();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.edit_proxy_fragment, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
this.proxySwitch = view.findViewById(R.id.edit_proxy_switch);
|
||||||
|
this.proxyTitle = view.findViewById(R.id.edit_proxy_address_title);
|
||||||
|
this.proxyText = view.findViewById(R.id.edit_proxy_host);
|
||||||
|
this.proxyStatus = view.findViewById(R.id.edit_proxy_status);
|
||||||
|
this.saveButton = view.findViewById(R.id.edit_proxy_save);
|
||||||
|
this.shareButton = view.findViewById(R.id.edit_proxy_share);
|
||||||
|
|
||||||
|
this.proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||||
|
this.proxySwitch.setChecked(SignalStore.proxy().isProxyEnabled());
|
||||||
|
|
||||||
|
initViewModel();
|
||||||
|
|
||||||
|
saveButton.setOnClickListener(v -> onSaveClicked());
|
||||||
|
shareButton.setOnClickListener(v -> onShareClicked());
|
||||||
|
proxySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> viewModel.onToggleProxy(isChecked));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
((ApplicationPreferencesActivity) requireActivity()).requireSupportActionBar().setTitle(R.string.preferences_use_proxy);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViewModel() {
|
||||||
|
viewModel = ViewModelProviders.of(this).get(EditProxyViewModel.class);
|
||||||
|
|
||||||
|
viewModel.getUiState().observe(getViewLifecycleOwner(), this::presentUiState);
|
||||||
|
viewModel.getProxyState().observe(getViewLifecycleOwner(), this::presentProxyState);
|
||||||
|
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvent);
|
||||||
|
viewModel.getSaveState().observe(getViewLifecycleOwner(), this::presentSaveState);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentUiState(@NonNull EditProxyViewModel.UiState uiState) {
|
||||||
|
switch (uiState) {
|
||||||
|
case ALL_ENABLED:
|
||||||
|
proxyText.setEnabled(true);
|
||||||
|
proxyText.setAlpha(1);
|
||||||
|
saveButton.setEnabled(true);
|
||||||
|
saveButton.setAlpha(1);
|
||||||
|
shareButton.setEnabled(true);
|
||||||
|
shareButton.setAlpha(1);
|
||||||
|
proxyTitle.setAlpha(1);
|
||||||
|
proxyStatus.setVisibility(View.VISIBLE);
|
||||||
|
break;
|
||||||
|
case ALL_DISABLED:
|
||||||
|
proxyText.setEnabled(false);
|
||||||
|
proxyText.setAlpha(0.5f);
|
||||||
|
saveButton.setEnabled(false);
|
||||||
|
saveButton.setAlpha(0.5f);
|
||||||
|
shareButton.setEnabled(false);
|
||||||
|
shareButton.setAlpha(0.5f);
|
||||||
|
proxyTitle.setAlpha(0.5f);
|
||||||
|
proxyStatus.setVisibility(View.GONE);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentProxyState(@NonNull PipeConnectivityListener.State proxyState) {
|
||||||
|
switch (proxyState) {
|
||||||
|
case DISCONNECTED:
|
||||||
|
case CONNECTING:
|
||||||
|
proxyStatus.setText(R.string.preferences_connecting_to_proxy);
|
||||||
|
proxyStatus.setTextColor(getResources().getColor(R.color.signal_text_secondary));
|
||||||
|
break;
|
||||||
|
case CONNECTED:
|
||||||
|
proxyStatus.setText(R.string.preferences_connected_to_proxy);
|
||||||
|
proxyStatus.setTextColor(getResources().getColor(R.color.signal_accent_green));
|
||||||
|
break;
|
||||||
|
case FAILURE:
|
||||||
|
proxyStatus.setText(R.string.preferences_connection_failed);
|
||||||
|
proxyStatus.setTextColor(getResources().getColor(R.color.signal_alert_primary));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentEvent(@NonNull EditProxyViewModel.Event event) {
|
||||||
|
switch (event) {
|
||||||
|
case PROXY_SUCCESS:
|
||||||
|
proxyStatus.setVisibility(View.VISIBLE);
|
||||||
|
proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.preferences_success)
|
||||||
|
.setMessage(R.string.preferences_you_are_connected_to_the_proxy)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d, i) -> d.dismiss())
|
||||||
|
.show();
|
||||||
|
break;
|
||||||
|
case PROXY_FAILURE:
|
||||||
|
proxyStatus.setVisibility(View.GONE);
|
||||||
|
proxyText.setText(Optional.fromNullable(SignalStore.proxy().getProxy()).transform(SignalProxy::getHost).or(""));
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.preferences_failed_to_connect)
|
||||||
|
.setMessage(R.string.preferences_couldnt_connect_to_the_proxy)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d, i) -> d.dismiss())
|
||||||
|
.show();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentSaveState(@NonNull EditProxyViewModel.SaveState state) {
|
||||||
|
switch (state) {
|
||||||
|
case IDLE:
|
||||||
|
saveButton.setClickable(true);
|
||||||
|
saveButton.setIndeterminateProgressMode(false);
|
||||||
|
saveButton.setProgress(0);
|
||||||
|
break;
|
||||||
|
case IN_PROGRESS:
|
||||||
|
saveButton.setClickable(false);
|
||||||
|
saveButton.setIndeterminateProgressMode(true);
|
||||||
|
saveButton.setProgress(50);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSaveClicked() {
|
||||||
|
viewModel.onSaveClicked(proxyText.getText().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onShareClicked() {
|
||||||
|
String host = proxyText.getText().toString();
|
||||||
|
ShareCompat.IntentBuilder.from(requireActivity())
|
||||||
|
.setText(host)
|
||||||
|
.setType("text/plain")
|
||||||
|
.startChooser();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package org.thoughtcrime.securesms.preferences;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import org.signal.core.util.concurrent.SignalExecutors;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
|
import org.thoughtcrime.securesms.util.SignalProxyUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.SingleLiveEvent;
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class EditProxyViewModel extends ViewModel {
|
||||||
|
|
||||||
|
private final SingleLiveEvent<Event> events;
|
||||||
|
private final MutableLiveData<UiState> uiState;
|
||||||
|
private final MutableLiveData<SaveState> saveState;
|
||||||
|
|
||||||
|
public EditProxyViewModel() {
|
||||||
|
this.events = new SingleLiveEvent<>();
|
||||||
|
this.uiState = new MutableLiveData<>();
|
||||||
|
this.saveState = new MutableLiveData<>(SaveState.IDLE);
|
||||||
|
|
||||||
|
if (SignalStore.proxy().isProxyEnabled()) {
|
||||||
|
uiState.setValue(UiState.ALL_ENABLED);
|
||||||
|
} else {
|
||||||
|
uiState.setValue(UiState.ALL_DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onToggleProxy(boolean enabled) {
|
||||||
|
if (enabled) {
|
||||||
|
SignalProxy currentProxy = SignalStore.proxy().getProxy();
|
||||||
|
|
||||||
|
if (currentProxy != null) {
|
||||||
|
SignalProxyUtil.enableProxy(currentProxy);
|
||||||
|
}
|
||||||
|
uiState.postValue(UiState.ALL_ENABLED);
|
||||||
|
} else {
|
||||||
|
SignalProxyUtil.disableProxy();
|
||||||
|
uiState.postValue(UiState.ALL_DISABLED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSaveClicked(@NonNull String host) {
|
||||||
|
String parsedHost = SignalProxyUtil.parseHostFromProxyLink(host);
|
||||||
|
String trueHost = parsedHost != null ? parsedHost : host;
|
||||||
|
|
||||||
|
saveState.postValue(SaveState.IN_PROGRESS);
|
||||||
|
|
||||||
|
SignalExecutors.BOUNDED.execute(() -> {
|
||||||
|
SignalProxyUtil.enableProxy(new SignalProxy(trueHost, 443));
|
||||||
|
|
||||||
|
boolean success = SignalProxyUtil.testWebsocketConnection(TimeUnit.SECONDS.toMillis(10));
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
events.postValue(Event.PROXY_SUCCESS);
|
||||||
|
} else {
|
||||||
|
SignalProxyUtil.disableProxy();
|
||||||
|
events.postValue(Event.PROXY_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveState.postValue(SaveState.IDLE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<UiState> getUiState() {
|
||||||
|
return uiState;
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull LiveData<Event> getEvents() {
|
||||||
|
return events;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull LiveData<PipeConnectivityListener.State> getProxyState() {
|
||||||
|
return ApplicationDependencies.getPipeListener().getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
public @NonNull LiveData<SaveState> getSaveState() {
|
||||||
|
return saveState;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum UiState {
|
||||||
|
ALL_DISABLED, ALL_ENABLED
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Event {
|
||||||
|
PROXY_SUCCESS, PROXY_FAILURE
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum SaveState {
|
||||||
|
IDLE, IN_PROGRESS
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -167,14 +167,17 @@ public class EditAboutFragment extends Fragment implements ManageProfileActivity
|
|||||||
private void presentSaveState(@NonNull EditAboutViewModel.SaveState state) {
|
private void presentSaveState(@NonNull EditAboutViewModel.SaveState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IDLE:
|
case IDLE:
|
||||||
|
saveButton.setClickable(true);
|
||||||
saveButton.setIndeterminateProgressMode(false);
|
saveButton.setIndeterminateProgressMode(false);
|
||||||
saveButton.setProgress(0);
|
saveButton.setProgress(0);
|
||||||
break;
|
break;
|
||||||
case IN_PROGRESS:
|
case IN_PROGRESS:
|
||||||
|
saveButton.setClickable(false);
|
||||||
saveButton.setIndeterminateProgressMode(true);
|
saveButton.setIndeterminateProgressMode(true);
|
||||||
saveButton.setProgress(50);
|
saveButton.setProgress(50);
|
||||||
break;
|
break;
|
||||||
case DONE:
|
case DONE:
|
||||||
|
saveButton.setClickable(false);
|
||||||
Navigation.findNavController(requireView()).popBackStack();
|
Navigation.findNavController(requireView()).popBackStack();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
@@ -81,14 +81,17 @@ public class EditProfileNameFragment extends Fragment {
|
|||||||
private void presentSaveState(@NonNull EditProfileNameViewModel.SaveState state) {
|
private void presentSaveState(@NonNull EditProfileNameViewModel.SaveState state) {
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case IDLE:
|
case IDLE:
|
||||||
|
saveButton.setClickable(true);
|
||||||
saveButton.setIndeterminateProgressMode(false);
|
saveButton.setIndeterminateProgressMode(false);
|
||||||
saveButton.setProgress(0);
|
saveButton.setProgress(0);
|
||||||
break;
|
break;
|
||||||
case IN_PROGRESS:
|
case IN_PROGRESS:
|
||||||
|
saveButton.setClickable(false);
|
||||||
saveButton.setIndeterminateProgressMode(true);
|
saveButton.setIndeterminateProgressMode(true);
|
||||||
saveButton.setProgress(50);
|
saveButton.setProgress(50);
|
||||||
break;
|
break;
|
||||||
case DONE:
|
case DONE:
|
||||||
|
saveButton.setClickable(false);
|
||||||
Navigation.findNavController(requireView()).popBackStack();
|
Navigation.findNavController(requireView()).popBackStack();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package org.thoughtcrime.securesms.proxy;
|
||||||
|
|
||||||
|
import android.app.AlertDialog;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.TextView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.DialogFragment;
|
||||||
|
import androidx.fragment.app.FragmentManager;
|
||||||
|
import androidx.lifecycle.ViewModelProviders;
|
||||||
|
|
||||||
|
import com.dd.CircularProgressButton;
|
||||||
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
|
import org.thoughtcrime.securesms.preferences.EditProxyViewModel;
|
||||||
|
import org.thoughtcrime.securesms.util.BottomSheetUtil;
|
||||||
|
import org.thoughtcrime.securesms.util.ThemeUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A bottom sheet shown in response to a deep link. Allows a user to set a proxy.
|
||||||
|
*/
|
||||||
|
public final class ProxyBottomSheetFragment extends BottomSheetDialogFragment {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(ProxyBottomSheetFragment.class);
|
||||||
|
|
||||||
|
private static final String ARG_PROXY_LINK = "proxy_link";
|
||||||
|
|
||||||
|
private TextView proxyText;
|
||||||
|
private View cancelButton;
|
||||||
|
private CircularProgressButton useProxyButton;
|
||||||
|
private EditProxyViewModel viewModel;
|
||||||
|
|
||||||
|
public static void showForProxy(@NonNull FragmentManager manager, @NonNull String proxyLink) {
|
||||||
|
ProxyBottomSheetFragment fragment = new ProxyBottomSheetFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putString(ARG_PROXY_LINK, proxyLink);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
|
||||||
|
fragment.show(manager, BottomSheetUtil.STANDARD_BOTTOM_SHEET_FRAGMENT_TAG);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
setStyle(DialogFragment.STYLE_NORMAL,
|
||||||
|
ThemeUtil.isDarkTheme(requireContext()) ? R.style.Theme_Signal_RoundedBottomSheet
|
||||||
|
: R.style.Theme_Signal_RoundedBottomSheet_Light);
|
||||||
|
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||||
|
return inflater.inflate(R.layout.proxy_bottom_sheet, container, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
this.proxyText = view.findViewById(R.id.proxy_sheet_host);
|
||||||
|
this.useProxyButton = view.findViewById(R.id.proxy_sheet_use_proxy);
|
||||||
|
this.cancelButton = view.findViewById(R.id.proxy_sheet_cancel);
|
||||||
|
|
||||||
|
String host = getArguments().getString(ARG_PROXY_LINK);
|
||||||
|
proxyText.setText(host);
|
||||||
|
|
||||||
|
initViewModel();
|
||||||
|
|
||||||
|
useProxyButton.setOnClickListener(v -> viewModel.onSaveClicked(host));
|
||||||
|
cancelButton.setOnClickListener(v -> dismiss());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initViewModel() {
|
||||||
|
this.viewModel = ViewModelProviders.of(this).get(EditProxyViewModel.class);
|
||||||
|
|
||||||
|
viewModel.getSaveState().observe(getViewLifecycleOwner(), this::presentSaveState);
|
||||||
|
viewModel.getEvents().observe(getViewLifecycleOwner(), this::presentEvents);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentSaveState(@NonNull EditProxyViewModel.SaveState state) {
|
||||||
|
switch (state) {
|
||||||
|
case IDLE:
|
||||||
|
useProxyButton.setClickable(true);
|
||||||
|
useProxyButton.setIndeterminateProgressMode(false);
|
||||||
|
useProxyButton.setProgress(0);
|
||||||
|
break;
|
||||||
|
case IN_PROGRESS:
|
||||||
|
useProxyButton.setClickable(false);
|
||||||
|
useProxyButton.setIndeterminateProgressMode(true);
|
||||||
|
useProxyButton.setProgress(50);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void presentEvents(@NonNull EditProxyViewModel.Event event) {
|
||||||
|
switch (event) {
|
||||||
|
case PROXY_SUCCESS:
|
||||||
|
Toast.makeText(requireContext(), R.string.ProxyBottomSheetFragment_successfully_connected_to_proxy, Toast.LENGTH_LONG).show();
|
||||||
|
dismiss();
|
||||||
|
break;
|
||||||
|
case PROXY_FAILURE:
|
||||||
|
new AlertDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.preferences_failed_to_connect)
|
||||||
|
.setMessage(R.string.preferences_couldnt_connect_to_the_proxy)
|
||||||
|
.setPositiveButton(android.R.string.ok, (d, i) -> d.dismiss())
|
||||||
|
.show();
|
||||||
|
dismiss();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -231,7 +231,7 @@ public class SignalServiceNetworkAccess {
|
|||||||
new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))},
|
new SignalStorageUrl[] {new SignalStorageUrl(BuildConfig.STORAGE_URL, new SignalServiceTrustStore(context))},
|
||||||
interceptors,
|
interceptors,
|
||||||
dns,
|
dns,
|
||||||
Optional.absent(),
|
SignalStore.proxy().isProxyEnabled() ? Optional.of(SignalStore.proxy().getProxy()) : Optional.absent(),
|
||||||
zkGroupServerPublicParams);
|
zkGroupServerPublicParams);
|
||||||
|
|
||||||
this.censoredCountries = this.censorshipConfiguration.keySet().toArray(new String[0]);
|
this.censoredCountries = this.censorshipConfiguration.keySet().toArray(new String[0]);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoin
|
|||||||
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinUpdateRequiredBottomSheetDialogFragment;
|
import org.thoughtcrime.securesms.groups.ui.invitesandrequests.joining.GroupJoinUpdateRequiredBottomSheetDialogFragment;
|
||||||
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
import org.thoughtcrime.securesms.groups.v2.GroupInviteLinkUrl;
|
||||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||||
|
import org.thoughtcrime.securesms.proxy.ProxyBottomSheetFragment;
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient;
|
import org.thoughtcrime.securesms.recipients.Recipient;
|
||||||
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
import org.thoughtcrime.securesms.ringrtc.RemotePeer;
|
||||||
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
import org.thoughtcrime.securesms.service.WebRtcCallService;
|
||||||
@@ -207,6 +208,21 @@ public class CommunicationActions {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the url is a proxy link it will handle it.
|
||||||
|
* Otherwise returns false, indicating was not a proxy link.
|
||||||
|
*/
|
||||||
|
public static boolean handlePotentialProxyLinkUrl(@NonNull FragmentActivity activity, @NonNull String potentialProxyLinkUrl) {
|
||||||
|
String proxy = SignalProxyUtil.parseHostFromProxyLink(potentialProxyLinkUrl);
|
||||||
|
|
||||||
|
if (proxy != null) {
|
||||||
|
ProxyBottomSheetFragment.showForProxy(activity.getSupportFragmentManager(), proxy);
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void startInsecureCallInternal(@NonNull Activity activity, @NonNull Recipient recipient) {
|
private static void startInsecureCallInternal(@NonNull Activity activity, @NonNull Recipient recipient) {
|
||||||
try {
|
try {
|
||||||
Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + recipient.requireSmsAddress()));
|
Intent dialIntent = new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + recipient.requireSmsAddress()));
|
||||||
|
|||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.WorkerThread;
|
||||||
|
import androidx.lifecycle.Observer;
|
||||||
|
|
||||||
|
import org.conscrypt.Conscrypt;
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.net.PipeConnectivityListener;
|
||||||
|
import org.whispersystems.signalservice.internal.configuration.SignalProxy;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
public final class SignalProxyUtil {
|
||||||
|
|
||||||
|
private static final String TAG = Log.tag(SignalProxyUtil.class);
|
||||||
|
|
||||||
|
private static final String PROXY_LINK_HOST = "signal.tube";
|
||||||
|
|
||||||
|
private SignalProxyUtil() {}
|
||||||
|
|
||||||
|
public static void startListeningToWebsocket() {
|
||||||
|
ApplicationDependencies.getIncomingMessageObserver();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all things related to enabling a proxy, including saving it and resetting the relevant
|
||||||
|
* network connections.
|
||||||
|
*/
|
||||||
|
public static void enableProxy(@NonNull SignalProxy proxy) {
|
||||||
|
SignalStore.proxy().enableProxy(proxy);
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(true);
|
||||||
|
ApplicationDependencies.resetNetworkConnectionsAfterProxyChange();
|
||||||
|
startListeningToWebsocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles all things related to disabling a proxy, including saving the change and resetting the
|
||||||
|
* relevant network connections.
|
||||||
|
*/
|
||||||
|
public static void disableProxy() {
|
||||||
|
SignalStore.proxy().disableProxy();
|
||||||
|
Conscrypt.setUseEngineSocketByDefault(false);
|
||||||
|
ApplicationDependencies.resetNetworkConnectionsAfterProxyChange();
|
||||||
|
startListeningToWebsocket();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A blocking call that will wait until the websocket either successfully connects, or fails.
|
||||||
|
* It is assumed that the app state is already configured how you would like it, e.g. you've
|
||||||
|
* already configured a proxy if relevant.
|
||||||
|
*
|
||||||
|
* @return True if the connection is successful within the specified timeout, otherwise false.
|
||||||
|
*/
|
||||||
|
@WorkerThread
|
||||||
|
public static boolean testWebsocketConnection(long timeout) {
|
||||||
|
startListeningToWebsocket();
|
||||||
|
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
AtomicBoolean success = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
Observer<PipeConnectivityListener.State> observer = state -> {
|
||||||
|
if (state == PipeConnectivityListener.State.CONNECTED) {
|
||||||
|
success.set(true);
|
||||||
|
latch.countDown();
|
||||||
|
} else if (state == PipeConnectivityListener.State.FAILURE) {
|
||||||
|
success.set(false);
|
||||||
|
latch.countDown();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Util.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().observeForever(observer));
|
||||||
|
|
||||||
|
try {
|
||||||
|
latch.await(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
Log.w(TAG, "Interrupted!", e);
|
||||||
|
} finally {
|
||||||
|
Util.runOnMainSync(() -> ApplicationDependencies.getPipeListener().getState().removeObserver(observer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return success.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If this is a valid proxy link, this will return the embedded host. If not, it will return
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
public static @Nullable String parseHostFromProxyLink(@NonNull String proxyLink) {
|
||||||
|
try {
|
||||||
|
URI uri = new URI(proxyLink);
|
||||||
|
|
||||||
|
if (!"https".equalsIgnoreCase(uri.getScheme())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!PROXY_LINK_HOST.equalsIgnoreCase(uri.getHost())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String path = uri.getPath();
|
||||||
|
|
||||||
|
if (Util.isEmpty(path) || "/".equals(path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path.startsWith("/")) {
|
||||||
|
return path.substring(1);
|
||||||
|
} else {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M7.5,3L12,1l4.5,2L21,5v5l0,0c0,5.5 -3.2,10.4 -8.2,12.7L12,23l-0.8,-0.3C6.2,20.4 3,15.5 3,10l0,0V5L7.5,3z"
|
||||||
|
android:fillColor="#27AE60"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M16.3,8.3l1.4,1.4L11,16.4l-3.7,-3.7l1.4,-1.4l2.3,2.3L16.3,8.3z"
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M16.5,3L12,1L7.5,3L3,5v5c0,5.5 3.2,10.4 8.2,12.7L12,23l0.8,-0.3c5,-2.2 8.2,-7.2 8.2,-12.7V5L16.5,3zM13.2,6l-0.5,7.5h-1.5L10.8,6H13.2zM13.1,17.6C12.8,17.8 12.4,18 12,18c-0.3,0 -0.6,-0.1 -0.8,-0.3c-0.2,-0.2 -0.4,-0.4 -0.6,-0.7c-0.1,-0.3 -0.1,-0.6 -0.1,-0.9c0.1,-0.3 0.2,-0.6 0.4,-0.8c0.2,-0.2 0.5,-0.4 0.8,-0.4c0.3,-0.1 0.6,0 0.9,0.1c0.3,0.1 0.5,0.3 0.7,0.6s0.3,0.5 0.3,0.8C13.5,16.9 13.3,17.3 13.1,17.6z"
|
||||||
|
android:fillColor="#FFFFFF"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M16.5,3L12,1L7.5,3L3,5v5c0,5.5 3.2,10.4 8.2,12.7L12,23l0.8,-0.3c5,-2.2 8.2,-7.2 8.2,-12.7V5L16.5,3zM13.2,6l-0.5,7.5h-1.5L10.8,6H13.2zM13.1,17.6C12.8,17.8 12.4,18 12,18c-0.3,0 -0.6,-0.1 -0.8,-0.3c-0.2,-0.2 -0.4,-0.4 -0.6,-0.7c-0.1,-0.3 -0.1,-0.6 -0.1,-0.9c0.1,-0.3 0.2,-0.6 0.4,-0.8c0.2,-0.2 0.5,-0.4 0.8,-0.4c0.3,-0.1 0.6,0 0.9,0.1c0.3,0.1 0.5,0.3 0.7,0.6s0.3,0.5 0.3,0.8C13.5,16.9 13.3,17.3 13.1,17.6z"
|
||||||
|
android:fillColor="@color/core_red"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M11,16.1l-3.5,-3.6l1,-1l2.5,2.4l5.5,-5.4l1,1z"
|
||||||
|
android:fillColor="#27AE60"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M12,23l-0.8,-0.3C6.2,20.4 3,15.5 3,10V5l9,-4l9,4v5c0,5.5 -3.2,10.4 -8.2,12.7L12,23zM4.5,6v4c0,4.9 2.9,9.3 7.3,11.3l0.2,0.1l0.2,-0.1c4.5,-2 7.3,-6.4 7.3,-11.3V6L12,2.6L4.5,6z"
|
||||||
|
android:fillColor="#27AE60"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,23l-0.8,-0.3C6.2,20.4 3,15.5 3,10V5l9,-4l9,4v5c0,5.5 -3.2,10.4 -8.2,12.7L12,23zM4.5,6v4c0,4.9 2.9,9.3 7.3,11.3l0.2,0.1l0.2,-0.1c4.5,-2 7.3,-6.4 7.3,-11.3V6L12,2.6L4.5,6z"
|
||||||
|
android:fillColor="#3B3B3B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.2,6h-2.5l0.5,7.5h1.5L13.2,6z"
|
||||||
|
android:fillColor="#3B3B3B"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.2,15.7c0.2,0.2 0.3,0.5 0.3,0.8c0,0.4 -0.2,0.8 -0.4,1.1C12.8,17.8 12.4,18 12,18c-0.3,0 -0.6,-0.1 -0.8,-0.3c-0.2,-0.2 -0.4,-0.4 -0.6,-0.7c-0.1,-0.3 -0.1,-0.6 -0.1,-0.9c0.1,-0.3 0.2,-0.6 0.4,-0.8c0.2,-0.2 0.5,-0.4 0.8,-0.4c0.3,-0.1 0.6,0 0.9,0.1C12.8,15.2 13.1,15.4 13.2,15.7z"
|
||||||
|
android:fillColor="#3B3B3B"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,23l-0.8,-0.3C6.2,20.4 3,15.5 3,10V5l9,-4l9,4v5c0,5.5 -3.2,10.4 -8.2,12.7L12,23zM4.5,6v4c0,4.9 2.9,9.3 7.3,11.3l0.2,0.1l0.2,-0.1c4.5,-2 7.3,-6.4 7.3,-11.3V6L12,2.6L4.5,6z"
|
||||||
|
android:fillColor="@color/core_red_highlight"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.2,6h-2.5l0.5,7.5h1.5L13.2,6z"
|
||||||
|
android:fillColor="@color/core_red_highlight"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M13.2,15.7c0.2,0.2 0.3,0.5 0.3,0.8c0,0.4 -0.2,0.8 -0.4,1.1C12.8,17.8 12.4,18 12,18c-0.3,0 -0.6,-0.1 -0.8,-0.3c-0.2,-0.2 -0.4,-0.4 -0.6,-0.7c-0.1,-0.3 -0.1,-0.6 -0.1,-0.9c0.1,-0.3 0.2,-0.6 0.4,-0.8c0.2,-0.2 0.5,-0.4 0.8,-0.4c0.3,-0.1 0.6,0 0.9,0.1C12.8,15.2 13.1,15.4 13.2,15.7z"
|
||||||
|
android:fillColor="@color/core_red_highlight"/>
|
||||||
|
</vector>
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="96dp"
|
||||||
|
android:height="96dp"
|
||||||
|
android:viewportWidth="96"
|
||||||
|
android:viewportHeight="96">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M48,48m-48,0a48,48 0,1 1,96 0a48,48 0,1 1,-96 0"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M44.6,58.2l-9.7,-9.6l2.2,-2.2l7.5,7.6l16.2,-16.2l2.1,2.1z"
|
||||||
|
android:fillColor="#F6F6F6"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M47.5,79.1l-2.2,-1c-14.4,-6.4 -23.6,-20.7 -23.6,-36.4V27.4l25.9,-11.5l25.9,11.5v14.4c0,15.7 -9.3,30 -23.6,36.4L47.5,79.1zM24.6,29.3v12.4c0,14.5 8.6,27.7 21.9,33.6l1,0.5l1,-0.5c13.3,-5.9 21.9,-19.1 21.9,-33.6V29.3L47.5,19.2L24.6,29.3z"
|
||||||
|
android:fillColor="#F6F6F6"/>
|
||||||
|
</vector>
|
||||||
@@ -39,6 +39,7 @@
|
|||||||
tools:src="@drawable/ic_contact_picture" />
|
tools:src="@drawable/ic_contact_picture" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/conversation_list_title"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="6dp"
|
android:layout_marginStart="6dp"
|
||||||
@@ -48,10 +49,24 @@
|
|||||||
android:textSize="20sp"
|
android:textSize="20sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toStartOf="@id/search_action"
|
app:layout_constraintEnd_toStartOf="@id/conversation_list_proxy_status"
|
||||||
app:layout_constraintStart_toEndOf="@id/toolbar_icon"
|
app:layout_constraintStart_toEndOf="@id/toolbar_icon"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/conversation_list_proxy_status"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="12dp"
|
||||||
|
android:background="?selectableItemBackgroundBorderless"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/conversation_list_title"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/search_action"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
tools:src="@drawable/ic_proxy_connected_24"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
<androidx.appcompat.widget.AppCompatImageView
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
android:id="@+id/search_action"
|
android:id="@+id/search_action"
|
||||||
android:layout_width="48dp"
|
android:layout_width="48dp"
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/edit_proxy_switch_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/preferences_use_proxy"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/edit_proxy_switch"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.SwitchCompat
|
||||||
|
android:id="@+id/edit_proxy_switch"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/edit_proxy_switch_title"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/edit_proxy_switch_title"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/edit_proxy_address_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="50dp"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:textColor="@color/signal_accent_primary"
|
||||||
|
android:text="@string/preferences_proxy_address"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/edit_proxy_switch_title"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/edit_proxy_host"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/edit_proxy_address_title"
|
||||||
|
tools:hint="https://proxy.parker.org"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/edit_proxy_status"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
style="@style/Signal.Text.Caption"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/edit_proxy_host"
|
||||||
|
tools:text="@string/preferences_connected_to_proxy"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/edit_proxy_share"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="19dp"
|
||||||
|
style="@style/Signal.Text.Body"
|
||||||
|
android:text="@string/preferences_share"
|
||||||
|
android:drawableStart="@drawable/ic_share_24"
|
||||||
|
android:drawableTint="@color/signal_text_primary"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:paddingStart="13dp"
|
||||||
|
android:paddingEnd="13dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:background="?selectableItemBackground"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/edit_proxy_status"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
<com.dd.CircularProgressButton
|
||||||
|
android:id="@+id/edit_proxy_save"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:text="@string/preferences_save"
|
||||||
|
app:cornerRadius="80dp"
|
||||||
|
app:elevation="4dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:cpb_colorIndicator="@color/white"
|
||||||
|
app:cpb_colorProgress="?colorAccent"
|
||||||
|
app:cpb_cornerRadius="28dp"
|
||||||
|
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||||
|
app:cpb_textIdle="@string/preferences_save" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:clipChildren="false"
|
||||||
|
android:clipToPadding="false"
|
||||||
|
tools:theme="@style/Theme.Signal.RoundedBottomSheet.Light">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/proxy_sheet_avatar"
|
||||||
|
android:layout_width="96dp"
|
||||||
|
android:layout_height="96dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
app:srcCompat="@drawable/proxy_avatar_96"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/proxy_sheet_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
style="@style/TextAppearance.Signal.Body1"
|
||||||
|
android:fontFamily="sans-serif-medium"
|
||||||
|
android:text="@string/ProxyBottomSheetFragment_proxy_server"
|
||||||
|
android:gravity="center"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/proxy_sheet_avatar"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/proxy_sheet_details_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_marginTop="28dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp"
|
||||||
|
android:paddingStart="18dp"
|
||||||
|
android:paddingEnd="18dp"
|
||||||
|
android:background="@drawable/wallpaper_bubble_background_12"
|
||||||
|
android:backgroundTint="@color/signal_background_tertiary"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/proxy_sheet_title">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Signal.Text.Caption"
|
||||||
|
android:text="@string/ProxyBottomSheetFragment_proxy_address" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/proxy_sheet_host"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
|
tools:text="https://signal.tube/proxy.parker.org" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/proxy_sheet_description"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
style="@style/Signal.Text.Preview"
|
||||||
|
android:text="@string/ProxyBottomSheetFragment_do_you_want_to_use_this_proxy_address"
|
||||||
|
android:textColor="@color/signal_text_secondary"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/proxy_sheet_details_container"/>
|
||||||
|
|
||||||
|
<com.dd.CircularProgressButton
|
||||||
|
android:id="@+id/proxy_sheet_use_proxy"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="7dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:text="@string/ProxyBottomSheetFragment_use_proxy"
|
||||||
|
app:cornerRadius="4dp"
|
||||||
|
app:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/proxy_sheet_description"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/proxy_sheet_cancel"
|
||||||
|
app:layout_constraintVertical_bias="0"
|
||||||
|
app:cpb_colorIndicator="@color/white"
|
||||||
|
app:cpb_colorProgress="?colorAccent"
|
||||||
|
app:cpb_cornerRadius="4dp"
|
||||||
|
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||||
|
app:cpb_textIdle="@string/ProxyBottomSheetFragment_use_proxy" />
|
||||||
|
|
||||||
|
<com.dd.CircularProgressButton
|
||||||
|
android:id="@+id/proxy_sheet_cancel"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:text="@android:string/cancel"
|
||||||
|
app:cornerRadius="4dp"
|
||||||
|
app:elevation="4dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/proxy_sheet_description"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/proxy_sheet_use_proxy"
|
||||||
|
app:layout_constraintVertical_bias="0"
|
||||||
|
app:cpb_colorIndicator="@color/white"
|
||||||
|
app:cpb_colorProgress="?colorAccent"
|
||||||
|
app:cpb_cornerRadius="4dp"
|
||||||
|
app:cpb_selectorIdle="@drawable/progress_button_state"
|
||||||
|
app:cpb_textIdle="@android:string/cancel" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -1396,6 +1396,14 @@
|
|||||||
<string name="CallParticipantView__cant_receive_audio_and_video_from_s">Can\'t receive audio and video from %1$s</string>
|
<string name="CallParticipantView__cant_receive_audio_and_video_from_s">Can\'t receive audio and video from %1$s</string>
|
||||||
<string name="CallParticipantView__this_may_be_Because_they_have_not_verified_your_safety_number_change">This may be because they have not verified your safety number change, there\'s a problem with their device, or they have blocked you.</string>
|
<string name="CallParticipantView__this_may_be_Because_they_have_not_verified_your_safety_number_change">This may be because they have not verified your safety number change, there\'s a problem with their device, or they have blocked you.</string>
|
||||||
|
|
||||||
|
<!-- ProxyBottomSheetFragment -->
|
||||||
|
<string name="ProxyBottomSheetFragment_proxy_server">Proxy server</string>
|
||||||
|
<string name="ProxyBottomSheetFragment_proxy_address">Proxy address</string>
|
||||||
|
<string name="ProxyBottomSheetFragment_do_you_want_to_use_this_proxy_address">Do you want to use this proxy address?</string>
|
||||||
|
<string name="ProxyBottomSheetFragment_use_proxy">Use proxy</string>
|
||||||
|
<string name="ProxyBottomSheetFragment_successfully_connected_to_proxy">Successfully connected to proxy.</string>
|
||||||
|
|
||||||
|
|
||||||
<!-- RegistrationActivity -->
|
<!-- RegistrationActivity -->
|
||||||
<string name="RegistrationActivity_select_your_country">Select your country</string>
|
<string name="RegistrationActivity_select_your_country">Select your country</string>
|
||||||
<string name="RegistrationActivity_you_must_specify_your_country_code">You must specify your
|
<string name="RegistrationActivity_you_must_specify_your_country_code">You must specify your
|
||||||
@@ -2303,6 +2311,21 @@
|
|||||||
<string name="preferences_communication__sealed_sender_allow_from_anyone_description">Enable sealed sender for incoming messages from non-contacts and people with whom you have not shared your profile.</string>
|
<string name="preferences_communication__sealed_sender_allow_from_anyone_description">Enable sealed sender for incoming messages from non-contacts and people with whom you have not shared your profile.</string>
|
||||||
<string name="preferences_communication__sealed_sender_learn_more">Learn more</string>
|
<string name="preferences_communication__sealed_sender_learn_more">Learn more</string>
|
||||||
<string name="preferences_setup_a_username">Setup a username</string>
|
<string name="preferences_setup_a_username">Setup a username</string>
|
||||||
|
<string name="preferences_proxy">Proxy</string>
|
||||||
|
<string name="preferences_use_proxy">Use proxy</string>
|
||||||
|
<string name="preferences_off">Off</string>
|
||||||
|
<string name="preferences_on">On</string>
|
||||||
|
<string name="preferences_proxy_address">Proxy address</string>
|
||||||
|
<string name="preferences_share">Share</string>
|
||||||
|
<string name="preferences_save">Save</string>
|
||||||
|
<string name="preferences_connecting_to_proxy">Connecting to proxy…</string>
|
||||||
|
<string name="preferences_connected_to_proxy">Connected to proxy</string>
|
||||||
|
<string name="preferences_connection_failed">Connection failed</string>
|
||||||
|
<string name="preferences_couldnt_connect_to_the_proxy">Couldn\'t connect to the proxy. Check the proxy address and try again.</string>
|
||||||
|
<string name="preferences_you_are_connected_to_the_proxy">You are connected to the proxy. You can turn the proxy off at any time from Settings.</string>
|
||||||
|
<string name="preferences_success">Success</string>
|
||||||
|
<string name="preferences_failed_to_connect">Failed to connect</string>
|
||||||
|
|
||||||
|
|
||||||
<string name="configurable_single_select__customize_option">Customize option</string>
|
<string name="configurable_single_select__customize_option">Customize option</string>
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,15 @@
|
|||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
<Preference android:layout="@layout/preference_data_and_storage_call_bandwidth_notice" />
|
<Preference android:layout="@layout/preference_data_and_storage_call_bandwidth_notice" />
|
||||||
|
|
||||||
|
<PreferenceCategory android:layout="@layout/preference_divider"/>
|
||||||
|
|
||||||
|
<PreferenceCategory
|
||||||
|
android:title="@string/preferences_proxy">
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
android:title="@string/preferences_use_proxy"
|
||||||
|
android:key="pref_use_proxy" />
|
||||||
|
|
||||||
|
</PreferenceCategory>
|
||||||
</PreferenceScreen>
|
</PreferenceScreen>
|
||||||
-2
@@ -9,13 +9,11 @@ import java.net.InetAddress;
|
|||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
import java.net.SocketException;
|
import java.net.SocketException;
|
||||||
import java.net.SocketOption;
|
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.nio.channels.SocketChannel;
|
import java.nio.channels.SocketChannel;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
import javax.net.ssl.SSLContext;
|
import javax.net.ssl.SSLContext;
|
||||||
|
|||||||
+3
@@ -1,9 +1,12 @@
|
|||||||
package org.whispersystems.signalservice.api.websocket;
|
package org.whispersystems.signalservice.api.websocket;
|
||||||
|
|
||||||
|
|
||||||
|
import okhttp3.Response;
|
||||||
|
|
||||||
public interface ConnectivityListener {
|
public interface ConnectivityListener {
|
||||||
void onConnected();
|
void onConnected();
|
||||||
void onConnecting();
|
void onConnecting();
|
||||||
void onDisconnected();
|
void onDisconnected();
|
||||||
void onAuthenticationFailure();
|
void onAuthenticationFailure();
|
||||||
|
boolean onGenericFailure(Response response, Throwable throwable);
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-1
@@ -304,7 +304,15 @@ public class WebSocketConnection extends WebSocketListener {
|
|||||||
Log.w(TAG, "onFailure()", t);
|
Log.w(TAG, "onFailure()", t);
|
||||||
|
|
||||||
if (response != null && (response.code() == 401 || response.code() == 403)) {
|
if (response != null && (response.code() == 401 || response.code() == 403)) {
|
||||||
if (listener != null) listener.onAuthenticationFailure();
|
if (listener != null) {
|
||||||
|
listener.onAuthenticationFailure();
|
||||||
|
}
|
||||||
|
} else if (listener != null) {
|
||||||
|
boolean shouldRetryConnection = listener.onGenericFailure(response, t);
|
||||||
|
if (!shouldRetryConnection) {
|
||||||
|
Log.w(TAG, "Experienced a failure, and the listener indicated we should not retry the connection. Disconnecting.");
|
||||||
|
disconnect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (client != null) {
|
if (client != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user