kopia lustrzana https://github.com/ryukoposting/Signal-Android
Add Device to Device Transfer UI.
rodzic
6f8be3260c
commit
75aab4c031
|
@ -155,6 +155,8 @@ android {
|
|||
exclude 'META-INF/LICENSE'
|
||||
exclude 'META-INF/NOTICE'
|
||||
exclude 'META-INF/proguard/androidx-annotations.pro'
|
||||
exclude '/org/spongycastle/x509/CertPathReviewerMessages.properties'
|
||||
exclude '/org/spongycastle/x509/CertPathReviewerMessages_de.properties'
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
@ -355,6 +357,7 @@ dependencies {
|
|||
implementation project(':paging')
|
||||
implementation project(':core-util')
|
||||
implementation project(':video')
|
||||
implementation project(':device-transfer')
|
||||
|
||||
implementation 'org.signal:zkgroup-android:0.7.0'
|
||||
implementation 'org.whispersystems:signal-client-android:0.1.7'
|
||||
|
|
|
@ -377,6 +377,16 @@
|
|||
android:windowSoftInputMode="stateAlwaysHidden">
|
||||
</activity>
|
||||
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceTransferActivity"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
android:launchMode="singleTask"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".devicetransfer.olddevice.OldDeviceExitActivity"
|
||||
android:noHistory="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
|
||||
|
||||
<activity android:name=".registration.RegistrationNavigationActivity"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/TextSecure.LightRegistrationTheme"
|
||||
|
|
|
@ -15,6 +15,12 @@ public abstract class LoggingFragment extends Fragment {
|
|||
|
||||
private static final String TAG = Log.tag(LoggingFragment.class);
|
||||
|
||||
public LoggingFragment() { }
|
||||
|
||||
public LoggingFragment(int contentLayoutId) {
|
||||
super(contentLayoutId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
logEvent("onCreate()");
|
||||
|
|
|
@ -11,10 +11,13 @@ import androidx.annotation.NonNull;
|
|||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.core.util.tracing.Tracer;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.crypto.MasterSecretUtil;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.lock.v2.CreateKbsPinActivity;
|
||||
|
@ -45,6 +48,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
private static final int STATE_ENTER_SIGNAL_PIN = 5;
|
||||
private static final int STATE_CREATE_PROFILE_NAME = 6;
|
||||
private static final int STATE_CREATE_SIGNAL_PIN = 7;
|
||||
private static final int STATE_TRANSFER_ONGOING = 8;
|
||||
|
||||
private SignalServiceNetworkAccess networkAccess;
|
||||
private BroadcastReceiver clearKeyReceiver;
|
||||
|
@ -146,6 +150,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
case STATE_ENTER_SIGNAL_PIN: return getEnterSignalPinIntent();
|
||||
case STATE_CREATE_SIGNAL_PIN: return getCreateSignalPinIntent();
|
||||
case STATE_CREATE_PROFILE_NAME: return getCreateProfileNameIntent();
|
||||
case STATE_TRANSFER_ONGOING: return getOldDeviceTransferIntent();
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +170,8 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
return STATE_CREATE_PROFILE_NAME;
|
||||
} else if (userMustCreateSignalPin()) {
|
||||
return STATE_CREATE_SIGNAL_PIN;
|
||||
} else if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null && getClass() != OldDeviceTransferActivity.class) {
|
||||
return STATE_TRANSFER_ONGOING;
|
||||
} else {
|
||||
return STATE_NORMAL;
|
||||
}
|
||||
|
@ -219,6 +226,12 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
|||
return getRoutedIntent(EditProfileActivity.class, getIntent());
|
||||
}
|
||||
|
||||
private Intent getOldDeviceTransferIntent() {
|
||||
Intent intent = new Intent(this, OldDeviceTransferActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
return intent;
|
||||
}
|
||||
|
||||
private Intent getRoutedIntent(Class<?> destination, @Nullable Intent nextIntent) {
|
||||
final Intent intent = new Intent(this, destination);
|
||||
if (nextIntent != null) intent.putExtra("next_intent", nextIntent);
|
||||
|
|
|
@ -26,8 +26,6 @@ import org.thoughtcrime.securesms.crypto.IdentityKeyUtil;
|
|||
import org.thoughtcrime.securesms.crypto.ModernDecryptingPartInputStream;
|
||||
import org.thoughtcrime.securesms.database.AttachmentDatabase;
|
||||
import org.thoughtcrime.securesms.database.GroupReceiptDatabase;
|
||||
import org.thoughtcrime.securesms.database.JobDatabase;
|
||||
import org.thoughtcrime.securesms.database.KeyValueDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsDatabase;
|
||||
import org.thoughtcrime.securesms.database.MmsSmsColumns;
|
||||
import org.thoughtcrime.securesms.database.OneTimePreKeyDatabase;
|
||||
|
@ -85,7 +83,7 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
throws IOException
|
||||
{
|
||||
try (OutputStream outputStream = new FileOutputStream(output)) {
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,15 +96,26 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
throws IOException
|
||||
{
|
||||
try (OutputStream outputStream = Objects.requireNonNull(context.getContentResolver().openOutputStream(output.getUri()))) {
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase);
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, true);
|
||||
}
|
||||
}
|
||||
|
||||
public static void transfer(@NonNull Context context,
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull OutputStream outputStream,
|
||||
@NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
internalExport(context, attachmentSecret, input, outputStream, passphrase, false);
|
||||
}
|
||||
|
||||
private static void internalExport(@NonNull Context context,
|
||||
@NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase input,
|
||||
@NonNull OutputStream fileOutputStream,
|
||||
@NonNull String passphrase)
|
||||
@NonNull String passphrase,
|
||||
boolean closeOutputStream)
|
||||
throws IOException
|
||||
{
|
||||
BackupFrameOutputStream outputStream = new BackupFrameOutputStream(fileOutputStream, passphrase);
|
||||
|
@ -155,7 +164,9 @@ public class FullBackupExporter extends FullBackupBase {
|
|||
|
||||
outputStream.writeEnd();
|
||||
} finally {
|
||||
outputStream.close();
|
||||
if (closeOutputStream) {
|
||||
outputStream.close();
|
||||
}
|
||||
EventBus.getDefault().post(new BackupEvent(BackupEvent.Type.FINISHED, ++count));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,10 +65,19 @@ public class FullBackupImporter extends FullBackupBase {
|
|||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase db, @NonNull Uri uri, @NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
try (InputStream is = getInputStream(context, uri)) {
|
||||
importFile(context, attachmentSecret, db, is, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
public static void importFile(@NonNull Context context, @NonNull AttachmentSecret attachmentSecret,
|
||||
@NonNull SQLiteDatabase db, @NonNull InputStream is, @NonNull String passphrase)
|
||||
throws IOException
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
try (InputStream is = getInputStream(context, uri)) {
|
||||
try {
|
||||
BackupRecordInputStream inputStream = new BackupRecordInputStream(is, passphrase);
|
||||
|
||||
db.beginTransaction();
|
||||
|
|
|
@ -200,7 +200,7 @@ public class ApplicationDependencies {
|
|||
}
|
||||
}
|
||||
|
||||
public static void closeConnectionsAfterProxyFailure() {
|
||||
public static void closeConnections() {
|
||||
synchronized (LOCK) {
|
||||
if (incomingMessageObserver != null) {
|
||||
incomingMessageObserver.terminateAsync();
|
||||
|
@ -220,7 +220,7 @@ public class ApplicationDependencies {
|
|||
public static void resetNetworkConnectionsAfterProxyChange() {
|
||||
synchronized (LOCK) {
|
||||
getPipeListener().reset();
|
||||
closeConnectionsAfterProxyFailure();
|
||||
closeConnections();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import static org.thoughtcrime.securesms.devicetransfer.SetupStep.VERIFY;
|
||||
|
||||
/**
|
||||
* State representation of the current {@link SetupStep} in the setup flow and
|
||||
* the SAS if one has been provided.
|
||||
*/
|
||||
public final class DeviceSetupState {
|
||||
|
||||
private final SetupStep currentSetupStep;
|
||||
private final int authenticationCode;
|
||||
|
||||
public DeviceSetupState() {
|
||||
this(SetupStep.INITIAL, 0);
|
||||
}
|
||||
|
||||
public DeviceSetupState(@NonNull SetupStep currentSetupStep, int authenticationCode) {
|
||||
this.currentSetupStep = currentSetupStep;
|
||||
this.authenticationCode = authenticationCode;
|
||||
}
|
||||
|
||||
public @NonNull SetupStep getCurrentSetupStep() {
|
||||
return currentSetupStep;
|
||||
}
|
||||
|
||||
public int getAuthenticationCode() {
|
||||
return authenticationCode;
|
||||
}
|
||||
|
||||
public @NonNull DeviceSetupState updateStep(@NonNull SetupStep currentSetupStep) {
|
||||
return new DeviceSetupState(currentSetupStep, this.authenticationCode);
|
||||
}
|
||||
|
||||
public @NonNull DeviceSetupState updateVerificationRequired(int authenticationCode) {
|
||||
return new DeviceSetupState(VERIFY, authenticationCode);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,142 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Drives the UI for the actual device transfer progress. Shown after setup is complete
|
||||
* and the two devices are transferring.
|
||||
* <p>
|
||||
* Handles show progress and error state.
|
||||
*/
|
||||
public abstract class DeviceTransferFragment extends LoggingFragment {
|
||||
|
||||
private final OnBackPressed onBackPressed = new OnBackPressed();
|
||||
private final TransferModeListener transferModeListener = new TransferModeListener();
|
||||
|
||||
protected TextView title;
|
||||
protected View tryAgain;
|
||||
protected Button cancel;
|
||||
protected View progress;
|
||||
protected View alert;
|
||||
protected TextView status;
|
||||
|
||||
public DeviceTransferFragment() {
|
||||
super(R.layout.device_transfer_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
title = view.findViewById(R.id.device_transfer_fragment_title);
|
||||
tryAgain = view.findViewById(R.id.device_transfer_fragment_try_again);
|
||||
cancel = view.findViewById(R.id.device_transfer_fragment_cancel);
|
||||
progress = view.findViewById(R.id.device_transfer_fragment_progress);
|
||||
alert = view.findViewById(R.id.device_transfer_fragment_alert);
|
||||
status = view.findViewById(R.id.device_transfer_fragment_status);
|
||||
|
||||
cancel.setOnClickListener(v -> cancelActiveTransfer());
|
||||
tryAgain.setOnClickListener(v -> {
|
||||
EventBus.getDefault().unregister(transferModeListener);
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
navigateToRestartTransfer();
|
||||
});
|
||||
|
||||
EventBus.getDefault().register(transferModeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
EventBus.getDefault().unregister(transferModeListener);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void cancelActiveTransfer() {
|
||||
new AlertDialog.Builder(requireContext()).setTitle(R.string.DeviceTransfer__stop_transfer)
|
||||
.setMessage(R.string.DeviceTransfer__all_transfer_progress_will_be_lost)
|
||||
.setPositiveButton(R.string.DeviceTransfer__stop_transfer, (d, w) -> {
|
||||
EventBus.getDefault().unregister(transferModeListener);
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
navigateAwayFromTransfer();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
}
|
||||
|
||||
protected void ignoreTransferStatusEvents() {
|
||||
EventBus.getDefault().unregister(transferModeListener);
|
||||
}
|
||||
|
||||
protected abstract void navigateToRestartTransfer();
|
||||
|
||||
protected abstract void navigateAwayFromTransfer();
|
||||
|
||||
private class TransferModeListener {
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(@NonNull TransferStatus event) {
|
||||
if (event.getTransferMode() != TransferStatus.TransferMode.SERVICE_CONNECTED) {
|
||||
abort();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void abort() {
|
||||
abort(R.string.DeviceTransfer__transfer_failed);
|
||||
}
|
||||
|
||||
protected void abort(@StringRes int errorMessage) {
|
||||
EventBus.getDefault().unregister(transferModeListener);
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
|
||||
progress.setVisibility(View.GONE);
|
||||
alert.setVisibility(View.VISIBLE);
|
||||
tryAgain.setVisibility(View.VISIBLE);
|
||||
|
||||
title.setText(R.string.DeviceTransfer__unable_to_transfer);
|
||||
status.setText(errorMessage);
|
||||
cancel.setText(R.string.DeviceTransfer__cancel);
|
||||
cancel.setOnClickListener(v -> navigateAwayFromTransfer());
|
||||
|
||||
onBackPressed.isActiveTransfer = false;
|
||||
}
|
||||
|
||||
protected class OnBackPressed extends OnBackPressedCallback {
|
||||
|
||||
private boolean isActiveTransfer = true;
|
||||
|
||||
public OnBackPressed() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (isActiveTransfer) {
|
||||
cancelActiveTransfer();
|
||||
} else {
|
||||
navigateAwayFromTransfer();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,393 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Intent;
|
||||
import android.location.LocationManager;
|
||||
import android.net.wifi.WifiManager;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.method.LinkMovementMethod;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.constraintlayout.widget.Group;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.lifecycle.ViewModelProviders;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.google.android.material.button.MaterialButton;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.signal.devicetransfer.WifiDirect;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logsubmit.SubmitDebugLogActivity;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.util.CommunicationActions;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
import org.thoughtcrime.securesms.util.ViewUtil;
|
||||
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Responsible for driving the UI of all the legwork to startup Wi-Fi Direct and
|
||||
* establish the connection between the two devices. It's capable of being used by both
|
||||
* the new and old device, but delegates some of the UI (mostly strings and navigation) to
|
||||
* a subclass for old or new device.
|
||||
* <p>
|
||||
* Handles showing setup progress, verification codes, connecting states, error states, and troubleshooting.
|
||||
* <p>
|
||||
* It's state driven by the view model so it's easy to transition from step to step in the
|
||||
* process.
|
||||
*/
|
||||
public abstract class DeviceTransferSetupFragment extends LoggingFragment {
|
||||
|
||||
private static final String TAG = Log.tag(DeviceTransferSetupFragment.class);
|
||||
|
||||
private static final long PREPARE_TAKING_TOO_LONG_TIME = TimeUnit.SECONDS.toMillis(30);
|
||||
private static final long WAITING_TAKING_TOO_LONG_TIME = TimeUnit.SECONDS.toMillis(90);
|
||||
|
||||
private final OnBackPressed onBackPressed = new OnBackPressed();
|
||||
private DeviceTransferSetupViewModel viewModel;
|
||||
private Runnable takingTooLong;
|
||||
|
||||
public DeviceTransferSetupFragment() {
|
||||
super(R.layout.device_transfer_setup_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Group progressGroup = view.findViewById(R.id.device_transfer_setup_fragment_progress_group);
|
||||
Group errorGroup = view.findViewById(R.id.device_transfer_setup_fragment_error_group);
|
||||
Group verifyGroup = view.findViewById(R.id.device_transfer_setup_fragment_verify_group);
|
||||
View troubleshooting = view.findViewById(R.id.device_transfer_setup_fragment_troubleshooting);
|
||||
TextView status = view.findViewById(R.id.device_transfer_setup_fragment_status);
|
||||
TextView error = view.findViewById(R.id.device_transfer_setup_fragment_error);
|
||||
MaterialButton errorResolve = view.findViewById(R.id.device_transfer_setup_fragment_error_resolve);
|
||||
TextView sasNumber = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_code);
|
||||
MaterialButton verifyNo = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_no);
|
||||
MaterialButton verifyYes = view.findViewById(R.id.device_transfer_setup_fragment_sas_verify_yes);
|
||||
|
||||
viewModel = ViewModelProviders.of(this).get(DeviceTransferSetupViewModel.class);
|
||||
|
||||
viewModel.getState().observe(getViewLifecycleOwner(), state -> {
|
||||
SetupStep step = state.getCurrentSetupStep();
|
||||
|
||||
progressGroup.setVisibility(step.isProgress() ? View.VISIBLE : View.GONE);
|
||||
errorGroup.setVisibility(step.isError() ? View.VISIBLE : View.GONE);
|
||||
verifyGroup.setVisibility(step == SetupStep.VERIFY ? View.VISIBLE : View.GONE);
|
||||
troubleshooting.setVisibility(step == SetupStep.TROUBLESHOOTING ? View.VISIBLE : View.GONE);
|
||||
|
||||
Log.i(TAG, "Handling step: " + step.name());
|
||||
switch (step) {
|
||||
case INITIAL:
|
||||
status.setText("");
|
||||
case PERMISSIONS_CHECK:
|
||||
requestLocationPermission();
|
||||
break;
|
||||
case PERMISSIONS_DENIED:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
errorResolve.setText(R.string.DeviceTransferSetup__grant_location_permission);
|
||||
errorResolve.setOnClickListener(v -> viewModel.checkPermissions());
|
||||
break;
|
||||
case LOCATION_CHECK:
|
||||
verifyLocationEnabled();
|
||||
break;
|
||||
case LOCATION_DISABLED:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
errorResolve.setText(R.string.DeviceTransferSetup__turn_on_location_services);
|
||||
errorResolve.setOnClickListener(v -> openLocationServices());
|
||||
break;
|
||||
case WIFI_CHECK:
|
||||
verifyWifiEnabled();
|
||||
break;
|
||||
case WIFI_DISABLED:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
errorResolve.setText(R.string.DeviceTransferSetup__turn_on_wifi);
|
||||
errorResolve.setOnClickListener(v -> openWifiSettings());
|
||||
break;
|
||||
case WIFI_DIRECT_CHECK:
|
||||
verifyWifiDirectAvailable();
|
||||
break;
|
||||
case WIFI_DIRECT_UNAVAILABLE:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
errorResolve.setText(getErrorResolveButtonTextForStep(step));
|
||||
errorResolve.setOnClickListener(v -> navigateWhenWifiDirectUnavailable());
|
||||
break;
|
||||
case START:
|
||||
status.setText(getStatusTextForStep(SetupStep.SETTING_UP, false));
|
||||
startTransfer();
|
||||
break;
|
||||
case SETTING_UP:
|
||||
status.setText(getStatusTextForStep(step, false));
|
||||
startTakingTooLong(() -> status.setText(getStatusTextForStep(step, true)), PREPARE_TAKING_TOO_LONG_TIME);
|
||||
break;
|
||||
case WAITING:
|
||||
status.setText(getStatusTextForStep(step, false));
|
||||
cancelTakingTooLong();
|
||||
startTakingTooLong(() -> {
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
viewModel.onWaitingTookTooLong();
|
||||
}, WAITING_TAKING_TOO_LONG_TIME);
|
||||
break;
|
||||
case VERIFY:
|
||||
cancelTakingTooLong();
|
||||
sasNumber.setText(String.format(Locale.US, "%07d", state.getAuthenticationCode()));
|
||||
//noinspection CodeBlock2Expr
|
||||
verifyNo.setOnClickListener(v -> {
|
||||
new AlertDialog.Builder(requireContext()).setTitle(R.string.DeviceTransferSetup__the_numbers_do_not_match)
|
||||
.setMessage(R.string.DeviceTransferSetup__if_the_numbers_on_your_devices_do_not_match_its_possible_you_connected_to_the_wrong_device)
|
||||
.setPositiveButton(R.string.DeviceTransferSetup__stop_transfer, (d, w) -> {
|
||||
EventBus.getDefault().unregister(this);
|
||||
DeviceToDeviceTransferService.setAuthenticationCodeVerified(requireContext(), false);
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
navigateAwayFromTransfer();
|
||||
})
|
||||
.setNegativeButton(android.R.string.cancel, null)
|
||||
.show();
|
||||
});
|
||||
verifyYes.setOnClickListener(v -> {
|
||||
DeviceToDeviceTransferService.setAuthenticationCodeVerified(requireContext(), true);
|
||||
viewModel.onVerified();
|
||||
});
|
||||
break;
|
||||
case CONNECTING:
|
||||
status.setText(getStatusTextForStep(step, false));
|
||||
break;
|
||||
case CONNECTED:
|
||||
Log.d(TAG, "Connected! isNotShutdown: " + viewModel.isNotShutdown());
|
||||
if (viewModel.isNotShutdown()) {
|
||||
navigateToTransferConnected();
|
||||
}
|
||||
break;
|
||||
case TROUBLESHOOTING:
|
||||
TextView title = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_title);
|
||||
title.setText(getStatusTextForStep(step, false));
|
||||
|
||||
int gapWidth = ViewUtil.dpToPx(12);
|
||||
TextView step1 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step1);
|
||||
step1.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__make_sure_the_following_permissions_are_enabled), gapWidth));
|
||||
TextView step2 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step2);
|
||||
step2.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
step2.setText(SpanUtil.clickSubstring(requireContext(),
|
||||
SpanUtil.bullet(getString(R.string.DeviceTransferSetup__on_the_wifi_direct_screen_remove_all_remembered_groups_and_unlink_any_invited_or_connected_devices), gapWidth),
|
||||
getString(R.string.DeviceTransferSetup__wifi_direct_screen),
|
||||
v -> openWifiDirectSettings()));
|
||||
TextView step3 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step3);
|
||||
step3.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__try_turning_wifi_off_and_on_on_both_devices), gapWidth));
|
||||
TextView step4 = troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_step4);
|
||||
step4.setText(SpanUtil.bullet(getString(R.string.DeviceTransferSetup__make_sure_both_devices_are_in_transfer_mode), gapWidth));
|
||||
|
||||
troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_location_permission)
|
||||
.setOnClickListener(v -> openApplicationSystemSettings());
|
||||
troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_location_services)
|
||||
.setOnClickListener(v -> openLocationServices());
|
||||
troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_wifi)
|
||||
.setOnClickListener(v -> openWifiSettings());
|
||||
troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_go_to_support)
|
||||
.setOnClickListener(v -> gotoSupport());
|
||||
troubleshooting.findViewById(R.id.device_transfer_setup_fragment_troubleshooting_try_again)
|
||||
.setOnClickListener(v -> viewModel.checkPermissions());
|
||||
break;
|
||||
case ERROR:
|
||||
error.setText(getErrorTextForStep(step));
|
||||
errorResolve.setText(R.string.DeviceTransferSetup__retry);
|
||||
errorResolve.setOnClickListener(v -> viewModel.checkPermissions());
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
cancelTakingTooLong();
|
||||
new AlertDialog.Builder(requireContext()).setTitle(R.string.DeviceTransferSetup__error_connecting)
|
||||
.setMessage(getStatusTextForStep(step, false))
|
||||
.setPositiveButton(R.string.DeviceTransferSetup__retry, (d, w) -> viewModel.checkPermissions())
|
||||
.setNegativeButton(android.R.string.cancel, (d, w) -> {
|
||||
EventBus.getDefault().unregister(this);
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
navigateAwayFromTransfer();
|
||||
})
|
||||
.setNeutralButton(R.string.DeviceTransferSetup__submit_debug_logs, (d, w) -> {
|
||||
EventBus.getDefault().unregister(this);
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
navigateAwayFromTransfer();
|
||||
startActivity(new Intent(requireContext(), SubmitDebugLogActivity.class));
|
||||
})
|
||||
.setCancelable(false)
|
||||
.show();
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract @StringRes int getStatusTextForStep(@NonNull SetupStep step, boolean takingTooLongInStep);
|
||||
|
||||
protected abstract @StringRes int getErrorTextForStep(@NonNull SetupStep step);
|
||||
|
||||
protected abstract @StringRes int getErrorResolveButtonTextForStep(@NonNull SetupStep step);
|
||||
|
||||
protected abstract void navigateWhenWifiDirectUnavailable();
|
||||
|
||||
protected abstract void startTransfer();
|
||||
|
||||
protected abstract void navigateToTransferConnected();
|
||||
|
||||
protected abstract void navigateAwayFromTransfer();
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), onBackPressed);
|
||||
|
||||
TransferStatus event = EventBus.getDefault().getStickyEvent(TransferStatus.class);
|
||||
if (event == null) {
|
||||
viewModel.checkPermissions();
|
||||
} else {
|
||||
Log.i(TAG, "Sticky event already exists for transfer, assuming service is running and we are reattaching");
|
||||
}
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
viewModel.onResume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
cancelTakingTooLong();
|
||||
EventBus.getDefault().unregister(this);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
private void requestLocationPermission() {
|
||||
Permissions.with(this)
|
||||
.request(Manifest.permission.ACCESS_FINE_LOCATION)
|
||||
.ifNecessary()
|
||||
.withRationaleDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED)), false, R.drawable.ic_location_on_white_24dp)
|
||||
.withPermanentDenialDialog(getString(getErrorTextForStep(SetupStep.PERMISSIONS_DENIED)))
|
||||
.onAllGranted(() -> viewModel.onPermissionsGranted())
|
||||
.onAnyDenied(() -> viewModel.onLocationPermissionDenied())
|
||||
.execute();
|
||||
}
|
||||
|
||||
private void openApplicationSystemSettings() {
|
||||
startActivity(Permissions.getApplicationSettingsIntent(requireContext()));
|
||||
}
|
||||
|
||||
private void verifyLocationEnabled() {
|
||||
LocationManager locationManager = ContextCompat.getSystemService(requireContext(), LocationManager.class);
|
||||
if (locationManager != null && locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) {
|
||||
viewModel.onLocationEnabled();
|
||||
} else {
|
||||
viewModel.onLocationDisabled();
|
||||
}
|
||||
}
|
||||
|
||||
private void openLocationServices() {
|
||||
try {
|
||||
startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "No location settings", e);
|
||||
Toast.makeText(requireContext(), R.string.DeviceTransferSetup__unable_to_open_wifi_settings, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyWifiEnabled() {
|
||||
WifiManager wifiManager = ContextCompat.getSystemService(requireContext(), WifiManager.class);
|
||||
if (wifiManager != null && wifiManager.isWifiEnabled()) {
|
||||
viewModel.onWifiEnabled();
|
||||
} else {
|
||||
viewModel.onWifiDisabled(wifiManager == null);
|
||||
}
|
||||
}
|
||||
|
||||
private void openWifiSettings() {
|
||||
try {
|
||||
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "No wifi settings", e);
|
||||
Toast.makeText(requireContext(), R.string.DeviceTransferSetup__unable_to_open_wifi_settings, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
private void openWifiDirectSettings() {
|
||||
try {
|
||||
Intent wifiDirect = new Intent(Intent.ACTION_MAIN);
|
||||
wifiDirect.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
.setClassName("com.android.settings", "com.android.settings.Settings$WifiP2pSettingsActivity");
|
||||
|
||||
startActivity(wifiDirect);
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Log.w(TAG, "Unable to open wifi direct settings", e);
|
||||
openWifiSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyWifiDirectAvailable() {
|
||||
WifiDirect.AvailableStatus availability = WifiDirect.getAvailability(requireContext());
|
||||
if (availability != WifiDirect.AvailableStatus.AVAILABLE) {
|
||||
viewModel.onWifiDirectUnavailable(availability);
|
||||
} else {
|
||||
viewModel.onWifiDirectAvailable();
|
||||
}
|
||||
}
|
||||
|
||||
private void gotoSupport() {
|
||||
CommunicationActions.openBrowserLink(requireContext(), getString(R.string.transfer_support_url));
|
||||
}
|
||||
|
||||
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(@NonNull TransferStatus event) {
|
||||
viewModel.onTransferEvent(event);
|
||||
}
|
||||
|
||||
private void startTakingTooLong(@NonNull Runnable runnable, long tooLong) {
|
||||
if (takingTooLong == null) {
|
||||
takingTooLong = () -> {
|
||||
takingTooLong = null;
|
||||
runnable.run();
|
||||
};
|
||||
ThreadUtil.runOnMainDelayed(takingTooLong, tooLong);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelTakingTooLong() {
|
||||
if (takingTooLong != null) {
|
||||
ThreadUtil.cancelRunnableOnMain(takingTooLong);
|
||||
takingTooLong = null;
|
||||
}
|
||||
}
|
||||
|
||||
private class OnBackPressed extends OnBackPressedCallback {
|
||||
|
||||
public OnBackPressed() {
|
||||
super(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
NavHostFragment.findNavController(DeviceTransferSetupFragment.this).popBackStack();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,137 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.signal.devicetransfer.WifiDirect;
|
||||
import org.thoughtcrime.securesms.util.livedata.LiveDataUtil;
|
||||
import org.thoughtcrime.securesms.util.livedata.Store;
|
||||
|
||||
/**
|
||||
* Drives and wraps the state of the transfer setup process.
|
||||
*/
|
||||
public final class DeviceTransferSetupViewModel extends ViewModel {
|
||||
|
||||
private static final String TAG = Log.tag(DeviceTransferSetupViewModel.class);
|
||||
|
||||
private final Store<DeviceSetupState> store;
|
||||
private final LiveData<DeviceSetupState> distinctStepChanges;
|
||||
|
||||
private boolean shutdown;
|
||||
|
||||
public DeviceTransferSetupViewModel() {
|
||||
this.store = new Store<>(new DeviceSetupState());
|
||||
this.distinctStepChanges = LiveDataUtil.distinctUntilChanged(this.store.getStateLiveData(), (current, next) -> current.getCurrentSetupStep() == next.getCurrentSetupStep());
|
||||
}
|
||||
|
||||
public @NonNull LiveData<DeviceSetupState> getState() {
|
||||
return distinctStepChanges;
|
||||
}
|
||||
|
||||
public boolean isNotShutdown() {
|
||||
return !shutdown;
|
||||
}
|
||||
|
||||
public void onTransferEvent(@NonNull TransferStatus event) {
|
||||
if (shutdown) {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.i(TAG, "Handling transferStatus: " + event.getTransferMode());
|
||||
switch (event.getTransferMode()) {
|
||||
case UNAVAILABLE:
|
||||
case NETWORK_CONNECTED:
|
||||
Log.d(TAG, "Ignore event: " + event.getTransferMode());
|
||||
break;
|
||||
case READY:
|
||||
case STARTING_UP:
|
||||
store.update(s -> s.updateStep(SetupStep.SETTING_UP));
|
||||
break;
|
||||
case DISCOVERY:
|
||||
store.update(s -> s.updateStep(SetupStep.WAITING));
|
||||
break;
|
||||
case VERIFICATION_REQUIRED:
|
||||
store.update(s -> s.updateVerificationRequired(event.getAuthenticationCode()));
|
||||
break;
|
||||
case SERVICE_CONNECTED:
|
||||
store.update(s -> s.updateStep(SetupStep.CONNECTED));
|
||||
break;
|
||||
case FAILED:
|
||||
store.update(s -> s.updateStep(SetupStep.ERROR));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void onLocationPermissionDenied() {
|
||||
Log.i(TAG, "Location permissions denied");
|
||||
store.update(s -> s.updateStep(SetupStep.PERMISSIONS_DENIED));
|
||||
}
|
||||
|
||||
public void onWifiDisabled(boolean wifiManagerNotAvailable) {
|
||||
Log.i(TAG, "Wifi disabled manager: " + wifiManagerNotAvailable);
|
||||
store.update(s -> s.updateStep(SetupStep.WIFI_DISABLED));
|
||||
}
|
||||
|
||||
public void onWifiDirectUnavailable(WifiDirect.AvailableStatus availability) {
|
||||
Log.i(TAG, "Wifi Direct unavailable: " + availability);
|
||||
if (availability == WifiDirect.AvailableStatus.FINE_LOCATION_PERMISSION_NOT_GRANTED) {
|
||||
store.update(s -> s.updateStep(SetupStep.PERMISSIONS_CHECK));
|
||||
} else {
|
||||
store.update(s -> s.updateStep(SetupStep.WIFI_DIRECT_UNAVAILABLE));
|
||||
}
|
||||
}
|
||||
|
||||
public void checkPermissions() {
|
||||
Log.d(TAG, "Check for permissions");
|
||||
shutdown = false;
|
||||
store.update(s -> s.updateStep(SetupStep.PERMISSIONS_CHECK));
|
||||
}
|
||||
|
||||
public void onPermissionsGranted() {
|
||||
Log.d(TAG, "Permissions granted");
|
||||
store.update(s -> s.updateStep(SetupStep.LOCATION_CHECK));
|
||||
}
|
||||
|
||||
public void onLocationEnabled() {
|
||||
Log.d(TAG, "Location enabled");
|
||||
store.update(s -> s.updateStep(SetupStep.WIFI_CHECK));
|
||||
}
|
||||
|
||||
public void onLocationDisabled() {
|
||||
Log.d(TAG, "Location disabled");
|
||||
store.update(s -> s.updateStep(SetupStep.LOCATION_DISABLED));
|
||||
}
|
||||
|
||||
public void onWifiEnabled() {
|
||||
Log.d(TAG, "Wifi enabled");
|
||||
store.update(s -> s.updateStep(SetupStep.WIFI_DIRECT_CHECK));
|
||||
}
|
||||
|
||||
public void onWifiDirectAvailable() {
|
||||
Log.d(TAG, "Wifi direct available");
|
||||
store.update(s -> s.updateStep(SetupStep.START));
|
||||
}
|
||||
|
||||
public void onVerified() {
|
||||
store.update(s -> s.updateStep(SetupStep.CONNECTING));
|
||||
}
|
||||
|
||||
public void onResume() {
|
||||
store.update(s -> {
|
||||
if (s.getCurrentSetupStep() == SetupStep.WIFI_DISABLED) {
|
||||
return s.updateStep(SetupStep.WIFI_CHECK);
|
||||
} else if (s.getCurrentSetupStep() == SetupStep.LOCATION_DISABLED) {
|
||||
return s.updateStep(SetupStep.LOCATION_CHECK);
|
||||
}
|
||||
return s;
|
||||
});
|
||||
}
|
||||
|
||||
public void onWaitingTookTooLong() {
|
||||
shutdown = true;
|
||||
store.update(s -> s.updateStep(SetupStep.TROUBLESHOOTING));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer;
|
||||
|
||||
/**
|
||||
* The various steps involved in setting up a transfer connection. Each step has a
|
||||
* corresponding UI.
|
||||
*/
|
||||
public enum SetupStep {
|
||||
INITIAL(true, false),
|
||||
PERMISSIONS_CHECK(true, false),
|
||||
PERMISSIONS_DENIED(false, true),
|
||||
LOCATION_CHECK(true, false),
|
||||
LOCATION_DISABLED(false, true),
|
||||
WIFI_CHECK(true, false),
|
||||
WIFI_DISABLED(false, true),
|
||||
WIFI_DIRECT_CHECK(true, false),
|
||||
WIFI_DIRECT_UNAVAILABLE(false, true),
|
||||
START(true, false),
|
||||
SETTING_UP(true, false),
|
||||
WAITING(true, false),
|
||||
VERIFY(false, false),
|
||||
CONNECTING(true, false),
|
||||
CONNECTED(true, false),
|
||||
TROUBLESHOOTING(false, false),
|
||||
ERROR(false, true);
|
||||
|
||||
private final boolean isProgress;
|
||||
private final boolean isError;
|
||||
|
||||
SetupStep(boolean isProgress, boolean isError) {
|
||||
this.isProgress = isProgress;
|
||||
this.isError = isError;
|
||||
}
|
||||
|
||||
public boolean isProgress() {
|
||||
return isProgress;
|
||||
}
|
||||
|
||||
public boolean isError() {
|
||||
return isError;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import net.sqlcipher.database.SQLiteDatabase;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.ServerTask;
|
||||
import org.thoughtcrime.securesms.AppInitialization;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupBase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupImporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Performs the restore with the backup data coming in over the input stream. Used in
|
||||
* conjunction with {@link org.signal.devicetransfer.DeviceToDeviceTransferService}.
|
||||
*/
|
||||
final class NewDeviceServerTask implements ServerTask {
|
||||
|
||||
private static final String TAG = Log.tag(NewDeviceServerTask.class);
|
||||
|
||||
@Override
|
||||
public void run(@NonNull Context context, @NonNull InputStream inputStream) {
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
Log.i(TAG, "Starting backup restore.");
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
try {
|
||||
SQLiteDatabase database = DatabaseFactory.getBackupDatabase(context);
|
||||
|
||||
String passphrase = "deadbeef";
|
||||
|
||||
BackupPassphrase.set(context, passphrase);
|
||||
FullBackupImporter.importFile(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
database,
|
||||
inputStream,
|
||||
passphrase);
|
||||
|
||||
DatabaseFactory.upgradeRestored(context, database);
|
||||
NotificationChannels.restoreContactNotificationChannels(context);
|
||||
|
||||
AppInitialization.onPostBackupRestore(context);
|
||||
|
||||
Log.i(TAG, "Backup restore complete.");
|
||||
} catch (FullBackupImporter.DatabaseDowngradeException e) {
|
||||
Log.w(TAG, "Failed due to the backup being from a newer version of Signal.", e);
|
||||
EventBus.getDefault().post(new Status(0, Status.State.FAILURE_VERSION_DOWNGRADE));
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, e);
|
||||
EventBus.getDefault().post(new Status(0, Status.State.FAILURE_UNKNOWN));
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
Log.i(TAG, "Receive took: " + (end - start));
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
public void onEvent(FullBackupBase.BackupEvent event) {
|
||||
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
|
||||
EventBus.getDefault().post(new Status(event.getCount(), Status.State.IN_PROGRESS));
|
||||
} else if (event.getType() == FullBackupBase.BackupEvent.Type.FINISHED) {
|
||||
EventBus.getDefault().post(new Status(event.getCount(), Status.State.SUCCESS));
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Status {
|
||||
private final long messageCount;
|
||||
private final State state;
|
||||
|
||||
public Status(long messageCount, State state) {
|
||||
this.messageCount = messageCount;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public long getMessageCount() {
|
||||
return messageCount;
|
||||
}
|
||||
|
||||
public @NonNull State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
public enum State {
|
||||
IN_PROGRESS,
|
||||
SUCCESS,
|
||||
FAILURE_VERSION_DOWNGRADE,
|
||||
FAILURE_UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Shown after the new device successfully completes receiving a backup from the old device.
|
||||
*/
|
||||
public final class NewDeviceTransferCompleteFragment extends LoggingFragment {
|
||||
public NewDeviceTransferCompleteFragment() {
|
||||
super(R.layout.new_device_transfer_complete_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.findViewById(R.id.new_device_transfer_complete_fragment_continue_registration)
|
||||
.setOnClickListener(v -> NavHostFragment.findNavController(this)
|
||||
.navigate(R.id.action_newDeviceTransferComplete_to_enterPhoneNumberFragment));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() { }
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment;
|
||||
|
||||
/**
|
||||
* Shows transfer progress on the new device. Most logic is in {@link DeviceTransferFragment}
|
||||
* and it delegates to this class for strings, navigation, and updating progress.
|
||||
*/
|
||||
public final class NewDeviceTransferFragment extends DeviceTransferFragment {
|
||||
|
||||
private final ServerTaskListener serverTaskListener = new ServerTaskListener();
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
EventBus.getDefault().register(serverTaskListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
EventBus.getDefault().unregister(serverTaskListener);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateToRestartTransfer() {
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_newDeviceTransfer_to_newDeviceTransferInstructions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateAwayFromTransfer() {
|
||||
EventBus.getDefault().unregister(serverTaskListener);
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(R.id.action_restart_to_welcomeFragment);
|
||||
}
|
||||
|
||||
private class ServerTaskListener {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(@NonNull NewDeviceServerTask.Status event) {
|
||||
status.setText(getString(R.string.DeviceTransfer__d_messages_so_far, event.getMessageCount()));
|
||||
switch (event.getState()) {
|
||||
case IN_PROGRESS:
|
||||
break;
|
||||
case SUCCESS:
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
NavHostFragment.findNavController(NewDeviceTransferFragment.this).navigate(R.id.action_newDeviceTransfer_to_newDeviceTransferComplete);
|
||||
break;
|
||||
case FAILURE_VERSION_DOWNGRADE:
|
||||
abort(R.string.NewDeviceTransfer__cannot_transfer_from_a_newer_version_of_signal);
|
||||
break;
|
||||
case FAILURE_UNKNOWN:
|
||||
abort();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Shows instructions for new device to being transfer.
|
||||
*/
|
||||
public final class NewDeviceTransferInstructionsFragment extends LoggingFragment {
|
||||
public NewDeviceTransferInstructionsFragment() {
|
||||
super(R.layout.new_device_transfer_instructions_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.findViewById(R.id.new_device_transfer_instructions_fragment_continue)
|
||||
.setOnClickListener(v -> Navigation.findNavController(v).navigate(R.id.action_device_transfer_setup));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService.TransferNotificationData;
|
||||
import org.thoughtcrime.securesms.MainActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.devicetransfer.SetupStep;
|
||||
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferSetupFragment;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
|
||||
/**
|
||||
* Most responsibility is in {@link DeviceTransferSetupFragment} and delegates here
|
||||
* for strings and behavior relevant to setting up device transfer for the new device.
|
||||
*
|
||||
* Also responsible for setting up {@link DeviceToDeviceTransferService}.
|
||||
*/
|
||||
public final class NewDeviceTransferSetupFragment extends DeviceTransferSetupFragment {
|
||||
|
||||
@Override
|
||||
protected void navigateAwayFromTransfer() {
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(R.id.action_deviceTransferSetup_to_transferOrRestore);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateToTransferConnected() {
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_new_device_transfer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getErrorTextForStep(@NonNull SetupStep step) {
|
||||
switch (step) {
|
||||
case PERMISSIONS_DENIED:
|
||||
return R.string.NewDeviceTransferSetup__signal_needs_the_location_permission_to_discover_and_connect_with_your_old_device;
|
||||
case LOCATION_DISABLED:
|
||||
return R.string.NewDeviceTransferSetup__signal_needs_location_services_enabled_to_discover_and_connect_with_your_old_device;
|
||||
case WIFI_DISABLED:
|
||||
return R.string.NewDeviceTransferSetup__signal_needs_wifi_on_to_discover_and_connect_with_your_old_device;
|
||||
case WIFI_DIRECT_UNAVAILABLE:
|
||||
return R.string.NewDeviceTransferSetup__sorry_it_appears_your_device_does_not_support_wifi_direct;
|
||||
case ERROR:
|
||||
return R.string.NewDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device;
|
||||
}
|
||||
throw new AssertionError("No error text for step: " + step);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getErrorResolveButtonTextForStep(@NonNull SetupStep step) {
|
||||
if (step == SetupStep.WIFI_DIRECT_UNAVAILABLE) {
|
||||
return R.string.NewDeviceTransferSetup__restore_a_backup;
|
||||
}
|
||||
throw new AssertionError("No error resolve button text for step: " + step);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getStatusTextForStep(@NonNull SetupStep step, boolean takingTooLongInStep) {
|
||||
switch (step) {
|
||||
case SETTING_UP:
|
||||
return takingTooLongInStep ? R.string.NewDeviceTransferSetup__take_a_moment_should_be_ready_soon
|
||||
: R.string.NewDeviceTransferSetup__preparing_to_connect_to_old_android_device;
|
||||
case WAITING:
|
||||
return R.string.NewDeviceTransferSetup__waiting_for_old_device_to_connect;
|
||||
case CONNECTING:
|
||||
return R.string.NewDeviceTransferSetup__connecting_to_old_android_device;
|
||||
case ERROR:
|
||||
return R.string.NewDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device;
|
||||
case TROUBLESHOOTING:
|
||||
return R.string.DeviceTransferSetup__unable_to_discover_old_device;
|
||||
}
|
||||
throw new AssertionError("No status text for step: " + step);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateWhenWifiDirectUnavailable() {
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(R.id.action_deviceTransferSetup_to_transferOrRestore);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startTransfer() {
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(requireContext(), 0, MainActivity.clearTop(requireContext()), 0);
|
||||
|
||||
TransferNotificationData notificationData = new TransferNotificationData(NotificationIds.DEVICE_TRANSFER, NotificationChannels.BACKUPS, R.drawable.ic_signal_backup);
|
||||
DeviceToDeviceTransferService.startServer(requireContext(), new NewDeviceServerTask(), notificationData, pendingIntent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.newdevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.SpanUtil;
|
||||
|
||||
/**
|
||||
* Simple jumping off menu to starts a device-to-device transfer or restore a backup.
|
||||
*/
|
||||
public final class TransferOrRestoreFragment extends LoggingFragment {
|
||||
|
||||
public TransferOrRestoreFragment() {
|
||||
super(R.layout.fragment_transfer_restore);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.findViewById(R.id.transfer_or_restore_fragment_transfer)
|
||||
.setOnClickListener(v -> Navigation.findNavController(v).navigate(R.id.action_new_device_transfer_instructions));
|
||||
|
||||
view.findViewById(R.id.transfer_or_restore_fragment_restore)
|
||||
.setOnClickListener(v -> Navigation.findNavController(v).navigate(R.id.action_choose_backup));
|
||||
|
||||
String description = getString(R.string.TransferOrRestoreFragment__transfer_your_account_and_message_history_from_your_old_android_device);
|
||||
String toBold = getString(R.string.TransferOrRestoreFragment__you_must_have_access_to_your_old_device);
|
||||
|
||||
TextView transferDescriptionView = view.findViewById(R.id.transfer_or_restore_fragment_transfer_description);
|
||||
transferDescriptionView.setText(SpanUtil.boldSubstring(description, toBold));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.ClientTask;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupBase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupExporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* Create the backup stream of the old device and sends it over the wire via the output stream.
|
||||
* Used in conjunction with {@link org.signal.devicetransfer.DeviceToDeviceTransferService}.
|
||||
*/
|
||||
final class OldDeviceClientTask implements ClientTask {
|
||||
|
||||
private static final String TAG = Log.tag(OldDeviceClientTask.class);
|
||||
|
||||
private static final long PROGRESS_UPDATE_THROTTLE = 250;
|
||||
|
||||
private long lastProgressUpdate = 0;
|
||||
|
||||
@Override
|
||||
public void run(@NonNull Context context, @NonNull OutputStream outputStream) throws IOException {
|
||||
DeviceTransferBlockingInterceptor.getInstance().blockNetwork();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
EventBus.getDefault().register(this);
|
||||
try {
|
||||
FullBackupExporter.transfer(context,
|
||||
AttachmentSecretProvider.getInstance(context).getOrCreateAttachmentSecret(),
|
||||
DatabaseFactory.getBackupDatabase(context),
|
||||
outputStream,
|
||||
"deadbeef");
|
||||
} catch (Exception e) {
|
||||
DeviceTransferBlockingInterceptor.getInstance().unblockNetwork();
|
||||
throw e;
|
||||
} finally {
|
||||
EventBus.getDefault().unregister(this);
|
||||
}
|
||||
|
||||
long end = System.currentTimeMillis();
|
||||
Log.i(TAG, "Sending took: " + (end - start));
|
||||
}
|
||||
|
||||
@Subscribe(threadMode = ThreadMode.POSTING)
|
||||
public void onEvent(FullBackupBase.BackupEvent event) {
|
||||
if (event.getType() == FullBackupBase.BackupEvent.Type.PROGRESS) {
|
||||
if (System.currentTimeMillis() > lastProgressUpdate + PROGRESS_UPDATE_THROTTLE) {
|
||||
EventBus.getDefault().post(new Status(event.getCount(), false));
|
||||
lastProgressUpdate = System.currentTimeMillis();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void success() {
|
||||
EventBus.getDefault().post(new Status(0, true));
|
||||
}
|
||||
|
||||
public static final class Status {
|
||||
private final long messages;
|
||||
private final boolean done;
|
||||
|
||||
public Status(long messages, boolean done) {
|
||||
this.messages = messages;
|
||||
this.done = done;
|
||||
}
|
||||
|
||||
public long getMessageCount() {
|
||||
return messages;
|
||||
}
|
||||
|
||||
public boolean isDone() {
|
||||
return done;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class OldDeviceExitActivity extends AppCompatActivity {
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
finish();
|
||||
}
|
||||
|
||||
public static void exit(Context context) {
|
||||
Intent intent = new Intent(context, OldDeviceExitActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
|
||||
context.startActivity(intent);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.Navigation;
|
||||
|
||||
import org.thoughtcrime.securesms.PassphraseRequiredActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.util.DynamicNoActionBarTheme;
|
||||
import org.thoughtcrime.securesms.util.DynamicTheme;
|
||||
|
||||
/**
|
||||
* Shell of an activity to hold the old device navigation graph. See the various
|
||||
* fragments in this package for actual implementation.
|
||||
*/
|
||||
public final class OldDeviceTransferActivity extends PassphraseRequiredActivity {
|
||||
|
||||
private final DynamicTheme dynamicTheme = new DynamicNoActionBarTheme();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState, boolean ready) {
|
||||
dynamicTheme.onCreate(this);
|
||||
|
||||
setContentView(R.layout.old_device_transfer_activity);
|
||||
|
||||
NavController controller = Navigation.findNavController(this, R.id.nav_host_fragment);
|
||||
controller.setGraph(R.navigation.old_device_transfer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
dynamicTheme.onResume(this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Shown after the old device successfully completes sending a backup to the new device.
|
||||
*/
|
||||
public final class OldDeviceTransferCompleteFragment extends LoggingFragment {
|
||||
public OldDeviceTransferCompleteFragment() {
|
||||
super(R.layout.old_device_transfer_complete_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
view.findViewById(R.id.old_device_transfer_complete_fragment_close)
|
||||
.setOnClickListener(v -> close());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void close() {
|
||||
OldDeviceExitActivity.exit(requireContext());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,63 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.greenrobot.eventbus.Subscribe;
|
||||
import org.greenrobot.eventbus.ThreadMode;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferFragment;
|
||||
|
||||
/**
|
||||
* Shows transfer progress on the old device. Most logic is in {@link DeviceTransferFragment}
|
||||
* and it delegates to this class for strings, navigation, and updating progress.
|
||||
*/
|
||||
public final class OldDeviceTransferFragment extends DeviceTransferFragment {
|
||||
|
||||
private final ClientTaskListener clientTaskListener = new ClientTaskListener();
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
EventBus.getDefault().register(clientTaskListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
EventBus.getDefault().unregister(clientTaskListener);
|
||||
super.onDestroyView();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateToRestartTransfer() {
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_directly_to_oldDeviceTransferInstructions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateAwayFromTransfer() {
|
||||
EventBus.getDefault().unregister(clientTaskListener);
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
private class ClientTaskListener {
|
||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||
public void onEventMainThread(@NonNull OldDeviceClientTask.Status event) {
|
||||
if (event.isDone()) {
|
||||
ignoreTransferStatusEvents();
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
NavHostFragment.findNavController(OldDeviceTransferFragment.this).navigate(R.id.action_oldDeviceTransfer_to_oldDeviceTransferComplete);
|
||||
} else {
|
||||
status.setText(getString(R.string.DeviceTransfer__d_messages_so_far, event.getMessageCount()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.widget.Toolbar;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.LoggingFragment;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
/**
|
||||
* Provides instructions for the old device on how to start a device-to-device transfer.
|
||||
*/
|
||||
public final class OldDeviceTransferInstructionsFragment extends LoggingFragment {
|
||||
|
||||
public OldDeviceTransferInstructionsFragment() {
|
||||
super(R.layout.old_device_transfer_instructions_fragment);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Toolbar toolbar = view.findViewById(R.id.old_device_transfer_instructions_fragment_toolbar);
|
||||
toolbar.setNavigationOnClickListener(v -> {
|
||||
if (!Navigation.findNavController(v).popBackStack()) {
|
||||
requireActivity().finish();
|
||||
}
|
||||
});
|
||||
|
||||
view.findViewById(R.id.old_device_transfer_instructions_fragment_continue)
|
||||
.setOnClickListener(v -> Navigation.findNavController(v)
|
||||
.navigate(R.id.action_oldDeviceTransferInstructions_to_oldDeviceTransferSetup));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null) {
|
||||
NavHostFragment.findNavController(this)
|
||||
.navigate(R.id.action_oldDeviceTransferInstructions_to_oldDeviceTransferSetup);
|
||||
} else {
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
package org.thoughtcrime.securesms.devicetransfer.olddevice;
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
import org.thoughtcrime.securesms.devicetransfer.DeviceTransferSetupFragment;
|
||||
import org.thoughtcrime.securesms.devicetransfer.SetupStep;
|
||||
import org.thoughtcrime.securesms.jobs.LocalBackupJob;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationIds;
|
||||
|
||||
/**
|
||||
* Most responsibility is in {@link DeviceTransferSetupFragment} and delegates here
|
||||
* for strings and behavior relevant to setting up device transfer for the old device.
|
||||
*
|
||||
* Also responsible for setting up {@link DeviceToDeviceTransferService}.
|
||||
*/
|
||||
public final class OldDeviceTransferSetupFragment extends DeviceTransferSetupFragment {
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
ApplicationDependencies.getJobManager().cancelAllInQueue(LocalBackupJob.QUEUE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateAwayFromTransfer() {
|
||||
NavHostFragment.findNavController(this).popBackStack();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateToTransferConnected() {
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_oldDeviceTransferSetup_to_oldDeviceTransfer);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void navigateWhenWifiDirectUnavailable() {
|
||||
Intent intent = new Intent(requireContext(), ApplicationPreferencesActivity.class);
|
||||
intent.putExtra(ApplicationPreferencesActivity.LAUNCH_TO_BACKUPS_FRAGMENT, true);
|
||||
startActivity(intent);
|
||||
requireActivity().finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startTransfer() {
|
||||
Intent intent = new Intent(requireContext(), OldDeviceTransferActivity.class);
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(requireContext(), 0, intent, 0);
|
||||
|
||||
DeviceToDeviceTransferService.TransferNotificationData notificationData = new DeviceToDeviceTransferService.TransferNotificationData(NotificationIds.DEVICE_TRANSFER, NotificationChannels.BACKUPS, R.drawable.ic_signal_backup);
|
||||
DeviceToDeviceTransferService.startClient(requireContext(), new OldDeviceClientTask(), notificationData, pendingIntent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getErrorTextForStep(@NonNull SetupStep step) {
|
||||
switch (step) {
|
||||
case PERMISSIONS_DENIED:
|
||||
return R.string.OldDeviceTransferSetup__signal_needs_the_location_permission_to_discover_and_connect_with_your_new_device;
|
||||
case LOCATION_DISABLED:
|
||||
return R.string.OldDeviceTransferSetup__signal_needs_location_services_enabled_to_discover_and_connect_with_your_new_device;
|
||||
case WIFI_DISABLED:
|
||||
return R.string.OldDeviceTransferSetup__signal_needs_wifi_on_to_discover_and_connect_with_your_new_device;
|
||||
case WIFI_DIRECT_UNAVAILABLE:
|
||||
return R.string.OldDeviceTransferSetup__sorry_it_appears_your_device_does_not_support_wifi_direct;
|
||||
case ERROR:
|
||||
return R.string.OldDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device;
|
||||
}
|
||||
throw new AssertionError("No error text for step: " + step);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getErrorResolveButtonTextForStep(@NonNull SetupStep step) {
|
||||
if (step == SetupStep.WIFI_DIRECT_UNAVAILABLE) {
|
||||
return R.string.OldDeviceTransferSetup__create_a_backup;
|
||||
}
|
||||
throw new AssertionError("No error resolve button text for step: " + step);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @StringRes int getStatusTextForStep(@NonNull SetupStep step, boolean takingTooLongInStep) {
|
||||
switch (step) {
|
||||
case SETTING_UP:
|
||||
case WAITING:
|
||||
return R.string.OldDeviceTransferSetup__searching_for_your_new_android_device;
|
||||
case CONNECTING:
|
||||
return R.string.OldDeviceTransferSetup__connecting_to_new_android_device;
|
||||
case ERROR:
|
||||
return R.string.OldDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device;
|
||||
case TROUBLESHOOTING:
|
||||
return R.string.DeviceTransferSetup__unable_to_discover_new_device;
|
||||
}
|
||||
throw new AssertionError("No status text for step: " + step);
|
||||
}
|
||||
}
|
|
@ -37,7 +37,7 @@ public final class LocalBackupJob extends BaseJob {
|
|||
|
||||
private static final String TAG = Log.tag(LocalBackupJob.class);
|
||||
|
||||
private static final String QUEUE = "__LOCAL_BACKUP__";
|
||||
public static final String QUEUE = "__LOCAL_BACKUP__";
|
||||
|
||||
public static final String TEMP_BACKUP_FILE_PREFIX = ".backup";
|
||||
public static final String TEMP_BACKUP_FILE_SUFFIX = ".tmp";
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
package org.thoughtcrime.securesms.net;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.Protocol;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
/**
|
||||
* Provide a way to block network access while performing a device transfer.
|
||||
*/
|
||||
public final class DeviceTransferBlockingInterceptor implements Interceptor {
|
||||
|
||||
private static final String TAG = Log.tag(DeviceTransferBlockingInterceptor.class);
|
||||
|
||||
private static final DeviceTransferBlockingInterceptor INSTANCE = new DeviceTransferBlockingInterceptor();
|
||||
|
||||
private volatile boolean blockNetworking = false;
|
||||
|
||||
public static DeviceTransferBlockingInterceptor getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Response intercept(@NonNull Chain chain) throws IOException {
|
||||
if (!blockNetworking) {
|
||||
return chain.proceed(chain.request());
|
||||
}
|
||||
|
||||
Log.w(TAG, "Preventing request because in transfer mode.");
|
||||
return new Response.Builder().request(chain.request())
|
||||
.protocol(Protocol.HTTP_1_1)
|
||||
.receivedResponseAtMillis(System.currentTimeMillis())
|
||||
.message("")
|
||||
.body(ResponseBody.create(null, ""))
|
||||
.code(500)
|
||||
.build();
|
||||
}
|
||||
|
||||
public void blockNetwork() {
|
||||
blockNetworking = true;
|
||||
ApplicationDependencies.closeConnections();
|
||||
}
|
||||
|
||||
public void unblockNetwork() {
|
||||
blockNetworking = false;
|
||||
ApplicationDependencies.getIncomingMessageObserver();
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@ public class PipeConnectivityListener implements ConnectivityListener {
|
|||
|
||||
if (SignalStore.proxy().isProxyEnabled()) {
|
||||
Log.w(TAG, "Encountered an error while we had a proxy set! Terminating the connection to prevent retry spam.");
|
||||
ApplicationDependencies.closeConnectionsAfterProxyFailure();
|
||||
ApplicationDependencies.closeConnections();
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
|
|
|
@ -10,6 +10,7 @@ public final class NotificationIds {
|
|||
public static final int PRE_REGISTRATION_SMS = 5050;
|
||||
public static final int THREAD = 50000;
|
||||
public static final int USER_NOTIFICATION_MIGRATION = 525600;
|
||||
public static final int DEVICE_TRANSFER = 625420;
|
||||
|
||||
private NotificationIds() { }
|
||||
|
||||
|
|
|
@ -61,8 +61,9 @@ public class Permissions {
|
|||
private Consumer<List<String>> someDeniedListener;
|
||||
private Consumer<List<String>> somePermanentlyDeniedListener;
|
||||
|
||||
private @DrawableRes int[] rationalDialogHeader;
|
||||
private String rationaleDialogMessage;
|
||||
private @DrawableRes int[] rationalDialogHeader;
|
||||
private String rationaleDialogMessage;
|
||||
private boolean rationaleDialogCancelable;
|
||||
|
||||
private boolean ifNecesary;
|
||||
|
||||
|
@ -89,8 +90,13 @@ public class Permissions {
|
|||
}
|
||||
|
||||
public PermissionsBuilder withRationaleDialog(@NonNull String message, @NonNull @DrawableRes int... headers) {
|
||||
this.rationalDialogHeader = headers;
|
||||
this.rationaleDialogMessage = message;
|
||||
return withRationaleDialog(message, true, headers);
|
||||
}
|
||||
|
||||
public PermissionsBuilder withRationaleDialog(@NonNull String message, boolean cancelable, @NonNull @DrawableRes int... headers) {
|
||||
this.rationalDialogHeader = headers;
|
||||
this.rationaleDialogMessage = message;
|
||||
this.rationaleDialogCancelable = cancelable;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -159,6 +165,7 @@ public class Permissions {
|
|||
RationaleDialog.createFor(permissionObject.getContext(), rationaleDialogMessage, rationalDialogHeader)
|
||||
.setPositiveButton(R.string.Permissions_continue, (dialog, which) -> executePermissionsRequest(request))
|
||||
.setNegativeButton(R.string.Permissions_not_now, (dialog, which) -> executeNoPermissionsRequest(request))
|
||||
.setCancelable(rationaleDialogCancelable)
|
||||
.show()
|
||||
.getWindow()
|
||||
.setLayout((int)(permissionObject.getWindowWidth() * .75), ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
|
@ -248,7 +255,7 @@ public class Permissions {
|
|||
resultListener.onResult(permissions, grantResults, shouldShowRationaleDialog);
|
||||
}
|
||||
|
||||
private static Intent getApplicationSettingsIntent(@NonNull Context context) {
|
||||
public static Intent getApplicationSettingsIntent(@NonNull Context context) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
Uri uri = Uri.fromParts("package", context.getPackageName(), null);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.thoughtcrime.securesms.preferences;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -10,6 +11,7 @@ import androidx.preference.ListPreference;
|
|||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.ApplicationPreferencesActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferActivity;
|
||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
|
||||
|
@ -34,6 +36,11 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
|
|||
return true;
|
||||
});
|
||||
|
||||
findPreference(TextSecurePreferences.TRANSFER).setOnPreferenceClickListener(unused -> {
|
||||
goToTransferAccount();
|
||||
return true;
|
||||
});
|
||||
|
||||
findPreference(PREFER_SYSTEM_CONTACT_PHOTOS)
|
||||
.setOnPreferenceChangeListener((preference, newValue) -> {
|
||||
SignalStore.settings().setPreferSystemContactPhotos(newValue == Boolean.TRUE);
|
||||
|
@ -71,6 +78,10 @@ public class ChatsPreferenceFragment extends ListSummaryPreferenceFragment {
|
|||
((ApplicationPreferencesActivity) requireActivity()).pushFragment(new BackupsPreferenceFragment());
|
||||
}
|
||||
|
||||
private void goToTransferAccount() {
|
||||
requireContext().startActivity(new Intent(requireContext(), OldDeviceTransferActivity.class));
|
||||
}
|
||||
|
||||
public static CharSequence getSummary(Context context) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import org.thoughtcrime.securesms.BuildConfig;
|
|||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||
import org.thoughtcrime.securesms.net.CustomDns;
|
||||
import org.thoughtcrime.securesms.net.DeprecatedClientPreventionInterceptor;
|
||||
import org.thoughtcrime.securesms.net.DeviceTransferBlockingInterceptor;
|
||||
import org.thoughtcrime.securesms.net.RemoteDeprecationDetectorInterceptor;
|
||||
import org.thoughtcrime.securesms.net.SequentialDns;
|
||||
import org.thoughtcrime.securesms.net.StandardUserAgentInterceptor;
|
||||
|
@ -180,7 +181,10 @@ public class SignalServiceNetworkAccess {
|
|||
|
||||
final String[] fastUrls = {"https://cdn.sstatic.net", "https://github.githubassets.com", "https://pinterest.com", "https://open.scdn.co", "https://www.redditstatic.com"};
|
||||
|
||||
final List<Interceptor> interceptors = Arrays.asList(new StandardUserAgentInterceptor(), new RemoteDeprecationDetectorInterceptor(), new DeprecatedClientPreventionInterceptor());
|
||||
final List<Interceptor> interceptors = Arrays.asList(new StandardUserAgentInterceptor(),
|
||||
new RemoteDeprecationDetectorInterceptor(),
|
||||
new DeprecatedClientPreventionInterceptor(),
|
||||
DeviceTransferBlockingInterceptor.getInstance());
|
||||
final Optional<Dns> dns = Optional.of(DNS);
|
||||
|
||||
final byte[] zkGroupServerPublicParams;
|
||||
|
|
|
@ -42,17 +42,12 @@ public class ChooseBackupFragment extends BaseRegistrationFragment {
|
|||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
if (BackupUtil.isUserSelectionRequired(requireContext())) {
|
||||
chooseBackupButton = view.findViewById(R.id.choose_backup_fragment_button);
|
||||
chooseBackupButton.setOnClickListener(this::onChooseBackupSelected);
|
||||
chooseBackupButton = view.findViewById(R.id.choose_backup_fragment_button);
|
||||
chooseBackupButton.setOnClickListener(this::onChooseBackupSelected);
|
||||
|
||||
learnMore = view.findViewById(R.id.choose_backup_fragment_learn_more);
|
||||
learnMore.setText(HtmlCompat.fromHtml(String.format("<a href=\"%s\">%s</a>", getString(R.string.backup_support_url), getString(R.string.ChooseBackupFragment__learn_more)), 0));
|
||||
learnMore.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
} else {
|
||||
Log.i(TAG, "User Selection is not required. Skipping.");
|
||||
Navigation.findNavController(requireView()).navigate(ChooseBackupFragmentDirections.actionSkip());
|
||||
}
|
||||
learnMore = view.findViewById(R.id.choose_backup_fragment_learn_more);
|
||||
learnMore.setText(HtmlCompat.fromHtml(String.format("<a href=\"%s\">%s</a>", getString(R.string.backup_support_url), getString(R.string.ChooseBackupFragment__learn_more)), 0));
|
||||
learnMore.setMovementMethod(LinkMovementMethod.getInstance());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.text.TextUtils;
|
|||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -19,12 +20,16 @@ import androidx.core.content.ContextCompat;
|
|||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.navigation.ActivityNavigator;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.dd.CircularProgressButton;
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil;
|
||||
import com.google.i18n.phonenumbers.Phonenumber;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.signal.core.util.logging.Log;
|
||||
import org.signal.devicetransfer.DeviceToDeviceTransferService;
|
||||
import org.signal.devicetransfer.TransferStatus;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.registration.viewmodel.RegistrationViewModel;
|
||||
|
@ -61,7 +66,7 @@ public final class WelcomeFragment extends BaseRegistrationFragment {
|
|||
private static final int[] HEADERS_API_29 = { R.drawable.ic_contacts_white_48dp };
|
||||
|
||||
private CircularProgressButton continueButton;
|
||||
private View restoreFromBackup;
|
||||
private Button restoreFromBackup;
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
|
@ -104,15 +109,14 @@ public final class WelcomeFragment extends BaseRegistrationFragment {
|
|||
continueButton = view.findViewById(R.id.welcome_continue_button);
|
||||
continueButton.setOnClickListener(this::continueClicked);
|
||||
|
||||
restoreFromBackup = view.findViewById(R.id.welcome_restore_backup);
|
||||
restoreFromBackup = view.findViewById(R.id.welcome_transfer_or_restore);
|
||||
restoreFromBackup.setOnClickListener(this::restoreFromBackupClicked);
|
||||
|
||||
TextView welcomeTermsButton = view.findViewById(R.id.welcome_terms_button);
|
||||
welcomeTermsButton.setOnClickListener(v -> onTermsClicked());
|
||||
|
||||
if (canUserSelectBackup()) {
|
||||
restoreFromBackup.setVisibility(View.VISIBLE);
|
||||
welcomeTermsButton.setTextColor(ContextCompat.getColor(requireActivity(), R.color.core_grey_60));
|
||||
if (!canUserSelectBackup()) {
|
||||
restoreFromBackup.setText(R.string.registration_activity__transfer_account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -122,6 +126,17 @@ public final class WelcomeFragment extends BaseRegistrationFragment {
|
|||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
if (EventBus.getDefault().getStickyEvent(TransferStatus.class) != null) {
|
||||
Log.i(TAG, "Found existing transferStatus, redirect to transfer flow");
|
||||
NavHostFragment.findNavController(this).navigate(R.id.action_welcomeFragment_to_deviceTransferSetup);
|
||||
} else {
|
||||
DeviceToDeviceTransferService.stop(requireContext());
|
||||
}
|
||||
}
|
||||
|
||||
private void continueClicked(@NonNull View view) {
|
||||
boolean isUserSelectionRequired = BackupUtil.isUserSelectionRequired(requireContext());
|
||||
|
||||
|
@ -177,7 +192,7 @@ public final class WelcomeFragment extends BaseRegistrationFragment {
|
|||
initializeNumber();
|
||||
|
||||
Navigation.findNavController(view)
|
||||
.navigate(WelcomeFragmentDirections.actionChooseBackup());
|
||||
.navigate(WelcomeFragmentDirections.actionTransferOrRestore());
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
|
|
|
@ -37,7 +37,7 @@ public final class SignalProxyUtil {
|
|||
public static void startListeningToWebsocket() {
|
||||
if (SignalStore.proxy().isProxyEnabled() && ApplicationDependencies.getPipeListener().getState().getValue() == PipeConnectivityListener.State.FAILURE) {
|
||||
Log.w(TAG, "Proxy is in a failed state. Restarting.");
|
||||
ApplicationDependencies.closeConnectionsAfterProxyFailure();
|
||||
ApplicationDependencies.closeConnections();
|
||||
}
|
||||
|
||||
ApplicationDependencies.getIncomingMessageObserver();
|
||||
|
|
|
@ -1,19 +1,28 @@
|
|||
package org.thoughtcrime.securesms.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Build;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.TextPaint;
|
||||
import android.text.TextUtils;
|
||||
import android.text.style.AbsoluteSizeSpan;
|
||||
import android.text.style.BulletSpan;
|
||||
import android.text.style.ClickableSpan;
|
||||
import android.text.style.DynamicDrawableSpan;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.text.style.ImageSpan;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.text.style.StyleSpan;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
|
||||
public class SpanUtil {
|
||||
|
||||
|
@ -45,6 +54,17 @@ public class SpanUtil {
|
|||
return spannable;
|
||||
}
|
||||
|
||||
public static CharSequence boldSubstring(CharSequence fullString, CharSequence substring) {
|
||||
SpannableString spannable = new SpannableString(fullString);
|
||||
int start = TextUtils.indexOf(fullString, substring);
|
||||
int end = start + substring.length();
|
||||
|
||||
if (start >= 0 && end <= fullString.length()) {
|
||||
spannable.setSpan(new StyleSpan(Typeface.BOLD), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
return spannable;
|
||||
}
|
||||
|
||||
public static CharSequence color(int color, CharSequence sequence) {
|
||||
SpannableString spannable = new SpannableString(sequence);
|
||||
spannable.setSpan(new ForegroundColorSpan(color), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
|
@ -52,8 +72,12 @@ public class SpanUtil {
|
|||
}
|
||||
|
||||
public static @NonNull CharSequence bullet(@NonNull CharSequence sequence) {
|
||||
return bullet(sequence, BulletSpan.STANDARD_GAP_WIDTH);
|
||||
}
|
||||
|
||||
public static @NonNull CharSequence bullet(@NonNull CharSequence sequence, int gapWidth) {
|
||||
SpannableString spannable = new SpannableString(sequence);
|
||||
spannable.setSpan(new BulletSpan(), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
spannable.setSpan(new BulletSpan(gapWidth), 0, sequence.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
return spannable;
|
||||
}
|
||||
|
||||
|
@ -66,4 +90,30 @@ public class SpanUtil {
|
|||
|
||||
return imageSpan;
|
||||
}
|
||||
|
||||
public static CharSequence clickSubstring(@NonNull Context context, @NonNull CharSequence fullString, @NonNull CharSequence substring, @NonNull View.OnClickListener clickListener) {
|
||||
ClickableSpan clickable = new ClickableSpan() {
|
||||
@Override
|
||||
public void updateDrawState(@NonNull TextPaint ds) {
|
||||
super.updateDrawState(ds);
|
||||
ds.setUnderlineText(false);
|
||||
ds.setColor(ContextCompat.getColor(context, R.color.signal_accent_primary));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(@NonNull View widget) {
|
||||
clickListener.onClick(widget);
|
||||
}
|
||||
};
|
||||
|
||||
SpannableString spannable = new SpannableString(fullString);
|
||||
int start = TextUtils.indexOf(fullString, substring);
|
||||
int end = start + substring.length();
|
||||
|
||||
if (start >= 0 && end <= fullString.length()) {
|
||||
spannable.setSpan(clickable, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
|
||||
return spannable;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -145,6 +145,8 @@ public class TextSecurePreferences {
|
|||
private static final String ENCRYPTED_BACKUP_PASSPHRASE = "pref_encrypted_backup_passphrase";
|
||||
private static final String BACKUP_TIME = "pref_backup_next_time";
|
||||
|
||||
public static final String TRANSFER = "pref_transfer";
|
||||
|
||||
public static final String SCREEN_LOCK = "pref_android_screen_lock";
|
||||
public static final String SCREEN_LOCK_TIMEOUT = "pref_android_screen_lock_timeout";
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.lifecycle.LiveData;
|
|||
import androidx.lifecycle.MediatorLiveData;
|
||||
import androidx.lifecycle.MutableLiveData;
|
||||
import androidx.lifecycle.Observer;
|
||||
import androidx.lifecycle.Transformations;
|
||||
|
||||
import com.annimon.stream.function.Predicate;
|
||||
|
||||
|
@ -76,6 +77,13 @@ public final class LiveDataUtil {
|
|||
return outputLiveData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a map operation on the source observable and then only emits the mapped item if it has changed since the previous emission.
|
||||
*/
|
||||
public static <A, B> LiveData<B> mapDistinct(@NonNull LiveData<A> source, @NonNull androidx.arch.core.util.Function<A, B> mapFunction) {
|
||||
return Transformations.distinctUntilChanged(Transformations.map(source, mapFunction));
|
||||
}
|
||||
|
||||
/**
|
||||
* Once there is non-null data on both input {@link LiveData}, the {@link Combine} function is run
|
||||
* and produces a live data of the combined data.
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
package org.thoughtcrime.securesms.util.livedata;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.lifecycle.LiveData;
|
||||
import androidx.lifecycle.MediatorLiveData;
|
||||
|
||||
import com.annimon.stream.function.Function;
|
||||
|
||||
import org.signal.core.util.concurrent.SignalExecutors;
|
||||
import org.thoughtcrime.securesms.util.concurrent.SerialExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* Manages a state to be updated from a view model and provide direct and live access. Updates
|
||||
* occur serially on the same executor to allow updating in a thread safe way. While not
|
||||
* every state update is guaranteed to be emitted, no update action will be dropped and state
|
||||
* that is emitted will be accurate.
|
||||
*/
|
||||
public class Store<State> {
|
||||
private final LiveDataStore liveStore;
|
||||
|
||||
public Store(@NonNull State state) {
|
||||
this.liveStore = new LiveDataStore(state);
|
||||
}
|
||||
|
||||
public @NonNull LiveData<State> getStateLiveData() {
|
||||
return liveStore;
|
||||
}
|
||||
|
||||
public @NonNull State getState() {
|
||||
return liveStore.getState();
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public void update(@NonNull Function<State, State> updater) {
|
||||
liveStore.update(updater);
|
||||
}
|
||||
|
||||
@MainThread
|
||||
public <Input> void update(@NonNull LiveData<Input> source, @NonNull Action<Input, State> action) {
|
||||
liveStore.update(source, action);
|
||||
}
|
||||
|
||||
private final class LiveDataStore extends MediatorLiveData<State> {
|
||||
private State state;
|
||||
private final Executor stateUpdater;
|
||||
|
||||
LiveDataStore(@NonNull State state) {
|
||||
this.stateUpdater = new SerialExecutor(SignalExecutors.BOUNDED);
|
||||
setState(state);
|
||||
}
|
||||
|
||||
synchronized @NonNull State getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
private synchronized void setState(@NonNull State state) {
|
||||
this.state = state;
|
||||
postValue(this.state);
|
||||
}
|
||||
|
||||
<Input> void update(@NonNull LiveData<Input> source, @NonNull Action<Input, State> action) {
|
||||
addSource(source, input -> stateUpdater.execute(() -> setState(action.apply(input, getState()))));
|
||||
}
|
||||
|
||||
void update(@NonNull Function<State, State> updater) {
|
||||
stateUpdater.execute(() -> setState(updater.apply(getState())));
|
||||
}
|
||||
}
|
||||
|
||||
public interface Action<Input, State> {
|
||||
State apply(Input input, State current);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="134dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="134"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:pathData="M67,56m-56,0a56,56 0,1 1,112 0a56,56 0,1 1,-112 0"
|
||||
android:strokeAlpha="0.1"
|
||||
android:fillColor="#fff"
|
||||
android:fillAlpha="0.1"/>
|
||||
<path
|
||||
android:pathData="M33.3,16.12 L6.9,16A6.91,6.91 0,0 0,0 22.92L-0.1,89a6.91,6.91 0,0 0,6.9 6.9l26.3,0.1a6.91,6.91 0,0 0,6.9 -6.9L40.2,23A6.91,6.91 0,0 0,33.3 16.12ZM36.8,89.12a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,3 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1A3.76,3.76 0,0 1,36.9 23Z"
|
||||
android:fillColor="#848484"/>
|
||||
<path
|
||||
android:pathData="M50,60a4,4 0,1 0,-4 -4A4,4 0,0 0,50 60Z"
|
||||
android:fillColor="#6191f3"/>
|
||||
<path
|
||||
android:pathData="M66.2,60a4,4 0,1 0,-4 -4A4,4 0,0 0,66.2 60Z"
|
||||
android:fillColor="#6191f3"/>
|
||||
<path
|
||||
android:pathData="M82,60a4,4 0,1 0,-4 -4A4,4 0,0 0,82 60Z"
|
||||
android:fillColor="#6191f3"/>
|
||||
<path
|
||||
android:pathData="M36.8,89.12a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,3 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1A3.76,3.76 0,0 1,36.9 23Z"
|
||||
android:fillColor="#121212"/>
|
||||
<path
|
||||
android:pathData="M24.86,46.24a10.83,10.83 0,0 0,-4.18 -1.12L20.68,43.8a12.12,12.12 0,0 1,4.69 1.26ZM9.11,50.58a12,12 0,0 0,-1.36 4.69L9.06,55.27a10.92,10.92 0,0 1,1.12 -4.19ZM24.86,65.8a10.83,10.83 0,0 1,-4.18 1.12v1.31A11.78,11.78 0,0 0,25.37 67ZM15.14,46.24a10.83,10.83 0,0 1,4.18 -1.12L19.32,43.8a12.12,12.12 0,0 0,-4.69 1.26ZM10.88,50A10.89,10.89 0,0 1,14 46.9l-0.73,-1.08a11.92,11.92 0,0 0,-3.5 3.5ZM32.25,56.79L30.94,56.79A10.83,10.83 0,0 1,29.82 61l1.17,0.58a12.11,12.11 0,0 0,1.26 -4.76ZM29.15,62a10.63,10.63 0,0 1,-3.08 3.08l0.74,1.09a11.92,11.92 0,0 0,3.5 -3.5ZM29.82,51.1a10.88,10.88 0,0 1,1.12 4.18h1.31A12.17,12.17 0,0 0,31 50.61ZM26,46.9A10.89,10.89 0,0 1,29.12 50l1.08,-0.73a11.92,11.92 0,0 0,-3.5 -3.5ZM8.54,61.69l1.29,0.21 0.23,-1.35a10.91,10.91 0,0 1,-1 -3.85l-1.35,0.07a12.31,12.31 0,0 0,0.91 3.92ZM12.74,66.4L9,67l0.61,-3.73L8.31,63l-0.56,3.5a1.55,1.55 0,0 0,0.06 0.71,1.65 1.65,0 0,0 0.38,0.6 1.36,1.36 0,0 0,0.48 0.34,1.51 1.51,0 0,0 0.58,0.12L9.5,68.27l3.5,-0.56ZM15.47,66l-1.35,0.23 0.21,1.29 1,-0.16a12.3,12.3 0,0 0,3.92 1L19.25,67a11.26,11.26 0,0 1,-3.78 -1ZM20,46.39A9.61,9.61 0,0 0,10.38 56a9.35,9.35 0,0 0,1 4.27l-0.84,5.25 5.25,-0.84A9.64,9.64 0,0 0,29.62 56,9.61 9.61,0 0,0 20,46.39Z"
|
||||
android:fillColor="#6191f3"/>
|
||||
<path
|
||||
android:pathData="M127.3,16.12 L100.9,16a6.91,6.91 0,0 0,-6.9 6.9L93.9,89a6.91,6.91 0,0 0,6.9 6.9l26.4,0.1a6.91,6.91 0,0 0,6.9 -6.9l0.1,-66.1A6.91,6.91 0,0 0,127.3 16.12ZM130.8,89.12a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,97 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1a3.76,3.76 0,0 1,3.7 3.7Z"
|
||||
android:fillColor="#848484"/>
|
||||
<path
|
||||
android:pathData="M130.8,89.12a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,97 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1a3.76,3.76 0,0 1,3.7 3.7Z"
|
||||
android:fillColor="#121212"/>
|
||||
</vector>
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:color="@color/signal_button_secondary">
|
||||
<item android:id="@android:id/mask">
|
||||
<shape android:shape="rectangle">
|
||||
<solid android:color="#000000" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
||||
</item>
|
||||
<item android:drawable="@drawable/rounded_outline" />
|
||||
</ripple>
|
|
@ -0,0 +1,35 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="134dp"
|
||||
android:height="112dp"
|
||||
android:viewportWidth="134"
|
||||
android:viewportHeight="112">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M67,56m-56,0a56,56 0,1 1,112 0a56,56 0,1 1,-112 0"
|
||||
android:strokeAlpha="0.05"
|
||||
android:fillAlpha="0.05"/>
|
||||
<path
|
||||
android:pathData="M33.3,16.1 L6.9,16A6.91,6.91 0,0 0,0 22.9L-0.1,89a6.91,6.91 0,0 0,6.9 6.9l26.3,0.1A6.91,6.91 0,0 0,40 89.1L40.2,23A6.91,6.91 0,0 0,33.3 16.1ZM36.8,89.1a3.76,3.76 0,0 1,-3.7 3.7L6.7,92.7A3.76,3.76 0,0 1,3 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1A3.76,3.76 0,0 1,36.9 23Z"
|
||||
android:fillColor="#c6c6c6"/>
|
||||
<path
|
||||
android:pathData="M50,60a4,4 0,1 0,-4 -4A4,4 0,0 0,50 60Z"
|
||||
android:fillColor="#2c6bed"/>
|
||||
<path
|
||||
android:pathData="M66.2,60a4,4 0,1 0,-4 -4A4,4 0,0 0,66.2 60Z"
|
||||
android:fillColor="#2c6bed"/>
|
||||
<path
|
||||
android:pathData="M82,60a4,4 0,1 0,-4 -4A4,4 0,0 0,82 60Z"
|
||||
android:fillColor="#2c6bed"/>
|
||||
<path
|
||||
android:pathData="M36.8,89.1a3.76,3.76 0,0 1,-3.7 3.7L6.7,92.7A3.76,3.76 0,0 1,3 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1A3.76,3.76 0,0 1,36.9 23Z"
|
||||
android:fillColor="#fff"/>
|
||||
<path
|
||||
android:pathData="M24.86,46.22a10.83,10.83 0,0 0,-4.18 -1.12L20.68,43.78A12.12,12.12 0,0 1,25.37 45ZM9.11,50.56a12,12 0,0 0,-1.36 4.69L9.06,55.25a10.92,10.92 0,0 1,1.12 -4.19ZM24.86,65.78a10.83,10.83 0,0 1,-4.18 1.12v1.31A11.94,11.94 0,0 0,25.37 67ZM15.14,46.22a10.83,10.83 0,0 1,4.18 -1.12L19.32,43.78A12.12,12.12 0,0 0,14.63 45ZM10.88,50A11,11 0,0 1,14 46.88l-0.73,-1.08a11.92,11.92 0,0 0,-3.5 3.5ZM32.25,56.79L30.94,56.79a10.92,10.92 0,0 1,-1.12 4.19l1.17,0.57a12.11,12.11 0,0 0,1.26 -4.76ZM29.15,62a10.51,10.51 0,0 1,-3.08 3.08l0.74,1.09a11.92,11.92 0,0 0,3.5 -3.5ZM29.82,51.1a10.92,10.92 0,0 1,1.12 4.18h1.31A12.17,12.17 0,0 0,31 50.59ZM26,46.88A11,11 0,0 1,29.12 50l1.08,-0.73a11.92,11.92 0,0 0,-3.5 -3.5ZM8.54,61.67l1.29,0.21 0.23,-1.35a10.91,10.91 0,0 1,-1 -3.85l-1.35,0.07a12.31,12.31 0,0 0,0.91 3.92ZM12.74,66.38L9,67l0.61,-3.73L8.31,63l-0.56,3.5a1.55,1.55 0,0 0,0.06 0.71,1.58 1.58,0 0,0 0.38,0.6 1.36,1.36 0,0 0,0.48 0.34,1.51 1.51,0 0,0 0.58,0.12L9.5,68.27l3.5,-0.56ZM15.47,65.94 L14.12,66.17 14.33,67.46 15.33,67.31a12.56,12.56 0,0 0,3.92 0.94L19.25,66.94a11,11 0,0 1,-3.78 -1ZM20,46.38A9.59,9.59 0,0 0,10.38 56a9.32,9.32 0,0 0,1 4.27l-0.84,5.25 5.25,-0.84a9.41,9.41 0,0 0,4.2 0.94,9.62 9.62,0 1,0 0,-19.24Z"
|
||||
android:fillColor="#3a76f0"/>
|
||||
<path
|
||||
android:pathData="M127.3,16.1 L100.9,16A6.91,6.91 0,0 0,94 22.9L93.9,89a6.91,6.91 0,0 0,6.9 6.9l26.4,0.1a6.91,6.91 0,0 0,6.9 -6.9l0.1,-66.1A6.91,6.91 0,0 0,127.3 16.1ZM130.8,89.1a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,97 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1a3.76,3.76 0,0 1,3.7 3.7Z"
|
||||
android:fillColor="#c6c6c6"/>
|
||||
<path
|
||||
android:pathData="M130.8,89.1a3.76,3.76 0,0 1,-3.7 3.7l-26.4,-0.1A3.76,3.76 0,0 1,97 89l0.1,-66.1a3.76,3.76 0,0 1,3.7 -3.7l26.4,0.1a3.76,3.76 0,0 1,3.7 3.7Z"
|
||||
android:fillColor="#fff"/>
|
||||
</vector>
|
|
@ -0,0 +1,12 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="60dp"
|
||||
android:height="60dp"
|
||||
android:viewportWidth="60"
|
||||
android:viewportHeight="60">
|
||||
<path
|
||||
android:pathData="M17.93,43.93L23,43.93v2L17.93,45.93a5,5 0,0 1,-5 -5v-7h2v7A3,3 0,0 0,17.93 43.93ZM31.93,1.93h-14a5,5 0,0 0,-5 5v21h2v-21a3,3 0,0 1,3 -3h14a3,3 0,0 1,3 3v5h2v-5A5,5 0,0 0,31.93 1.93Z"
|
||||
android:fillColor="#848484"/>
|
||||
<path
|
||||
android:pathData="M22,32.41h0L18.45,36 17,34.54l2.61,-2.61H10.93a5,5 0,0 1,0 -10v2a3,3 0,0 0,0 6h8.6l-2.47,-2.47 1.41,-1.41 4.4,4.4h0l0.54,0.55ZM43.93,15.93h-14a3,3 0,0 0,-3 3v34a3,3 0,0 0,3 3h14a3,3 0,0 0,3 -3v-34a3,3 0,0 0,-3 -3m0,-2a5,5 0,0 1,5 5v34a5,5 0,0 1,-5 5h-14a5,5 0,0 1,-5 -5v-34a5,5 0,0 1,5 -5Z"
|
||||
android:fillColor="#2c6bed"/>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke android:color="@color/signal_inverse_transparent_20" android:width="1dp" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape android:shape="rectangle" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/signal_button_secondary" />
|
||||
<stroke android:color="@color/signal_inverse_transparent_20" android:width="1dp" />
|
||||
<corners android:radius="10dp" />
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/rounded_outline_pressed" android:state_pressed="true" />
|
||||
<item android:drawable="@drawable/rounded_outline" />
|
||||
</selector>
|
|
@ -0,0 +1,99 @@
|
|||
<?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:orientation="vertical"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="64dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_fragment_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/DeviceTransfer__transferring_data"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_fragment_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/DeviceTransfer__keep_both_devices_near_each_other"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/device_transfer_fragment_title" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/device_transfer_fragment_alert"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_warning_40"
|
||||
app:tint="@color/signal_alert_primary" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/device_transfer_fragment_progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/device_Transfer_fragment_middle_content_barrier"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="device_transfer_fragment_alert,device_transfer_fragment_progress" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_fragment_status"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/device_Transfer_fragment_middle_content_barrier"
|
||||
tools:text="Status" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_fragment_try_again"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DeviceTransfer__try_again"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toTopOf="@+id/device_transfer_fragment_cancel"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_fragment_cancel"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/DeviceTransfer__stop_transfer"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,166 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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:clipToPadding="false"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="@dimen/transfer_split_top_padding"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<!-- region Progress -->
|
||||
<ProgressBar
|
||||
android:id="@+id/device_transfer_setup_fragment_progress"
|
||||
style="?android:attr/progressBarStyleLarge"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_status"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="@+id/device_transfer_setup_fragment_progress"
|
||||
app:layout_constraintStart_toStartOf="@+id/device_transfer_setup_fragment_progress"
|
||||
app:layout_constraintTop_toBottomOf="@+id/device_transfer_setup_fragment_progress"
|
||||
tools:text="Status" />
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- region Error -->
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_error"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="There was an error" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_error_resolve"
|
||||
style="@style/Signal.Widget.Button.Medium.Primary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="@+id/device_transfer_setup_fragment_error"
|
||||
app:layout_constraintStart_toStartOf="@+id/device_transfer_setup_fragment_error"
|
||||
app:layout_constraintTop_toBottomOf="@+id/device_transfer_setup_fragment_error"
|
||||
tools:text="Fix me!" />
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- region Verify code -->
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_verify_code_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/DeviceTransferSetup__verify_code"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_verify_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/DeviceTransferSetup__verify_that_the_code_below_matches_on_both_of_your_devices"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/device_transfer_setup_fragment_verify_code_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_sas_verify_code"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:letterSpacing=".4"
|
||||
android:textSize="40sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:text="1234567" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_sas_verify_yes"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DeviceTransferSetup__continue"
|
||||
app:layout_constraintBottom_toTopOf="@+id/device_transfer_setup_fragment_sas_verify_no"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_sas_verify_no"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/DeviceTransferSetup__the_numbers_do_not_match"
|
||||
android:textColor="@color/signal_alert_primary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- region Troubleshooting -->
|
||||
|
||||
<include
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting"
|
||||
layout="@layout/device_transfer_setup_troubleshooting_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="@dimen/transfer_split_top_padding"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- region Groups -->
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/device_transfer_setup_fragment_progress_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:constraint_referenced_ids="device_transfer_setup_fragment_progress,device_transfer_setup_fragment_status" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/device_transfer_setup_fragment_error_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="device_transfer_setup_fragment_error,device_transfer_setup_fragment_error_resolve" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/device_transfer_setup_fragment_verify_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
app:constraint_referenced_ids="device_transfer_setup_fragment_verify_code_title,device_transfer_setup_fragment_verify_description,device_transfer_setup_fragment_sas_verify_code,device_transfer_setup_fragment_sas_verify_no,device_transfer_setup_fragment_sas_verify_yes" />
|
||||
<!-- endregion -->
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -0,0 +1,102 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
tools:text="@string/DeviceTransferSetup__unable_to_discover_old_device" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_step1"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:text="@string/DeviceTransferSetup__make_sure_the_following_permissions_are_enabled"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_location_permission"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DeviceTransferSetup__location_permission" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_location_services"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DeviceTransferSetup__location_services" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_wifi"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="46dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minWidth="0dp"
|
||||
android:text="@string/DeviceTransferSetup__wifi" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_step2"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:text="@string/DeviceTransferSetup__on_the_wifi_direct_screen_remove_all_remembered_groups_and_unlink_any_invited_or_connected_devices"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_step3"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:text="@string/DeviceTransferSetup__try_turning_wifi_off_and_on_on_both_devices"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_step4"
|
||||
style="@style/TextAppearance.Signal.Body1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/DeviceTransferSetup__make_sure_both_devices_are_in_transfer_mode"
|
||||
android:textAlignment="viewStart" />
|
||||
|
||||
<Space
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1" />
|
||||
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_try_again"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:text="@string/DeviceTransferSetup__try_again" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/device_transfer_setup_fragment_troubleshooting_go_to_support"
|
||||
style="@style/Signal.Widget.Button.Medium.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/DeviceTransferSetup__go_to_support_page" />
|
||||
|
||||
</LinearLayout>
|
|
@ -37,7 +37,7 @@
|
|||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/RegistrationActivity_terms_and_privacy"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/core_ultramarine"
|
||||
android:textColor="@color/core_grey_60"
|
||||
app:layout_constraintBottom_toTopOf="@+id/welcome_continue_button"
|
||||
app:layout_constraintEnd_toEndOf="@+id/welcome_continue_button"
|
||||
app:layout_constraintStart_toStartOf="@+id/welcome_continue_button" />
|
||||
|
@ -51,27 +51,22 @@
|
|||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="17dp"
|
||||
app:cpb_textIdle="@string/RegistrationActivity_continue"
|
||||
app:layout_constraintBottom_toTopOf="@id/welcome_restore_backup"
|
||||
app:layout_constraintBottom_toTopOf="@id/welcome_transfer_or_restore"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_goneMarginBottom="@dimen/registration_button_bottom_margin" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/welcome_restore_backup"
|
||||
style="@style/Signal.Text.Body.Registration"
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/welcome_transfer_or_restore"
|
||||
style="@style/Signal.Widget.Button.Large.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/registration_activity__restore_backup"
|
||||
android:textColor="?attr/colorAccent"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone"
|
||||
android:text="@string/registration_activity__transfer_or_restore_account"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="@+id/welcome_continue_button"
|
||||
app:layout_constraintStart_toStartOf="@+id/welcome_continue_button"
|
||||
tools:visibility="visible" />
|
||||
app:layout_constraintStart_toStartOf="@+id/welcome_continue_button" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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:fillViewport="true">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="@dimen/transfer_top_padding"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/TransferOrRestoreFragment__transfer_or_restore_account"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:gravity="center"
|
||||
android:text="@string/TransferOrRestoreFragment__if_you_have_previously_registered_a_signal_account"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/transfer_or_restore_fragment_transfer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/selectable_rounded_background"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/transfer_or_restore_fragment_transfer_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_transfer_phone_outline_60" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_or_restore_fragment_transfer_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/TransferOrRestoreFragment__transfer_from_android_device"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/transfer_or_restore_fragment_transfer_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@+id/transfer_or_restore_fragment_transfer_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_or_restore_fragment_transfer_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="@+id/transfer_or_restore_fragment_transfer_header"
|
||||
app:layout_constraintTop_toBottomOf="@+id/transfer_or_restore_fragment_transfer_header"
|
||||
tools:text="@string/TransferOrRestoreFragment__transfer_your_account_and_message_history_from_your_old_android_device" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/transfer_or_restore_fragment_restore"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:background="@drawable/selectable_rounded_background"
|
||||
android:padding="16dp">
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:id="@+id/transfer_or_restore_fragment_restore_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_backup_outline_60"
|
||||
app:tint="@color/signal_accent_primary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_or_restore_fragment_restore_header"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:text="@string/TransferOrRestoreFragment__restore_from_backup"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1.Bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/transfer_or_restore_fragment_restore_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toEndOf="@+id/transfer_or_restore_fragment_restore_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/transfer_or_restore_fragment_restore_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/TransferOrRestoreFragment__restore_your_messages_and_media_from_a_local_backup"
|
||||
android:textColor="@color/signal_text_secondary"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
app:layout_constraintStart_toStartOf="@+id/transfer_or_restore_fragment_restore_header"
|
||||
app:layout_constraintTop_toBottomOf="@+id/transfer_or_restore_fragment_restore_header" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
|
@ -0,0 +1,81 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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:clipToPadding="false"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="@dimen/transfer_top_padding"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="@dimen/transfer_top_padding">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/new_device_transfer_complete_fragment_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/NewDeviceTransferComplete__transfer_successful"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/old_device_trasnfer_complete_fragment_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/transfer_item_spacing"
|
||||
android:gravity="center"
|
||||
android:text="@string/NewDeviceTransferComplete__to_complete_the_transfer_process_you_must_continue_registration"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/new_device_transfer_complete_fragment_title"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.SquareImageView
|
||||
android:id="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_check_white_48dp"
|
||||
app:backgroundTint="@color/core_green"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/transfer_item_spacing"
|
||||
android:text="@string/NewDeviceTransferComplete__transfer_complete"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintBottom_toTopOf="@+id/new_device_transfer_complete_fragment_continue_registration"
|
||||
app:layout_constraintEnd_toEndOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintStart_toStartOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintTop_toBottomOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintVertical_bias="0" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/new_device_transfer_complete_fragment_continue_registration"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/NewDeviceTransferComplete__continue_registration"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -0,0 +1,114 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView 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:clipToPadding="false"
|
||||
android:fillViewport="true">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="@dimen/transfer_top_padding"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/new_device_transfer_instructions_fragment_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/NewDeviceTransferInstructions__open_signal_on_your_old_android_phone"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.gridlayout.widget.GridLayout
|
||||
android:id="@+id/new_device_transfer_instructions_fragment_instructions"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
app:alignmentMode="alignBounds"
|
||||
app:columnCount="2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/new_device_transfer_instructions_fragment_title">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/NewDeviceTransferInstructions__first_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:text="@string/NewDeviceTransferInstructions__tap_on_your_profile_photo_in_the_top_left_to_open_settings"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/NewDeviceTransferInstructions__second_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="@dimen/transfer_item_spacing"
|
||||
android:text="@string/NewDeviceTransferInstructions__tap_on_chats"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/NewDeviceTransferInstructions__third_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/NewDeviceTransferInstructions__tap_transfer_account_and_then_continue_on_both_devices"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
</androidx.gridlayout.widget.GridLayout>
|
||||
|
||||
<com.airbnb.lottie.LottieAnimationView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleX="@integer/image_scale_flip"
|
||||
app:layout_constraintBottom_toTopOf="@+id/new_device_transfer_instructions_fragment_continue"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/new_device_transfer_instructions_fragment_instructions"
|
||||
app:lottie_autoPlay="true"
|
||||
app:lottie_loop="true"
|
||||
app:lottie_rawRes="@raw/lottie_settings_android"
|
||||
tools:background="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/new_device_transfer_instructions_fragment_continue"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/NewDeviceTransferInstructions__continue"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/nav_host_fragment"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:defaultNavHost="true" />
|
||||
|
||||
</FrameLayout>
|
|
@ -0,0 +1,71 @@
|
|||
<?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:orientation="vertical"
|
||||
android:paddingStart="32dp"
|
||||
android:paddingTop="64dp"
|
||||
android:paddingEnd="32dp"
|
||||
android:paddingBottom="64dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/old_device_transfer_complete_fragment_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/OldDeviceTransferComplete__go_to_your_new_device"
|
||||
android:textAppearance="@style/Signal.Text.Headline.Registration"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/old_device_trasnfer_complete_fragment_subtitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/transfer_item_spacing"
|
||||
android:gravity="center"
|
||||
android:text="@string/OldDeviceTransferComplete__your_signal_data_has_Been_transferred_to_your_new_device"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/old_device_transfer_complete_fragment_title" />
|
||||
|
||||
<org.thoughtcrime.securesms.components.SquareImageView
|
||||
android:id="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
android:layout_width="56dp"
|
||||
android:layout_height="56dp"
|
||||
android:layout_gravity="center"
|
||||
android:background="@drawable/circle_tintable"
|
||||
android:gravity="center_vertical"
|
||||
android:src="@drawable/ic_check_white_48dp"
|
||||
app:backgroundTint="@color/core_green"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/transfer_item_spacing"
|
||||
android:text="@string/OldDeviceTransferComplete__transfer_complete"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_constraintEnd_toEndOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintStart_toStartOf="@+id/old_device_trasnfer_complete_fragment_success"
|
||||
app:layout_constraintTop_toBottomOf="@+id/old_device_trasnfer_complete_fragment_success" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/old_device_transfer_complete_fragment_close"
|
||||
style="@style/Signal.Widget.Button.Large.Secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/OldDeviceTransferComplete__close"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:strokeWidth="0dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,101 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/old_device_transfer_instructions_fragment_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="@drawable/ic_arrow_left_24" />
|
||||
|
||||
<androidx.appcompat.widget.AppCompatImageView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:scaleX="@integer/image_scale_flip"
|
||||
app:srcCompat="@drawable/ic_transfer_account" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:gravity="center"
|
||||
android:text="@string/OldDeviceTransferInstructions__transfer_account"
|
||||
android:textAppearance="@style/Signal.Text.Headline" />
|
||||
|
||||
<androidx.gridlayout.widget.GridLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:layout_weight="1"
|
||||
app:alignmentMode="alignBounds"
|
||||
app:columnCount="2">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/OldDeviceTransferInstructions__first_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/OldDeviceTransferInstructions__download_signal_on_your_new_android_device"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/OldDeviceTransferInstructions__second_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:text="@string/OldDeviceTransferInstructions__tap_on_transfer_or_restore_account"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/OldDeviceTransferInstructions__third_bullet"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/OldDeviceTransferInstructions__select_transfer_from_android_device_when_prompted_and_then_continue"
|
||||
android:textAlignment="viewStart"
|
||||
android:textAppearance="@style/TextAppearance.Signal.Body1"
|
||||
app:layout_columnWeight="1" />
|
||||
|
||||
</androidx.gridlayout.widget.GridLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/old_device_transfer_instructions_fragment_continue"
|
||||
style="@style/Signal.Widget.Button.Large.Primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="32dp"
|
||||
android:layout_marginEnd="32dp"
|
||||
android:text="@string/OldDeviceTransferInstructions__continue" />
|
||||
|
||||
</LinearLayout>
|
|
@ -0,0 +1,56 @@
|
|||
<?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">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<org.thoughtcrime.securesms.components.camera.CameraView
|
||||
android:id="@+id/scanner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:camera="0" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/overlay"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:weightSum="2">
|
||||
|
||||
<org.thoughtcrime.securesms.components.ShapeScrim
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="?android:windowBackground"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Position the QR code within the frame"
|
||||
android:textColor="?android:textColorSecondary"
|
||||
tools:ignore="HardcodedText" />
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -0,0 +1,68 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<navigation 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:id="@+id/old_device_transfer"
|
||||
app:startDestination="@id/oldDeviceTransferInstructions">
|
||||
|
||||
<fragment
|
||||
android:id="@+id/oldDeviceTransferInstructions"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferInstructionsFragment"
|
||||
tools:layout="@layout/old_device_transfer_instructions_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_oldDeviceTransferInstructions_to_oldDeviceTransferSetup"
|
||||
app:destination="@id/oldDeviceTransferSetup"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/oldDeviceTransferSetup"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferSetupFragment"
|
||||
tools:layout="@layout/device_transfer_setup_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_oldDeviceTransferSetup_to_oldDeviceTransfer"
|
||||
app:destination="@id/oldDeviceTransfer"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/oldDeviceTransferInstructions" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/oldDeviceTransfer"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferFragment"
|
||||
tools:layout="@layout/device_transfer_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_oldDeviceTransfer_to_oldDeviceTransferComplete"
|
||||
app:destination="@id/oldDeviceTransferComplete"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/oldDeviceTransferInstructions"
|
||||
app:popUpToInclusive="true" />
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/oldDeviceTransferComplete"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.olddevice.OldDeviceTransferCompleteFragment"
|
||||
tools:layout="@layout/old_device_transfer_complete_fragment" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_directly_to_oldDeviceTransferInstructions"
|
||||
app:destination="@id/oldDeviceTransferInstructions"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/oldDeviceTransferInstructions"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</navigation>
|
|
@ -28,8 +28,16 @@
|
|||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_choose_backup"
|
||||
app:destination="@id/chooseBackupFragment"
|
||||
android:id="@+id/action_transfer_or_restore"
|
||||
app:destination="@id/transferOrRestore"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_welcomeFragment_to_deviceTransferSetup"
|
||||
app:destination="@id/deviceTransferSetup"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
|
@ -203,7 +211,7 @@
|
|||
android:id="@+id/accountLockedFragment"
|
||||
android:name="org.thoughtcrime.securesms.registration.fragments.AccountLockedFragment"
|
||||
android:label="fragment_account_locked"
|
||||
tools:layout="@layout/account_locked_fragment"/>
|
||||
tools:layout="@layout/account_locked_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/captchaFragment"
|
||||
|
@ -262,10 +270,11 @@
|
|||
app:popUpTo="@+id/restoreBackupFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<argument app:nullable="true"
|
||||
app:argType="android.net.Uri"
|
||||
android:defaultValue="@null"
|
||||
android:name="uri" />
|
||||
<argument
|
||||
android:name="uri"
|
||||
android:defaultValue="@null"
|
||||
app:argType="android.net.Uri"
|
||||
app:nullable="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
|
@ -281,4 +290,118 @@
|
|||
android:label="fragment_registration_edit_proxy"
|
||||
tools:layout="@layout/edit_proxy_fragment" />
|
||||
|
||||
<fragment
|
||||
android:id="@+id/transferOrRestore"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.TransferOrRestoreFragment"
|
||||
tools:layout="@layout/fragment_transfer_restore">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_choose_backup"
|
||||
app:destination="@id/chooseBackupFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_new_device_transfer_instructions"
|
||||
app:destination="@id/newDeviceTransferInstructions"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/newDeviceTransferInstructions"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.NewDeviceTransferInstructionsFragment"
|
||||
tools:layout="@layout/new_device_transfer_instructions_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_device_transfer_setup"
|
||||
app:destination="@id/deviceTransferSetup"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/deviceTransferSetup"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.NewDeviceTransferSetupFragment"
|
||||
tools:layout="@layout/device_transfer_setup_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_new_device_transfer"
|
||||
app:destination="@id/newDeviceTransfer"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_deviceTransferSetup_to_transferOrRestore"
|
||||
app:destination="@id/transferOrRestore"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/transferOrRestore"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/newDeviceTransfer"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.NewDeviceTransferFragment"
|
||||
tools:layout="@layout/device_transfer_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_newDeviceTransfer_to_newDeviceTransferInstructions"
|
||||
app:destination="@id/newDeviceTransferInstructions"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/newDeviceTransferInstructions"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
<action
|
||||
android:id="@+id/action_newDeviceTransfer_to_newDeviceTransferComplete"
|
||||
app:destination="@id/newDeviceTransferComplete"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/welcomeFragment" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<fragment
|
||||
android:id="@+id/newDeviceTransferComplete"
|
||||
android:name="org.thoughtcrime.securesms.devicetransfer.newdevice.NewDeviceTransferCompleteFragment"
|
||||
tools:layout="@layout/new_device_transfer_complete_fragment">
|
||||
|
||||
<action
|
||||
android:id="@+id/action_newDeviceTransferComplete_to_enterPhoneNumberFragment"
|
||||
app:destination="@id/enterPhoneNumberFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim" />
|
||||
|
||||
</fragment>
|
||||
|
||||
<action
|
||||
android:id="@+id/action_restart_to_welcomeFragment"
|
||||
app:destination="@id/welcomeFragment"
|
||||
app:enterAnim="@anim/nav_default_enter_anim"
|
||||
app:exitAnim="@anim/nav_default_exit_anim"
|
||||
app:popEnterAnim="@anim/nav_default_pop_enter_anim"
|
||||
app:popExitAnim="@anim/nav_default_pop_exit_anim"
|
||||
app:popUpTo="@id/welcomeFragment"
|
||||
app:popUpToInclusive="true" />
|
||||
|
||||
</navigation>
|
File diff suppressed because one or more lines are too long
|
@ -13,4 +13,7 @@
|
|||
<dimen name="insights_modal_percent_text_size">20sp</dimen>
|
||||
<dimen name="insights_modal_percent_sign_text_size">16sp</dimen>
|
||||
|
||||
<dimen name="transfer_top_padding">16dp</dimen>
|
||||
<dimen name="transfer_split_top_padding">8dp</dimen>
|
||||
<dimen name="transfer_item_spacing">16dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="image_scale_flip">-1</integer>
|
||||
</resources>
|
|
@ -166,4 +166,8 @@
|
|||
<dimen name="conversation_update_vertical_margin">4dp</dimen>
|
||||
<dimen name="conversation_update_vertical_padding">5dp</dimen>
|
||||
<dimen name="conversation_update_vertical_padding_collapsed">2dp</dimen>
|
||||
|
||||
<dimen name="transfer_top_padding">64dp</dimen>
|
||||
<dimen name="transfer_split_top_padding">32dp</dimen>
|
||||
<dimen name="transfer_item_spacing">24dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<string name="install_url" translatable="false">https://signal.org/install</string>
|
||||
<string name="donate_url" translatable="false">https://signal.org/donate</string>
|
||||
<string name="backup_support_url" translatable="false">https://support.signal.org/hc/articles/360007059752</string>
|
||||
<string name="transfer_support_url" translatable="false">https://support.signal.org/hc/articles/360007059752</string>
|
||||
|
||||
<string name="yes">Yes</string>
|
||||
<string name="no">No</string>
|
||||
|
@ -2667,9 +2668,13 @@
|
|||
<string name="backup_enable_dialog__folder">Folder</string>
|
||||
<string name="backup_enable_dialog__i_have_written_down_this_passphrase">I have written down this passphrase. Without it, I will be unable to restore a backup.</string>
|
||||
<string name="registration_activity__restore_backup">Restore backup</string>
|
||||
<string name="registration_activity__transfer_or_restore_account">Transfer or restore account</string>
|
||||
<string name="registration_activity__transfer_account">Transfer account</string>
|
||||
<string name="registration_activity__skip">Skip</string>
|
||||
<string name="preferences_chats__chat_backups">Chat backups</string>
|
||||
<string name="preferences_chats__backup_chats_to_external_storage">Backup chats to external storage</string>
|
||||
<string name="preferences_chats__transfer_account">Transfer account</string>
|
||||
<string name="preferences_chats__transfer_account_to_a_new_android_device">Transfer account to a new Android device</string>
|
||||
<string name="RegistrationActivity_enter_backup_passphrase">Enter backup passphrase</string>
|
||||
<string name="RegistrationActivity_restore">Restore</string>
|
||||
<string name="RegistrationActivity_backup_failure_downgrade">Cannot import backups from newer versions of Signal</string>
|
||||
|
@ -2744,6 +2749,123 @@
|
|||
<string name="prompt_passphrase_activity__tap_to_unlock">TAP TO UNLOCK</string>
|
||||
<string name="Recipient_unknown">Unknown</string>
|
||||
|
||||
<!-- TransferOrRestoreFragment -->
|
||||
<string name="TransferOrRestoreFragment__transfer_or_restore_account">Transfer or restore account</string>
|
||||
<string name="TransferOrRestoreFragment__if_you_have_previously_registered_a_signal_account">If you have previously registered a Signal account, you can transfer or restore your account and messages</string>
|
||||
<string name="TransferOrRestoreFragment__transfer_from_android_device">Transfer from Android device</string>
|
||||
<string name="TransferOrRestoreFragment__transfer_your_account_and_message_history_from_your_old_android_device">Transfer your account and message history from your old Android device. You must have access to your old device.</string>
|
||||
<string name="TransferOrRestoreFragment__you_must_have_access_to_your_old_device">You must have access to your old device.</string>
|
||||
<string name="TransferOrRestoreFragment__restore_from_backup">Restore from backup</string>
|
||||
<string name="TransferOrRestoreFragment__restore_your_messages_and_media_from_a_local_backup">Restore your messages and media from a local backup. If you don\'t restore now, you wouldn\'t be able to restore later.</string>
|
||||
|
||||
<!-- NewDeviceTransferInstructionsFragment -->
|
||||
<string name="NewDeviceTransferInstructions__open_signal_on_your_old_android_phone">Open Signal on your old Android phone</string>
|
||||
<string name="NewDeviceTransferInstructions__continue">Continue</string>
|
||||
<string name="NewDeviceTransferInstructions__first_bullet">1.</string>
|
||||
<string name="NewDeviceTransferInstructions__tap_on_your_profile_photo_in_the_top_left_to_open_settings">Tap on your profile photo in the top left to open Settings</string>
|
||||
<string name="NewDeviceTransferInstructions__second_bullet">2.</string>
|
||||
<string name="NewDeviceTransferInstructions__tap_on_chats">Tap on "Chats"</string>
|
||||
<string name="NewDeviceTransferInstructions__third_bullet">3.</string>
|
||||
<string name="NewDeviceTransferInstructions__tap_transfer_account_and_then_continue_on_both_devices">Tap "Transfer Account" and then "Continue" on both devices</string>
|
||||
|
||||
<!-- NewDeviceTransferSetupFragment -->
|
||||
<string name="NewDeviceTransferSetup__preparing_to_connect_to_old_android_device">Preparing to connect to old Android device…</string>
|
||||
<string name="NewDeviceTransferSetup__take_a_moment_should_be_ready_soon">Taking a moment, should be ready soon</string>
|
||||
<string name="NewDeviceTransferSetup__waiting_for_old_device_to_connect">Waiting for old Android device to connect…</string>
|
||||
<string name="NewDeviceTransferSetup__connecting_to_old_android_device">Connecting to old Android device…</string>
|
||||
<string name="NewDeviceTransferSetup__signal_needs_the_location_permission_to_discover_and_connect_with_your_old_device">Signal needs the location permission to discover and connect to your old Android device.</string>
|
||||
<string name="NewDeviceTransferSetup__signal_needs_location_services_enabled_to_discover_and_connect_with_your_old_device">Signal needs location services enabled to discover and connect with your old Android device.</string>
|
||||
<string name="NewDeviceTransferSetup__signal_needs_wifi_on_to_discover_and_connect_with_your_old_device">Signal needs Wi-Fi on to discover and connect with your old Android device. Wi-Fi needs to be on but it does not have to be connected to a Wi-Fi network.</string>
|
||||
<string name="NewDeviceTransferSetup__sorry_it_appears_your_device_does_not_support_wifi_direct">Sorry, it appears this device does not support Wi-Fi Direct. Signal uses Wi-Fi Direct to discover and connect with your old Android device. You can still restore a backup to restore your account from your old Android device.</string>
|
||||
<string name="NewDeviceTransferSetup__restore_a_backup">Restore a backup</string>
|
||||
<string name="NewDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device">An unexpected error occurred while attempting to connect to your old Android device.</string>
|
||||
|
||||
<!-- OldDeviceTransferSetupFragment -->
|
||||
<string name="OldDeviceTransferSetup__searching_for_your_new_android_device">Searching for your new Android device…</string>
|
||||
<string name="OldDeviceTransferSetup__connecting_to_new_android_device">Connecting to new Android device…</string>
|
||||
<string name="OldDeviceTransferSetup__signal_needs_the_location_permission_to_discover_and_connect_with_your_new_device">Signal needs the location permission to discover and connect to your new Android device.</string>
|
||||
<string name="OldDeviceTransferSetup__signal_needs_location_services_enabled_to_discover_and_connect_with_your_new_device">Signal needs location services enabled to discover and connect with your new Android device.</string>
|
||||
<string name="OldDeviceTransferSetup__signal_needs_wifi_on_to_discover_and_connect_with_your_new_device">Signal needs Wi-Fi on to discover and connect with your new Android device. Wi-Fi needs to be on but it does not have to be connected to a Wi-Fi network.</string>
|
||||
<string name="OldDeviceTransferSetup__sorry_it_appears_your_device_does_not_support_wifi_direct">Sorry, it appears this device does not support Wi-Fi Direct. Signal uses Wi-Fi Direct to discover and connect with your new Android device. You can still create a backup to restore your account on your new Android device.</string>
|
||||
<string name="OldDeviceTransferSetup__create_a_backup">Create a backup</string>
|
||||
<string name="OldDeviceTransferSetup__an_unexpected_error_occurred_while_attempting_to_connect_to_your_old_device">An unexpected error occurred while attempting to connect to your new Android device.</string>
|
||||
|
||||
<!-- DeviceTransferSetupFragment -->
|
||||
<string name="DeviceTransferSetup__unable_to_open_wifi_settings">Unable to open Wi-Fi Settings. Please turn on Wi-Fi manually.</string>
|
||||
<string name="DeviceTransferSetup__grant_location_permission">Grant location permission</string>
|
||||
<string name="DeviceTransferSetup__turn_on_location_services">Turn on location services</string>
|
||||
<string name="DeviceTransferSetup__unable_to_open_location_settings">Unable to open location settings.</string>
|
||||
<string name="DeviceTransferSetup__turn_on_wifi">Turn on Wi-Fi</string>
|
||||
<string name="DeviceTransferSetup__error_connecting">Error Connecting</string>
|
||||
<string name="DeviceTransferSetup__retry">Retry</string>
|
||||
<string name="DeviceTransferSetup__submit_debug_logs">Submit debug logs</string>
|
||||
<string name="DeviceTransferSetup__verify_code">Verify code</string>
|
||||
<string name="DeviceTransferSetup__verify_that_the_code_below_matches_on_both_of_your_devices">Verify that the code below matches on both of your devices. Then tap continue.</string>
|
||||
<string name="DeviceTransferSetup__the_numbers_do_not_match">The numbers do not match</string>
|
||||
<string name="DeviceTransferSetup__continue">Continue</string>
|
||||
<string name="DeviceTransferSetup__number_is_not_the_same">Number is not the same</string>
|
||||
<string name="DeviceTransferSetup__if_the_numbers_on_your_devices_do_not_match_its_possible_you_connected_to_the_wrong_device">If the numbers on your devices do not match, it\'s possible you connected to the wrong device. To fix this, stop the transfer and try again, and keep both of your devices close.</string>
|
||||
<string name="DeviceTransferSetup__stop_transfer">Stop transfer</string>
|
||||
<string name="DeviceTransferSetup__unable_to_discover_old_device">Unable to discover old device</string>
|
||||
<string name="DeviceTransferSetup__unable_to_discover_new_device">Unable to discover new device</string>
|
||||
<string name="DeviceTransferSetup__make_sure_the_following_permissions_are_enabled">Make sure the following permissions and services are enabled:</string>
|
||||
<string name="DeviceTransferSetup__location_permission">Location permission</string>
|
||||
<string name="DeviceTransferSetup__location_services">Location services</string>
|
||||
<string name="DeviceTransferSetup__wifi">Wi-Fi</string>
|
||||
<string name="DeviceTransferSetup__on_the_wifi_direct_screen_remove_all_remembered_groups_and_unlink_any_invited_or_connected_devices">On the WiFi Direct screen, remove all remembered groups and unlink any invited or connected devices.</string>
|
||||
<string name="DeviceTransferSetup__wifi_direct_screen">WiFi Direct screen</string>
|
||||
<string name="DeviceTransferSetup__try_turning_wifi_off_and_on_on_both_devices">Try turning Wi-Fi off and on, on both devices.</string>
|
||||
<string name="DeviceTransferSetup__make_sure_both_devices_are_in_transfer_mode">Make sure both devices are in transfer mode.</string>
|
||||
<string name="DeviceTransferSetup__go_to_support_page">Go to support page</string>
|
||||
<string name="DeviceTransferSetup__try_again">Try again</string>
|
||||
|
||||
<!-- NewDeviceTransferFragment -->
|
||||
<string name="NewDeviceTransfer__cannot_transfer_from_a_newer_version_of_signal">Cannot transfer from a newer versions of Signal</string>
|
||||
|
||||
<!-- DeviceTransferFragment -->
|
||||
<string name="DeviceTransfer__transferring_data">Transferring data</string>
|
||||
<string name="DeviceTransfer__keep_both_devices_near_each_other">Keep both devices near each other. Do not turn off the devices and keep Signal open. Transfers are end-to-end encrypted.</string>
|
||||
<string name="DeviceTransfer__d_messages_so_far">%1$d messages so far…</string>
|
||||
<string name="DeviceTransfer__cancel">Cancel</string>
|
||||
<string name="DeviceTransfer__try_again">Try again</string>
|
||||
<string name="DeviceTransfer__stop_transfer_question">Stop transfer?</string>
|
||||
<string name="DeviceTransfer__stop_transfer">Stop transfer</string>
|
||||
<string name="DeviceTransfer__all_transfer_progress_will_be_lost">All transfer progress will be lost.</string>
|
||||
<string name="DeviceTransfer__transfer_failed">Transfer failed</string>
|
||||
<string name="DeviceTransfer__unable_to_transfer">Unable to transfer</string>
|
||||
|
||||
<!-- OldDeviceTransferInstructionsFragment -->
|
||||
<string name="OldDeviceTransferInstructions__transfer_account">Transfer Account</string>
|
||||
<string name="OldDeviceTransferInstructions__you_can_transfer_your_signal_account_when_setting_up_signal_on_a_new_android_device">You can transfer your Signal account when setting up Signal on a new Android device. Before continuing:</string>
|
||||
<string name="OldDeviceTransferInstructions__first_bullet">1.</string>
|
||||
<string name="OldDeviceTransferInstructions__download_signal_on_your_new_android_device">Download Signal on your new Android device</string>
|
||||
<string name="OldDeviceTransferInstructions__second_bullet">2.</string>
|
||||
<string name="OldDeviceTransferInstructions__tap_on_transfer_or_restore_account">Tap on "Transfer or restore account"</string>
|
||||
<string name="OldDeviceTransferInstructions__third_bullet">3.</string>
|
||||
<string name="OldDeviceTransferInstructions__select_transfer_from_android_device_when_prompted_and_then_continue">Select "Transfer from Android device" when prompted and then "Continue". Keep both devices nearby.</string>
|
||||
<string name="OldDeviceTransferInstructions__continue">Continue</string>
|
||||
|
||||
<!-- OldDeviceTransferComplete -->
|
||||
<string name="OldDeviceTransferComplete__transfer_complete">Transfer complete</string>
|
||||
<string name="OldDeviceTransferComplete__go_to_your_new_device">Go to your new device</string>
|
||||
<string name="OldDeviceTransferComplete__your_signal_data_has_Been_transferred_to_your_new_device">Your Signal data has been transferred to your new device. To complete the transfer process, you must continue registration on your new device.</string>
|
||||
<string name="OldDeviceTransferComplete__close">Close</string>
|
||||
|
||||
<!-- NewDeviceTransferComplete -->
|
||||
<string name="NewDeviceTransferComplete__transfer_successful">Transfer successful</string>
|
||||
<string name="NewDeviceTransferComplete__transfer_complete">Transfer complete</string>
|
||||
<string name="NewDeviceTransferComplete__to_complete_the_transfer_process_you_must_continue_registration">To complete the transfer process, you must continue registration.</string>
|
||||
<string name="NewDeviceTransferComplete__continue_registration">Continue registration</string>
|
||||
|
||||
<!-- DeviceToDeviceTransferService -->
|
||||
<string name="DeviceToDeviceTransferService_content_title">Account transfer</string>
|
||||
<string name="DeviceToDeviceTransferService_status_ready">Preparing to connect to your other Android device…</string>
|
||||
<string name="DeviceToDeviceTransferService_status_starting_up">Preparing to connect to your other Android device…</string>
|
||||
<string name="DeviceToDeviceTransferService_status_discovery">Searching for your other Android device…</string>
|
||||
<string name="DeviceToDeviceTransferService_status_network_connected">Connecting to your other Android device…</string>
|
||||
<string name="DeviceToDeviceTransferService_status_verification_required">Verification required</string>
|
||||
<string name="DeviceToDeviceTransferService_status_service_connected">Transferring account…</string>
|
||||
|
||||
<!-- RecipientBottomSheet -->
|
||||
<string name="RecipientBottomSheet_block">Block</string>
|
||||
<string name="RecipientBottomSheet_unblock">Unblock</string>
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
<resources>
|
||||
<bool name="enable_alarm_manager">true</bool>
|
||||
<bool name="enable_job_service">false</bool>
|
||||
<integer name="image_scale_flip">1</integer>
|
||||
</resources>
|
||||
|
|
|
@ -46,6 +46,12 @@
|
|||
android:summary="@string/preferences_chats__backup_chats_to_external_storage"
|
||||
android:persistent="false"
|
||||
android:title="@string/preferences_chats__chat_backups" />
|
||||
|
||||
<androidx.preference.Preference
|
||||
android:key="pref_transfer"
|
||||
android:summary="@string/preferences_chats__transfer_account_to_a_new_android_device"
|
||||
android:persistent="false"
|
||||
android:title="@string/preferences_chats__transfer_account" />
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
||||
|
|
|
@ -101,7 +101,7 @@ public class MainActivity extends AppCompatActivity {
|
|||
|
||||
if (event.getTransferMode() == TransferStatus.TransferMode.VERIFICATION_REQUIRED) {
|
||||
new AlertDialog.Builder(this).setTitle("Verification Required")
|
||||
.setMessage("Code: " + ((TransferStatus.VerificationTransferStatus) event).getAuthenticationCode())
|
||||
.setMessage("Code: " + event.getAuthenticationCode())
|
||||
.setPositiveButton("Yes, Same", (d, w) -> DeviceToDeviceTransferService.setAuthenticationCodeVerified(this, true))
|
||||
.setNegativeButton("No, different", (d, w) -> DeviceToDeviceTransferService.setAuthenticationCodeVerified(this, false))
|
||||
.setCancelable(false)
|
||||
|
|
|
@ -19,6 +19,7 @@ import org.greenrobot.eventbus.ThreadMode;
|
|||
import org.signal.core.util.ThreadUtil;
|
||||
import org.signal.core.util.logging.Log;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
|
@ -195,11 +196,39 @@ public class DeviceToDeviceTransferService extends Service implements ShutdownCa
|
|||
private @NonNull Notification createNotification(@NonNull TransferStatus transferStatus, @NonNull TransferNotificationData notificationData) {
|
||||
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, notificationData.channelId);
|
||||
|
||||
//TODO [cody] build notification to spec
|
||||
String contentText = "";
|
||||
switch (transferStatus.getTransferMode()) {
|
||||
case READY:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_ready);
|
||||
break;
|
||||
case STARTING_UP:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_starting_up);
|
||||
break;
|
||||
case DISCOVERY:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_discovery);
|
||||
break;
|
||||
case NETWORK_CONNECTED:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_network_connected);
|
||||
break;
|
||||
case VERIFICATION_REQUIRED:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_verification_required);
|
||||
break;
|
||||
case SERVICE_CONNECTED:
|
||||
contentText = getString(R.string.DeviceToDeviceTransferService_status_service_connected);
|
||||
break;
|
||||
case UNAVAILABLE:
|
||||
case FAILED:
|
||||
case SERVICE_DISCONNECTED:
|
||||
Log.d(TAG, "Intentionally no notification text for: " + transferStatus.getTransferMode());
|
||||
break;
|
||||
default:
|
||||
throw new AssertionError("No notification text for: " + transferStatus.getTransferMode());
|
||||
}
|
||||
|
||||
builder.setSmallIcon(notificationData.icon)
|
||||
.setOngoing(true)
|
||||
.setContentTitle("Device Transfer")
|
||||
.setContentText("Status: " + transferStatus.getTransferMode().name())
|
||||
.setContentTitle(getString(R.string.DeviceToDeviceTransferService_content_title))
|
||||
.setContentText(contentText)
|
||||
.setContentIntent(pendingIntent);
|
||||
|
||||
return builder.build();
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.signal.devicetransfer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.p2p.WifiP2pDevice;
|
||||
import android.net.wifi.p2p.WifiP2pInfo;
|
||||
import android.os.Handler;
|
||||
|
@ -8,6 +9,7 @@ import android.os.HandlerThread;
|
|||
import android.os.Message;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -16,6 +18,7 @@ import org.signal.core.util.concurrent.SignalExecutors;
|
|||
import org.signal.core.util.logging.Log;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Encapsulates the logic to find and establish a WiFi Direct connection with another
|
||||
|
@ -39,15 +42,16 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
|
||||
private static final String TAG = Log.tag(DeviceTransferClient.class);
|
||||
|
||||
private static final int START_CLIENT = 0;
|
||||
private static final int STOP_CLIENT = 1;
|
||||
private static final int START_NETWORK_CLIENT = 2;
|
||||
private static final int NETWORK_DISCONNECTED = 3;
|
||||
private static final int CONNECT_TO_SERVICE = 4;
|
||||
private static final int RESTART_CLIENT = 5;
|
||||
private static final int START_IP_EXCHANGE = 6;
|
||||
private static final int IP_EXCHANGE_SUCCESS = 7;
|
||||
private static final int SET_VERIFIED = 8;
|
||||
private static final int START_CLIENT = 0;
|
||||
private static final int STOP_CLIENT = 1;
|
||||
private static final int START_NETWORK_CLIENT = 2;
|
||||
private static final int NETWORK_DISCONNECTED = 3;
|
||||
private static final int CONNECT_TO_SERVICE = 4;
|
||||
private static final int RESTART_CLIENT = 5;
|
||||
private static final int START_IP_EXCHANGE = 6;
|
||||
private static final int IP_EXCHANGE_SUCCESS = 7;
|
||||
private static final int SET_VERIFIED = 8;
|
||||
private static final int NETWORK_CONNECTION_CHANGED = 9;
|
||||
|
||||
private final Context context;
|
||||
private int remotePort;
|
||||
|
@ -60,6 +64,9 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
private final Runnable autoRestart;
|
||||
private IpExchange.IpExchangeThread ipExchangeThread;
|
||||
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
private final AtomicBoolean stopped = new AtomicBoolean(false);
|
||||
|
||||
private static void update(@NonNull TransferStatus transferStatus) {
|
||||
Log.d(TAG, "transferStatus: " + transferStatus.getTransferMode().name());
|
||||
EventBus.getDefault().postSticky(transferStatus);
|
||||
|
@ -81,31 +88,31 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
};
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void start() {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void start() {
|
||||
if (started.compareAndSet(false, true)) {
|
||||
update(TransferStatus.ready());
|
||||
handler.sendEmptyMessage(START_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void stop() {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void stop() {
|
||||
if (stopped.compareAndSet(false, true)) {
|
||||
handler.sendEmptyMessage(STOP_CLIENT);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void setVerified(boolean isVerified) {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void setVerified(boolean isVerified) {
|
||||
if (!stopped.get()) {
|
||||
handler.sendMessage(handler.obtainMessage(SET_VERIFIED, isVerified));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void shutdown() {
|
||||
private void shutdown() {
|
||||
stopIpExchange();
|
||||
stopClient();
|
||||
stopNetworkClient();
|
||||
stopWifiDirect();
|
||||
|
||||
if (commandAndControlThread != null) {
|
||||
|
@ -115,7 +122,7 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
commandAndControlThread = null;
|
||||
}
|
||||
|
||||
update(TransferStatus.shutdown());
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
}
|
||||
|
||||
private void internalShutdown() {
|
||||
|
@ -136,17 +143,17 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
shutdown();
|
||||
break;
|
||||
case START_NETWORK_CLIENT:
|
||||
startClient((String) message.obj);
|
||||
startNetworkClient((String) message.obj);
|
||||
break;
|
||||
case NETWORK_DISCONNECTED:
|
||||
stopClient();
|
||||
stopNetworkClient();
|
||||
break;
|
||||
case CONNECT_TO_SERVICE:
|
||||
stopServiceDiscovery();
|
||||
connectToService((String) message.obj, message.arg1);
|
||||
break;
|
||||
case RESTART_CLIENT:
|
||||
stopClient();
|
||||
stopNetworkClient();
|
||||
stopWifiDirect();
|
||||
startWifiDirect();
|
||||
break;
|
||||
|
@ -161,6 +168,9 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
clientThread.setVerified((Boolean) message.obj);
|
||||
}
|
||||
break;
|
||||
case NETWORK_CONNECTION_CHANGED:
|
||||
requestNetworkInfo((Boolean) message.obj);
|
||||
break;
|
||||
case NetworkClientThread.NETWORK_CLIENT_SSL_ESTABLISHED:
|
||||
update(TransferStatus.verificationRequired((Integer) message.obj));
|
||||
break;
|
||||
|
@ -231,7 +241,7 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private void startClient(@NonNull String serverHostAddress) {
|
||||
private void startNetworkClient(@NonNull String serverHostAddress) {
|
||||
if (clientThread != null) {
|
||||
Log.i(TAG, "Client already running");
|
||||
return;
|
||||
|
@ -246,14 +256,14 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
clientThread.start();
|
||||
}
|
||||
|
||||
private void stopClient() {
|
||||
private void stopNetworkClient() {
|
||||
if (clientThread != null) {
|
||||
Log.i(TAG, "Shutting down ClientThread");
|
||||
clientThread.shutdown();
|
||||
try {
|
||||
clientThread.join(TimeUnit.SECONDS.toMillis(1));
|
||||
} catch (InterruptedException e) {
|
||||
Log.i(TAG, "Server thread took too long to shutdown", e);
|
||||
Log.i(TAG, "Client thread took too long to shutdown", e);
|
||||
}
|
||||
clientThread = null;
|
||||
}
|
||||
|
@ -265,6 +275,11 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
return;
|
||||
}
|
||||
|
||||
if (clientThread != null) {
|
||||
Log.i(TAG, "Client is running we shouldn't be connecting again");
|
||||
return;
|
||||
}
|
||||
|
||||
handler.removeCallbacks(autoRestart);
|
||||
|
||||
int tries = 5;
|
||||
|
@ -288,6 +303,26 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
handler.sendMessage(handler.obtainMessage(RESTART_CLIENT));
|
||||
}
|
||||
|
||||
private void requestNetworkInfo(boolean isNetworkConnected) {
|
||||
if (wifiDirect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNetworkConnected) {
|
||||
Log.i(TAG, "Network connected, requesting network info");
|
||||
try {
|
||||
wifiDirect.requestNetworkInfo();
|
||||
} catch (WifiDirectUnavailableException e) {
|
||||
Log.e(TAG, e);
|
||||
internalShutdown();
|
||||
update(TransferStatus.failed());
|
||||
}
|
||||
} else {
|
||||
Log.i(TAG, "Network disconnected");
|
||||
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
|
||||
}
|
||||
}
|
||||
|
||||
private void startIpExchange(@NonNull String groupOwnerHostAddress) {
|
||||
ipExchangeThread = IpExchange.getIp(groupOwnerHostAddress, remotePort, handler, IP_EXCHANGE_SUCCESS);
|
||||
}
|
||||
|
@ -309,7 +344,7 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
handler.sendMessage(handler.obtainMessage(START_NETWORK_CLIENT, host));
|
||||
}
|
||||
|
||||
public class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
|
||||
final class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
|
||||
|
||||
@Override
|
||||
public void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo) {
|
||||
|
@ -325,17 +360,15 @@ final class DeviceTransferClient implements Handler.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkDisconnected() {
|
||||
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkFailure() {
|
||||
handler.sendEmptyMessage(NETWORK_DISCONNECTED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice) { }
|
||||
public void onConnectionChanged(@NonNull NetworkInfo networkInfo) {
|
||||
handler.sendMessage(handler.obtainMessage(NETWORK_CONNECTION_CHANGED, networkInfo.isConnected()));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package org.signal.devicetransfer;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.wifi.p2p.WifiP2pDevice;
|
||||
import android.net.wifi.p2p.WifiP2pInfo;
|
||||
import android.os.Handler;
|
||||
|
@ -8,6 +9,7 @@ import android.os.HandlerThread;
|
|||
import android.os.Message;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.MainThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
|
@ -17,6 +19,7 @@ import org.signal.core.util.logging.Log;
|
|||
import org.signal.devicetransfer.SelfSignedIdentity.SelfSignedKeys;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
/**
|
||||
* Encapsulates the logic to advertise the availability of a transfer service over a WiFi Direct
|
||||
|
@ -34,12 +37,13 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
|
||||
private static final String TAG = Log.tag(DeviceTransferServer.class);
|
||||
|
||||
private static final int START_SERVER = 0;
|
||||
private static final int STOP_SERVER = 1;
|
||||
private static final int START_IP_EXCHANGE = 2;
|
||||
private static final int IP_EXCHANGE_SUCCESS = 3;
|
||||
private static final int NETWORK_FAILURE = 4;
|
||||
private static final int SET_VERIFIED = 5;
|
||||
private static final int START_SERVER = 0;
|
||||
private static final int STOP_SERVER = 1;
|
||||
private static final int START_IP_EXCHANGE = 2;
|
||||
private static final int IP_EXCHANGE_SUCCESS = 3;
|
||||
private static final int NETWORK_FAILURE = 4;
|
||||
private static final int SET_VERIFIED = 5;
|
||||
private static final int NETWORK_CONNECTION_CHANGED = 6;
|
||||
|
||||
private NetworkServerThread serverThread;
|
||||
private HandlerThread commandAndControlThread;
|
||||
|
@ -50,6 +54,9 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
private final ShutdownCallback shutdownCallback;
|
||||
private IpExchange.IpExchangeThread ipExchangeThread;
|
||||
|
||||
private final AtomicBoolean started = new AtomicBoolean(false);
|
||||
private final AtomicBoolean stopped = new AtomicBoolean(false);
|
||||
|
||||
private static void update(@NonNull TransferStatus transferStatus) {
|
||||
Log.d(TAG, "transferStatus: " + transferStatus.getTransferMode().name());
|
||||
EventBus.getDefault().postSticky(transferStatus);
|
||||
|
@ -67,29 +74,29 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
this.handler = new Handler(commandAndControlThread.getLooper(), this);
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void start() {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void start() {
|
||||
if (started.compareAndSet(false, true)) {
|
||||
update(TransferStatus.ready());
|
||||
handler.sendEmptyMessage(START_SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void stop() {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void stop() {
|
||||
if (stopped.compareAndSet(false, true)) {
|
||||
handler.sendEmptyMessage(STOP_SERVER);
|
||||
}
|
||||
}
|
||||
|
||||
@AnyThread
|
||||
public synchronized void setVerified(boolean isVerified) {
|
||||
if (commandAndControlThread != null) {
|
||||
@MainThread
|
||||
public void setVerified(boolean isVerified) {
|
||||
if (!stopped.get()) {
|
||||
handler.sendMessage(handler.obtainMessage(SET_VERIFIED, isVerified));
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void shutdown() {
|
||||
private void shutdown() {
|
||||
stopIpExchange();
|
||||
stopServer();
|
||||
stopWifiDirect();
|
||||
|
@ -101,7 +108,7 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
commandAndControlThread = null;
|
||||
}
|
||||
|
||||
update(TransferStatus.shutdown());
|
||||
EventBus.getDefault().removeStickyEvent(TransferStatus.class);
|
||||
}
|
||||
|
||||
private void internalShutdown() {
|
||||
|
@ -132,6 +139,9 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
serverThread.setVerified((Boolean) message.obj);
|
||||
}
|
||||
break;
|
||||
case NETWORK_CONNECTION_CHANGED:
|
||||
requestNetworkInfo((Boolean) message.obj);
|
||||
break;
|
||||
case NetworkServerThread.NETWORK_SERVER_STARTED:
|
||||
startWifiDirect(message.arg1);
|
||||
break;
|
||||
|
@ -201,6 +211,22 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
private void requestNetworkInfo(boolean isNetworkConnected) {
|
||||
if (wifiDirect == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isNetworkConnected) {
|
||||
try {
|
||||
wifiDirect.requestNetworkInfo();
|
||||
} catch (WifiDirectUnavailableException e) {
|
||||
Log.e(TAG, e);
|
||||
internalShutdown();
|
||||
update(TransferStatus.failed());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startNetworkServer() {
|
||||
if (serverThread != null) {
|
||||
Log.i(TAG, "Server already running");
|
||||
|
@ -253,7 +279,7 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
stopIpExchange();
|
||||
}
|
||||
|
||||
public class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
|
||||
final class WifiDirectListener implements WifiDirect.WifiDirectConnectionListener {
|
||||
|
||||
@Override
|
||||
public void onNetworkConnected(@NonNull WifiP2pInfo info) {
|
||||
|
@ -262,16 +288,15 @@ final class DeviceTransferServer implements Handler.Callback {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNetworkDisconnected() { }
|
||||
|
||||
@Override
|
||||
public void onNetworkFailure() {
|
||||
handler.sendEmptyMessage(NETWORK_FAILURE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice) { }
|
||||
public void onConnectionChanged(@NonNull NetworkInfo networkInfo) {
|
||||
handler.sendMessage(handler.obtainMessage(NETWORK_CONNECTION_CHANGED, networkInfo.isConnected()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo) { }
|
||||
|
|
|
@ -15,14 +15,11 @@ import java.io.InputStream;
|
|||
import java.io.OutputStream;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.SSLHandshakeException;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
import static org.signal.devicetransfer.DeviceTransferAuthentication.DIGEST_LENGTH;
|
||||
|
||||
/**
|
||||
* Performs the networking setup/tear down for the client. This includes
|
||||
* connecting to the server, performing the TLS/SAS verification, running an
|
||||
|
@ -101,11 +98,14 @@ final class NetworkClientThread extends Thread {
|
|||
handler.sendEmptyMessage(NETWORK_CLIENT_CONNECTED);
|
||||
clientTask.run(context, outputStream);
|
||||
outputStream.flush();
|
||||
client.shutdownOutput();
|
||||
|
||||
Log.d(TAG, "Waiting for server to tell us they got everything");
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputStream.read();
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
inputStream.read();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Something happened confirming with server, mostly like bad SSL shutdown state, assuming success", e);
|
||||
}
|
||||
success = true;
|
||||
isRunning = false;
|
||||
} catch (IOException e) {
|
||||
|
@ -117,6 +117,9 @@ final class NetworkClientThread extends Thread {
|
|||
Log.w(TAG, e);
|
||||
isRunning = false;
|
||||
} finally {
|
||||
if (success) {
|
||||
clientTask.success();
|
||||
}
|
||||
StreamUtil.close(client);
|
||||
handler.sendEmptyMessage(NETWORK_CLIENT_DISCONNECTED);
|
||||
}
|
||||
|
@ -127,9 +130,6 @@ final class NetworkClientThread extends Thread {
|
|||
}
|
||||
|
||||
Log.i(TAG, "Client exiting");
|
||||
if (success) {
|
||||
clientTask.success();
|
||||
}
|
||||
handler.sendEmptyMessage(NETWORK_CLIENT_STOPPED);
|
||||
}
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@ final class NetworkServerThread extends Thread {
|
|||
outputStream.write(0x43);
|
||||
outputStream.flush();
|
||||
serverTask.run(context, inputStream);
|
||||
|
||||
outputStream.write(0x53);
|
||||
outputStream.flush();
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -63,10 +63,6 @@ public class TransferStatus {
|
|||
return new TransferStatus(TransferMode.FAILED);
|
||||
}
|
||||
|
||||
public static @NonNull TransferStatus shutdown() {
|
||||
return new TransferStatus(TransferMode.SHUTDOWN);
|
||||
}
|
||||
|
||||
public enum TransferMode {
|
||||
UNAVAILABLE,
|
||||
FAILED,
|
||||
|
@ -76,7 +72,6 @@ public class TransferStatus {
|
|||
NETWORK_CONNECTED,
|
||||
VERIFICATION_REQUIRED,
|
||||
SERVICE_CONNECTED,
|
||||
SERVICE_DISCONNECTED,
|
||||
SHUTDOWN,
|
||||
SERVICE_DISCONNECTED
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,10 +46,7 @@ public final class WifiDirect {
|
|||
private static final String TAG = Log.tag(WifiDirect.class);
|
||||
|
||||
private static final IntentFilter intentFilter = new IntentFilter() {{
|
||||
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
|
||||
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
|
||||
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
|
||||
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
|
||||
}};
|
||||
|
||||
private static final String EXTRA_INFO_PLACEHOLDER = "%%EXTRA_INFO%%";
|
||||
|
@ -57,6 +54,9 @@ public final class WifiDirect {
|
|||
private static final Pattern SERVICE_INSTANCE_PATTERN = Pattern.compile("_devicetransfer(\\._(.+))?\\._signal\\.org");
|
||||
private static final String SERVICE_REG_TYPE = "_presence._tcp";
|
||||
|
||||
private static final long SAFE_FOR_LONG_AWAIT_TIMEOUT = TimeUnit.SECONDS.toMillis(5);
|
||||
private static final long NOT_SAFE_FOR_LONG_AWAIT_TIMEOUT = 50;
|
||||
|
||||
private final Context context;
|
||||
private WifiDirectConnectionListener connectionListener;
|
||||
private WifiDirectCallbacks wifiDirectCallbacks;
|
||||
|
@ -115,7 +115,7 @@ public final class WifiDirect {
|
|||
throw new WifiDirectUnavailableException(Reason.WIFI_P2P_MANAGER);
|
||||
}
|
||||
|
||||
wifiDirectCallbacks = new WifiDirectCallbacks();
|
||||
wifiDirectCallbacks = new WifiDirectCallbacks(connectionListener);
|
||||
channel = manager.initialize(context, wifiDirectCallbacksHandler.getLooper(), wifiDirectCallbacks);
|
||||
if (channel == null) {
|
||||
Log.i(TAG, "Unable to initialize channel");
|
||||
|
@ -139,18 +139,23 @@ public final class WifiDirect {
|
|||
connectionListener = null;
|
||||
|
||||
if (manager != null) {
|
||||
retry(manager::clearServiceRequests, "clear service requests");
|
||||
retry(manager::stopPeerDiscovery, "stop peer discovery");
|
||||
retry(manager::clearLocalServices, "clear local services");
|
||||
retrySync(manager::clearServiceRequests, "clear service requests", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
retrySync(manager::stopPeerDiscovery, "stop peer discovery", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
retrySync(manager::clearLocalServices, "clear local services", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
if (Build.VERSION.SDK_INT < 27) {
|
||||
retrySync(manager::removeGroup, "remove group", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
channel = null;
|
||||
}
|
||||
manager = null;
|
||||
}
|
||||
|
||||
if (channel != null) {
|
||||
if (channel != null && Build.VERSION.SDK_INT >= 27) {
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
|
||||
if (wifiDirectCallbacks != null) {
|
||||
wifiDirectCallbacks.clearConnectionListener();
|
||||
context.unregisterReceiver(wifiDirectCallbacks);
|
||||
wifiDirectCallbacks = null;
|
||||
}
|
||||
|
@ -173,10 +178,10 @@ public final class WifiDirect {
|
|||
|
||||
WifiP2pDnsSdServiceInfo serviceInfo = WifiP2pDnsSdServiceInfo.newInstance(buildServiceInstanceName(extraInfo), SERVICE_REG_TYPE, Collections.emptyMap());
|
||||
|
||||
SyncActionListener addLocalServiceListener = new SyncActionListener("add local service");
|
||||
SyncActionListener addLocalServiceListener = new SyncActionListener("add local service", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
manager.addLocalService(channel, serviceInfo, addLocalServiceListener);
|
||||
|
||||
SyncActionListener discoverPeersListener = new SyncActionListener("discover peers");
|
||||
SyncActionListener discoverPeersListener = new SyncActionListener("discover peers", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
manager.discoverPeers(channel, discoverPeersListener);
|
||||
|
||||
if (!addLocalServiceListener.successful() || !discoverPeersListener.successful()) {
|
||||
|
@ -190,8 +195,8 @@ public final class WifiDirect {
|
|||
synchronized void stopDiscoveryService() throws WifiDirectUnavailableException {
|
||||
ensureInitialized();
|
||||
|
||||
retry(manager::stopPeerDiscovery, "stop peer discovery");
|
||||
retry(manager::clearLocalServices, "clear local services");
|
||||
retryAsync(manager::stopPeerDiscovery, "stop peer discovery");
|
||||
retryAsync(manager::clearLocalServices, "clear local services");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,8 +219,9 @@ public final class WifiDirect {
|
|||
String extraInfo = isInstanceNameMatching(instanceName);
|
||||
if (extraInfo != null) {
|
||||
Log.d(TAG, "Service found!");
|
||||
if (connectionListener != null) {
|
||||
connectionListener.onServiceDiscovered(sourceDevice, extraInfo);
|
||||
WifiDirectConnectionListener listener = connectionListener;
|
||||
if (listener != null) {
|
||||
listener.onServiceDiscovered(sourceDevice, extraInfo);
|
||||
}
|
||||
} else {
|
||||
Log.d(TAG, "Found unusable service, ignoring.");
|
||||
|
@ -226,10 +232,10 @@ public final class WifiDirect {
|
|||
|
||||
serviceRequest = WifiP2pDnsSdServiceRequest.newInstance();
|
||||
|
||||
SyncActionListener addServiceListener = new SyncActionListener("add service request");
|
||||
SyncActionListener addServiceListener = new SyncActionListener("add service request", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
manager.addServiceRequest(channel, serviceRequest, addServiceListener);
|
||||
|
||||
SyncActionListener startDiscovery = new SyncActionListener("discover services");
|
||||
SyncActionListener startDiscovery = new SyncActionListener("discover services", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
manager.discoverServices(channel, startDiscovery);
|
||||
|
||||
if (!addServiceListener.successful() || !startDiscovery.successful()) {
|
||||
|
@ -245,7 +251,7 @@ public final class WifiDirect {
|
|||
synchronized void stopServiceDiscovery() throws WifiDirectUnavailableException {
|
||||
ensureInitialized();
|
||||
|
||||
retry(manager::clearServiceRequests, "clear service requests");
|
||||
retryAsync(manager::clearServiceRequests, "clear service requests");
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -268,7 +274,7 @@ public final class WifiDirect {
|
|||
serviceRequest = null;
|
||||
}
|
||||
|
||||
SyncActionListener listener = new SyncActionListener("service connect");
|
||||
SyncActionListener listener = new SyncActionListener("service connect", SAFE_FOR_LONG_AWAIT_TIMEOUT);
|
||||
manager.connect(channel, config, listener);
|
||||
|
||||
if (listener.successful()) {
|
||||
|
@ -278,19 +284,39 @@ public final class WifiDirect {
|
|||
}
|
||||
}
|
||||
|
||||
private synchronized void retry(@NonNull ManagerRetry retryFunction, @NonNull String message) {
|
||||
public synchronized void requestNetworkInfo() throws WifiDirectUnavailableException {
|
||||
ensureInitialized();
|
||||
|
||||
manager.requestConnectionInfo(channel, info -> {
|
||||
Log.i(TAG, "Connection information available. group_formed: " + info.groupFormed + " group_owner: " + info.isGroupOwner);
|
||||
WifiDirectConnectionListener listener = connectionListener;
|
||||
if (listener != null) {
|
||||
listener.onNetworkConnected(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private synchronized void retrySync(@NonNull ManagerRetry retryFunction, @NonNull String message, long awaitTimeout) {
|
||||
int tries = 3;
|
||||
|
||||
while ((tries--) > 0) {
|
||||
SyncActionListener listener = new SyncActionListener(message);
|
||||
if (isNotInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SyncActionListener listener = new SyncActionListener(message, awaitTimeout);
|
||||
retryFunction.call(channel, listener);
|
||||
if (listener.successful()) {
|
||||
if (listener.successful() || listener.failureReason == SyncActionListener.FAILURE_TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
ThreadUtil.sleep(TimeUnit.SECONDS.toMillis(1));
|
||||
}
|
||||
}
|
||||
|
||||
private void retryAsync(@NonNull ManagerRetry retryFunction, @NonNull String message) {
|
||||
SignalExecutors.BOUNDED.execute(() -> retrySync(retryFunction, message, WifiDirect.NOT_SAFE_FOR_LONG_AWAIT_TIMEOUT));
|
||||
}
|
||||
|
||||
private synchronized boolean isInitialized() {
|
||||
return manager != null && channel != null;
|
||||
}
|
||||
|
@ -328,57 +354,41 @@ public final class WifiDirect {
|
|||
void call(@NonNull WifiP2pManager.Channel a, @NonNull WifiP2pManager.ActionListener b);
|
||||
}
|
||||
|
||||
private class WifiDirectCallbacks extends BroadcastReceiver implements WifiP2pManager.ChannelListener, WifiP2pManager.ConnectionInfoListener {
|
||||
private static class WifiDirectCallbacks extends BroadcastReceiver implements WifiP2pManager.ChannelListener {
|
||||
private WifiDirectConnectionListener connectionListener;
|
||||
|
||||
public WifiDirectCallbacks(@NonNull WifiDirectConnectionListener connectionListener) {
|
||||
this.connectionListener = connectionListener;
|
||||
}
|
||||
|
||||
public void clearConnectionListener() {
|
||||
connectionListener = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(@NonNull Context context, @NonNull Intent intent) {
|
||||
String action = intent.getAction();
|
||||
if (action != null) {
|
||||
switch (action) {
|
||||
case WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:
|
||||
WifiP2pDevice localDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
|
||||
if (localDevice != null && connectionListener != null) {
|
||||
connectionListener.onLocalDeviceChanged(localDevice);
|
||||
}
|
||||
break;
|
||||
case WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:
|
||||
if (isNotInitialized()) {
|
||||
Log.w(TAG, "WiFi P2P broadcast connection changed action without being initialized.");
|
||||
return;
|
||||
}
|
||||
if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
|
||||
WifiDirectConnectionListener listener = connectionListener;
|
||||
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
|
||||
if (networkInfo == null) {
|
||||
Log.w(TAG, "WiFi P2P broadcast connection changed action with null network info.");
|
||||
return;
|
||||
}
|
||||
|
||||
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
|
||||
|
||||
if (networkInfo == null) {
|
||||
Log.w(TAG, "WiFi P2P broadcast connection changed action with null network info.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (networkInfo.isConnected()) {
|
||||
Log.i(TAG, "Connected to P2P network, requesting connection information.");
|
||||
manager.requestConnectionInfo(channel, this);
|
||||
} else {
|
||||
Log.i(TAG, "Disconnected from P2P network");
|
||||
if (connectionListener != null) {
|
||||
connectionListener.onNetworkDisconnected();
|
||||
}
|
||||
}
|
||||
break;
|
||||
if (listener != null) {
|
||||
listener.onConnectionChanged(networkInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionInfoAvailable(@NonNull WifiP2pInfo info) {
|
||||
Log.i(TAG, "Connection information available. group_formed: " + info.groupFormed + " group_owner: " + info.isGroupOwner);
|
||||
if (connectionListener != null) {
|
||||
connectionListener.onNetworkConnected(info);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChannelDisconnected() {
|
||||
if (connectionListener != null) {
|
||||
connectionListener.onNetworkFailure();
|
||||
WifiDirectConnectionListener listener = connectionListener;
|
||||
if (listener != null) {
|
||||
listener.onNetworkFailure();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -388,13 +398,16 @@ public final class WifiDirect {
|
|||
*/
|
||||
private static class SyncActionListener extends LoggingActionListener {
|
||||
|
||||
private final CountDownLatch sync;
|
||||
private static final int FAILURE_TIMEOUT = -2;
|
||||
|
||||
private volatile int failureReason = -1;
|
||||
private final CountDownLatch sync;
|
||||
private final long awaitTimeout;
|
||||
private volatile int failureReason = -1;
|
||||
|
||||
public SyncActionListener(@NonNull String message) {
|
||||
public SyncActionListener(@NonNull String message, long awaitTimeout) {
|
||||
super(message);
|
||||
this.sync = new CountDownLatch(1);
|
||||
this.awaitTimeout = awaitTimeout;
|
||||
this.sync = new CountDownLatch(1);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -412,9 +425,14 @@ public final class WifiDirect {
|
|||
|
||||
public boolean successful() {
|
||||
try {
|
||||
sync.await();
|
||||
boolean completed = sync.await(awaitTimeout, TimeUnit.MILLISECONDS);
|
||||
if (!completed) {
|
||||
Log.i(TAG, "SyncListener [" + message + "] timed out after " + awaitTimeout + "ms");
|
||||
failureReason = FAILURE_TIMEOUT;
|
||||
return false;
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
throw new AssertionError(ie);
|
||||
Log.i(TAG, "SyncListener [" + message + "] interrupted");
|
||||
}
|
||||
return failureReason < 0;
|
||||
}
|
||||
|
@ -422,7 +440,7 @@ public final class WifiDirect {
|
|||
|
||||
private static class LoggingActionListener implements WifiP2pManager.ActionListener {
|
||||
|
||||
private final String message;
|
||||
protected final String message;
|
||||
|
||||
public static @NonNull LoggingActionListener message(@Nullable String message) {
|
||||
return new LoggingActionListener(message);
|
||||
|
@ -452,14 +470,13 @@ public final class WifiDirect {
|
|||
}
|
||||
|
||||
public interface WifiDirectConnectionListener {
|
||||
void onLocalDeviceChanged(@NonNull WifiP2pDevice localDevice);
|
||||
|
||||
void onServiceDiscovered(@NonNull WifiP2pDevice serviceDevice, @NonNull String extraInfo);
|
||||
|
||||
void onNetworkConnected(@NonNull WifiP2pInfo info);
|
||||
|
||||
void onNetworkDisconnected();
|
||||
|
||||
void onNetworkFailure();
|
||||
|
||||
void onConnectionChanged(@NonNull NetworkInfo networkInfo);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<item name="DeviceToDeviceTransferService_content_title" type="string" />
|
||||
|
||||
<item name="DeviceToDeviceTransferService_status_ready" type="string" />
|
||||
<item name="DeviceToDeviceTransferService_status_starting_up" type="string" />
|
||||
<item name="DeviceToDeviceTransferService_status_discovery" type="string" />
|
||||
<item name="DeviceToDeviceTransferService_status_network_connected" type="string" />
|
||||
<item name="DeviceToDeviceTransferService_status_verification_required" type="string" />
|
||||
<item name="DeviceToDeviceTransferService_status_service_connected" type="string" />
|
||||
</resources>
|
Ładowanie…
Reference in New Issue