Compare commits

...

27 Commits

Author SHA1 Message Date
Moxie Marlinspike
fb523985ed Bump version to 2.5.1
// FREEBIE
2015-02-09 13:45:41 -08:00
Moxie Marlinspike
7ffe6f053c Merge pull request #2435 from mcginty/dark-actionmode-popup
Fix white-on-white ActionMode popup in dark theme
2015-02-09 12:07:54 -08:00
Jake McGinty
ea77191b70 Fix white-on-white ActionMode popup
// FREEBIE
2015-02-09 12:00:52 -08:00
Moxie Marlinspike
e0b882d4d2 Merge pull request #2434 from mcginty/plurals
move plurals to strings.xml
2015-02-09 11:24:58 -08:00
Jake McGinty
10a74d6d08 move plurals to strings.xml
Resolves #2421
// FREEBIE
2015-02-09 11:19:30 -08:00
Jake McGinty
f092e85b62 Workaround for LGE-related NPEs
See: https://code.google.com/p/android/issues/detail?id=78154

Fixes #2424
Closes #2428

// FREEBIE
2015-02-09 09:15:44 -08:00
Moxie Marlinspike
8aa0f15740 Bump version to 2.5.0
// FREEBIE
2015-02-06 16:42:04 -08:00
Moxie Marlinspike
18d4d4de24 Update language translations
// FREEBIE
2015-02-06 16:21:16 -08:00
Moxie Marlinspike
b3c42dee7e Merge pull request #2416 from mcginty/better-relative-dates
more succinct relative dates
2015-02-06 16:20:23 -08:00
Moxie Marlinspike
5836f35291 Merge pull request #2400 from mcginty/mms-bad-base64-fix
Fix NPE when encrypted MMS has bad base64 encoding
2015-02-06 16:19:20 -08:00
Jake McGinty
dbd8a4083c Fix NPE when encrypted MMS has bad base64 encoding
// FREEBIE
2015-02-06 13:01:36 -08:00
Jake McGinty
0fd52ad1fe more succinct relative dates
// FREEBIE
2015-02-06 13:00:21 -08:00
Moxie Marlinspike
8031c788d9 Merge pull request #2415 from mcginty/empty-group-updates
fix empty group update messages
2015-02-05 19:49:09 -08:00
Moxie Marlinspike
10a29db93d Merge pull request #2414 from mcginty/unread-readability
increase unread visibility in conv list
2015-02-05 19:46:31 -08:00
Jake McGinty
8f3f2e6921 fix empty group update messages
// FREEBIE
2015-02-05 19:38:08 -08:00
Jake McGinty
65c262acef increase unread visibility in conv list
// FREEBIE
2015-02-05 18:41:54 -08:00
AsamK
d586893402 Also crop contact photos in ShareList
// FREEBIE
2015-02-05 14:14:48 -08:00
Jake McGinty
ba62e018db Refine icon
1) fix glow
2) improve shadows
3) fix density-inconsistent assets
2015-01-28 12:52:51 -10:00
Moxie Marlinspike
57c17e705f Fix provisioning dialog strings.
// FREEBIE

Closes #2368
2015-01-24 10:16:33 -08:00
Jake McGinty
2e253fb6a8 provisioning dialog rework
// FREEBIE
2015-01-24 10:15:47 -08:00
Moxie Marlinspike
0c32001fe4 Support for multi-device provisioning flow.
// FREEBIE
2015-01-24 10:15:47 -08:00
Jake McGinty
48f6c2c526 Fix 'indicator tab' coloring
Was previously white-on-near-white
// FREEBIE
2015-01-23 10:59:22 -10:00
Jake McGinty
39fd1e8f46 fix incoming lock asset
// FREEBIE
2015-01-21 20:06:05 -10:00
Jake McGinty
810abe0275 material actionbar and fab
// FREEBIE
2015-01-21 19:01:23 -10:00
Jake McGinty
62816ee51a rounded ImageView instead of Bitmap, crop-select
// FREEBIE
2015-01-21 17:39:54 -10:00
Calvin Hu
a0599c1639 add theme to import/export activity
closes #2359
2015-01-16 10:41:01 -10:00
Jake McGinty
8a4a1b385d libpastelog 1.0.4
// FREEBIE
2015-01-15 13:06:42 -10:00
424 changed files with 5208 additions and 45043 deletions

View File

@@ -2,8 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="org.thoughtcrime.securesms"
android:versionCode="90"
android:versionName="2.4.2">
android:versionCode="92"
android:versionName="2.5.1">
<permission android:name="org.thoughtcrime.securesms.ACCESS_SECRETS"
android:label="Access to TextSecure Secrets"
@@ -94,6 +94,17 @@
android:theme="@style/TextSecure.Light.Dialog"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name=".DeviceProvisioningActivity"
android:theme="@style/TextSecure.DialogActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tsdevice"/>
</intent-filter>
</activity>
<activity android:name=".MmsPreferencesActivity"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
@@ -212,6 +223,8 @@
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" />
<service android:enabled="true" android:name=".service.ApplicationMigrationService"/>
<service android:enabled="true" android:name=".service.KeyCachingService"/>
<service android:enabled="true" android:name=".service.RegistrationService"/>

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 93 KiB

BIN
artwork/logo-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

Binary file not shown.

View File

@@ -5,13 +5,12 @@ buildscript {
}
}
dependencies {
classpath 'com.android.tools.build:gradle:1.0.0'
classpath 'com.android.tools.build:gradle:1.0.1'
classpath files('libs/gradle-witness.jar')
}
}
apply plugin: 'com.android.application'
apply from: 'strip_play_services.gradle'
apply plugin: 'witness'
repositories {
@@ -30,30 +29,34 @@ repositories {
maven {
url "https://raw.github.com/whispersystems/maven/master/shortcutbadger/releases/"
}
jcenter()
}
dependencies {
compile 'me.leolin:ShortcutBadger:1.0.2-WS2'
compile 'se.emilsjolander:stickylistheaders:2.2.0'
compile 'com.google.android.gms:play-services:6.1.71'
compile 'com.google.android.gms:play-services-base:6.5.87'
compile 'com.astuetz:pagerslidingtabstrip:1.0.1'
compile 'org.w3c:smil:1.0.0'
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
compile 'com.github.chrisbanes.photoview:library:1.2.3'
compile 'com.android.support:appcompat-v7:20.0.0'
compile 'com.madgag.spongycastle:prov:1.51.0.0'
compile 'com.makeramen:roundedimageview:1.5.0'
compile 'com.afollestad:material-dialogs:0.6.1.5'
compile 'com.soundcloud.android:android-crop:0.9.10@aar'
compile 'com.android.support:appcompat-v7:21.0.3'
compile 'com.melnykov:floatingactionbutton:1.1.0'
compile 'com.google.zxing:android-integration:3.1.0'
compile ('com.android.support:support-v4-preferencefragment:1.0.0@aar'){
exclude module: 'support-v4'
}
compile 'com.squareup.dagger:dagger:1.2.2'
compile ("com.doomonafireball.betterpickers:library:1.5.2") {
compile ("com.doomonafireball.betterpickers:library:1.5.3") {
exclude group: 'com.android.support', module: 'support-v4'
}
provided 'com.squareup.dagger:dagger-compiler:1.2.2'
compile 'org.whispersystems:jobmanager:0.10.0'
compile 'org.whispersystems:libpastelog:1.0.2'
compile 'org.whispersystems:libpastelog:1.0.4'
androidTestCompile 'com.squareup:fest-android:1.0.8'
androidTestCompile 'com.google.dexmaker:dexmaker:1.1'
@@ -66,28 +69,31 @@ dependencyVerification {
verify = [
'me.leolin:ShortcutBadger:027977c718035e5997035e04e05152d6c72d94df645e8b7099a274ada722bd14',
'se.emilsjolander:stickylistheaders:89146b46c96fea0e40200474a2625cda10fe94891e4128f53cdb42375091b9b6',
'com.google.android.gms:play-services:32e7d1834a1cf8fa4b17e8d359db580c286e26c1eefbf84fdb9996eac8d74919',
'com.google.android.gms:play-services-base:832cb6b3130e871db6a412c4ab585656dbcc5e7948101f190186757785703f75',
'com.astuetz:pagerslidingtabstrip:f1641396732c7132a7abb837e482e5ee2b0ebb8d10813fc52bbaec2c15c184c2',
'org.w3c:smil:085dc40f2bb249651578bfa07499fd08b16ad0886dbe2c4078586a408da62f9b',
'org.apache.httpcomponents:httpclient-android:6f56466a9bd0d42934b90bfbfe9977a8b654c058bf44a12bdc2877c4e1f033f1',
'com.github.chrisbanes.photoview:library:8b5344e206f125e7ba9d684008f36c4992d03853c57e5814125f88496126e3cc',
'com.android.support:appcompat-v7:736f576ab0b68d27bdf18b1e7931566e6d8254b73965175313e87f8866b91547',
'com.madgag.spongycastle:prov:b8c3fec3a59aac1aa04ccf4dad7179351e54ef7672f53f508151b614c131398a',
'com.makeramen:roundedimageview:7dda2e78c406760e5c356ccce59b0df46b5b171cf18abb891998594405021548',
'com.afollestad:material-dialogs:ccb013e6572c86cfcca433855cf0dbfbff9b5e7bb9d1f504b761a6bc6f467b60',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.android.support:appcompat-v7:5dbeb5316d0a6027d646ae552804c3baa5e3bd53f7f33db50904d51505c8a0e5',
'com.melnykov:floatingactionbutton:0679ad9f7d61eb7aeab91e8dc56358cdedd5b1c1b9c48464499ffa05c40d3985',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.android.support:support-v4-preferencefragment:5470f5872514a6226fa1fc6f4e000991f38805691c534cf0bd2778911fc773ad',
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.doomonafireball.betterpickers:library:1c2dd66b0f4555e9e68427b28eed8826b7cd2e7074b8c038c88836a6aa9abe64',
'org.whispersystems:libpastelog:9798b3c93a91082c2c68542ce5b5c182e18556aebdcb7c8cebbd89eb48ac4047',
'com.doomonafireball.betterpickers:library:132ecd685c95a99e7377c4e27bfadbb2d7ed0bea995944060cd62d4369fdaf3d',
'org.whispersystems:jobmanager:01f35586c43aa3806f1c18d3d6a5a972def98103ba1a5a9ca3eec08d15f974b7',
'com.android.support:support-v4:81f2b1c2c94efd5a4ec7fcd97b6cdcd00e87a933905c5c86103c7319eb024572',
'com.madgag.spongycastle:core:8d6240b974b0aca4d3da9c7dd44d42339d8a374358aca5fc98e50a995764511f',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'org.whispersystems:libpastelog:3ccf00fe1597eb8ca1e5de99b17fc225387a1b80b5bbc00ec1bc4d4f3ea9cdde',
'com.android.support:recyclerview-v7:ab2390d688601b65e2f3a0718b3d25487e61546c4e20f81eb0b033f30ca15b31',
'com.nineoldandroids:library:68025a14e3e7673d6ad2f95e4b46d78d7d068343aa99256b686fe59de1b3163a',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
'javax.inject:javax.inject:91c77044a50c481636c32d916fd89c9118a72195390452c81065080f957de7ff',
'com.google.protobuf:protobuf-java:e0c1c64575c005601725e7c6a02cebf9e1285e888f756b2a1d73ffa8d725cc74',
'com.googlecode.libphonenumber:libphonenumber:eba17eae81dd622ea89a00a3a8c025b2f25d342e0d9644c5b62e16f15687c3ab',
'org.whispersystems:gson:08f4f7498455d1539c9233e5aac18e9b1805815ef29221572996508eb512fe51',
'org.whispersystems:axolotl-android:7617256d05aaecd7b5475cd55e42773d7079167a22ca48512bcb0f84f8473cc9',
'com.android.support:support-annotations:1aa96ef0cc4a445bfc2f93ccf762305bc57fa107b12afe9d11f3863ae8a11036',
'com.android.support:support-v4:703572d3015a088cc5604b7e38885af3d307c829d0c5ceaf8654ff41c71cd160',
'com.android.support:support-annotations:fdee2354787ef66b268e75958de3f7f6c4f8f325510a6dac9f49c929f83a63de',
]
}
@@ -113,10 +119,12 @@ android {
buildTypes {
debug {
minifyEnabled false
minifyEnabled true
proguardFiles 'proguard.cfg'
}
release {
minifyEnabled false
minifyEnabled true
proguardFiles 'proguard.cfg'
signingConfig signingConfigs.release
}
}
@@ -139,7 +147,6 @@ android {
}
}
lintOptions {
abortOnError false
}

View File

@@ -42,12 +42,18 @@ message PushMessageContent {
optional AttachmentPointer avatar = 5;
}
message SyncMessageContext {
optional string destination = 1;
optional uint64 timestamp = 2;
}
enum Flags {
END_SESSION = 1;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional uint32 flags = 4;
}
optional string body = 1;
repeated AttachmentPointer attachments = 2;
optional GroupContext group = 3;
optional uint32 flags = 4;
optional SyncMessageContext sync = 5;
}

View File

@@ -1,3 +1,3 @@
all:
protoc --java_out=../src/main/java/ IncomingPushMessageSignal.proto
protoc --java_out=../src/main/java/ IncomingPushMessageSignal.proto Provisioning.proto

View File

@@ -0,0 +1,16 @@
package textsecure;
option java_package = "org.whispersystems.textsecure.internal.push";
option java_outer_classname = "ProvisioningProtos";
message ProvisionEnvelope {
optional bytes publicKey = 1;
optional bytes body = 2; // Encrypted ProvisionMessage
}
message ProvisionMessage {
optional bytes identityKeyPublic = 1;
optional bytes identityKeyPrivate = 2;
optional string number = 3;
optional string provisioningCode = 4;
}

View File

@@ -16,29 +16,37 @@
*/
package org.whispersystems.textsecure.api;
import com.google.protobuf.ByteString;
import org.whispersystems.libaxolotl.IdentityKey;
import org.whispersystems.libaxolotl.IdentityKeyPair;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.state.PreKeyRecord;
import org.whispersystems.libaxolotl.state.SignedPreKeyRecord;
import org.whispersystems.libaxolotl.util.guava.Optional;
import org.whispersystems.textsecure.api.push.ContactTokenDetails;
import org.whispersystems.textsecure.api.push.TrustStore;
import org.whispersystems.textsecure.api.push.exceptions.NonSuccessfulResponseCodeException;
import org.whispersystems.textsecure.api.push.exceptions.PushNetworkException;
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import org.whispersystems.textsecure.api.push.SignedPreKeyEntity;
import org.whispersystems.textsecure.api.push.TrustStore;
import org.whispersystems.textsecure.internal.crypto.ProvisioningCipher;
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import static org.whispersystems.textsecure.internal.push.ProvisioningProtos.ProvisionMessage;
public class TextSecureAccountManager {
private final PushServiceSocket pushServiceSocket;
private final String user;
public TextSecureAccountManager(String url, TrustStore trustStore,
String user, String password)
{
this.pushServiceSocket = new PushServiceSocket(url, trustStore, user, password);
this.user = user;
}
public void setGcmId(Optional<String> gcmRegistrationId) throws IOException {
@@ -94,4 +102,26 @@ public class TextSecureAccountManager {
return this.pushServiceSocket.retrieveDirectory(contactTokens);
}
public String getNewDeviceVerificationCode() throws IOException {
return this.pushServiceSocket.getNewDeviceVerificationCode();
}
public void addDevice(String deviceIdentifier,
ECPublicKey deviceKey,
IdentityKeyPair identityKeyPair,
String code)
throws InvalidKeyException, IOException
{
ProvisioningCipher cipher = new ProvisioningCipher(deviceKey);
ProvisionMessage message = ProvisionMessage.newBuilder()
.setIdentityKeyPublic(ByteString.copyFrom(identityKeyPair.getPublicKey().serialize()))
.setIdentityKeyPrivate(ByteString.copyFrom(identityKeyPair.getPrivateKey().serialize()))
.setNumber(user)
.setProvisioningCode(code)
.build();
byte[] ciphertext = cipher.encrypt(message);
this.pushServiceSocket.sendProvisioningMessage(deviceIdentifier, ciphertext);
}
}

View File

@@ -19,6 +19,7 @@ package org.whispersystems.textsecure.api;
import android.util.Log;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.SessionBuilder;
@@ -40,6 +41,7 @@ import org.whispersystems.textsecure.internal.push.OutgoingPushMessageList;
import org.whispersystems.textsecure.internal.push.PushAttachmentData;
import org.whispersystems.textsecure.internal.push.PushBody;
import org.whispersystems.textsecure.internal.push.PushServiceSocket;
import org.whispersystems.textsecure.internal.push.SendMessageResponse;
import org.whispersystems.textsecure.internal.push.StaleDevices;
import org.whispersystems.textsecure.api.push.exceptions.UnregisteredUserException;
import org.whispersystems.textsecure.api.push.exceptions.EncapsulatedExceptions;
@@ -62,14 +64,17 @@ public class TextSecureMessageSender {
private final PushServiceSocket socket;
private final AxolotlStore store;
private final PushAddress syncAddress;
private final Optional<EventListener> eventListener;
public TextSecureMessageSender(String url, TrustStore trustStore,
String user, String password, AxolotlStore store,
String user, String password,
long userId, AxolotlStore store,
Optional<EventListener> eventListener)
{
this.socket = new PushServiceSocket(url, trustStore, user, password);
this.store = store;
this.syncAddress = new PushAddress(userId, user, null);
this.eventListener = eventListener;
}
@@ -80,8 +85,14 @@ public class TextSecureMessageSender {
public void sendMessage(PushAddress recipient, TextSecureMessage message)
throws UntrustedIdentityException, IOException
{
byte[] content = createMessageContent(message);
sendMessage(recipient, message.getTimestamp(), content);
byte[] content = createMessageContent(message);
long timestamp = message.getTimestamp();
SendMessageResponse response = sendMessage(recipient, timestamp, content);
if (response != null && response.getNeedsSync()) {
byte[] syncMessage = createSyncMessageContent(content, recipient, timestamp);
sendMessage(syncAddress, timestamp, syncMessage);
}
if (message.isEndSession()) {
store.deleteAllSessions(recipient.getRecipientId());
@@ -122,6 +133,20 @@ public class TextSecureMessageSender {
return builder.build().toByteArray();
}
private byte[] createSyncMessageContent(byte[] content, PushAddress recipient, long timestamp) {
try {
PushMessageContent.Builder builder = PushMessageContent.parseFrom(content).toBuilder();
builder.setSync(PushMessageContent.SyncMessageContext.newBuilder()
.setDestination(recipient.getNumber())
.setTimestamp(timestamp)
.build());
return builder.build().toByteArray();
} catch (InvalidProtocolBufferException e) {
throw new AssertionError(e);
}
}
private GroupContext createGroupContent(TextSecureGroup group) throws IOException {
GroupContext.Builder builder = GroupContext.newBuilder();
builder.setId(ByteString.copyFrom(group.getGroupId()));
@@ -168,15 +193,13 @@ public class TextSecureMessageSender {
}
}
private void sendMessage(PushAddress recipient, long timestamp, byte[] content)
private SendMessageResponse sendMessage(PushAddress recipient, long timestamp, byte[] content)
throws UntrustedIdentityException, IOException
{
for (int i=0;i<3;i++) {
try {
OutgoingPushMessageList messages = getEncryptedMessages(socket, recipient, timestamp, content);
socket.sendMessage(messages);
return;
return socket.sendMessage(messages);
} catch (MismatchedDevicesException mde) {
Log.w(TAG, mde);
handleMismatchedDevices(socket, recipient, mde.getMismatchedDevices());
@@ -185,6 +208,8 @@ public class TextSecureMessageSender {
handleStaleDevices(recipient, ste.getStaleDevices());
}
}
throw new IOException("Failed to resolve conflicts after 3 attempts!");
}
private List<AttachmentPointer> createAttachmentPointers(Optional<List<TextSecureAttachment>> attachments) throws IOException {
@@ -230,31 +255,31 @@ public class TextSecureMessageSender {
byte[] plaintext)
throws IOException, UntrustedIdentityException
{
PushBody masterBody = getEncryptedMessage(socket, recipient, plaintext);
List<OutgoingPushMessage> messages = new LinkedList<>();
messages.add(new OutgoingPushMessage(recipient, masterBody));
if (!recipient.equals(syncAddress)) {
PushBody masterBody = getEncryptedMessage(socket, recipient, PushAddress.DEFAULT_DEVICE_ID, plaintext);
messages.add(new OutgoingPushMessage(recipient, PushAddress.DEFAULT_DEVICE_ID, masterBody));
}
for (int deviceId : store.getSubDeviceSessions(recipient.getRecipientId())) {
PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(), deviceId, recipient.getRelay());
PushBody body = getEncryptedMessage(socket, device, plaintext);
messages.add(new OutgoingPushMessage(device, body));
PushBody body = getEncryptedMessage(socket, recipient, deviceId, plaintext);
messages.add(new OutgoingPushMessage(recipient, deviceId, body));
}
return new OutgoingPushMessageList(recipient.getNumber(), timestamp, recipient.getRelay(), messages);
}
private PushBody getEncryptedMessage(PushServiceSocket socket, PushAddress recipient, byte[] plaintext)
private PushBody getEncryptedMessage(PushServiceSocket socket, PushAddress recipient, int deviceId, byte[] plaintext)
throws IOException, UntrustedIdentityException
{
if (!store.containsSession(recipient.getRecipientId(), recipient.getDeviceId())) {
if (!store.containsSession(recipient.getRecipientId(), deviceId)) {
try {
List<PreKeyBundle> preKeys = socket.getPreKeys(recipient);
List<PreKeyBundle> preKeys = socket.getPreKeys(recipient, deviceId);
for (PreKeyBundle preKey : preKeys) {
try {
SessionBuilder sessionBuilder = new SessionBuilder(store, recipient.getRecipientId(), recipient.getDeviceId());
SessionBuilder sessionBuilder = new SessionBuilder(store, recipient.getRecipientId(), deviceId);
sessionBuilder.process(preKey);
} catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) {
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey());
@@ -269,7 +294,7 @@ public class TextSecureMessageSender {
}
}
TextSecureCipher cipher = new TextSecureCipher(store, recipient.getRecipientId(), recipient.getDeviceId());
TextSecureCipher cipher = new TextSecureCipher(store, recipient.getRecipientId(), deviceId);
CiphertextMessage message = cipher.encrypt(plaintext);
int remoteRegistrationId = cipher.getRemoteRegistrationId();
@@ -292,12 +317,10 @@ public class TextSecureMessageSender {
}
for (int missingDeviceId : mismatchedDevices.getMissingDevices()) {
PushAddress device = new PushAddress(recipient.getRecipientId(), recipient.getNumber(),
missingDeviceId, recipient.getRelay());
PreKeyBundle preKey = socket.getPreKey(device);
PreKeyBundle preKey = socket.getPreKey(recipient, missingDeviceId);
try {
SessionBuilder sessionBuilder = new SessionBuilder(store, device.getRecipientId(), device.getDeviceId());
SessionBuilder sessionBuilder = new SessionBuilder(store, recipient.getRecipientId(), missingDeviceId);
sessionBuilder.process(preKey);
} catch (org.whispersystems.libaxolotl.UntrustedIdentityException e) {
throw new UntrustedIdentityException("Untrusted identity key!", recipient.getNumber(), preKey.getIdentityKey());

View File

@@ -22,13 +22,11 @@ public class PushAddress {
private final long recipientId;
private final String e164number;
private final int deviceId;
private final String relay;
public PushAddress(long recipientId, String e164number, int deviceId, String relay) {
public PushAddress(long recipientId, String e164number, String relay) {
this.recipientId = recipientId;
this.e164number = e164number;
this.deviceId = deviceId;
this.relay = relay;
}
@@ -44,7 +42,29 @@ public class PushAddress {
return recipientId;
}
public int getDeviceId() {
return deviceId;
@Override
public boolean equals(Object other) {
if (other == null || !(other instanceof PushAddress)) return false;
PushAddress that = (PushAddress)other;
return this.recipientId == that.recipientId &&
equals(this.e164number, that.e164number) &&
equals(this.relay, that.relay);
}
@Override
public int hashCode() {
int hashCode = (int)this.recipientId;
if (this.e164number != null) hashCode ^= this.e164number.hashCode();
if (this.relay != null) hashCode ^= this.relay.hashCode();
return hashCode;
}
private boolean equals(String one, String two) {
if (one == null) return two == null;
return one.equals(two);
}
}

View File

@@ -0,0 +1,75 @@
package org.whispersystems.textsecure.internal.crypto;
import com.google.protobuf.ByteString;
import org.whispersystems.libaxolotl.InvalidKeyException;
import org.whispersystems.libaxolotl.ecc.Curve;
import org.whispersystems.libaxolotl.ecc.ECKeyPair;
import org.whispersystems.libaxolotl.ecc.ECPublicKey;
import org.whispersystems.libaxolotl.kdf.HKDFv3;
import org.whispersystems.textsecure.internal.util.Util;
import java.security.NoSuchAlgorithmException;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.Mac;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.SecretKeySpec;
import static org.whispersystems.textsecure.internal.push.ProvisioningProtos.ProvisionEnvelope;
import static org.whispersystems.textsecure.internal.push.ProvisioningProtos.ProvisionMessage;
public class ProvisioningCipher {
private static final String TAG = ProvisioningCipher.class.getSimpleName();
private final ECPublicKey theirPublicKey;
public ProvisioningCipher(ECPublicKey theirPublicKey) {
this.theirPublicKey = theirPublicKey;
}
public byte[] encrypt(ProvisionMessage message) throws InvalidKeyException {
ECKeyPair ourKeyPair = Curve.generateKeyPair();
byte[] sharedSecret = Curve.calculateAgreement(theirPublicKey, ourKeyPair.getPrivateKey());
byte[] derivedSecret = new HKDFv3().deriveSecrets(sharedSecret, "TextSecure Provisioning Message".getBytes(), 64);
byte[][] parts = Util.split(derivedSecret, 32, 32);
byte[] version = {0x01};
byte[] ciphertext = getCiphertext(parts[0], message.toByteArray());
byte[] mac = getMac(parts[1], Util.join(version, ciphertext));
byte[] body = Util.join(version, ciphertext, mac);
return ProvisionEnvelope.newBuilder()
.setPublicKey(ByteString.copyFrom(ourKeyPair.getPublicKey().serialize()))
.setBody(ByteString.copyFrom(body))
.build()
.toByteArray();
}
private byte[] getCiphertext(byte[] key, byte[] message) {
try {
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"));
return Util.join(cipher.getIV(), cipher.doFinal(message));
} catch (NoSuchAlgorithmException | NoSuchPaddingException | java.security.InvalidKeyException | IllegalBlockSizeException | BadPaddingException e) {
throw new AssertionError(e);
}
}
private byte[] getMac(byte[] key, byte[] message) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec(key, "HmacSHA256"));
return mac.doFinal(message);
} catch (NoSuchAlgorithmException | java.security.InvalidKeyException e) {
throw new AssertionError(e);
}
}
}

View File

@@ -0,0 +1,10 @@
package org.whispersystems.textsecure.internal.push;
public class DeviceCode {
private String verificationCode;
public String getVerificationCode() {
return verificationCode;
}
}

View File

@@ -27,9 +27,9 @@ public class OutgoingPushMessage {
private int destinationRegistrationId;
private String body;
public OutgoingPushMessage(PushAddress address, PushBody body) {
public OutgoingPushMessage(PushAddress address, int deviceId, PushBody body) {
this.type = body.getType();
this.destinationDeviceId = address.getDeviceId();
this.destinationDeviceId = deviceId;
this.destinationRegistrationId = body.getRemoteRegistrationId();
this.body = Base64.encodeBytes(body.getBody());
}

View File

@@ -0,0 +1,11 @@
package org.whispersystems.textsecure.internal.push;
public class ProvisioningMessage {
private String body;
public ProvisioningMessage(String body) {
this.body = body;
}
}

View File

@@ -244,6 +244,10 @@ public final class PushMessageProtos {
* <code>RECEIPT = 5;</code>
*/
RECEIPT(5, 5),
/**
* <code>COPY = 6;</code>
*/
COPY(6, 6),
;
/**
@@ -270,6 +274,10 @@ public final class PushMessageProtos {
* <code>RECEIPT = 5;</code>
*/
public static final int RECEIPT_VALUE = 5;
/**
* <code>COPY = 6;</code>
*/
public static final int COPY_VALUE = 6;
public final int getNumber() { return value; }
@@ -282,6 +290,7 @@ public final class PushMessageProtos {
case 3: return PREKEY_BUNDLE;
case 4: return PLAINTEXT;
case 5: return RECEIPT;
case 6: return COPY;
default: return null;
}
}
@@ -1187,6 +1196,20 @@ public final class PushMessageProtos {
* <code>optional uint32 flags = 4;</code>
*/
int getFlags();
// optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
boolean hasSync();
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext getSync();
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder getSyncOrBuilder();
}
/**
* Protobuf type {@code textsecure.PushMessageContent}
@@ -1270,6 +1293,19 @@ public final class PushMessageProtos {
flags_ = input.readUInt32();
break;
}
case 42: {
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder subBuilder = null;
if (((bitField0_ & 0x00000008) == 0x00000008)) {
subBuilder = sync_.toBuilder();
}
sync_ = input.readMessage(org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.PARSER, extensionRegistry);
if (subBuilder != null) {
subBuilder.mergeFrom(sync_);
sync_ = subBuilder.buildPartial();
}
bitField0_ |= 0x00000008;
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
@@ -3143,6 +3179,558 @@ public final class PushMessageProtos {
// @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.GroupContext)
}
public interface SyncMessageContextOrBuilder
extends com.google.protobuf.MessageOrBuilder {
// optional string destination = 1;
/**
* <code>optional string destination = 1;</code>
*/
boolean hasDestination();
/**
* <code>optional string destination = 1;</code>
*/
java.lang.String getDestination();
/**
* <code>optional string destination = 1;</code>
*/
com.google.protobuf.ByteString
getDestinationBytes();
// optional uint64 timestamp = 2;
/**
* <code>optional uint64 timestamp = 2;</code>
*/
boolean hasTimestamp();
/**
* <code>optional uint64 timestamp = 2;</code>
*/
long getTimestamp();
}
/**
* Protobuf type {@code textsecure.PushMessageContent.SyncMessageContext}
*/
public static final class SyncMessageContext extends
com.google.protobuf.GeneratedMessage
implements SyncMessageContextOrBuilder {
// Use SyncMessageContext.newBuilder() to construct.
private SyncMessageContext(com.google.protobuf.GeneratedMessage.Builder<?> builder) {
super(builder);
this.unknownFields = builder.getUnknownFields();
}
private SyncMessageContext(boolean noInit) { this.unknownFields = com.google.protobuf.UnknownFieldSet.getDefaultInstance(); }
private static final SyncMessageContext defaultInstance;
public static SyncMessageContext getDefaultInstance() {
return defaultInstance;
}
public SyncMessageContext getDefaultInstanceForType() {
return defaultInstance;
}
private final com.google.protobuf.UnknownFieldSet unknownFields;
@java.lang.Override
public final com.google.protobuf.UnknownFieldSet
getUnknownFields() {
return this.unknownFields;
}
private SyncMessageContext(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
initFields();
int mutable_bitField0_ = 0;
com.google.protobuf.UnknownFieldSet.Builder unknownFields =
com.google.protobuf.UnknownFieldSet.newBuilder();
try {
boolean done = false;
while (!done) {
int tag = input.readTag();
switch (tag) {
case 0:
done = true;
break;
default: {
if (!parseUnknownField(input, unknownFields,
extensionRegistry, tag)) {
done = true;
}
break;
}
case 10: {
bitField0_ |= 0x00000001;
destination_ = input.readBytes();
break;
}
case 16: {
bitField0_ |= 0x00000002;
timestamp_ = input.readUInt64();
break;
}
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
throw e.setUnfinishedMessage(this);
} catch (java.io.IOException e) {
throw new com.google.protobuf.InvalidProtocolBufferException(
e.getMessage()).setUnfinishedMessage(this);
} finally {
this.unknownFields = unknownFields.build();
makeExtensionsImmutable();
}
}
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_SyncMessageContext_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.class, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder.class);
}
public static com.google.protobuf.Parser<SyncMessageContext> PARSER =
new com.google.protobuf.AbstractParser<SyncMessageContext>() {
public SyncMessageContext parsePartialFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return new SyncMessageContext(input, extensionRegistry);
}
};
@java.lang.Override
public com.google.protobuf.Parser<SyncMessageContext> getParserForType() {
return PARSER;
}
private int bitField0_;
// optional string destination = 1;
public static final int DESTINATION_FIELD_NUMBER = 1;
private java.lang.Object destination_;
/**
* <code>optional string destination = 1;</code>
*/
public boolean hasDestination() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>optional string destination = 1;</code>
*/
public java.lang.String getDestination() {
java.lang.Object ref = destination_;
if (ref instanceof java.lang.String) {
return (java.lang.String) ref;
} else {
com.google.protobuf.ByteString bs =
(com.google.protobuf.ByteString) ref;
java.lang.String s = bs.toStringUtf8();
if (bs.isValidUtf8()) {
destination_ = s;
}
return s;
}
}
/**
* <code>optional string destination = 1;</code>
*/
public com.google.protobuf.ByteString
getDestinationBytes() {
java.lang.Object ref = destination_;
if (ref instanceof java.lang.String) {
com.google.protobuf.ByteString b =
com.google.protobuf.ByteString.copyFromUtf8(
(java.lang.String) ref);
destination_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
// optional uint64 timestamp = 2;
public static final int TIMESTAMP_FIELD_NUMBER = 2;
private long timestamp_;
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public boolean hasTimestamp() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public long getTimestamp() {
return timestamp_;
}
private void initFields() {
destination_ = "";
timestamp_ = 0L;
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
byte isInitialized = memoizedIsInitialized;
if (isInitialized != -1) return isInitialized == 1;
memoizedIsInitialized = 1;
return true;
}
public void writeTo(com.google.protobuf.CodedOutputStream output)
throws java.io.IOException {
getSerializedSize();
if (((bitField0_ & 0x00000001) == 0x00000001)) {
output.writeBytes(1, getDestinationBytes());
}
if (((bitField0_ & 0x00000002) == 0x00000002)) {
output.writeUInt64(2, timestamp_);
}
getUnknownFields().writeTo(output);
}
private int memoizedSerializedSize = -1;
public int getSerializedSize() {
int size = memoizedSerializedSize;
if (size != -1) return size;
size = 0;
if (((bitField0_ & 0x00000001) == 0x00000001)) {
size += com.google.protobuf.CodedOutputStream
.computeBytesSize(1, getDestinationBytes());
}
if (((bitField0_ & 0x00000002) == 0x00000002)) {
size += com.google.protobuf.CodedOutputStream
.computeUInt64Size(2, timestamp_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
}
private static final long serialVersionUID = 0L;
@java.lang.Override
protected java.lang.Object writeReplace()
throws java.io.ObjectStreamException {
return super.writeReplace();
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
com.google.protobuf.ByteString data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
com.google.protobuf.ByteString data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(byte[] data)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
byte[] data,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws com.google.protobuf.InvalidProtocolBufferException {
return PARSER.parseFrom(data, extensionRegistry);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseDelimitedFrom(java.io.InputStream input)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseDelimitedFrom(
java.io.InputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseDelimitedFrom(input, extensionRegistry);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
com.google.protobuf.CodedInputStream input)
throws java.io.IOException {
return PARSER.parseFrom(input);
}
public static org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parseFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
return PARSER.parseFrom(input, extensionRegistry);
}
public static Builder newBuilder() { return Builder.create(); }
public Builder newBuilderForType() { return newBuilder(); }
public static Builder newBuilder(org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext prototype) {
return newBuilder().mergeFrom(prototype);
}
public Builder toBuilder() { return newBuilder(this); }
@java.lang.Override
protected Builder newBuilderForType(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
Builder builder = new Builder(parent);
return builder;
}
/**
* Protobuf type {@code textsecure.PushMessageContent.SyncMessageContext}
*/
public static final class Builder extends
com.google.protobuf.GeneratedMessage.Builder<Builder>
implements org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder {
public static final com.google.protobuf.Descriptors.Descriptor
getDescriptor() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor;
}
protected com.google.protobuf.GeneratedMessage.FieldAccessorTable
internalGetFieldAccessorTable() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_SyncMessageContext_fieldAccessorTable
.ensureFieldAccessorsInitialized(
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.class, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder.class);
}
// Construct using org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.newBuilder()
private Builder() {
maybeForceBuilderInitialization();
}
private Builder(
com.google.protobuf.GeneratedMessage.BuilderParent parent) {
super(parent);
maybeForceBuilderInitialization();
}
private void maybeForceBuilderInitialization() {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
}
}
private static Builder create() {
return new Builder();
}
public Builder clear() {
super.clear();
destination_ = "";
bitField0_ = (bitField0_ & ~0x00000001);
timestamp_ = 0L;
bitField0_ = (bitField0_ & ~0x00000002);
return this;
}
public Builder clone() {
return create().mergeFrom(buildPartial());
}
public com.google.protobuf.Descriptors.Descriptor
getDescriptorForType() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor;
}
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext getDefaultInstanceForType() {
return org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance();
}
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext build() {
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext result = buildPartial();
if (!result.isInitialized()) {
throw newUninitializedMessageException(result);
}
return result;
}
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext buildPartial() {
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext result = new org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext(this);
int from_bitField0_ = bitField0_;
int to_bitField0_ = 0;
if (((from_bitField0_ & 0x00000001) == 0x00000001)) {
to_bitField0_ |= 0x00000001;
}
result.destination_ = destination_;
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
to_bitField0_ |= 0x00000002;
}
result.timestamp_ = timestamp_;
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
}
public Builder mergeFrom(com.google.protobuf.Message other) {
if (other instanceof org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext) {
return mergeFrom((org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext)other);
} else {
super.mergeFrom(other);
return this;
}
}
public Builder mergeFrom(org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext other) {
if (other == org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance()) return this;
if (other.hasDestination()) {
bitField0_ |= 0x00000001;
destination_ = other.destination_;
onChanged();
}
if (other.hasTimestamp()) {
setTimestamp(other.getTimestamp());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
public final boolean isInitialized() {
return true;
}
public Builder mergeFrom(
com.google.protobuf.CodedInputStream input,
com.google.protobuf.ExtensionRegistryLite extensionRegistry)
throws java.io.IOException {
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext parsedMessage = null;
try {
parsedMessage = PARSER.parsePartialFrom(input, extensionRegistry);
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
parsedMessage = (org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext) e.getUnfinishedMessage();
throw e;
} finally {
if (parsedMessage != null) {
mergeFrom(parsedMessage);
}
}
return this;
}
private int bitField0_;
// optional string destination = 1;
private java.lang.Object destination_ = "";
/**
* <code>optional string destination = 1;</code>
*/
public boolean hasDestination() {
return ((bitField0_ & 0x00000001) == 0x00000001);
}
/**
* <code>optional string destination = 1;</code>
*/
public java.lang.String getDestination() {
java.lang.Object ref = destination_;
if (!(ref instanceof java.lang.String)) {
java.lang.String s = ((com.google.protobuf.ByteString) ref)
.toStringUtf8();
destination_ = s;
return s;
} else {
return (java.lang.String) ref;
}
}
/**
* <code>optional string destination = 1;</code>
*/
public com.google.protobuf.ByteString
getDestinationBytes() {
java.lang.Object ref = destination_;
if (ref instanceof String) {
com.google.protobuf.ByteString b =
com.google.protobuf.ByteString.copyFromUtf8(
(java.lang.String) ref);
destination_ = b;
return b;
} else {
return (com.google.protobuf.ByteString) ref;
}
}
/**
* <code>optional string destination = 1;</code>
*/
public Builder setDestination(
java.lang.String value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000001;
destination_ = value;
onChanged();
return this;
}
/**
* <code>optional string destination = 1;</code>
*/
public Builder clearDestination() {
bitField0_ = (bitField0_ & ~0x00000001);
destination_ = getDefaultInstance().getDestination();
onChanged();
return this;
}
/**
* <code>optional string destination = 1;</code>
*/
public Builder setDestinationBytes(
com.google.protobuf.ByteString value) {
if (value == null) {
throw new NullPointerException();
}
bitField0_ |= 0x00000001;
destination_ = value;
onChanged();
return this;
}
// optional uint64 timestamp = 2;
private long timestamp_ ;
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public boolean hasTimestamp() {
return ((bitField0_ & 0x00000002) == 0x00000002);
}
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public long getTimestamp() {
return timestamp_;
}
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public Builder setTimestamp(long value) {
bitField0_ |= 0x00000002;
timestamp_ = value;
onChanged();
return this;
}
/**
* <code>optional uint64 timestamp = 2;</code>
*/
public Builder clearTimestamp() {
bitField0_ = (bitField0_ & ~0x00000002);
timestamp_ = 0L;
onChanged();
return this;
}
// @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent.SyncMessageContext)
}
static {
defaultInstance = new SyncMessageContext(true);
defaultInstance.initFields();
}
// @@protoc_insertion_point(class_scope:textsecure.PushMessageContent.SyncMessageContext)
}
private int bitField0_;
// optional string body = 1;
public static final int BODY_FIELD_NUMBER = 1;
@@ -3261,11 +3849,34 @@ public final class PushMessageProtos {
return flags_;
}
// optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;
public static final int SYNC_FIELD_NUMBER = 5;
private org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext sync_;
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public boolean hasSync() {
return ((bitField0_ & 0x00000008) == 0x00000008);
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext getSync() {
return sync_;
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder getSyncOrBuilder() {
return sync_;
}
private void initFields() {
body_ = "";
attachments_ = java.util.Collections.emptyList();
group_ = org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.GroupContext.getDefaultInstance();
flags_ = 0;
sync_ = org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance();
}
private byte memoizedIsInitialized = -1;
public final boolean isInitialized() {
@@ -3291,6 +3902,9 @@ public final class PushMessageProtos {
if (((bitField0_ & 0x00000004) == 0x00000004)) {
output.writeUInt32(4, flags_);
}
if (((bitField0_ & 0x00000008) == 0x00000008)) {
output.writeMessage(5, sync_);
}
getUnknownFields().writeTo(output);
}
@@ -3316,6 +3930,10 @@ public final class PushMessageProtos {
size += com.google.protobuf.CodedOutputStream
.computeUInt32Size(4, flags_);
}
if (((bitField0_ & 0x00000008) == 0x00000008)) {
size += com.google.protobuf.CodedOutputStream
.computeMessageSize(5, sync_);
}
size += getUnknownFields().getSerializedSize();
memoizedSerializedSize = size;
return size;
@@ -3426,6 +4044,7 @@ public final class PushMessageProtos {
if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {
getAttachmentsFieldBuilder();
getGroupFieldBuilder();
getSyncFieldBuilder();
}
}
private static Builder create() {
@@ -3450,6 +4069,12 @@ public final class PushMessageProtos {
bitField0_ = (bitField0_ & ~0x00000004);
flags_ = 0;
bitField0_ = (bitField0_ & ~0x00000008);
if (syncBuilder_ == null) {
sync_ = org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance();
} else {
syncBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
@@ -3503,6 +4128,14 @@ public final class PushMessageProtos {
to_bitField0_ |= 0x00000004;
}
result.flags_ = flags_;
if (((from_bitField0_ & 0x00000010) == 0x00000010)) {
to_bitField0_ |= 0x00000008;
}
if (syncBuilder_ == null) {
result.sync_ = sync_;
} else {
result.sync_ = syncBuilder_.build();
}
result.bitField0_ = to_bitField0_;
onBuilt();
return result;
@@ -3556,6 +4189,9 @@ public final class PushMessageProtos {
if (other.hasFlags()) {
setFlags(other.getFlags());
}
if (other.hasSync()) {
mergeSync(other.getSync());
}
this.mergeUnknownFields(other.getUnknownFields());
return this;
}
@@ -4047,6 +4683,123 @@ public final class PushMessageProtos {
return this;
}
// optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;
private org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext sync_ = org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance();
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder> syncBuilder_;
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public boolean hasSync() {
return ((bitField0_ & 0x00000010) == 0x00000010);
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext getSync() {
if (syncBuilder_ == null) {
return sync_;
} else {
return syncBuilder_.getMessage();
}
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public Builder setSync(org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext value) {
if (syncBuilder_ == null) {
if (value == null) {
throw new NullPointerException();
}
sync_ = value;
onChanged();
} else {
syncBuilder_.setMessage(value);
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public Builder setSync(
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder builderForValue) {
if (syncBuilder_ == null) {
sync_ = builderForValue.build();
onChanged();
} else {
syncBuilder_.setMessage(builderForValue.build());
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public Builder mergeSync(org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext value) {
if (syncBuilder_ == null) {
if (((bitField0_ & 0x00000010) == 0x00000010) &&
sync_ != org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance()) {
sync_ =
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.newBuilder(sync_).mergeFrom(value).buildPartial();
} else {
sync_ = value;
}
onChanged();
} else {
syncBuilder_.mergeFrom(value);
}
bitField0_ |= 0x00000010;
return this;
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public Builder clearSync() {
if (syncBuilder_ == null) {
sync_ = org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.getDefaultInstance();
onChanged();
} else {
syncBuilder_.clear();
}
bitField0_ = (bitField0_ & ~0x00000010);
return this;
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder getSyncBuilder() {
bitField0_ |= 0x00000010;
onChanged();
return getSyncFieldBuilder().getBuilder();
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
public org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder getSyncOrBuilder() {
if (syncBuilder_ != null) {
return syncBuilder_.getMessageOrBuilder();
} else {
return sync_;
}
}
/**
* <code>optional .textsecure.PushMessageContent.SyncMessageContext sync = 5;</code>
*/
private com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder>
getSyncFieldBuilder() {
if (syncBuilder_ == null) {
syncBuilder_ = new com.google.protobuf.SingleFieldBuilder<
org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContext.Builder, org.whispersystems.textsecure.internal.push.PushMessageProtos.PushMessageContent.SyncMessageContextOrBuilder>(
sync_,
getParentForChildren(),
isClean());
sync_ = null;
}
return syncBuilder_;
}
// @@protoc_insertion_point(builder_scope:textsecure.PushMessageContent)
}
@@ -4078,6 +4831,11 @@ public final class PushMessageProtos {
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_PushMessageContent_GroupContext_fieldAccessorTable;
private static com.google.protobuf.Descriptors.Descriptor
internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor;
private static
com.google.protobuf.GeneratedMessage.FieldAccessorTable
internal_static_textsecure_PushMessageContent_SyncMessageContext_fieldAccessorTable;
public static com.google.protobuf.Descriptors.FileDescriptor
getDescriptor() {
@@ -4088,28 +4846,32 @@ public final class PushMessageProtos {
static {
java.lang.String[] descriptorData = {
"\n\037IncomingPushMessageSignal.proto\022\ntexts" +
"ecure\"\224\002\n\031IncomingPushMessageSignal\0228\n\004t" +
"ecure\"\236\002\n\031IncomingPushMessageSignal\0228\n\004t" +
"ype\030\001 \001(\0162*.textsecure.IncomingPushMessa" +
"geSignal.Type\022\016\n\006source\030\002 \001(\t\022\024\n\014sourceD" +
"evice\030\007 \001(\r\022\r\n\005relay\030\003 \001(\t\022\021\n\ttimestamp\030" +
"\005 \001(\004\022\017\n\007message\030\006 \001(\014\"d\n\004Type\022\013\n\007UNKNOW" +
"\005 \001(\004\022\017\n\007message\030\006 \001(\014\"n\n\004Type\022\013\n\007UNKNOW" +
"N\020\000\022\016\n\nCIPHERTEXT\020\001\022\020\n\014KEY_EXCHANGE\020\002\022\021\n" +
"\rPREKEY_BUNDLE\020\003\022\r\n\tPLAINTEXT\020\004\022\013\n\007RECEI" +
"PT\020\005\"\207\004\n\022PushMessageContent\022\014\n\004body\030\001 \001(" +
"\t\022E\n\013attachments\030\002 \003(\01320.textsecure.Push",
"MessageContent.AttachmentPointer\022:\n\005grou" +
"p\030\003 \001(\0132+.textsecure.PushMessageContent." +
"GroupContext\022\r\n\005flags\030\004 \001(\r\032A\n\021Attachmen" +
"tPointer\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(" +
"\t\022\013\n\003key\030\003 \001(\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 " +
"\001(\014\022>\n\004type\030\002 \001(\01620.textsecure.PushMessa" +
"geContent.GroupContext.Type\022\014\n\004name\030\003 \001(" +
"\t\022\017\n\007members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.tex" +
"tsecure.PushMessageContent.AttachmentPoi" +
"nter\"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n",
"\007DELIVER\020\002\022\010\n\004QUIT\020\003\"\030\n\005Flags\022\017\n\013END_SES" +
"SION\020\001B@\n+org.whispersystems.textsecure." +
"internal.pushB\021PushMessageProtos"
"PT\020\005\022\010\n\004COPY\020\006\"\206\005\n\022PushMessageContent\022\014\n" +
"\004body\030\001 \001(\t\022E\n\013attachments\030\002 \003(\01320.texts",
"ecure.PushMessageContent.AttachmentPoint" +
"er\022:\n\005group\030\003 \001(\0132+.textsecure.PushMessa" +
"geContent.GroupContext\022\r\n\005flags\030\004 \001(\r\022?\n" +
"\004sync\030\005 \001(\01321.textsecure.PushMessageCont" +
"ent.SyncMessageContext\032A\n\021AttachmentPoin" +
"ter\022\n\n\002id\030\001 \001(\006\022\023\n\013contentType\030\002 \001(\t\022\013\n\003" +
"key\030\003 \001(\014\032\363\001\n\014GroupContext\022\n\n\002id\030\001 \001(\014\022>" +
"\n\004type\030\002 \001(\01620.textsecure.PushMessageCon" +
"tent.GroupContext.Type\022\014\n\004name\030\003 \001(\t\022\017\n\007" +
"members\030\004 \003(\t\022@\n\006avatar\030\005 \001(\01320.textsecu",
"re.PushMessageContent.AttachmentPointer\"" +
"6\n\004Type\022\013\n\007UNKNOWN\020\000\022\n\n\006UPDATE\020\001\022\013\n\007DELI" +
"VER\020\002\022\010\n\004QUIT\020\003\032<\n\022SyncMessageContext\022\023\n" +
"\013destination\030\001 \001(\t\022\021\n\ttimestamp\030\002 \001(\004\"\030\n" +
"\005Flags\022\017\n\013END_SESSION\020\001B@\n+org.whispersy" +
"stems.textsecure.internal.pushB\021PushMess" +
"ageProtos"
};
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
@@ -4127,7 +4889,7 @@ public final class PushMessageProtos {
internal_static_textsecure_PushMessageContent_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PushMessageContent_descriptor,
new java.lang.String[] { "Body", "Attachments", "Group", "Flags", });
new java.lang.String[] { "Body", "Attachments", "Group", "Flags", "Sync", });
internal_static_textsecure_PushMessageContent_AttachmentPointer_descriptor =
internal_static_textsecure_PushMessageContent_descriptor.getNestedTypes().get(0);
internal_static_textsecure_PushMessageContent_AttachmentPointer_fieldAccessorTable = new
@@ -4140,6 +4902,12 @@ public final class PushMessageProtos {
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PushMessageContent_GroupContext_descriptor,
new java.lang.String[] { "Id", "Type", "Name", "Members", "Avatar", });
internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor =
internal_static_textsecure_PushMessageContent_descriptor.getNestedTypes().get(2);
internal_static_textsecure_PushMessageContent_SyncMessageContext_fieldAccessorTable = new
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
internal_static_textsecure_PushMessageContent_SyncMessageContext_descriptor,
new java.lang.String[] { "Destination", "Timestamp", });
return null;
}
};

View File

@@ -79,11 +79,15 @@ public class PushServiceSocket {
private static final String CREATE_ACCOUNT_VOICE_PATH = "/v1/accounts/voice/code/%s";
private static final String VERIFY_ACCOUNT_PATH = "/v1/accounts/code/%s";
private static final String REGISTER_GCM_PATH = "/v1/accounts/gcm/";
private static final String PREKEY_METADATA_PATH = "/v2/keys/";
private static final String PREKEY_PATH = "/v2/keys/%s";
private static final String PREKEY_DEVICE_PATH = "/v2/keys/%s/%s";
private static final String SIGNED_PREKEY_PATH = "/v2/keys/signed";
private static final String PROVISIONING_CODE_PATH = "/v1/devices/provisioning/code";
private static final String PROVISIONING_MESSAGE_PATH = "/v1/provisioning/%s";
private static final String DIRECTORY_TOKENS_PATH = "/v1/directory/tokens";
private static final String DIRECTORY_VERIFY_PATH = "/v1/directory/%s";
private static final String MESSAGE_PATH = "/v1/messages/%s";
@@ -120,6 +124,16 @@ public class PushServiceSocket {
"PUT", new Gson().toJson(signalingKeyEntity));
}
public String getNewDeviceVerificationCode() throws IOException {
String responseText = makeRequest(PROVISIONING_CODE_PATH, "GET", null);
return new Gson().fromJson(responseText, DeviceCode.class).getVerificationCode();
}
public void sendProvisioningMessage(String destination, byte[] body) throws IOException {
makeRequest(String.format(PROVISIONING_MESSAGE_PATH, destination), "PUT",
new Gson().toJson(new ProvisioningMessage(Base64.encodeBytes(body))));
}
public void sendReceipt(String destination, long messageId, String relay) throws IOException {
String path = String.format(RECEIPT_PATH, destination, messageId);
@@ -139,11 +153,15 @@ public class PushServiceSocket {
makeRequest(REGISTER_GCM_PATH, "DELETE", null);
}
public void sendMessage(OutgoingPushMessageList bundle)
public SendMessageResponse sendMessage(OutgoingPushMessageList bundle)
throws IOException
{
try {
makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
String responseText = makeRequest(String.format(MESSAGE_PATH, bundle.getDestination()), "PUT", new Gson().toJson(bundle));
if (responseText == null) return new SendMessageResponse(false);
else return new Gson().fromJson(responseText, SendMessageResponse.class);
} catch (NotFoundException nfe) {
throw new UnregisteredUserException(bundle.getDestination(), nfe);
}
@@ -183,9 +201,9 @@ public class PushServiceSocket {
return preKeyStatus.getCount();
}
public List<PreKeyBundle> getPreKeys(PushAddress destination) throws IOException {
public List<PreKeyBundle> getPreKeys(PushAddress destination, int deviceIdInteger) throws IOException {
try {
String deviceId = String.valueOf(destination.getDeviceId());
String deviceId = String.valueOf(deviceIdInteger);
if (deviceId.equals("1"))
deviceId = "*";
@@ -231,10 +249,10 @@ public class PushServiceSocket {
}
}
public PreKeyBundle getPreKey(PushAddress destination) throws IOException {
public PreKeyBundle getPreKey(PushAddress destination, int deviceId) throws IOException {
try {
String path = String.format(PREKEY_DEVICE_PATH, destination.getNumber(),
String.valueOf(destination.getDeviceId()));
String.valueOf(deviceId));
if (!Util.isEmpty(destination.getRelay())) {
path = path + "?relay=" + destination.getRelay();

View File

@@ -0,0 +1,16 @@
package org.whispersystems.textsecure.internal.push;
public class SendMessageResponse {
private boolean needsSync;
public SendMessageResponse() {}
public SendMessageResponse(boolean needsSync) {
this.needsSync = needsSync;
}
public boolean getNeedsSync() {
return needsSync;
}
}

View File

@@ -25,6 +25,19 @@ import java.security.SecureRandom;
public class Util {
public static byte[] join(byte[]... input) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (byte[] part : input) {
baos.write(part);
}
return baos.toByteArray();
} catch (IOException e) {
throw new AssertionError(e);
}
}
public static byte[][] split(byte[] input, int firstLength, int secondLength) {
byte[][] parts = new byte[2][];

7
proguard.cfg Normal file
View File

@@ -0,0 +1,7 @@
-keepattributes **
-keep class !android.support.v7.internal.view.menu.**,** {*;}
-dontpreverify
-dontoptimize
-dontshrink
-dontwarn **

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_enabled="false" android:color="@color/gray27"/>
<item android:color="@color/gray95"/>
</selector>

View File

@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_enabled="false" android:color="@color/gray27"/>
<item android:color="@color/gray50"/>
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 371 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 309 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 496 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 258 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 254 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 146 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 321 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 331 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 178 B

After

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 794 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 302 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 405 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 759 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 437 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 448 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 482 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 719 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 344 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 B

Some files were not shown because too many files have changed in this diff Show More