aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/eu/siacs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java3
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java136
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java27
-rw-r--r--src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java30
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java9
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java25
-rw-r--r--src/main/java/eu/siacs/conversations/utils/DNSHelper.java121
-rw-r--r--src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java57
-rw-r--r--src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java62
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java178
10 files changed, 436 insertions, 212 deletions
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 3077c4889..c73a05647 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -577,6 +577,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void writeRoster(final Roster roster) {
final Account account = roster.getAccount();
final SQLiteDatabase db = this.getWritableDatabase();
+ db.beginTransaction();
for (Contact contact : roster.getContacts()) {
if (contact.getOption(Contact.Options.IN_ROSTER)) {
db.insert(Contact.TABLENAME, null, contact.getContentValues());
@@ -586,6 +587,8 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.delete(Contact.TABLENAME, where, whereArgs);
}
}
+ db.setTransactionSuccessful();
+ db.endTransaction();
account.setRosterVersion(roster.getVersion());
updateAccount(account);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 4f9011366..4e57d7f81 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -1,6 +1,5 @@
package eu.siacs.conversations.ui;
-import android.Manifest;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.AlertDialog;
@@ -18,6 +17,7 @@ import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
+import android.provider.Settings;
import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
import android.util.Log;
@@ -38,6 +38,8 @@ import android.widget.Toast;
import net.java.otr4j.session.SessionStatus;
+import org.openintents.openpgp.util.OpenPgpApi;
+
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@@ -66,7 +68,6 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
-import org.openintents.openpgp.util.OpenPgpApi;
public class ConversationActivity extends XmppActivity
implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast {
@@ -207,7 +208,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onItemClick(AdapterView<?> arg0, View clickedView,
- int position, long arg3) {
+ int position, long arg3) {
if (getSelectedConversation() != conversationList.get(position)) {
setSelectedConversation(conversationList.get(position));
ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
@@ -296,7 +297,7 @@ public class ConversationActivity extends XmppActivity
SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
mSlidingPaneLayout.setParallaxDistance(150);
mSlidingPaneLayout
- .setShadowResource(R.drawable.es_slidingpane_shadow);
+ .setShadowResource(R.drawable.es_slidingpane_shadow);
mSlidingPaneLayout.setSliderFadeColor(0);
mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
@@ -307,7 +308,7 @@ public class ConversationActivity extends XmppActivity
hideKeyboard();
if (xmppConnectionServiceBound) {
xmppConnectionService.getNotificationService()
- .setOpenConversation(null);
+ .setOpenConversation(null);
}
closeContextMenu();
}
@@ -504,7 +505,7 @@ public class ConversationActivity extends XmppActivity
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
+ intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
}
intent.setType("image/*");
chooser = true;
@@ -571,16 +572,16 @@ public class ConversationActivity extends XmppActivity
}
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_LOCATION:
- getPreferences().edit().putString("recently_used_quick_action","location").apply();
+ getPreferences().edit().putString("recently_used_quick_action", "location").apply();
break;
case ATTACHMENT_CHOICE_RECORD_VOICE:
- getPreferences().edit().putString("recently_used_quick_action","voice").apply();
+ getPreferences().edit().putString("recently_used_quick_action", "voice").apply();
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
- getPreferences().edit().putString("recently_used_quick_action","photo").apply();
+ getPreferences().edit().putString("recently_used_quick_action", "photo").apply();
break;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- getPreferences().edit().putString("recently_used_quick_action","picture").apply();
+ getPreferences().edit().putString("recently_used_quick_action", "picture").apply();
break;
}
final Conversation conversation = getSelectedConversation();
@@ -620,19 +621,19 @@ public class ConversationActivity extends XmppActivity
selectPresenceToAttachFile(attachmentChoice, encryption);
} else {
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
- .findFragmentByTag("conversation");
+ .findFragmentByTag("conversation");
if (fragment != null) {
fragment.showNoPGPKeyDialog(false,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog,
- int which) {
+ int which) {
conversation
- .setNextEncryption(Message.ENCRYPTION_NONE);
+ .setNextEncryption(Message.ENCRYPTION_NONE);
xmppConnectionService.databaseBackend
- .updateConversation(conversation);
- selectPresenceToAttachFile(attachmentChoice,Message.ENCRYPTION_NONE);
+ .updateConversation(conversation);
+ selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE);
}
});
}
@@ -647,19 +648,6 @@ public class ConversationActivity extends XmppActivity
}
}
- public boolean hasStoragePermission(int attachmentChoice) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
- requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, attachmentChoice);
- return false;
- } else {
- return true;
- }
- } else {
- return true;
- }
- }
-
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
if (grantResults.length > 0)
@@ -672,8 +660,8 @@ public class ConversationActivity extends XmppActivity
attachFile(requestCode);
}
} else {
- Toast.makeText(this,R.string.no_storage_permission,Toast.LENGTH_SHORT).show();
- }
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
}
public void startDownloadable(Message message) {
@@ -684,14 +672,14 @@ public class ConversationActivity extends XmppActivity
Transferable transferable = message.getTransferable();
if (transferable != null) {
if (!transferable.start()) {
- Toast.makeText(this, R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
}
} else if (message.treatAsDownloadable() != Message.Decision.NEVER) {
xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true);
}
}
- @Override
+ @Override
public boolean onOptionsItemSelected(final MenuItem item) {
if (item.getItemId() == android.R.id.home) {
showConversationsOverview();
@@ -762,9 +750,9 @@ public class ConversationActivity extends XmppActivity
this.mConversationFragment.reInit(getSelectedConversation());
} else {
setSelectedConversation(null);
- if (mRedirected.compareAndSet(false,true)) {
+ if (mRedirected.compareAndSet(false, true)) {
Intent intent = new Intent(this, StartConversationActivity.class);
- intent.putExtra("init",true);
+ intent.putExtra("init", true);
startActivity(intent);
finish();
}
@@ -779,7 +767,7 @@ public class ConversationActivity extends XmppActivity
View dialogView = getLayoutInflater().inflate(
R.layout.dialog_clear_history, null);
final CheckBox endConversationCheckBox = (CheckBox) dialogView
- .findViewById(R.id.end_conversation_checkbox);
+ .findViewById(R.id.end_conversation_checkbox);
builder.setView(dialogView);
builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.delete_messages),
@@ -881,7 +869,7 @@ public class ConversationActivity extends XmppActivity
}
PopupMenu popup = new PopupMenu(this, menuItemView);
final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
- .findFragmentByTag("conversation");
+ .findFragmentByTag("conversation");
if (fragment != null) {
popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
@@ -902,7 +890,7 @@ public class ConversationActivity extends XmppActivity
conversation.setNextEncryption(Message.ENCRYPTION_PGP);
item.setChecked(true);
} else {
- announcePgp(conversation.getAccount(),conversation);
+ announcePgp(conversation.getAccount(), conversation);
}
} else {
showInstallPgpDialog();
@@ -1008,7 +996,7 @@ public class ConversationActivity extends XmppActivity
int rotation = getWindowManager().getDefaultDisplay().getRotation();
final int upKey;
final int downKey;
- switch(rotation) {
+ switch (rotation) {
case Surface.ROTATION_90:
upKey = KeyEvent.KEYCODE_DPAD_LEFT;
downKey = KeyEvent.KEYCODE_DPAD_RIGHT;
@@ -1032,7 +1020,7 @@ public class ConversationActivity extends XmppActivity
} else if (modifier && key == downKey) {
if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
showConversationsOverview();
- }
+ }
return selectDownConversation();
} else if (modifier && key == upKey) {
if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
@@ -1169,11 +1157,11 @@ public class ConversationActivity extends XmppActivity
public void onSaveInstanceState(final Bundle savedInstanceState) {
Conversation conversation = getSelectedConversation();
if (conversation != null) {
- savedInstanceState.putString(STATE_OPEN_CONVERSATION,conversation.getUuid());
+ savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid());
} else {
savedInstanceState.remove(STATE_OPEN_CONVERSATION);
}
- savedInstanceState.putBoolean(STATE_PANEL_OPEN,isConversationsOverviewVisable());
+ savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable());
if (this.mPendingImageUris.size() >= 1) {
savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
} else {
@@ -1200,7 +1188,7 @@ public class ConversationActivity extends XmppActivity
}
if (xmppConnectionService.getAccounts().size() == 0) {
- if (mRedirected.compareAndSet(false,true)) {
+ if (mRedirected.compareAndSet(false, true)) {
if (Config.X509_VERIFICATION) {
startActivity(new Intent(this, ManageAccountActivity.class));
} else {
@@ -1209,9 +1197,9 @@ public class ConversationActivity extends XmppActivity
finish();
}
} else if (conversationList.size() <= 0) {
- if (mRedirected.compareAndSet(false,true)) {
+ if (mRedirected.compareAndSet(false, true)) {
Intent intent = new Intent(this, StartConversationActivity.class);
- intent.putExtra("init",true);
+ intent.putExtra("init", true);
startActivity(intent);
finish();
}
@@ -1243,7 +1231,7 @@ public class ConversationActivity extends XmppActivity
this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
}
- if(!forbidProcessingPendings) {
+ if (!forbidProcessingPendings) {
for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
Uri foo = i.next();
attachImageToConversation(getSelectedConversation(), foo);
@@ -1260,7 +1248,9 @@ public class ConversationActivity extends XmppActivity
}
forbidProcessingPendings = false;
- ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
+ if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) {
+ openBatteryOptimizationDialogIfNeeded();
+ }
setIntent(new Intent());
}
@@ -1269,14 +1259,14 @@ public class ConversationActivity extends XmppActivity
final String downloadUuid = intent.getStringExtra(MESSAGE);
final String text = intent.getStringExtra(TEXT);
final String nick = intent.getStringExtra(NICK);
- final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE,false);
+ final boolean pm = intent.getBooleanExtra(PRIVATE_MESSAGE, false);
if (selectConversationByUuid(uuid)) {
this.mConversationFragment.reInit(getSelectedConversation());
if (nick != null) {
if (pm) {
Jid jid = getSelectedConversation().getJid();
try {
- Jid next = Jid.fromParts(jid.getLocalpart(),jid.getDomainpart(),nick);
+ Jid next = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), nick);
this.mConversationFragment.privateMessageWith(next);
} catch (final InvalidJidException ignored) {
//do nothing
@@ -1326,7 +1316,7 @@ public class ConversationActivity extends XmppActivity
Uri uri = intent.getData();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) {
ClipData clipData = intent.getClipData();
- for(int i = 0; i < clipData.getItemCount(); ++i) {
+ for (int i = 0; i < clipData.getItemCount(); ++i) {
uris.add(clipData.getItemAt(i).getUri());
}
} else {
@@ -1336,8 +1326,7 @@ public class ConversationActivity extends XmppActivity
}
@Override
- protected void onActivityResult(int requestCode, int resultCode,
- final Intent data) {
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK) {
if (requestCode == REQUEST_DECRYPT_PGP) {
@@ -1365,15 +1354,15 @@ public class ConversationActivity extends XmppActivity
mPendingImageUris.clear();
mPendingImageUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
- for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
- attachImageToConversation(getSelectedConversation(),i.next());
+ for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ attachImageToConversation(getSelectedConversation(), i.next());
}
}
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
mPendingFileUris.clear();
mPendingFileUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
- for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
attachFileToConversation(getSelectedConversation(), i.next());
}
}
@@ -1391,9 +1380,9 @@ public class ConversationActivity extends XmppActivity
mPendingImageUris.clear();
}
} else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
- double latitude = data.getDoubleExtra("latitude",0);
- double longitude = data.getDoubleExtra("longitude",0);
- this.mPendingGeoUri = Uri.parse("geo:"+String.valueOf(latitude)+","+String.valueOf(longitude));
+ double latitude = data.getDoubleExtra("latitude", 0);
+ double longitude = data.getDoubleExtra("longitude", 0);
+ this.mPendingGeoUri = Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude));
if (xmppConnectionServiceBound) {
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
this.mPendingGeoUri = null;
@@ -1408,6 +1397,39 @@ public class ConversationActivity extends XmppActivity
if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
mConversationFragment.onActivityResult(requestCode, resultCode, data);
}
+ if (requestCode == REQUEST_BATTERY_OP) {
+ setNeverAskForBatteryOptimizationsAgain();
+ }
+ }
+ }
+
+ private void setNeverAskForBatteryOptimizationsAgain() {
+ getPreferences().edit().putBoolean("show_battery_optimization", false).commit();
+ }
+
+ private void openBatteryOptimizationDialogIfNeeded() {
+ if (showBatteryOptimizationWarning() && getPreferences().getBoolean("show_battery_optimizationF", true)) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.battery_optimizations_enabled);
+ builder.setMessage(R.string.battery_optimizations_enabled_dialog);
+ builder.setPositiveButton(R.string.next, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ Uri uri = Uri.parse("package:" + getPackageName());
+ intent.setData(uri);
+ startActivityForResult(intent, REQUEST_BATTERY_OP);
+ }
+ });
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
+ builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ setNeverAskForBatteryOptimizationsAgain();
+ }
+ });
+ }
+ builder.create().show();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 1545de0f3..22b875c2e 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -5,9 +5,10 @@ import android.app.AlertDialog.Builder;
import android.app.PendingIntent;
import android.content.DialogInterface;
import android.content.Intent;
-import android.content.res.Configuration;
import android.graphics.Bitmap;
+import android.net.Uri;
import android.os.Bundle;
+import android.provider.Settings;
import android.security.KeyChain;
import android.security.KeyChainAliasCallback;
import android.text.Editable;
@@ -52,16 +53,17 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast {
- private LinearLayout mMainLayout;
private AutoCompleteTextView mAccountJid;
private EditText mPassword;
private EditText mPasswordConfirm;
private CheckBox mRegisterNew;
private Button mCancelButton;
private Button mSaveButton;
+ private Button mDisableBatterOptimizations;
private TableLayout mMoreTable;
private LinearLayout mStats;
+ private RelativeLayout mBatteryOptimizations;
private TextView mServerInfoSm;
private TextView mServerInfoRosterVersion;
private TextView mServerInfoCarbons;
@@ -311,6 +313,14 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
});
}
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_BATTERY_OP) {
+ updateAccountInformation(mAccount == null);
+ }
+ }
+
protected void updateSaveButton() {
if (accountInfoEdited() && !mInitMode) {
this.mSaveButton.setText(R.string.save);
@@ -372,7 +382,6 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_account);
- this.mMainLayout = (LinearLayout) findViewById(R.id.account_main_layout);
this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
this.mAccountJid.addTextChangedListener(this.mTextWatcher);
this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label);
@@ -387,6 +396,17 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
this.mAvatar.setOnClickListener(this.mAvatarClickListener);
this.mRegisterNew = (CheckBox) findViewById(R.id.account_register_new);
this.mStats = (LinearLayout) findViewById(R.id.stats);
+ this.mBatteryOptimizations = (RelativeLayout) findViewById(R.id.battery_optimization);
+ this.mDisableBatterOptimizations = (Button) findViewById(R.id.batt_op_disable);
+ this.mDisableBatterOptimizations.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
+ Uri uri = Uri.parse("package:"+getPackageName());
+ intent.setData(uri);
+ startActivityForResult(intent,REQUEST_BATTERY_OP);
+ }
+ });
this.mSessionEst = (TextView) findViewById(R.id.session_est);
this.mServerInfoRosterVersion = (TextView) findViewById(R.id.server_info_roster_version);
this.mServerInfoCarbons = (TextView) findViewById(R.id.server_info_carbons);
@@ -595,6 +615,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
this.mStats.setVisibility(View.VISIBLE);
+ this.mBatteryOptimizations.setVisibility(showBatteryOptimizationWarning() ? View.VISIBLE : View.GONE);
this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
.getLastSessionEstablished()));
Features features = this.mAccount.getXmppConnection().getFeatures();
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index 3313ce318..edd6cf150 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.ui;
import android.app.PendingIntent;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
@@ -137,16 +138,35 @@ public class PublishProfilePictureActivity extends XmppActivity {
@Override
public void onClick(View v) {
- Intent attachFileIntent = new Intent();
- attachFileIntent.setType("image/*");
- attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
- Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
- startActivityForResult(chooser, REQUEST_CHOOSE_FILE);
+ if (hasStoragePermission(REQUEST_CHOOSE_FILE)) {
+ chooseAvatar();
+ }
+
}
});
this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext());
}
+ private void chooseAvatar() {
+ Intent attachFileIntent = new Intent();
+ attachFileIntent.setType("image/*");
+ attachFileIntent.setAction(Intent.ACTION_GET_CONTENT);
+ Intent chooser = Intent.createChooser(attachFileIntent, getString(R.string.attach_file));
+ startActivityForResult(chooser, REQUEST_CHOOSE_FILE);
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ if (grantResults.length > 0)
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (requestCode == REQUEST_CHOOSE_FILE) {
+ chooseAvatar();
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
@Override
protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index 2e6d32466..bc319108c 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -42,6 +42,8 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
private Button mSaveButton;
private Button mCancelButton;
+ private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
+
private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
private final Map<String, Boolean> foreignKeysToTrust = new HashMap<>();
@@ -160,7 +162,11 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
} else {
if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
keyErrorMessageCard.setVisibility(View.VISIBLE);
- keyErrorMessage.setText(R.string.error_no_keys_to_trust);
+ if (lastFetchReport == AxolotlService.FetchStatus.ERROR) {
+ keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error);
+ } else {
+ keyErrorMessage.setText(R.string.error_no_keys_to_trust);
+ }
ownKeys.removeAllViews(); ownKeysCard.setVisibility(View.GONE);
foreignKeys.removeAllViews(); foreignKeysCard.setVisibility(View.GONE);
}
@@ -216,6 +222,7 @@ public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdate
@Override
public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
if (report != null) {
+ lastFetchReport = report;
runOnUiThread(new Runnable() {
@Override
public void run() {
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index be6255127..85077c9df 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -1,5 +1,6 @@
package eu.siacs.conversations.ui;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.ActionBar;
@@ -35,6 +36,7 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.PowerManager;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
@@ -91,6 +93,7 @@ public abstract class XmppActivity extends Activity {
protected static final int REQUEST_ANNOUNCE_PGP = 0x0101;
protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102;
protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103;
+ protected static final int REQUEST_BATTERY_OP = 0x13849ff;
public XmppConnectionService xmppConnectionService;
public boolean xmppConnectionServiceBound = false;
@@ -375,6 +378,15 @@ public abstract class XmppActivity extends Activity {
}
}
+ protected boolean showBatteryOptimizationWarning() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
+ return !pm.isIgnoringBatteryOptimizations(getPackageName());
+ } else {
+ return false;
+ }
+ }
+
protected boolean usingEnterKey() {
return getPreferences().getBoolean("display_enter_key", false);
}
@@ -785,6 +797,19 @@ public abstract class XmppActivity extends Activity {
builder.create().show();
}
+ public boolean hasStoragePermission(int requestCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+
public void selectPresence(final Conversation conversation,
final OnPresenceSelected listener) {
final Contact contact = conversation.getContact();
diff --git a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
index e07df627e..87790d64d 100644
--- a/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/DNSHelper.java
@@ -19,6 +19,7 @@ import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
+import java.util.Map;
import java.util.regex.Pattern;
import de.measite.minidns.Client;
@@ -57,7 +58,7 @@ public class DNSHelper {
if (!b.containsKey("values")) {
Log.d(Config.LOGTAG,"all dns queries failed. provide fallback A record");
ArrayList<Parcelable> values = new ArrayList<>();
- values.add(createNamePortBundle(host,5222));
+ values.add(createNamePortBundle(host, 5222, false));
b.putParcelableArrayList("values",values);
}
return b;
@@ -96,57 +97,73 @@ public class DNSHelper {
return servers;
}
- public static Bundle queryDNS(String host, InetAddress dnsServer) {
- Bundle bundle = new Bundle();
- try {
- client.setTimeout(Config.PING_TIMEOUT * 1000);
- String qname = "_xmpp-client._tcp." + host;
- Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
- DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
-
- TreeMap<Integer, ArrayList<SRV>> priorities = new TreeMap<>();
- TreeMap<String, ArrayList<String>> ips4 = new TreeMap<>();
- TreeMap<String, ArrayList<String>> ips6 = new TreeMap<>();
-
- for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
- for (Record rr : rrset) {
- Data d = rr.getPayload();
- if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
- SRV srv = (SRV) d;
- if (!priorities.containsKey(srv.getPriority())) {
- priorities.put(srv.getPriority(),new ArrayList<SRV>());
- }
- priorities.get(srv.getPriority()).add(srv);
+ private static class TlsSrv {
+ private final SRV srv;
+ private final boolean tls;
+
+ public TlsSrv(SRV srv, boolean tls) {
+ this.srv = srv;
+ this.tls = tls;
+ }
+ }
+
+ private static void fillSrvMaps(final String qname, final InetAddress dnsServer, final Map<Integer, List<TlsSrv>> priorities, final Map<String, List<String>> ips4, final Map<String, List<String>> ips6, final boolean tls) throws IOException {
+ final DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServer.getHostAddress());
+ for (Record[] rrset : new Record[][] { message.getAnswers(), message.getAdditionalResourceRecords() }) {
+ for (Record rr : rrset) {
+ Data d = rr.getPayload();
+ if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) {
+ SRV srv = (SRV) d;
+ if (!priorities.containsKey(srv.getPriority())) {
+ priorities.put(srv.getPriority(),new ArrayList<TlsSrv>());
}
- if (d instanceof A) {
- A a = (A) d;
- if (!ips4.containsKey(rr.getName())) {
- ips4.put(rr.getName(), new ArrayList<String>());
- }
- ips4.get(rr.getName()).add(a.toString());
+ priorities.get(srv.getPriority()).add(new TlsSrv(srv, tls));
+ }
+ if (d instanceof A) {
+ A a = (A) d;
+ if (!ips4.containsKey(rr.getName())) {
+ ips4.put(rr.getName(), new ArrayList<String>());
}
- if (d instanceof AAAA) {
- AAAA aaaa = (AAAA) d;
- if (!ips6.containsKey(rr.getName())) {
- ips6.put(rr.getName(), new ArrayList<String>());
- }
- ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
+ ips4.get(rr.getName()).add(a.toString());
+ }
+ if (d instanceof AAAA) {
+ AAAA aaaa = (AAAA) d;
+ if (!ips6.containsKey(rr.getName())) {
+ ips6.put(rr.getName(), new ArrayList<String>());
}
+ ips6.get(rr.getName()).add("[" + aaaa.toString() + "]");
}
}
+ }
+ }
+
+ public static Bundle queryDNS(String host, InetAddress dnsServer) {
+ Bundle bundle = new Bundle();
+ try {
+ client.setTimeout(Config.PING_TIMEOUT * 1000);
+ final String qname = "_xmpp-client._tcp." + host;
+ final String tlsQname = "_xmpps-client._tcp." + host;
+ Log.d(Config.LOGTAG, "using dns server: " + dnsServer.getHostAddress() + " to look up " + host);
+
+ final Map<Integer, List<TlsSrv>> priorities = new TreeMap<>();
+ final Map<String, List<String>> ips4 = new TreeMap<>();
+ final Map<String, List<String>> ips6 = new TreeMap<>();
+
+ fillSrvMaps(qname, dnsServer, priorities, ips4, ips6, false);
+ fillSrvMaps(tlsQname, dnsServer, priorities, ips4, ips6, true);
- ArrayList<SRV> result = new ArrayList<>();
- for (ArrayList<SRV> s : priorities.values()) {
+ final List<TlsSrv> result = new ArrayList<>();
+ for (final List<TlsSrv> s : priorities.values()) {
result.addAll(s);
}
- ArrayList<Bundle> values = new ArrayList<>();
+ final ArrayList<Bundle> values = new ArrayList<>();
if (result.size() == 0) {
DNSMessage response;
try {
response = client.query(host, TYPE.A, CLASS.IN, dnsServer.getHostAddress());
for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload()));
+ values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
}
} catch (SocketTimeoutException e) {
Log.d(Config.LOGTAG,"ignoring timeout exception when querying A record on "+dnsServer.getHostAddress());
@@ -154,37 +171,38 @@ public class DNSHelper {
try {
response = client.query(host, TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload()));
+ values.add(createNamePortBundle(host, 5222, response.getAnswers()[i].getPayload(), false));
}
} catch (SocketTimeoutException e) {
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
}
- values.add(createNamePortBundle(host,5222));
+ values.add(createNamePortBundle(host, 5222, false));
bundle.putParcelableArrayList("values", values);
return bundle;
}
- for (SRV srv : result) {
+ for (final TlsSrv tlsSrv : result) {
+ final SRV srv = tlsSrv.srv;
if (ips6.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6));
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips6, tlsSrv.tls));
} else {
try {
DNSMessage response = client.query(srv.getName(), TYPE.AAAA, CLASS.IN, dnsServer.getHostAddress());
for (int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload()));
+ values.add(createNamePortBundle(srv.getName(), srv.getPort(), response.getAnswers()[i].getPayload(), tlsSrv.tls));
}
} catch (SocketTimeoutException e) {
Log.d(Config.LOGTAG,"ignoring timeout exception when querying AAAA record on "+dnsServer.getHostAddress());
}
}
if (ips4.containsKey(srv.getName())) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4));
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),ips4, tlsSrv.tls));
} else {
DNSMessage response = client.query(srv.getName(), TYPE.A, CLASS.IN, dnsServer.getHostAddress());
for(int i = 0; i < response.getAnswers().length; ++i) {
- values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload()));
+ values.add(createNamePortBundle(srv.getName(),srv.getPort(),response.getAnswers()[i].getPayload(), tlsSrv.tls));
}
}
- values.add(createNamePortBundle(srv.getName(), srv.getPort()));
+ values.add(createNamePortBundle(srv.getName(), srv.getPort(), tlsSrv.tls));
}
bundle.putParcelableArrayList("values", values);
} catch (SocketTimeoutException e) {
@@ -195,28 +213,31 @@ public class DNSHelper {
return bundle;
}
- private static Bundle createNamePortBundle(String name, int port) {
+ private static Bundle createNamePortBundle(String name, int port, final boolean tls) {
Bundle namePort = new Bundle();
namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
namePort.putInt("port", port);
return namePort;
}
- private static Bundle createNamePortBundle(String name, int port, TreeMap<String, ArrayList<String>> ips) {
+ private static Bundle createNamePortBundle(String name, int port, Map<String, List<String>> ips, final boolean tls) {
Bundle namePort = new Bundle();
namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
namePort.putInt("port", port);
if (ips!=null) {
- ArrayList<String> ip = ips.get(name);
+ List<String> ip = ips.get(name);
Collections.shuffle(ip, new Random());
namePort.putString("ip", ip.get(0));
}
return namePort;
}
- private static Bundle createNamePortBundle(String name, int port, Data data) {
+ private static Bundle createNamePortBundle(String name, int port, Data data, final boolean tls) {
Bundle namePort = new Bundle();
namePort.putString("name", name);
+ namePort.putBoolean("tls", tls);
namePort.putInt("port", port);
if (data instanceof A) {
namePort.putString("ip", data.toString());
diff --git a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
index 6d1d1074d..f2c3ea18a 100644
--- a/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/ExceptionHelper.java
@@ -24,6 +24,7 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -35,14 +36,13 @@ public class ExceptionHelper {
}
}
- public static void checkForCrash(Context context,
- final XmppConnectionService service) {
+ public static boolean checkForCrash(ConversationActivity activity, final XmppConnectionService service) {
try {
final SharedPreferences preferences = PreferenceManager
- .getDefaultSharedPreferences(context);
+ .getDefaultSharedPreferences(activity);
boolean neverSend = preferences.getBoolean("never_send", false);
if (neverSend) {
- return;
+ return false;
}
List<Account> accounts = service.getAccounts();
Account account = null;
@@ -53,24 +53,25 @@ public class ExceptionHelper {
}
}
if (account == null) {
- return;
+ return false;
}
final Account finalAccount = account;
- FileInputStream file = context.openFileInput("stacktrace.txt");
+ FileInputStream file = activity.openFileInput("stacktrace.txt");
InputStreamReader inputStreamReader = new InputStreamReader(file);
BufferedReader stacktrace = new BufferedReader(inputStreamReader);
final StringBuilder report = new StringBuilder();
- PackageManager pm = context.getPackageManager();
+ PackageManager pm = activity.getPackageManager();
PackageInfo packageInfo = null;
try {
- packageInfo = pm.getPackageInfo(context.getPackageName(), 0);
+ packageInfo = pm.getPackageInfo(activity.getPackageName(), 0);
report.append("Version: " + packageInfo.versionName + '\n');
report.append("Last Update: "
- + DateUtils.formatDateTime(context,
- packageInfo.lastUpdateTime,
- DateUtils.FORMAT_SHOW_TIME
- | DateUtils.FORMAT_SHOW_DATE) + '\n');
+ + DateUtils.formatDateTime(activity,
+ packageInfo.lastUpdateTime,
+ DateUtils.FORMAT_SHOW_TIME
+ | DateUtils.FORMAT_SHOW_DATE) + '\n');
} catch (NameNotFoundException e) {
+ return false;
}
String line;
while ((line = stacktrace.readLine()) != null) {
@@ -78,11 +79,11 @@ public class ExceptionHelper {
report.append('\n');
}
file.close();
- context.deleteFile("stacktrace.txt");
- AlertDialog.Builder builder = new AlertDialog.Builder(context);
- builder.setTitle(context.getString(R.string.crash_report_title));
- builder.setMessage(context.getText(R.string.crash_report_message));
- builder.setPositiveButton(context.getText(R.string.send_now),
+ activity.deleteFile("stacktrace.txt");
+ AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ builder.setTitle(activity.getString(R.string.crash_report_title));
+ builder.setMessage(activity.getText(R.string.crash_report_message));
+ builder.setPositiveButton(activity.getText(R.string.send_now),
new OnClickListener() {
@Override
@@ -91,18 +92,19 @@ public class ExceptionHelper {
Log.d(Config.LOGTAG, "using account="
+ finalAccount.getJid().toBareJid()
+ " to send in stack trace");
- Conversation conversation = null;
- try {
- conversation = service.findOrCreateConversation(finalAccount,
- Jid.fromString("bugs@pix-art.de"), false);
- } catch (final InvalidJidException ignored) {
- }
- Message message = new Message(conversation, report
+
+ Conversation conversation = null;
+ try {
+ conversation = service.findOrCreateConversation(finalAccount,
+ Jid.fromString("bugs@pix-art.de"), false);
+ } catch (final InvalidJidException ignored) {
+ }
+ Message message = new Message(conversation, report
.toString(), Message.ENCRYPTION_NONE);
service.sendMessage(message);
}
});
- builder.setNegativeButton(context.getText(R.string.send_never),
+ builder.setNegativeButton(activity.getText(R.string.send_never),
new OnClickListener() {
@Override
@@ -112,8 +114,9 @@ public class ExceptionHelper {
}
});
builder.create().show();
+ return true;
} catch (final IOException ignored) {
- }
-
+ return false;
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java
new file mode 100644
index 000000000..49e9a81ad
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/SSLSocketHelper.java
@@ -0,0 +1,62 @@
+package eu.siacs.conversations.utils;
+
+import java.lang.reflect.Method;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.LinkedList;
+
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+
+public class SSLSocketHelper {
+
+ public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException {
+ final String[] supportProtocols;
+ final Collection<String> supportedProtocols = new LinkedList<>(
+ Arrays.asList(sslSocket.getSupportedProtocols()));
+ supportedProtocols.remove("SSLv3");
+ supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
+
+ sslSocket.setEnabledProtocols(supportProtocols);
+
+ final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
+ sslSocket.getSupportedCipherSuites());
+ if (cipherSuites.length > 0) {
+ sslSocket.setEnabledCipherSuites(cipherSuites);
+ }
+ }
+
+ public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) {
+ if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname);
+ } else {
+ try {
+ socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname);
+ } catch (Throwable e) {
+ // ignore any error, we just can't set the hostname...
+ }
+ }
+ }
+
+ public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) {
+ try {
+ if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
+ // can't call directly because of @hide?
+ //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")});
+ android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}});
+ } else {
+ final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class);
+ // the concatenation of 8-bit, length prefixed protocol names, just one in our case...
+ // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4
+ final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8");
+ final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1];
+ lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow
+ System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length);
+ method.invoke(socket, new Object[]{lengthPrefixedProtocols});
+ }
+ } catch (Throwable e) {
+ // ignore any error, we just can't set the alpn protocol...
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index c64fa196d..a56a64aff 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -20,7 +20,6 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.OutputStream;
import java.math.BigInteger;
import java.net.ConnectException;
import java.net.IDN;
@@ -35,12 +34,9 @@ import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
-import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
@@ -66,6 +62,7 @@ import eu.siacs.conversations.generator.IqGenerator;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.DNSHelper;
+import eu.siacs.conversations.utils.SSLSocketHelper;
import eu.siacs.conversations.utils.SocksSocketFactory;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
@@ -247,6 +244,7 @@ public class XmppConnection implements Runnable {
}
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": connect to "+destination+" via TOR");
socket = SocksSocketFactory.createSocketOverTor(destination,account.getPort());
+ startXmpp();
} else if (DNSHelper.isIp(account.getServer().toString())) {
socket = new Socket();
try {
@@ -254,13 +252,12 @@ public class XmppConnection implements Runnable {
} catch (IOException e) {
throw new UnknownHostException();
}
+ startXmpp();
} else {
- final Bundle result = DNSHelper.getSRVRecord(account.getServer(),mXmppConnectionService);
+ final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
final ArrayList<Parcelable>values = result.getParcelableArrayList("values");
- int i = 0;
- boolean socketError = true;
- while (socketError && values.size() > i) {
- final Bundle namePort = (Bundle) values.get(i);
+ for(Iterator<Parcelable> iterator = values.iterator(); iterator.hasNext();) {
+ final Bundle namePort = (Bundle) iterator.next();
try {
String srvRecordServer;
try {
@@ -271,48 +268,57 @@ public class XmppConnection implements Runnable {
}
final int srvRecordPort = namePort.getInt("port");
final String srvIpServer = namePort.getString("ip");
+ // if tls is true, encryption is implied and must not be started
+ features.encryptionEnabled = namePort.getBoolean("tls");
final InetSocketAddress addr;
if (srvIpServer != null) {
addr = new InetSocketAddress(srvIpServer, srvRecordPort);
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ ": using values from dns " + srvRecordServer
- + "[" + srvIpServer + "]:" + srvRecordPort);
+ + "[" + srvIpServer + "]:" + srvRecordPort + " tls: " + features.encryptionEnabled);
} else {
addr = new InetSocketAddress(srvRecordServer, srvRecordPort);
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString()
+ ": using values from dns "
- + srvRecordServer + ":" + srvRecordPort);
+ + srvRecordServer + ":" + srvRecordPort + " tls: " + features.encryptionEnabled);
}
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
- socketError = false;
+
+ if (!features.encryptionEnabled) {
+ socket = new Socket();
+ socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ } else {
+ final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
+ socket = tlsFactoryVerifier.factory.createSocket();
+
+ if (socket == null) {
+ throw new IOException("could not initialize ssl socket");
+ }
+
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+
+ socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+
+ if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
+ throw new SecurityException();
+ }
+ }
+
+ if (startXmpp())
+ break; // successfully connected to server that speaks xmpp
+ } catch(final SecurityException e) {
+ throw e;
} catch (final Throwable e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() +"("+e.getClass().getName()+")");
- i++;
+ if (!iterator.hasNext()) {
+ throw new UnknownHostException();
+ }
}
}
- if (socketError) {
- throw new UnknownHostException();
- }
- }
- final OutputStream out = socket.getOutputStream();
- tagWriter.setOutputStream(out);
- final InputStream in = socket.getInputStream();
- tagReader.setInputStream(in);
- tagWriter.beginDocument();
- sendStartStream();
- Tag nextTag;
- while ((nextTag = tagReader.readTag()) != null) {
- if (nextTag.isStart("stream")) {
- processStream();
- break;
- } else {
- throw new IOException("unknown tag on connect");
- }
- }
- if (socket.isConnected()) {
- socket.close();
}
+ processStream();
} catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) {
@@ -344,6 +350,66 @@ public class XmppConnection implements Runnable {
}
}
+ /**
+ * Starts xmpp protocol, call after connecting to socket
+ * @return true if server returns with valid xmpp, false otherwise
+ * @throws IOException Unknown tag on connect
+ * @throws XmlPullParserException Bad Xml
+ * @throws NoSuchAlgorithmException Other error
+ */
+ private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException {
+ tagWriter.setOutputStream(socket.getOutputStream());
+ tagReader.setInputStream(socket.getInputStream());
+ tagWriter.beginDocument();
+ sendStartStream();
+ Tag nextTag;
+ while ((nextTag = tagReader.readTag()) != null) {
+ if (nextTag.isStart("stream")) {
+ return true;
+ } else {
+ throw new IOException("unknown tag on connect");
+ }
+ }
+ if (socket.isConnected()) {
+ socket.close();
+ }
+ return false;
+ }
+
+ private static class TlsFactoryVerifier {
+ private final SSLSocketFactory factory;
+ private final HostnameVerifier verifier;
+
+ public TlsFactoryVerifier(final SSLSocketFactory factory, final HostnameVerifier verifier) throws IOException {
+ this.factory = factory;
+ this.verifier = verifier;
+ if (factory == null || verifier == null) {
+ throw new IOException("could not setup ssl");
+ }
+ }
+ }
+
+ private TlsFactoryVerifier getTlsFactoryVerifier() throws NoSuchAlgorithmException, KeyManagementException, IOException {
+ final SSLContext sc = SSLContext.getInstance("TLS");
+ MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager();
+ KeyManager[] keyManager;
+ if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) {
+ keyManager = new KeyManager[]{mKeyManager};
+ } else {
+ keyManager = null;
+ }
+ sc.init(keyManager, new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()}, mXmppConnectionService.getRNG());
+ final SSLSocketFactory factory = sc.getSocketFactory();
+ final HostnameVerifier verifier;
+ if (mInteractive) {
+ verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier());
+ } else {
+ verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier());
+ }
+
+ return new TlsFactoryVerifier(factory, verifier);
+ }
+
@Override
public void run() {
try {
@@ -605,53 +671,27 @@ public class XmppConnection implements Runnable {
tagWriter.writeTag(startTLS);
}
+
+
private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException {
tagReader.readTag();
try {
- final SSLContext sc = SSLContext.getInstance("TLS");
- MemorizingTrustManager trustManager = this.mXmppConnectionService.getMemorizingTrustManager();
- KeyManager[] keyManager;
- if (account.getPrivateKeyAlias() != null && account.getPassword().isEmpty()) {
- keyManager = new KeyManager[]{ mKeyManager };
- } else {
- keyManager = null;
- }
- sc.init(keyManager,new X509TrustManager[]{mInteractive ? trustManager : trustManager.getNonInteractive()},mXmppConnectionService.getRNG());
- final SSLSocketFactory factory = sc.getSocketFactory();
- final HostnameVerifier verifier;
- if (mInteractive) {
- verifier = trustManager.wrapHostnameVerifier(new XmppDomainVerifier());
- } else {
- verifier = trustManager.wrapHostnameVerifierNonInteractive(new XmppDomainVerifier());
- }
+ final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
final InetAddress address = socket == null ? null : socket.getInetAddress();
- if (factory == null || address == null || verifier == null) {
+ if (address == null) {
throw new IOException("could not setup ssl");
}
- final SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket,address.getHostAddress(), socket.getPort(),true);
+ final SSLSocket sslSocket = (SSLSocket) tlsFactoryVerifier.factory.createSocket(socket, address.getHostAddress(), socket.getPort(), true);
if (sslSocket == null) {
throw new IOException("could not initialize ssl socket");
}
- final String[] supportProtocols;
- final Collection<String> supportedProtocols = new LinkedList<>(
- Arrays.asList(sslSocket.getSupportedProtocols()));
- supportedProtocols.remove("SSLv3");
- supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]);
-
- sslSocket.setEnabledProtocols(supportProtocols);
-
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sslSocket.getSupportedCipherSuites());
- //Log.d(Config.LOGTAG, "Using ciphers: " + Arrays.toString(cipherSuites));
- if (cipherSuites.length > 0) {
- sslSocket.setEnabledCipherSuites(cipherSuites);
- }
+ SSLSocketHelper.setSecurity(sslSocket);
- if (!verifier.verify(account.getServer().getDomainpart(),sslSocket.getSession())) {
+ if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), sslSocket.getSession())) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": TLS certificate verification failed");
throw new SecurityException();
}