Skip to content

Commit

Permalink
Allow registering as a linked device
Browse files Browse the repository at this point in the history
  • Loading branch information
SlugFiller committed Mar 26, 2023
1 parent dbf98eb commit 9b599bf
Show file tree
Hide file tree
Showing 64 changed files with 1,698 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.RefreshAttributesJob;
import org.thoughtcrime.securesms.jobs.RefreshKbsCredentialsJob;
import org.thoughtcrime.securesms.jobs.RefreshOwnProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.RetrieveRemoteAnnouncementsJob;
import org.thoughtcrime.securesms.jobs.StoryOnboardingDownloadJob;
Expand Down Expand Up @@ -557,9 +558,14 @@ private void initializeCircumvention() {
}

private void ensureProfileUploaded() {
if (SignalStore.account().isRegistered() && !SignalStore.registrationValues().hasUploadedProfile() && !Recipient.self().getProfileName().isEmpty()) {
Log.w(TAG, "User has a profile, but has not uploaded one. Uploading now.");
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
if (SignalStore.account().isRegistered()) {
if (SignalStore.registrationValues().needDownloadProfileOrAvatar()) {
Log.w(TAG, "User has linked a device, but has not yet downloaded the profile. Downloading now.");
ApplicationDependencies.getJobManager().add(new RefreshOwnProfileJob());
} else if (!SignalStore.registrationValues().hasUploadedProfile() && !Recipient.self().getProfileName().isEmpty()) {
Log.w(TAG, "User has a profile, but has not uploaded one. Uploading now.");
ApplicationDependencies.getJobManager().add(new ProfileUploadJob());
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,20 @@
import androidx.loader.app.LoaderManager;
import androidx.loader.content.Loader;

import com.annimon.stream.Stream;

import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.floatingactionbutton.FloatingActionButton;

import org.signal.core.util.logging.Log;
import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.task.ProgressDialogAsyncTask;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;

import java.io.IOException;
import java.util.List;
Expand All @@ -44,7 +48,6 @@ public class DeviceListFragment extends ListFragment

private SignalServiceAccountManager accountManager;
private Locale locale;
private View empty;
private View progressContainer;
private FloatingActionButton addDeviceButton;
private Button.OnClickListener addDeviceButtonListener;
Expand All @@ -65,10 +68,13 @@ public void onAttach(@NonNull Context context) {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
View view = inflater.inflate(R.layout.device_list_fragment, container, false);

this.empty = view.findViewById(R.id.empty);
this.progressContainer = view.findViewById(R.id.progress_container);
this.addDeviceButton = view.findViewById(R.id.add_device);
this.addDeviceButton.setOnClickListener(this);
if (SignalStore.account().isPrimaryDevice()) {
this.addDeviceButton.setOnClickListener(this);
} else {
this.addDeviceButton.setVisibility(View.GONE);
}

return view;
}
Expand All @@ -86,7 +92,6 @@ public void setAddDeviceButtonListener(Button.OnClickListener listener) {

@Override
public @NonNull Loader<List<Device>> onCreateLoader(int id, Bundle args) {
empty.setVisibility(View.GONE);
progressContainer.setVisibility(View.VISIBLE);

return new DeviceListLoader(getActivity(), accountManager);
Expand All @@ -101,14 +106,9 @@ public void onLoadFinished(@NonNull Loader<List<Device>> loader, List<Device> da
return;
}

setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data, locale));
setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data, locale, SignalStore.account().getDeviceId()));

if (data.isEmpty()) {
empty.setVisibility(View.VISIBLE);
TextSecurePreferences.setMultiDevice(getActivity(), false);
} else {
empty.setVisibility(View.GONE);
}
TextSecurePreferences.setMultiDevice(getActivity(), SignalStore.account().isLinkedDevice() || Stream.of(data).noneMatch(d -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID));
}

@Override
Expand All @@ -120,6 +120,10 @@ public void onLoaderReset(@NonNull Loader<List<Device>> loader) {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
final String deviceName = ((DeviceListItem)view).getDeviceName();
final long deviceId = ((DeviceListItem)view).getDeviceId();
if (deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID || deviceId == SignalStore.account().getDeviceId()) {
// Sanity check
return;
}

AlertDialog.Builder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(getString(R.string.DeviceListActivity_unlink_s, deviceName));
Expand Down Expand Up @@ -179,11 +183,13 @@ private static class DeviceListAdapter extends ArrayAdapter<Device> {

private final int resource;
private final Locale locale;
private final long thisDeviceId;

public DeviceListAdapter(Context context, int resource, List<Device> objects, Locale locale) {
public DeviceListAdapter(Context context, int resource, List<Device> objects, Locale locale, long thisDeviceId) {
super(context, resource, objects);
this.resource = resource;
this.locale = locale;
this.thisDeviceId = thisDeviceId;
}

@Override
Expand All @@ -192,9 +198,15 @@ public DeviceListAdapter(Context context, int resource, List<Device> objects, Lo
convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
}

((DeviceListItem)convertView).set(getItem(position), locale);
((DeviceListItem)convertView).set(getItem(position), locale, thisDeviceId);

return convertView;
}

@Override
public boolean isEnabled(int position) {
long deviceId = getItem(position).getId();
return deviceId != SignalServiceAddress.DEFAULT_DEVICE_ID && deviceId != thisDeviceId;
}
}
}
17 changes: 13 additions & 4 deletions app/src/main/java/org/thoughtcrime/securesms/DeviceListItem.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.view.View;

import org.thoughtcrime.securesms.devicelist.Device;
import org.thoughtcrime.securesms.util.DateUtils;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;

import java.util.Locale;

public class DeviceListItem extends LinearLayout {

private long deviceId;
private TextView name;
private View primary;
private View thisDevice;
private TextView created;
private TextView lastActive;

Expand All @@ -30,15 +34,19 @@ public DeviceListItem(Context context, AttributeSet attrs) {
public void onFinishInflate() {
super.onFinishInflate();
this.name = (TextView) findViewById(R.id.name);
this.primary = findViewById(R.id.primary);
this.thisDevice = findViewById(R.id.this_device);
this.created = (TextView) findViewById(R.id.created);
this.lastActive = (TextView) findViewById(R.id.active);
}

public void set(Device deviceInfo, Locale locale) {
if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(R.string.DeviceListItem_unnamed_device);
public void set(Device deviceInfo, Locale locale, long thisDeviceId) {
if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(getContext().getString(R.string.DeviceListItem_device_d, deviceInfo.getId()));
else this.name.setText(deviceInfo.getName());

this.created.setText(getContext().getString(R.string.DeviceListItem_linked_s,
this.deviceId = deviceInfo.getId();

this.created.setText(getContext().getString(this.deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID ? R.string.DeviceListItem_registered_s : R.string.DeviceListItem_linked_s,
DateUtils.getDayPrecisionTimeSpanString(getContext(),
locale,
deviceInfo.getCreated())));
Expand All @@ -48,7 +56,8 @@ public void set(Device deviceInfo, Locale locale) {
locale,
deviceInfo.getLastSeen())));

this.deviceId = deviceInfo.getId();
this.primary.setVisibility(this.deviceId == SignalServiceAddress.DEFAULT_DEVICE_ID ? View.VISIBLE : View.GONE);
this.thisDevice.setVisibility(this.deviceId == thisDeviceId ? View.VISIBLE : View.GONE);
}

public long getDeviceId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class AccountSettingsViewModel : ViewModel() {

private fun getCurrentState(): AccountSettingsState {
return AccountSettingsState(
hasPin = SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut(),
hasPin = (SignalStore.kbsValues().hasPin() && !SignalStore.kbsValues().hasOptedOut()) || SignalStore.storageService().hasStorageKeyFromPrimary(),
pinRemindersEnabled = SignalStore.pinValues().arePinRemindersEnabled(),
registrationLockEnabled = SignalStore.kbsValues().isV2RegistrationLockEnabled
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import org.thoughtcrime.securesms.components.settings.DSLConfiguration
import org.thoughtcrime.securesms.components.settings.DSLSettingsFragment
import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.util.adapter.mapping.MappingAdapter
import org.thoughtcrime.securesms.util.navigation.safeNavigate

Expand All @@ -33,11 +34,18 @@ class ChatsSettingsFragment : DSLSettingsFragment(R.string.preferences_chats__ch
title = DSLSettingsText.from(R.string.preferences__generate_link_previews),
summary = DSLSettingsText.from(R.string.preferences__retrieve_link_previews_from_websites_for_messages),
isChecked = state.generateLinkPreviews,
isEnabled = SignalStore.account().isPrimaryDevice,
onClick = {
viewModel.setGenerateLinkPreviewsEnabled(!state.generateLinkPreviews)
}
)

if (SignalStore.account().isLinkedDevice) {
textPref(
summary = DSLSettingsText.from(R.string.preferences__primary_only)
)
}

switchPref(
title = DSLSettingsText.from(R.string.preferences__pref_use_address_book_photos),
summary = DSLSettingsText.from(R.string.preferences__display_contact_photos_from_your_address_book_if_available),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ class ChatsSettingsViewModel @JvmOverloads constructor(
}

fun setGenerateLinkPreviewsEnabled(enabled: Boolean) {
if (SignalStore.account().isLinkedDevice) {
return
}
store.update { it.copy(generateLinkPreviews = enabled) }
SignalStore.settings().isLinkPreviewsEnabled = enabled
repository.syncLinkPreviewsState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.thoughtcrime.securesms.components.settings.DSLSettingsText
import org.thoughtcrime.securesms.components.settings.PreferenceModel
import org.thoughtcrime.securesms.components.settings.PreferenceViewHolder
import org.thoughtcrime.securesms.components.settings.configure
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.preferences.widgets.PassphraseLockTriggerPreference
import org.thoughtcrime.securesms.util.CommunicationActions
import org.thoughtcrime.securesms.util.ConversationUtil
Expand Down Expand Up @@ -189,6 +190,7 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
title = DSLSettingsText.from(R.string.preferences__read_receipts),
summary = DSLSettingsText.from(R.string.preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts),
isChecked = state.readReceipts,
isEnabled = SignalStore.account().isPrimaryDevice,
onClick = {
viewModel.setReadReceiptsEnabled(!state.readReceipts)
}
Expand All @@ -198,11 +200,18 @@ class PrivacySettingsFragment : DSLSettingsFragment(R.string.preferences__privac
title = DSLSettingsText.from(R.string.preferences__typing_indicators),
summary = DSLSettingsText.from(R.string.preferences__if_typing_indicators_are_disabled_you_wont_be_able_to_see_typing_indicators),
isChecked = state.typingIndicators,
isEnabled = SignalStore.account().isPrimaryDevice,
onClick = {
viewModel.setTypingIndicatorsEnabled(!state.typingIndicators)
}
)

if (SignalStore.account().isLinkedDevice) {
textPref(
summary = DSLSettingsText.from(R.string.preferences__primary_only)
)
}

dividerPref()

sectionHeaderPref(R.string.PrivacySettingsFragment__disappearing_messages)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,18 @@ class PrivacySettingsViewModel(
}

fun setReadReceiptsEnabled(enabled: Boolean) {
if (SignalStore.account().isLinkedDevice) {
return
}
sharedPreferences.edit().putBoolean(TextSecurePreferences.READ_RECEIPTS_PREF, enabled).apply()
repository.syncReadReceiptState()
refresh()
}

fun setTypingIndicatorsEnabled(enabled: Boolean) {
if (SignalStore.account().isLinkedDevice) {
return
}
sharedPreferences.edit().putBoolean(TextSecurePreferences.TYPING_INDICATORS, enabled).apply()
repository.syncTypingIndicatorsState()
refresh()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -182,11 +182,18 @@ class AdvancedPrivacySettingsFragment : DSLSettingsFragment(R.string.preferences
.append(statusIcon)
),
summary = DSLSettingsText.from(R.string.AdvancedPrivacySettingsFragment__show_an_icon),
isChecked = state.showSealedSenderStatusIcon
isChecked = state.showSealedSenderStatusIcon,
isEnabled = SignalStore.account().isPrimaryDevice
) {
viewModel.setShowStatusIconForSealedSender(!state.showSealedSenderStatusIcon)
}

if (SignalStore.account().isLinkedDevice) {
textPref(
summary = DSLSettingsText.from(R.string.preferences__primary_only)
)
}

switchPref(
title = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone),
summary = DSLSettingsText.from(R.string.preferences_communication__sealed_sender_allow_from_anyone_description),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ class AdvancedPrivacySettingsViewModel(
}

fun setShowStatusIconForSealedSender(enabled: Boolean) {
if (SignalStore.account().isLinkedDevice) {
return
}
sharedPreferences.edit().putBoolean(TextSecurePreferences.SHOW_UNIDENTIFIED_DELIVERY_INDICATORS, enabled).apply()
repository.syncShowSealedSenderIconState()
refresh()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ public SignalServiceEnvelope get(long id) throws NoSuchMessageException {
cursor.getString(cursor.getColumnIndexOrThrow(SERVER_GUID)),
"",
true,
"",
false,
null);
}
Expand Down Expand Up @@ -176,6 +177,7 @@ public SignalServiceEnvelope getNext() {
serverGuid,
"",
true,
"",
false,
null);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import org.thoughtcrime.securesms.util.Base64;
import org.whispersystems.signalservice.api.SignalServiceAccountManager;
import org.whispersystems.signalservice.api.messages.multidevice.DeviceInfo;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;

import java.io.IOException;
import java.security.GeneralSecurityException;
Expand Down Expand Up @@ -53,7 +52,6 @@ public DeviceListLoader(Context context, SignalServiceAccountManager accountMana
public List<Device> loadInBackground() {
try {
List<Device> devices = Stream.of(accountManager.getDevices())
.filter(d -> d.getId() != SignalServiceAddress.DEFAULT_DEVICE_ID)
.map(this::mapToDevice)
.toList();

Expand Down
Loading

0 comments on commit 9b599bf

Please sign in to comment.