diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 538f960447..3045165bbf 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -192,6 +192,10 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/device_list_item_view.xml b/res/layout/device_list_item_view.xml
new file mode 100644
index 0000000000..d62de7fbc6
--- /dev/null
+++ b/res/layout/device_list_item_view.xml
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 9ca060e1fc..1c3d9b7639 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -122,6 +122,7 @@
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 8aa13a829b..eada056c18 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -166,6 +166,20 @@
Now
%d min
+
+ Disconnect \'%s\'?
+ By disconnecting this device, it will no longer be able to send or receive messages.
+ Network connection failed...
+ Try again
+ Disconnecting device..
+ Disconnecting device
+ Network failed!
+
+
+ Unnamed device
+ Created %s
+ Last active %s
+
Share with
@@ -283,6 +297,7 @@
No device found.
Network error.
Invalid QR code.
+ Sorry, you have too many devices registered already, try removing some...
Enter passphrase
@@ -532,6 +547,9 @@
Loading countries...
Search
+
+ No devices paired...
+
Could not grab logs from your device. You can still use ADB to get debug logs instead.
Thanks for your help!
@@ -715,6 +733,7 @@
All images
All images with %1$s
Message Details
+ Manage paired devices
Import / export
@@ -959,6 +978,7 @@
Transport icon
+
diff --git a/res/values/themes.xml b/res/values/themes.xml
index 62171cc14c..6ac92c8023 100644
--- a/res/values/themes.xml
+++ b/res/values/themes.xml
@@ -173,6 +173,7 @@
- @drawable/ic_app_protection_black
- @drawable/ic_brightness_6_black
- @drawable/ic_delete_black
+ - @drawable/ic_devices_black_48dp
- @drawable/ic_advanced_black
- @style/BetterPickersDialogFragment.Light
@@ -297,6 +298,7 @@
- @drawable/ic_app_protection_gray
- @drawable/ic_brightness_6_gray
- @drawable/ic_delete_gray
+ - @drawable/ic_devices_grey600_48dp
- @drawable/ic_advanced_gray
- @style/BetterPickersDialogFragment
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
index ff44032dcb..bea5750b3d 100644
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -21,6 +21,12 @@
android:title="@string/preferences__delete_old_messages"
android:icon="?pref_ic_storage"/>
+
+
+
+
diff --git a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
index cef095caec..733ee79da8 100644
--- a/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
+++ b/src/org/thoughtcrime/securesms/ApplicationPreferencesActivity.java
@@ -55,6 +55,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
private static final String PREFERENCE_CATEGORY_APP_PROTECTION = "preference_category_app_protection";
private static final String PREFERENCE_CATEGORY_APPEARANCE = "preference_category_appearance";
private static final String PREFERENCE_CATEGORY_STORAGE = "preference_category_storage";
+ private static final String PREFERENCE_CATEGORY_DEVICES = "preference_category_devices";
private static final String PREFERENCE_CATEGORY_ADVANCED = "preference_category_advanced";
private final DynamicTheme dynamicTheme = new DynamicTheme();
@@ -131,6 +132,8 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
.setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_APPEARANCE));
this.findPreference(PREFERENCE_CATEGORY_STORAGE)
.setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_STORAGE));
+ this.findPreference(PREFERENCE_CATEGORY_DEVICES)
+ .setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_DEVICES));
this.findPreference(PREFERENCE_CATEGORY_ADVANCED)
.setOnPreferenceClickListener(new CategoryClickListener(masterSecret, PREFERENCE_CATEGORY_ADVANCED));
}
@@ -166,7 +169,7 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
@Override
public boolean onPreferenceClick(Preference preference) {
- Fragment fragment;
+ Fragment fragment = null;
switch (category) {
case PREFERENCE_CATEGORY_SMS_MMS:
@@ -184,6 +187,10 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
case PREFERENCE_CATEGORY_STORAGE:
fragment = new StoragePreferenceFragment();
break;
+ case PREFERENCE_CATEGORY_DEVICES:
+ Intent intent = new Intent(getActivity(), DeviceListActivity.class);
+ startActivity(intent);
+ break;
case PREFERENCE_CATEGORY_ADVANCED:
fragment = new AdvancedPreferenceFragment();
break;
@@ -191,15 +198,17 @@ public class ApplicationPreferencesActivity extends PassphraseRequiredActionBarA
throw new AssertionError();
}
- Bundle args = new Bundle();
- args.putParcelable("master_secret", masterSecret);
- fragment.setArguments(args);
+ if (fragment != null) {
+ Bundle args = new Bundle();
+ args.putParcelable("master_secret", masterSecret);
+ fragment.setArguments(args);
- FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
- FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
- fragmentTransaction.replace(android.R.id.content, fragment);
- fragmentTransaction.addToBackStack(null);
- fragmentTransaction.commit();
+ FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
+ fragmentTransaction.replace(android.R.id.content, fragment);
+ fragmentTransaction.addToBackStack(null);
+ fragmentTransaction.commit();
+ }
return true;
}
diff --git a/src/org/thoughtcrime/securesms/DeviceListActivity.java b/src/org/thoughtcrime/securesms/DeviceListActivity.java
new file mode 100644
index 0000000000..895a4b0c53
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DeviceListActivity.java
@@ -0,0 +1,211 @@
+package org.thoughtcrime.securesms;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v4.app.ListFragment;
+import android.support.v4.app.LoaderManager;
+import android.support.v4.content.Loader;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.afollestad.materialdialogs.AlertDialogWrapper;
+
+import org.thoughtcrime.securesms.crypto.MasterSecret;
+import org.thoughtcrime.securesms.database.loaders.DeviceListLoader;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.util.DynamicLanguage;
+import org.thoughtcrime.securesms.util.DynamicTheme;
+import org.thoughtcrime.securesms.util.ProgressDialogAsyncTask;
+import org.whispersystems.textsecure.api.TextSecureAccountManager;
+import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
+
+import java.io.IOException;
+import java.util.List;
+
+import javax.inject.Inject;
+
+public class DeviceListActivity extends PassphraseRequiredActionBarActivity {
+
+
+ private final DynamicTheme dynamicTheme = new DynamicTheme();
+ private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
+
+ @Override
+ public void onPreCreate() {
+ dynamicTheme.onCreate(this);
+ dynamicLanguage.onCreate(this);
+ }
+
+
+ @Override
+ public void onCreate(Bundle bundle, @NonNull MasterSecret masterSecret) {
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ initFragment(android.R.id.content, new DeviceListFragment(), masterSecret);
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ dynamicTheme.onResume(this);
+ dynamicLanguage.onResume(this);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home: finish(); return true;
+ }
+
+ return false;
+ }
+
+ public static class DeviceListFragment extends ListFragment
+ implements LoaderManager.LoaderCallbacks>, ListView.OnItemClickListener, InjectableType
+ {
+
+ private static final String TAG = DeviceListFragment.class.getSimpleName();
+
+ @Inject TextSecureAccountManager accountManager;
+
+ private View empty;
+ private View progressContainer;
+
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ ApplicationContext.getInstance(activity).injectDependencies(this);
+ }
+
+ @Override
+ 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);
+
+ return view;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle bundle) {
+ super.onActivityCreated(bundle);
+ getLoaderManager().initLoader(0, null, this).forceLoad();
+ getListView().setOnItemClickListener(this);
+ }
+
+ @Override
+ public Loader> onCreateLoader(int id, Bundle args) {
+ empty.setVisibility(View.GONE);
+ progressContainer.setVisibility(View.VISIBLE);
+
+ return new DeviceListLoader(getActivity(), accountManager);
+ }
+
+ @Override
+ public void onLoadFinished(Loader> loader, List data) {
+ progressContainer.setVisibility(View.GONE);
+
+ if (data == null) {
+ handleLoaderFailed();
+ return;
+ }
+
+ setListAdapter(new DeviceListAdapter(getActivity(), R.layout.device_list_item_view, data));
+
+ if (data.isEmpty()) empty.setVisibility(View.VISIBLE);
+ else empty.setVisibility(View.GONE);
+ }
+
+ @Override
+ public void onLoaderReset(Loader> loader) {
+ setListAdapter(null);
+ }
+
+ @Override
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ final String deviceName = ((DeviceListItem)view).getDeviceName();
+ final long deviceId = ((DeviceListItem)view).getDeviceId();
+
+ AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
+ builder.setTitle(getActivity().getString(R.string.DeviceListActivity_disconnect_s, deviceName));
+ builder.setMessage(R.string.DeviceListActivity_by_disconnecting_this_device_it_will_no_longer_be_able_to_send_or_receive);
+ builder.setNegativeButton(android.R.string.cancel, null);
+ builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ handleDisconnectDevice(deviceId);
+ }
+ });
+ builder.show();
+ }
+
+ private void handleLoaderFailed() {
+ AlertDialogWrapper.Builder builder = new AlertDialogWrapper.Builder(getActivity());
+ builder.setMessage(R.string.DeviceListActivity_network_connection_failed);
+ builder.setPositiveButton(R.string.DeviceListActivity_try_again,
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ getLoaderManager().initLoader(0, null, DeviceListFragment.this);
+ }
+ });
+ builder.show();
+ }
+
+ private void handleDisconnectDevice(final long deviceId) {
+ new ProgressDialogAsyncTask(getActivity(),
+ R.string.DeviceListActivity_disconnecting_device,
+ R.string.DeviceListActivity_disconnecting_device_no_ellipse)
+ {
+ @Override
+ protected Void doInBackground(Void... params) {
+ try {
+ accountManager.removeDevice(deviceId);
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ Toast.makeText(getActivity(), R.string.DeviceListActivity_network_failed, Toast.LENGTH_LONG).show();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void result) {
+ super.onPostExecute(result);
+ getLoaderManager().restartLoader(0, null, DeviceListFragment.this);
+ }
+ }.execute();
+ }
+
+ private static class DeviceListAdapter extends ArrayAdapter {
+
+ private final int resource;
+
+ public DeviceListAdapter(Context context, int resource, List objects) {
+ super(context, resource, objects);
+ this.resource = resource;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = ((Activity)getContext()).getLayoutInflater().inflate(resource, parent, false);
+ }
+
+ ((DeviceListItem)convertView).set(getItem(position));
+
+ return convertView;
+ }
+ }
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceListItem.java b/src/org/thoughtcrime/securesms/DeviceListItem.java
new file mode 100644
index 0000000000..39b32f5bac
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/DeviceListItem.java
@@ -0,0 +1,62 @@
+package org.thoughtcrime.securesms;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import org.thoughtcrime.securesms.util.DateUtils;
+import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
+
+import java.util.Locale;
+
+public class DeviceListItem extends LinearLayout {
+
+ private long deviceId;
+ private TextView name;
+ private TextView created;
+ private TextView lastActive;
+
+ public DeviceListItem(Context context) {
+ super(context);
+ }
+
+ public DeviceListItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ public void onFinishInflate() {
+ super.onFinishInflate();
+ this.name = (TextView) findViewById(R.id.name);
+ this.created = (TextView) findViewById(R.id.created);
+ this.lastActive = (TextView) findViewById(R.id.active);
+ }
+
+ public void set(DeviceInfo deviceInfo) {
+ if (TextUtils.isEmpty(deviceInfo.getName())) this.name.setText(R.string.DeviceListItem_unnamed_device);
+ else this.name.setText(deviceInfo.getName());
+
+ this.created.setText(getContext().getString(R.string.DeviceListItem_created_s,
+ DateUtils.getExtendedRelativeTimeSpanString(getContext(),
+ Locale.getDefault(),
+ deviceInfo.getCreated())));
+
+ this.lastActive.setText(getContext().getString(R.string.DeviceListItem_last_active_s,
+ DateUtils.getExtendedRelativeTimeSpanString(getContext(),
+ Locale.getDefault(),
+ deviceInfo.getLastSeen())));
+
+ this.deviceId = deviceInfo.getId();
+ }
+
+ public long getDeviceId() {
+ return deviceId;
+ }
+
+ public String getDeviceName() {
+ return name.getText().toString();
+ }
+
+}
diff --git a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
index 2749ad5421..4f6149a3f5 100644
--- a/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
+++ b/src/org/thoughtcrime/securesms/DeviceProvisioningActivity.java
@@ -6,13 +6,11 @@ import android.content.DialogInterface.OnDismissListener;
import android.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.view.Window;
import android.widget.Toast;
-import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.afollestad.materialdialogs.MaterialDialog.Builder;
import com.afollestad.materialdialogs.MaterialDialog.ButtonCallback;
@@ -28,6 +26,7 @@ import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.textsecure.api.TextSecureAccountManager;
import org.whispersystems.textsecure.api.push.exceptions.NotFoundException;
+import org.whispersystems.textsecure.internal.push.DeviceLimitExceededException;
import java.io.IOException;
@@ -94,10 +93,11 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
R.string.DeviceProvisioningActivity_content_progress_title,
R.string.DeviceProvisioningActivity_content_progress_content)
{
- private static final int SUCCESS = 0;
- private static final int NO_DEVICE = 1;
- private static final int NETWORK_ERROR = 2;
- private static final int KEY_ERROR = 3;
+ private static final int SUCCESS = 0;
+ private static final int NO_DEVICE = 1;
+ private static final int NETWORK_ERROR = 2;
+ private static final int KEY_ERROR = 3;
+ private static final int LIMIT_EXCEEDED = 4;
@Override
protected Integer doInBackground(Void... params) {
@@ -113,9 +113,13 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
accountManager.addDevice(ephemeralId, publicKey, identityKeyPair, verificationCode);
return SUCCESS;
+
} catch (NotFoundException e) {
Log.w(TAG, e);
return NO_DEVICE;
+ } catch (DeviceLimitExceededException e) {
+ Log.w(TAG, e);
+ return LIMIT_EXCEEDED;
} catch (IOException e) {
Log.w(TAG, e);
return NETWORK_ERROR;
@@ -144,6 +148,9 @@ public class DeviceProvisioningActivity extends PassphraseRequiredActionBarActiv
case KEY_ERROR:
Toast.makeText(context, R.string.DeviceProvisioningActivity_content_progress_key_error, Toast.LENGTH_LONG).show();
break;
+ case LIMIT_EXCEEDED:
+ Toast.makeText(context, R.string.DeviceProvisioningActivity_sorry_you_have_too_many_devices_registered_already, Toast.LENGTH_LONG).show();
+ break;
}
dialog.dismiss();
}
diff --git a/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
new file mode 100644
index 0000000000..dfdf07ce39
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/database/loaders/DeviceListLoader.java
@@ -0,0 +1,58 @@
+package org.thoughtcrime.securesms.database.loaders;
+
+import android.content.Context;
+import android.support.v4.content.AsyncTaskLoader;
+import android.util.Log;
+
+import org.whispersystems.textsecure.api.TextSecureAccountManager;
+import org.whispersystems.textsecure.api.messages.multidevice.DeviceInfo;
+import org.whispersystems.textsecure.api.push.TextSecureAddress;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+public class DeviceListLoader extends AsyncTaskLoader> {
+
+ private static final String TAG = DeviceListLoader.class.getSimpleName();
+
+ private final TextSecureAccountManager accountManager;
+
+ public DeviceListLoader(Context context, TextSecureAccountManager accountManager) {
+ super(context);
+ this.accountManager = accountManager;
+ }
+
+ @Override
+ public List loadInBackground() {
+ try {
+ List devices = accountManager.getDevices();
+ Iterator iterator = devices.iterator();
+
+ while (iterator.hasNext()) {
+ if ((iterator.next().getId() == TextSecureAddress.DEFAULT_DEVICE_ID)) {
+ iterator.remove();
+ }
+ }
+
+ Collections.sort(devices, new DeviceInfoComparator());
+
+ return devices;
+ } catch (IOException e) {
+ Log.w(TAG, e);
+ return null;
+ }
+ }
+
+ private static class DeviceInfoComparator implements Comparator {
+
+ @Override
+ public int compare(DeviceInfo lhs, DeviceInfo rhs) {
+ if (lhs.getCreated() < rhs.getCreated()) return -1;
+ else if (lhs.getCreated() != rhs.getCreated()) return 1;
+ else return 0;
+ }
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
index c9a4e77d7a..14e014a27a 100644
--- a/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/TextSecureCommunicationModule.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.dependencies;
import android.content.Context;
import org.thoughtcrime.securesms.BuildConfig;
+import org.thoughtcrime.securesms.DeviceListActivity;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.crypto.storage.TextSecureAxolotlStore;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
@@ -12,7 +13,6 @@ import org.thoughtcrime.securesms.jobs.DeliveryReceiptJob;
import org.thoughtcrime.securesms.jobs.MultiDeviceContactUpdateJob;
import org.thoughtcrime.securesms.jobs.PushGroupSendJob;
import org.thoughtcrime.securesms.jobs.PushMediaSendJob;
-import org.thoughtcrime.securesms.jobs.PushContentReceiveJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.jobs.PushTextSendJob;
import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob;
@@ -39,7 +39,8 @@ import dagger.Provides;
RefreshPreKeysJob.class,
MessageRetrievalService.class,
PushNotificationReceiveJob.class,
- MultiDeviceContactUpdateJob.class})
+ MultiDeviceContactUpdateJob.class,
+ DeviceListActivity.DeviceListFragment.class})
public class TextSecureCommunicationModule {
private final Context context;
diff --git a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
index a3f1c88ac9..2d2d2dd25f 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushDecryptJob.java
@@ -50,6 +50,7 @@ import org.whispersystems.textsecure.api.messages.TextSecureContent;
import org.whispersystems.textsecure.api.messages.TextSecureEnvelope;
import org.whispersystems.textsecure.api.messages.TextSecureGroup;
import org.whispersystems.textsecure.api.messages.TextSecureDataMessage;
+import org.whispersystems.textsecure.api.messages.multidevice.RequestMessage;
import org.whispersystems.textsecure.api.messages.multidevice.SentTranscriptMessage;
import org.whispersystems.textsecure.api.messages.multidevice.TextSecureSyncMessage;
import org.whispersystems.textsecure.api.push.TextSecureAddress;
@@ -126,7 +127,8 @@ public class PushDecryptJob extends MasterSecretJob {
} else if (content.getSyncMessage().isPresent()) {
TextSecureSyncMessage syncMessage = content.getSyncMessage().get();
- if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, syncMessage.getSent().get(), smsMessageId);
+ if (syncMessage.getSent().isPresent()) handleSynchronizeSentMessage(masterSecret, syncMessage.getSent().get(), smsMessageId);
+ else if (syncMessage.getRequest().isPresent()) handleSynchronizeRequestMessage(masterSecret, syncMessage.getRequest().get());
}
if (envelope.isPreKeyWhisperMessage()) {
@@ -198,6 +200,14 @@ public class PushDecryptJob extends MasterSecretJob {
}
}
+ private void handleSynchronizeRequestMessage(MasterSecret masterSecret, RequestMessage message) {
+ if (message.isContactsRequest()) {
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new MultiDeviceContactUpdateJob(getContext()));
+ }
+ }
+
private void handleMediaMessage(MasterSecret masterSecret, TextSecureEnvelope envelope,
TextSecureDataMessage message, Optional smsMessageId)
throws MmsException