aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java/de/pixart/messenger/ui
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/de/pixart/messenger/ui')
-rw-r--r--src/main/java/de/pixart/messenger/ui/AboutActivity.java15
-rw-r--r--src/main/java/de/pixart/messenger/ui/AboutPreference.java32
-rw-r--r--src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java124
-rw-r--r--src/main/java/de/pixart/messenger/ui/BlockContactDialog.java41
-rw-r--r--src/main/java/de/pixart/messenger/ui/BlocklistActivity.java74
-rw-r--r--src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java122
-rw-r--r--src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java246
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java683
-rw-r--r--src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java578
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationActivity.java2044
-rw-r--r--src/main/java/de/pixart/messenger/ui/ConversationFragment.java1420
-rw-r--r--src/main/java/de/pixart/messenger/ui/EditAccountActivity.java1070
-rw-r--r--src/main/java/de/pixart/messenger/ui/EditMessage.java92
-rw-r--r--src/main/java/de/pixart/messenger/ui/EnterJidDialog.java127
-rw-r--r--src/main/java/de/pixart/messenger/ui/ExportLogsPreference.java36
-rw-r--r--src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java116
-rw-r--r--src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java383
-rw-r--r--src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java335
-rw-r--r--src/main/java/de/pixart/messenger/ui/RecordingActivity.java157
-rw-r--r--src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java232
-rw-r--r--src/main/java/de/pixart/messenger/ui/SettingsActivity.java235
-rw-r--r--src/main/java/de/pixart/messenger/ui/SettingsFragment.java65
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java240
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShareWithActivity.java349
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java176
-rw-r--r--src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java156
-rw-r--r--src/main/java/de/pixart/messenger/ui/StartConversationActivity.java1039
-rw-r--r--src/main/java/de/pixart/messenger/ui/StartUI.java109
-rw-r--r--src/main/java/de/pixart/messenger/ui/TimePreference.java105
-rw-r--r--src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java337
-rw-r--r--src/main/java/de/pixart/messenger/ui/UiCallback.java11
-rw-r--r--src/main/java/de/pixart/messenger/ui/UpdaterActivity.java336
-rw-r--r--src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java445
-rw-r--r--src/main/java/de/pixart/messenger/ui/WelcomeActivity.java208
-rw-r--r--src/main/java/de/pixart/messenger/ui/XmppActivity.java1389
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java59
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java231
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java70
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java182
-rw-r--r--src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java919
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java80
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java30
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java93
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java44
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java97
-rw-r--r--src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java72
-rw-r--r--src/main/java/de/pixart/messenger/ui/widget/Switch.java68
47 files changed, 15072 insertions, 0 deletions
diff --git a/src/main/java/de/pixart/messenger/ui/AboutActivity.java b/src/main/java/de/pixart/messenger/ui/AboutActivity.java
new file mode 100644
index 000000000..b3b296c42
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/AboutActivity.java
@@ -0,0 +1,15 @@
+package de.pixart.messenger.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import de.pixart.messenger.R;
+
+public class AboutActivity extends Activity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_about);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/AboutPreference.java b/src/main/java/de/pixart/messenger/ui/AboutPreference.java
new file mode 100644
index 000000000..d4cfa982b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/AboutPreference.java
@@ -0,0 +1,32 @@
+package de.pixart.messenger.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+import de.pixart.messenger.utils.PhoneHelper;
+
+public class AboutPreference extends Preference {
+ public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
+ setSummary();
+ }
+
+ public AboutPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ setSummary();
+ }
+
+ @Override
+ protected void onClick() {
+ super.onClick();
+ final Intent intent = new Intent(getContext(), AboutActivity.class);
+ getContext().startActivity(intent);
+ }
+
+ private void setSummary() {
+ setSummary("Pix-Art Messenger " + PhoneHelper.getVersionName(getContext()));
+ }
+}
+
diff --git a/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java
new file mode 100644
index 000000000..9b12e43ef
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java
@@ -0,0 +1,124 @@
+package de.pixart.messenger.ui;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.ui.adapter.ListItemAdapter;
+
+public abstract class AbstractSearchableListItemActivity extends XmppActivity {
+ private ListView mListView;
+ private final List<ListItem> listItems = new ArrayList<>();
+ private ArrayAdapter<ListItem> mListItemsAdapter;
+
+ private EditText mSearchEditText;
+
+ private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
+
+ @Override
+ public boolean onMenuItemActionExpand(final MenuItem item) {
+ mSearchEditText.post(new Runnable() {
+
+ @Override
+ public void run() {
+ mSearchEditText.requestFocus();
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mSearchEditText,
+ InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(final MenuItem item) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ mSearchEditText.setText("");
+ filterContacts();
+ return true;
+ }
+ };
+
+ private final TextWatcher mSearchTextWatcher = new TextWatcher() {
+
+ @Override
+ public void afterTextChanged(final Editable editable) {
+ filterContacts(editable.toString());
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count,
+ final int after) {
+ }
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before,
+ final int count) {
+ }
+ };
+
+ public ListView getListView() {
+ return mListView;
+ }
+
+ public List<ListItem> getListItems() {
+ return listItems;
+ }
+
+ public EditText getSearchEditText() {
+ return mSearchEditText;
+ }
+
+ public ArrayAdapter<ListItem> getListItemAdapter() {
+ return mListItemsAdapter;
+ }
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_choose_contact);
+ mListView = (ListView) findViewById(R.id.choose_contact_list);
+ mListView.setFastScrollEnabled(true);
+ mListItemsAdapter = new ListItemAdapter(this, listItems);
+ mListView.setAdapter(mListItemsAdapter);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.choose_contact, menu);
+ final MenuItem menuSearchView = menu.findItem(R.id.action_search);
+ final View mSearchView = menuSearchView.getActionView();
+ mSearchEditText = (EditText) mSearchView
+ .findViewById(R.id.search_field);
+ mSearchEditText.addTextChangedListener(mSearchTextWatcher);
+ menuSearchView.setOnActionExpandListener(mOnActionExpandListener);
+ return true;
+ }
+
+ protected void filterContacts() {
+ filterContacts(null);
+ }
+
+ protected abstract void filterContacts(final String needle);
+
+ @Override
+ void onBackendConnected() {
+ filterContacts();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java
new file mode 100644
index 000000000..b625e8b62
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java
@@ -0,0 +1,41 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Blockable;
+import de.pixart.messenger.services.XmppConnectionService;
+
+public final class BlockContactDialog {
+ public static void show(final Context context,
+ final XmppConnectionService xmppConnectionService,
+ final Blockable blockable) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ final boolean isBlocked = blockable.isBlocked();
+ builder.setNegativeButton(R.string.cancel, null);
+
+ if (blockable.getJid().isDomainJid() || blockable.getAccount().isBlocked(blockable.getJid().toDomainJid())) {
+ builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain);
+ builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text,
+ blockable.getJid().toDomainJid()));
+ } else {
+ builder.setTitle(isBlocked ? R.string.action_unblock_contact : R.string.action_block_contact);
+ builder.setMessage(context.getResources().getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text,
+ blockable.getJid().toBareJid()));
+ }
+ builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ if (isBlocked) {
+ xmppConnectionService.sendUnblockRequest(blockable);
+ } else {
+ xmppConnectionService.sendBlockRequest(blockable);
+ }
+ }
+ });
+ builder.create().show();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java
new file mode 100644
index 000000000..cefa17de6
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/BlocklistActivity.java
@@ -0,0 +1,74 @@
+package de.pixart.messenger.ui;
+
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.View;
+import android.widget.AdapterView;
+
+import java.util.Collections;
+
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.xmpp.OnUpdateBlocklist;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class BlocklistActivity extends AbstractSearchableListItemActivity implements OnUpdateBlocklist {
+
+ private Account account = null;
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getListView().setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
+
+ @Override
+ public boolean onItemLongClick(final AdapterView<?> parent,
+ final View view,
+ final int position,
+ final long id) {
+ BlockContactDialog.show(parent.getContext(), xmppConnectionService,(Contact) getListItems().get(position));
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onBackendConnected() {
+ for (final Account account : xmppConnectionService.getAccounts()) {
+ if (account.getJid().toString().equals(getIntent().getStringExtra(EXTRA_ACCOUNT))) {
+ this.account = account;
+ break;
+ }
+ }
+ filterContacts();
+ }
+
+ @Override
+ protected void filterContacts(final String needle) {
+ getListItems().clear();
+ if (account != null) {
+ for (final Jid jid : account.getBlocklist()) {
+ final Contact contact = account.getRoster().getContact(jid);
+ if (contact.match(this, needle) && contact.isBlocked()) {
+ getListItems().add(contact);
+ }
+ }
+ Collections.sort(getListItems());
+ }
+ getListItemAdapter().notifyDataSetChanged();
+ }
+
+ protected void refreshUiReal() {
+ final Editable editable = getSearchEditText().getText();
+ if (editable != null) {
+ filterContacts(editable.toString());
+ } else {
+ filterContacts();
+ }
+ }
+
+ @Override
+ public void OnUpdateBlocklist(final OnUpdateBlocklist.Status status) {
+ refreshUi();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java
new file mode 100644
index 000000000..361caf182
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java
@@ -0,0 +1,122 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.services.XmppConnectionService;
+
+public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged {
+
+ private Button mChangePasswordButton;
+ private View.OnClickListener mOnChangePasswordButtonClicked = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mAccount != null) {
+ final String currentPassword = mCurrentPassword.getText().toString();
+ final String newPassword = mNewPassword.getText().toString();
+ final String newPasswordConfirm = mNewPasswordConfirm.getText().toString();
+ if (!mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(mAccount.getPassword())) {
+ mCurrentPassword.requestFocus();
+ mCurrentPassword.setError(getString(R.string.account_status_unauthorized));
+ } else if (!newPassword.equals(newPasswordConfirm)) {
+ mNewPasswordConfirm.requestFocus();
+ mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ } else if (newPassword.trim().isEmpty()) {
+ mNewPassword.requestFocus();
+ mNewPassword.setError(getString(R.string.password_should_not_be_empty));
+ } else {
+ mCurrentPassword.setError(null);
+ mNewPassword.setError(null);
+ mNewPasswordConfirm.setError(null);
+ xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this);
+ mChangePasswordButton.setEnabled(false);
+ mChangePasswordButton.setTextColor(getSecondaryTextColor());
+ mChangePasswordButton.setText(R.string.updating);
+ }
+ }
+ }
+ };
+ private TextView mCurrentPasswordLabel;
+ private EditText mCurrentPassword;
+ private EditText mNewPassword;
+ private EditText mNewPasswordConfirm;
+ private Account mAccount;
+
+ @Override
+ void onBackendConnected() {
+ this.mAccount = extractAccount(getIntent());
+ if (this.mAccount != null && this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
+ this.mCurrentPasswordLabel.setVisibility(View.GONE);
+ this.mCurrentPassword.setVisibility(View.GONE);
+ } else {
+ this.mCurrentPasswordLabel.setVisibility(View.VISIBLE);
+ this.mCurrentPassword.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_change_password);
+ Button mCancelButton = (Button) findViewById(R.id.left_button);
+ mCancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ finish();
+ }
+ });
+ this.mChangePasswordButton = (Button) findViewById(R.id.right_button);
+ this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked);
+ this.mCurrentPasswordLabel = (TextView) findViewById(R.id.current_password_label);
+ this.mCurrentPassword = (EditText) findViewById(R.id.current_password);
+ this.mNewPassword = (EditText) findViewById(R.id.new_password);
+ this.mNewPasswordConfirm = (EditText) findViewById(R.id.new_password_confirm);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Intent intent = getIntent();
+ String password = intent != null ? intent.getStringExtra("password") : null;
+ if (password != null) {
+ this.mNewPassword.getEditableText().clear();
+ this.mNewPassword.getEditableText().append(password);
+ }
+ }
+
+ @Override
+ public void onPasswordChangeSucceeded() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ChangePasswordActivity.this,R.string.password_changed,Toast.LENGTH_LONG).show();
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public void onPasswordChangeFailed() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mNewPassword.setError(getString(R.string.could_not_change_password));
+ mChangePasswordButton.setEnabled(true);
+ mChangePasswordButton.setTextColor(getPrimaryTextColor());
+ mChangePasswordButton.setText(R.string.change_password);
+ }
+ });
+
+ }
+
+ public void refreshUiReal() {
+
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java
new file mode 100644
index 000000000..e38433893
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java
@@ -0,0 +1,246 @@
+package de.pixart.messenger.ui;
+
+import android.app.ActionBar;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.StringRes;
+import android.view.ActionMode;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView.MultiChoiceModeListener;
+import android.widget.AdapterView;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ChooseContactActivity extends AbstractSearchableListItemActivity {
+ private List<String> mActivatedAccounts = new ArrayList<String>();
+ private List<String> mKnownHosts;
+
+ private Set<Contact> selected;
+ private Set<String> filterContacts;
+ public static final String EXTRA_TITLE_RES_ID = "extra_title_res_id";
+
+ @Override
+ public void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ filterContacts = new HashSet<>();
+ String[] contacts = getIntent().getStringArrayExtra("filter_contacts");
+ if (contacts != null) {
+ Collections.addAll(filterContacts, contacts);
+ }
+
+ if (getIntent().getBooleanExtra("multiple", false)) {
+ getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
+ getListView().setMultiChoiceModeListener(new MultiChoiceModeListener() {
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ return false;
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.select_multiple, menu);
+ selected = new HashSet<Contact>();
+ return true;
+ }
+
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.selection_submit:
+ final Intent request = getIntent();
+ final Intent data = new Intent();
+ data.putExtra("conversation",
+ request.getStringExtra("conversation"));
+ String[] selection = getSelectedContactJids();
+ data.putExtra("contacts", selection);
+ data.putExtra("multiple", true);
+ data.putExtra(EXTRA_ACCOUNT,request.getStringExtra(EXTRA_ACCOUNT));
+ data.putExtra("subject", request.getStringExtra("subject"));
+ setResult(RESULT_OK, data);
+ finish();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
+ Contact item = (Contact) getListItems().get(position);
+ if (checked) {
+ selected.add(item);
+ } else {
+ selected.remove(item);
+ }
+ int numSelected = selected.size();
+ MenuItem selectButton = mode.getMenu().findItem(R.id.selection_submit);
+ String buttonText = getResources().getQuantityString(R.plurals.select_contact,
+ numSelected, numSelected);
+ selectButton.setTitle(buttonText);
+ }
+ });
+ }
+
+ getListView().setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
+ @Override
+ public void onItemClick(final AdapterView<?> parent, final View view,
+ final int position, final long id) {
+ final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow(getSearchEditText().getWindowToken(),
+ InputMethodManager.HIDE_IMPLICIT_ONLY);
+ final Intent request = getIntent();
+ final Intent data = new Intent();
+ final ListItem mListItem = getListItems().get(position);
+ data.putExtra("contact", mListItem.getJid().toString());
+ String account = request.getStringExtra(EXTRA_ACCOUNT);
+ if (account == null && mListItem instanceof Contact) {
+ account = ((Contact) mListItem).getAccount().getJid().toBareJid().toString();
+ }
+ data.putExtra(EXTRA_ACCOUNT, account);
+ data.putExtra("conversation",
+ request.getStringExtra("conversation"));
+ data.putExtra("multiple", false);
+ data.putExtra("subject", request.getStringExtra("subject"));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+ });
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Intent intent = getIntent();
+ @StringRes
+ int res = intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID,R.string.title_activity_choose_contact) : R.string.title_activity_choose_contact;
+ ActionBar bar = getActionBar();
+ if (bar != null) {
+ try {
+ bar.setTitle(res);
+ } catch (Exception e) {
+ bar.setTitle(R.string.title_activity_choose_contact);
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ final Intent i = getIntent();
+ boolean showEnterJid = i != null && i.getBooleanExtra("show_enter_jid", false);
+ menu.findItem(R.id.action_create_contact).setVisible(showEnterJid);
+ return true;
+ }
+
+ protected void filterContacts(final String needle) {
+ getListItems().clear();
+ for (final Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ for (final Contact contact : account.getRoster().getContacts()) {
+ if (contact.showInRoster() &&
+ !filterContacts.contains(contact.getJid().toBareJid().toString())
+ && contact.match(this, needle)) {
+ getListItems().add(contact);
+ }
+ }
+ }
+ }
+ Collections.sort(getListItems());
+ getListItemAdapter().notifyDataSetChanged();
+ }
+
+ private String[] getSelectedContactJids() {
+ List<String> result = new ArrayList<>();
+ for (Contact contact : selected) {
+ result.add(contact.getJid().toString());
+ }
+ return result.toArray(new String[result.size()]);
+ }
+
+
+ public void refreshUiReal() {
+ //nothing to do. This Activity doesn't implement any listeners
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_create_contact:
+ showEnterJidDialog();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ protected void showEnterJidDialog() {
+ EnterJidDialog dialog = new EnterJidDialog(
+ this, mKnownHosts, mActivatedAccounts,
+ getString(R.string.enter_contact), getString(R.string.select),
+ null, getIntent().getStringExtra(EXTRA_ACCOUNT), true
+ );
+
+ dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
+ @Override
+ public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError {
+ final Intent request = getIntent();
+ final Intent data = new Intent();
+ data.putExtra("contact", contactJid.toString());
+ data.putExtra(EXTRA_ACCOUNT, accountJid.toString());
+ data.putExtra("conversation",
+ request.getStringExtra("conversation"));
+ data.putExtra("multiple", false);
+ data.putExtra("subject", request.getStringExtra("subject"));
+ setResult(RESULT_OK, data);
+ finish();
+
+ return true;
+ }
+ });
+
+ dialog.show();
+ }
+
+ @Override
+ void onBackendConnected() {
+ filterContacts();
+
+ this.mActivatedAccounts.clear();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ if (Config.DOMAIN_LOCK != null) {
+ this.mActivatedAccounts.add(account.getJid().getLocalpart());
+ } else {
+ this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
+ }
+ }
+ }
+ this.mKnownHosts = xmppConnectionService.getKnownHosts();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
new file mode 100644
index 000000000..ef66f0c41
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java
@@ -0,0 +1,683 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.IntentSender.SendIntentException;
+import android.os.Bundle;
+import android.view.ContextMenu;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.PgpEngine;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Bookmark;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.entities.MucOptions.User;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate;
+import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConferenceOptionsPushed {
+ public static final String ACTION_VIEW_MUC = "view_muc";
+ private Conversation mConversation;
+ private OnClickListener inviteListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ inviteToConversation(mConversation);
+ }
+ };
+ private TextView mYourNick;
+ private ImageView mYourPhoto;
+ private ImageButton mEditNickButton;
+ private TextView mRoleAffiliaton;
+ private TextView mFullJid;
+ private TextView mAccountJid;
+ private LinearLayout membersView;
+ private LinearLayout mMoreDetails;
+ private TextView mConferenceType;
+ private LinearLayout mConferenceInfoTable;
+ private TextView mConferenceInfoMam;
+ private TextView mNotifyStatusText;
+ private ImageButton mChangeConferenceSettingsButton;
+ private ImageButton mNotifyStatusButton;
+ private Button mInviteButton;
+ private String uuid = null;
+ private User mSelectedUser = null;
+
+ private boolean mAdvancedMode = false;
+
+ private UiCallback<Conversation> renameCallback = new UiCallback<Conversation>() {
+ @Override
+ public void success(Conversation object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConferenceDetailsActivity.this,getString(R.string.your_nick_has_been_changed),Toast.LENGTH_SHORT).show();
+ updateView();
+ }
+ });
+
+ }
+
+ @Override
+ public void error(final int errorCode, Conversation object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConferenceDetailsActivity.this,getString(errorCode),Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Conversation object) {
+
+ }
+ };
+
+ private OnClickListener mNotifyStatusClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
+ builder.setTitle(R.string.pref_notification_settings);
+ String[] choices = {
+ getString(R.string.notify_on_all_messages),
+ getString(R.string.notify_only_when_highlighted),
+ getString(R.string.notify_never)
+ };
+ final AtomicInteger choice;
+ if (mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0) == Long.MAX_VALUE) {
+ choice = new AtomicInteger(2);
+ } else {
+ choice = new AtomicInteger(mConversation.alwaysNotify() ? 0 : 1);
+ }
+ builder.setSingleChoiceItems(choices, choice.get(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ choice.set(which);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (choice.get() == 2) {
+ mConversation.setMutedTill(Long.MAX_VALUE);
+ } else {
+ mConversation.setMutedTill(0);
+ mConversation.setAttribute(Conversation.ATTRIBUTE_ALWAYS_NOTIFY,String.valueOf(choice.get() == 0));
+ }
+ xmppConnectionService.updateConversation(mConversation);
+ updateView();
+ }
+ });
+ builder.create().show();
+ }
+ };
+
+ private OnClickListener mChangeConferenceSettings = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final MucOptions mucOptions = mConversation.getMucOptions();
+ AlertDialog.Builder builder = new AlertDialog.Builder(ConferenceDetailsActivity.this);
+ builder.setTitle(R.string.conference_options);
+ final String[] options;
+ final boolean[] values;
+ if (mAdvancedMode) {
+ options = new String[]{
+ getString(R.string.members_only),
+ getString(R.string.moderated),
+ getString(R.string.non_anonymous)
+ };
+ values = new boolean[]{
+ mucOptions.membersOnly(),
+ mucOptions.moderated(),
+ mucOptions.nonanonymous()
+ };
+ } else {
+ options = new String[]{
+ getString(R.string.members_only),
+ getString(R.string.non_anonymous)
+ };
+ values = new boolean[]{
+ mucOptions.membersOnly(),
+ mucOptions.nonanonymous()
+ };
+ }
+ builder.setMultiChoiceItems(options,values,new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which, boolean isChecked) {
+ values[which] = isChecked;
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.confirm,new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (!mucOptions.membersOnly() && values[0]) {
+ xmppConnectionService.changeAffiliationsInConference(mConversation,
+ MucOptions.Affiliation.NONE,
+ MucOptions.Affiliation.MEMBER);
+ }
+ Bundle options = new Bundle();
+ options.putString("muc#roomconfig_membersonly", values[0] ? "1" : "0");
+ if (values.length == 2) {
+ options.putString("muc#roomconfig_whois", values[1] ? "anyone" : "moderators");
+ } else if (values.length == 3) {
+ options.putString("muc#roomconfig_moderatedroom", values[1] ? "1" : "0");
+ options.putString("muc#roomconfig_whois", values[2] ? "anyone" : "moderators");
+ }
+ options.putString("muc#roomconfig_persistentroom", "1");
+ xmppConnectionService.pushConferenceConfiguration(mConversation,
+ options,
+ ConferenceDetailsActivity.this);
+ }
+ });
+ builder.create().show();
+ }
+ };
+ private OnValueEdited onSubjectEdited = new OnValueEdited() {
+
+ @Override
+ public void onValueEdited(String value) {
+ xmppConnectionService.pushSubjectToConference(mConversation,value);
+ }
+ };
+
+ @Override
+ public void onConversationUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ public void onMucRosterUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ updateView();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_muc_details);
+ mYourNick = (TextView) findViewById(R.id.muc_your_nick);
+ mYourPhoto = (ImageView) findViewById(R.id.your_photo);
+ mEditNickButton = (ImageButton) findViewById(R.id.edit_nick_button);
+ mFullJid = (TextView) findViewById(R.id.muc_jabberid);
+ membersView = (LinearLayout) findViewById(R.id.muc_members);
+ mAccountJid = (TextView) findViewById(R.id.details_account);
+ mMoreDetails = (LinearLayout) findViewById(R.id.muc_more_details);
+ mMoreDetails.setVisibility(View.GONE);
+ mChangeConferenceSettingsButton = (ImageButton) findViewById(R.id.change_conference_button);
+ mChangeConferenceSettingsButton.setOnClickListener(this.mChangeConferenceSettings);
+ mInviteButton = (Button) findViewById(R.id.invite);
+ mInviteButton.setOnClickListener(inviteListener);
+ mConferenceType = (TextView) findViewById(R.id.muc_conference_type);
+ if (getActionBar() != null) {
+ getActionBar().setHomeButtonEnabled(true);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ mEditNickButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ quickEdit(mConversation.getMucOptions().getActualNick(),
+ 0,
+ new OnValueEdited() {
+
+ @Override
+ public void onValueEdited(String value) {
+ xmppConnectionService.renameInMuc(mConversation,value,renameCallback);
+ }
+ });
+ }
+ });
+ this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false);
+ this.mConferenceInfoTable = (LinearLayout) findViewById(R.id.muc_info_more);
+ mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
+ this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam);
+ this.mNotifyStatusButton = (ImageButton) findViewById(R.id.notification_status_button);
+ this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener);
+ this.mNotifyStatusText = (TextView) findViewById(R.id.notification_status_text);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem menuItem) {
+ switch (menuItem.getItemId()) {
+ case android.R.id.home:
+ finish();
+ break;
+ case R.id.action_edit_subject:
+ if (mConversation != null) {
+ quickEdit(mConversation.getMucOptions().getSubject(),
+ R.string.edit_subject_hint,
+ this.onSubjectEdited);
+ }
+ break;
+ case R.id.action_share:
+ shareUri();
+ break;
+ case R.id.action_save_as_bookmark:
+ saveAsBookmark();
+ break;
+ case R.id.action_delete_bookmark:
+ deleteBookmark();
+ break;
+ case R.id.action_advanced_mode:
+ this.mAdvancedMode = !menuItem.isChecked();
+ menuItem.setChecked(this.mAdvancedMode);
+ getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit();
+ mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE);
+ invalidateOptionsMenu();
+ updateView();
+ break;
+ }
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ protected String getShareableUri() {
+ if (mConversation != null) {
+ return "xmpp:" + mConversation.getJid().toBareJid().toString() + "?join";
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
+ MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
+ MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
+ MenuItem menuItemChangeSubject = menu.findItem(R.id.action_edit_subject);
+ menuItemAdvancedMode.setChecked(mAdvancedMode);
+ if (mConversation == null) {
+ return true;
+ }
+ Account account = mConversation.getAccount();
+ if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) {
+ menuItemSaveBookmark.setVisible(false);
+ menuItemDeleteBookmark.setVisible(true);
+ } else {
+ menuItemDeleteBookmark.setVisible(false);
+ menuItemSaveBookmark.setVisible(true);
+ }
+ menuItemChangeSubject.setVisible(mConversation.getMucOptions().canChangeSubject());
+ return true;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.muc_details, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
+ Object tag = v.getTag();
+ if (tag instanceof User) {
+ getMenuInflater().inflate(R.menu.muc_details_context,menu);
+ final User user = (User) tag;
+ final User self = mConversation.getMucOptions().getSelf();
+ this.mSelectedUser = user;
+ String name;
+ final Contact contact = user.getContact();
+ if (contact != null) {
+ name = contact.getDisplayName();
+ } else if (user.getRealJid() != null){
+ name = user.getRealJid().toBareJid().toString();
+ } else {
+ name = user.getName();
+ }
+ menu.setHeaderTitle(name);
+ if (user.getRealJid() != null) {
+ MenuItem showContactDetails = menu.findItem(R.id.action_contact_details);
+ MenuItem startConversation = menu.findItem(R.id.start_conversation);
+ MenuItem giveMembership = menu.findItem(R.id.give_membership);
+ MenuItem removeMembership = menu.findItem(R.id.remove_membership);
+ MenuItem giveAdminPrivileges = menu.findItem(R.id.give_admin_privileges);
+ MenuItem removeAdminPrivileges = menu.findItem(R.id.remove_admin_privileges);
+ MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room);
+ MenuItem banFromConference = menu.findItem(R.id.ban_from_conference);
+ MenuItem invite = menu.findItem(R.id.invite);
+ startConversation.setVisible(true);
+ if (contact != null) {
+ showContactDetails.setVisible(true);
+ }
+ if (user.getRole() == MucOptions.Role.NONE) {
+ invite.setVisible(true);
+ }
+ if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) &&
+ self.getAffiliation().outranks(user.getAffiliation())) {
+ if (mAdvancedMode) {
+ if (user.getAffiliation() == MucOptions.Affiliation.NONE) {
+ giveMembership.setVisible(true);
+ } else {
+ removeMembership.setVisible(true);
+ }
+ banFromConference.setVisible(true);
+ } else {
+ removeFromRoom.setVisible(true);
+ }
+ if (user.getAffiliation() != MucOptions.Affiliation.ADMIN) {
+ giveAdminPrivileges.setVisible(true);
+ } else {
+ removeAdminPrivileges.setVisible(true);
+ }
+ }
+ } else {
+ MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message);
+ sendPrivateMessage.setVisible(user.getRole().ranks(MucOptions.Role.PARTICIPANT));
+ }
+
+ }
+ super.onCreateContextMenu(menu, v, menuInfo);
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ Jid jid = mSelectedUser.getRealJid();
+ switch (item.getItemId()) {
+ case R.id.action_contact_details:
+ Contact contact = mSelectedUser.getContact();
+ if (contact != null) {
+ switchToContactDetails(contact);
+ }
+ return true;
+ case R.id.start_conversation:
+ startConversation(mSelectedUser);
+ return true;
+ case R.id.give_admin_privileges:
+ xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.ADMIN,this);
+ return true;
+ case R.id.give_membership:
+ xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER,this);
+ return true;
+ case R.id.remove_membership:
+ xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.NONE,this);
+ return true;
+ case R.id.remove_admin_privileges:
+ xmppConnectionService.changeAffiliationInConference(mConversation, jid, MucOptions.Affiliation.MEMBER,this);
+ return true;
+ case R.id.remove_from_room:
+ removeFromRoom(mSelectedUser);
+ return true;
+ case R.id.ban_from_conference:
+ xmppConnectionService.changeAffiliationInConference(mConversation,jid, MucOptions.Affiliation.OUTCAST,this);
+ if (mSelectedUser.getRole() != MucOptions.Role.NONE) {
+ xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, this);
+ }
+ return true;
+ case R.id.send_private_message:
+ privateMsgInMuc(mConversation,mSelectedUser.getName());
+ return true;
+ case R.id.invite:
+ xmppConnectionService.directInvite(mConversation, jid);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ private void removeFromRoom(final User user) {
+ if (mConversation.getMucOptions().membersOnly()) {
+ xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.NONE,this);
+ if (user.getRole() != MucOptions.Role.NONE) {
+ xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
+ }
+ } else {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.ban_from_conference);
+ builder.setMessage(getString(R.string.removing_from_public_conference,user.getName()));
+ builder.setNegativeButton(R.string.cancel,null);
+ builder.setPositiveButton(R.string.ban_now,new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ xmppConnectionService.changeAffiliationInConference(mConversation,user.getRealJid(), MucOptions.Affiliation.OUTCAST,ConferenceDetailsActivity.this);
+ if (user.getRole() != MucOptions.Role.NONE) {
+ xmppConnectionService.changeRoleInConference(mConversation, mSelectedUser.getName(), MucOptions.Role.NONE, ConferenceDetailsActivity.this);
+ }
+ }
+ });
+ builder.create().show();
+ }
+ }
+
+ protected void startConversation(User user) {
+ if (user.getRealJid() != null) {
+ Conversation conversation = xmppConnectionService.findOrCreateConversation(this.mConversation.getAccount(),user.getRealJid().toBareJid(),false);
+ switchToConversation(conversation);
+ }
+ }
+
+ protected void saveAsBookmark() {
+ xmppConnectionService.saveConversationAsBookmark(mConversation,
+ mConversation.getMucOptions().getSubject());
+ }
+
+ protected void deleteBookmark() {
+ Account account = mConversation.getAccount();
+ Bookmark bookmark = mConversation.getBookmark();
+ bookmark.unregisterConversation();
+ account.getBookmarks().remove(bookmark);
+ xmppConnectionService.pushBookmarks(account);
+ }
+
+ @Override
+ void onBackendConnected() {
+ if (mPendingConferenceInvite != null) {
+ mPendingConferenceInvite.execute(this);
+ mPendingConferenceInvite = null;
+ }
+ if (getIntent().getAction().equals(ACTION_VIEW_MUC)) {
+ this.uuid = getIntent().getExtras().getString("uuid");
+ }
+ if (uuid != null) {
+ this.mConversation = xmppConnectionService
+ .findConversationByUuid(uuid);
+ if (this.mConversation != null) {
+ updateView();
+ }
+ }
+ }
+
+ private void updateView() {
+ final MucOptions mucOptions = mConversation.getMucOptions();
+ final User self = mucOptions.getSelf();
+ String account;
+ if (Config.DOMAIN_LOCK != null) {
+ account = mConversation.getAccount().getJid().getLocalpart();
+ } else {
+ account = mConversation.getAccount().getJid().toBareJid().toString();
+ }
+ mAccountJid.setText(getString(R.string.using_account, account));
+ mYourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48)));
+ setTitle(mConversation.getName());
+ mFullJid.setText(mConversation.getJid().toBareJid().toString());
+ mYourNick.setText(mucOptions.getActualNick());
+ mRoleAffiliaton = (TextView) findViewById(R.id.muc_role);
+ if (mucOptions.online()) {
+ mMoreDetails.setVisibility(View.VISIBLE);
+ final String status = getStatus(self);
+ if (status != null) {
+ mRoleAffiliaton.setVisibility(View.VISIBLE);
+ mRoleAffiliaton.setText(status);
+ } else {
+ mRoleAffiliaton.setVisibility(View.GONE);
+ }
+ if (mucOptions.membersOnly()) {
+ mConferenceType.setText(R.string.private_conference);
+ } else {
+ mConferenceType.setText(R.string.public_conference);
+ }
+ if (mucOptions.mamSupport()) {
+ mConferenceInfoMam.setText(R.string.server_info_available);
+ } else {
+ mConferenceInfoMam.setText(R.string.server_info_unavailable);
+ }
+ if (self.getAffiliation().ranks(MucOptions.Affiliation.OWNER)) {
+ mChangeConferenceSettingsButton.setVisibility(View.VISIBLE);
+ } else {
+ mChangeConferenceSettingsButton.setVisibility(View.GONE);
+ }
+ }
+
+ long mutedTill = mConversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL,0);
+ if (mutedTill == Long.MAX_VALUE) {
+ mNotifyStatusText.setText(R.string.notify_never);
+ mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_off_grey600_24dp);
+ } else if (System.currentTimeMillis() < mutedTill) {
+ mNotifyStatusText.setText(R.string.notify_paused);
+ mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp);
+ } else if (mConversation.alwaysNotify()) {
+ mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp);
+ mNotifyStatusText.setText(R.string.notify_on_all_messages);
+ } else {
+ mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_none_grey600_24dp);
+ mNotifyStatusText.setText(R.string.notify_only_when_highlighted);
+ }
+
+ LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ membersView.removeAllViews();
+ final ArrayList<User> users = mucOptions.getUsers();
+ Collections.sort(users);
+ for (final User user : users) {
+ View view = inflater.inflate(R.layout.contact, membersView,false);
+ this.setListItemBackgroundOnView(view);
+ view.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ highlightInMuc(mConversation, user.getName());
+ }
+ });
+ registerForContextMenu(view);
+ view.setTag(user);
+ TextView tvDisplayName = (TextView) view.findViewById(R.id.contact_display_name);
+ TextView tvKey = (TextView) view.findViewById(R.id.key);
+ TextView tvStatus = (TextView) view.findViewById(R.id.contact_jid);
+ if (mAdvancedMode && user.getPgpKeyId() != 0) {
+ tvKey.setVisibility(View.VISIBLE);
+ tvKey.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ viewPgpKey(user);
+ }
+ });
+ tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId()));
+ }
+ Contact contact = user.getContact();
+ String name = user.getName();
+ if (contact != null) {
+ tvDisplayName.setText(contact.getDisplayName());
+ tvStatus.setText((name != null ? name+ " \u2022 " : "") + getStatus(user));
+ } else {
+ tvDisplayName.setText(name == null ? "" : name);
+ tvStatus.setText(getStatus(user));
+
+ }
+ ImageView iv = (ImageView) view.findViewById(R.id.contact_photo);
+ iv.setImageBitmap(avatarService().get(user, getPixel(48), false));
+ membersView.addView(view);
+ if (mConversation.getMucOptions().canInvite()) {
+ mInviteButton.setVisibility(View.VISIBLE);
+ } else {
+ mInviteButton.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ private String getStatus(User user) {
+ if (mAdvancedMode) {
+ StringBuilder builder = new StringBuilder();
+ builder.append(getString(user.getAffiliation().getResId()));
+ builder.append(" (");
+ builder.append(getString(user.getRole().getResId()));
+ builder.append(')');
+ return builder.toString();
+ } else {
+ return getString(user.getAffiliation().getResId());
+ }
+ }
+
+ private void viewPgpKey(User user) {
+ PgpEngine pgp = xmppConnectionService.getPgpEngine();
+ if (pgp != null) {
+ PendingIntent intent = pgp.getIntentForKey(
+ mConversation.getAccount(), user.getPgpKeyId());
+ if (intent != null) {
+ try {
+ startIntentSenderForResult(intent.getIntentSender(), 0,
+ null, 0, 0, 0);
+ } catch (SendIntentException ignored) {
+
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onAffiliationChangedSuccessful(Jid jid) {
+ refreshUi();
+ }
+
+ @Override
+ public void onAffiliationChangeFailed(Jid jid, int resId) {
+ displayToast(getString(resId,jid.toBareJid().toString()));
+ }
+
+ @Override
+ public void onRoleChangedSuccessful(String nick) {
+
+ }
+
+ @Override
+ public void onRoleChangeFailed(String nick, int resId) {
+ displayToast(getString(resId,nick));
+ }
+
+ @Override
+ public void onPushSucceeded() {
+ displayToast(getString(R.string.modified_conference_options));
+ }
+
+ @Override
+ public void onPushFailed() {
+ displayToast(getString(R.string.could_not_modify_conference_options));
+ }
+
+ private void displayToast(final String msg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConferenceDetailsActivity.this,msg,Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
new file mode 100644
index 000000000..d1250c1d6
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java
@@ -0,0 +1,578 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.content.SharedPreferences;
+import android.net.Uri;
+import android.os.Bundle;
+import android.preference.PreferenceManager;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.Intents;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.QuickContactBadge;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import java.security.cert.X509Certificate;
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.PgpEngine;
+import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
+import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.utils.UIHelper;
+import de.pixart.messenger.xmpp.OnKeyStatusUpdated;
+import de.pixart.messenger.xmpp.OnUpdateBlocklist;
+import de.pixart.messenger.xmpp.XmppConnection;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
+ public static final String ACTION_VIEW_CONTACT = "view_contact";
+
+ private Conversation mConversation;
+ private Contact contact;
+ private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ xmppConnectionService.deleteContactOnServer(contact);
+ }
+ };
+ private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ if (isChecked) {
+ if (contact
+ .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ xmppConnectionService.sendPresencePacket(contact
+ .getAccount(),
+ xmppConnectionService.getPresenceGenerator()
+ .sendPresenceUpdatesTo(contact));
+ } else {
+ contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
+ }
+ } else {
+ contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
+ xmppConnectionService.sendPresencePacket(contact.getAccount(),
+ xmppConnectionService.getPresenceGenerator()
+ .stopPresenceUpdatesTo(contact));
+ }
+ }
+ };
+ private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() {
+
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView,
+ boolean isChecked) {
+ if (isChecked) {
+ xmppConnectionService.sendPresencePacket(contact.getAccount(),
+ xmppConnectionService.getPresenceGenerator()
+ .requestPresenceUpdatesFrom(contact));
+ } else {
+ xmppConnectionService.sendPresencePacket(contact.getAccount(),
+ xmppConnectionService.getPresenceGenerator()
+ .stopPresenceUpdatesFrom(contact));
+ }
+ }
+ };
+ private Jid accountJid;
+ private TextView lastseen;
+ private Jid contactJid;
+ private TextView contactJidTv;
+ private TextView accountJidTv;
+ private TextView statusMessage;
+ private CheckBox send;
+ private CheckBox receive;
+ private Button addContactButton;
+ private QuickContactBadge badge;
+ private LinearLayout keys;
+ private LinearLayout tags;
+ private boolean showDynamicTags = false;
+ private boolean showLastSeen = false;
+ private String messageFingerprint;
+
+ private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
+ intent.setType(Contacts.CONTENT_ITEM_TYPE);
+ intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toString());
+ intent.putExtra(Intents.Insert.IM_PROTOCOL,
+ CommonDataKinds.Im.PROTOCOL_JABBER);
+ intent.putExtra("finishActivityOnSaveCompleted", true);
+ ContactDetailsActivity.this.startActivityForResult(intent, 0);
+ }
+ };
+
+ private OnClickListener onBadgeClick = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (contact.getSystemAccount() == null) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ ContactDetailsActivity.this);
+ builder.setTitle(getString(R.string.action_add_phone_book));
+ builder.setMessage(getString(R.string.add_phone_book_text,
+ contact.getDisplayJid()));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.add), addToPhonebook);
+ builder.create().show();
+ } else {
+ String[] systemAccount = contact.getSystemAccount().split("#");
+ long id = Long.parseLong(systemAccount[0]);
+ Uri uri = ContactsContract.Contacts.getLookupUri(id, systemAccount[1]);
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(uri);
+ startActivity(intent);
+ }
+ }
+ };
+
+ @Override
+ public void onRosterUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ public void OnUpdateBlocklist(final Status status) {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ populateView();
+ }
+
+ @Override
+ protected String getShareableUri() {
+ if (contact != null) {
+ return "xmpp:"+contact.getJid().toBareJid().toString();
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
+ try {
+ this.accountJid = Jid.fromString(getIntent().getExtras().getString(EXTRA_ACCOUNT));
+ } catch (final InvalidJidException ignored) {
+ }
+ try {
+ this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
+ } catch (final InvalidJidException ignored) {
+ }
+ }
+ this.messageFingerprint = getIntent().getStringExtra("fingerprint");
+ setContentView(R.layout.activity_contact_details);
+
+ contactJidTv = (TextView) findViewById(R.id.details_contactjid);
+ accountJidTv = (TextView) findViewById(R.id.details_account);
+ lastseen = (TextView) findViewById(R.id.details_lastseen);
+ statusMessage = (TextView) findViewById(R.id.status_message);
+ send = (CheckBox) findViewById(R.id.details_send_presence);
+ receive = (CheckBox) findViewById(R.id.details_receive_presence);
+ badge = (QuickContactBadge) findViewById(R.id.details_contact_badge);
+ addContactButton = (Button) findViewById(R.id.add_contact_button);
+ addContactButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showAddToRosterDialog(contact);
+ }
+ });
+ keys = (LinearLayout) findViewById(R.id.details_contact_keys);
+ tags = (LinearLayout) findViewById(R.id.tags);
+ if (getActionBar() != null) {
+ getActionBar().setHomeButtonEnabled(true);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
+ this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ this.showLastSeen = preferences.getBoolean("last_activity", true);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem menuItem) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ switch (menuItem.getItemId()) {
+ case android.R.id.home:
+ finish();
+ break;
+ case R.id.action_share:
+ shareUri();
+ break;
+ case R.id.action_delete_contact:
+ builder.setTitle(getString(R.string.action_delete_contact))
+ .setMessage(
+ getString(R.string.remove_contact_text,
+ contact.getDisplayJid()))
+ .setPositiveButton(getString(R.string.delete),
+ removeFromRoster).create().show();
+ break;
+ case R.id.action_edit_contact:
+ if (contact.getSystemAccount() == null) {
+ quickEdit(contact.getDisplayName(), 0, new OnValueEdited() {
+
+ @Override
+ public void onValueEdited(String value) {
+ contact.setServerName(value);
+ ContactDetailsActivity.this.xmppConnectionService
+ .pushContactToServer(contact);
+ populateView();
+ }
+ });
+ } else {
+ Intent intent = new Intent(Intent.ACTION_EDIT);
+ String[] systemAccount = contact.getSystemAccount().split("#");
+ long id = Long.parseLong(systemAccount[0]);
+ Uri uri = Contacts.getLookupUri(id, systemAccount[1]);
+ intent.setDataAndType(uri, Contacts.CONTENT_ITEM_TYPE);
+ intent.putExtra("finishActivityOnSaveCompleted", true);
+ startActivity(intent);
+ }
+ break;
+ case R.id.action_block:
+ BlockContactDialog.show(this, xmppConnectionService, contact);
+ break;
+ case R.id.action_unblock:
+ BlockContactDialog.show(this, xmppConnectionService, contact);
+ break;
+ }
+ return super.onOptionsItemSelected(menuItem);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ getMenuInflater().inflate(R.menu.contact_details, menu);
+ MenuItem block = menu.findItem(R.id.action_block);
+ MenuItem unblock = menu.findItem(R.id.action_unblock);
+ MenuItem edit = menu.findItem(R.id.action_edit_contact);
+ MenuItem delete = menu.findItem(R.id.action_delete_contact);
+ if (contact == null) {
+ return true;
+ }
+ final XmppConnection connection = contact.getAccount().getXmppConnection();
+ if (connection != null && connection.getFeatures().blocking()) {
+ if (this.contact.isBlocked()) {
+ block.setVisible(false);
+ } else {
+ unblock.setVisible(false);
+ }
+ } else {
+ unblock.setVisible(false);
+ block.setVisible(false);
+ }
+ if (!contact.showInRoster()) {
+ edit.setVisible(false);
+ delete.setVisible(false);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ private void populateView() {
+ invalidateOptionsMenu();
+ setTitle(contact.getDisplayName());
+ if (contact.showInRoster()) {
+ send.setVisibility(View.VISIBLE);
+ receive.setVisibility(View.VISIBLE);
+ addContactButton.setVisibility(View.GONE);
+ send.setOnCheckedChangeListener(null);
+ receive.setOnCheckedChangeListener(null);
+
+ List<String> statusMessages = contact.getPresences().getStatusMessages();
+ if (statusMessages.size() == 0) {
+ statusMessage.setVisibility(View.GONE);
+ } else {
+ StringBuilder builder = new StringBuilder();
+ statusMessage.setVisibility(View.VISIBLE);
+ int s = statusMessages.size();
+ for(int i = 0; i < s; ++i) {
+ if (s > 1) {
+ builder.append("• ");
+ }
+ builder.append(statusMessages.get(i));
+ if (i < s - 1) {
+ builder.append("\n");
+ }
+ }
+ statusMessage.setText(builder);
+ }
+
+ if (contact.getOption(Contact.Options.FROM)) {
+ send.setText(R.string.send_presence_updates);
+ send.setChecked(true);
+ } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ send.setChecked(false);
+ send.setText(R.string.send_presence_updates);
+ } else {
+ send.setText(R.string.preemptively_grant);
+ if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
+ send.setChecked(true);
+ } else {
+ send.setChecked(false);
+ }
+ }
+ if (contact.getOption(Contact.Options.TO)) {
+ receive.setText(R.string.receive_presence_updates);
+ receive.setChecked(true);
+ } else {
+ receive.setText(R.string.ask_for_presence_updates);
+ if (contact.getOption(Contact.Options.ASKING)) {
+ receive.setChecked(true);
+ } else {
+ receive.setChecked(false);
+ }
+ }
+ if (contact.getAccount().isOnlineAndConnected()) {
+ receive.setEnabled(true);
+ send.setEnabled(true);
+ } else {
+ receive.setEnabled(false);
+ send.setEnabled(false);
+ }
+ send.setOnCheckedChangeListener(this.mOnSendCheckedChange);
+ receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange);
+ } else {
+ addContactButton.setVisibility(View.VISIBLE);
+ send.setVisibility(View.GONE);
+ receive.setVisibility(View.GONE);
+ statusMessage.setVisibility(View.GONE);
+ }
+
+ if (contact.isBlocked() && !this.showDynamicTags) {
+ lastseen.setVisibility(View.VISIBLE);
+ lastseen.setText(R.string.contact_blocked);
+ } else {
+ if (showLastSeen && contact.getLastseen() > 0) {
+ lastseen.setVisibility(View.VISIBLE);
+ lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen()));
+ } else {
+ lastseen.setVisibility(View.GONE);
+ }
+ }
+
+ if (contact.getPresences().size() > 1) {
+ contactJidTv.setText(contact.getDisplayJid() + " ("
+ + contact.getPresences().size() + ")");
+ } else {
+ contactJidTv.setText(contact.getDisplayJid());
+ }
+ String account;
+ if (Config.DOMAIN_LOCK != null) {
+ account = contact.getAccount().getJid().getLocalpart();
+ } else {
+ account = contact.getAccount().getJid().toBareJid().toString();
+ }
+ accountJidTv.setText(getString(R.string.using_account, account));
+ badge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE)));
+ badge.setOnClickListener(this.onBadgeClick);
+
+ keys.removeAllViews();
+ boolean hasKeys = false;
+ LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ if (Config.supportOtr()) {
+ for (final String otrFingerprint : contact.getOtrFingerprints()) {
+ hasKeys = true;
+ View view = inflater.inflate(R.layout.contact_key, keys, false);
+ TextView key = (TextView) view.findViewById(R.id.key);
+ TextView keyType = (TextView) view.findViewById(R.id.key_type);
+ ImageButton removeButton = (ImageButton) view
+ .findViewById(R.id.button_remove);
+ removeButton.setVisibility(View.VISIBLE);
+ key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
+ if (otrFingerprint != null && otrFingerprint.equals(messageFingerprint)) {
+ keyType.setText(R.string.otr_fingerprint_selected_message);
+ keyType.setTextColor(getResources().getColor(R.color.accent));
+ } else {
+ keyType.setText(R.string.otr_fingerprint);
+ }
+ keys.addView(view);
+ removeButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ confirmToDeleteFingerprint(otrFingerprint);
+ }
+ });
+ }
+ }
+ if (Config.supportOmemo()) {
+ for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
+ boolean highlight = fingerprint.equals(messageFingerprint);
+ hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight, new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onOmemoKeyClicked(contact.getAccount(), fingerprint);
+ }
+ });
+ }
+ }
+ if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) {
+ hasKeys = true;
+ View view = inflater.inflate(R.layout.contact_key, keys, false);
+ TextView key = (TextView) view.findViewById(R.id.key);
+ TextView keyType = (TextView) view.findViewById(R.id.key_type);
+ keyType.setText(R.string.openpgp_key_id);
+ if ("pgp".equals(messageFingerprint)) {
+ keyType.setTextColor(getResources().getColor(R.color.accent));
+ }
+ key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
+ view.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService
+ .getPgpEngine();
+ if (pgp != null) {
+ PendingIntent intent = pgp.getIntentForKey(contact);
+ if (intent != null) {
+ try {
+ startIntentSenderForResult(
+ intent.getIntentSender(), 0, null, 0,
+ 0, 0);
+ } catch (SendIntentException e) {
+
+ }
+ }
+ }
+ }
+ });
+ keys.addView(view);
+ }
+ if (hasKeys) {
+ keys.setVisibility(View.VISIBLE);
+ } else {
+ keys.setVisibility(View.GONE);
+ }
+
+ List<ListItem.Tag> tagList = contact.getTags(this);
+ if (tagList.size() == 0 || !this.showDynamicTags) {
+ tags.setVisibility(View.GONE);
+ } else {
+ tags.setVisibility(View.VISIBLE);
+ tags.removeAllViewsInLayout();
+ for(final ListItem.Tag tag : tagList) {
+ final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tags,false);
+ tv.setText(tag.getName());
+ tv.setBackgroundColor(tag.getColor());
+ tags.addView(tv);
+ }
+ }
+ }
+
+ private void onOmemoKeyClicked(Account account, String fingerprint) {
+ final XmppAxolotlSession.Trust trust = account.getAxolotlService().getFingerprintTrust(fingerprint);
+ if (Config.X509_VERIFICATION && trust != null && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
+ X509Certificate x509Certificate = account.getAxolotlService().getFingerprintCertificate(fingerprint);
+ if (x509Certificate != null) {
+ showCertificateInformationDialog(CryptoHelper.extractCertificateInformation(x509Certificate));
+ } else {
+ Toast.makeText(this,R.string.certificate_not_found, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ private void showCertificateInformationDialog(Bundle bundle) {
+ View view = getLayoutInflater().inflate(R.layout.certificate_information, null);
+ final String not_available = getString(R.string.certicate_info_not_available);
+ TextView subject_cn = (TextView) view.findViewById(R.id.subject_cn);
+ TextView subject_o = (TextView) view.findViewById(R.id.subject_o);
+ TextView issuer_cn = (TextView) view.findViewById(R.id.issuer_cn);
+ TextView issuer_o = (TextView) view.findViewById(R.id.issuer_o);
+ TextView sha1 = (TextView) view.findViewById(R.id.sha1);
+
+ subject_cn.setText(bundle.getString("subject_cn", not_available));
+ subject_o.setText(bundle.getString("subject_o", not_available));
+ issuer_cn.setText(bundle.getString("issuer_cn", not_available));
+ issuer_o.setText(bundle.getString("issuer_o", not_available));
+ sha1.setText(bundle.getString("sha1", not_available));
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.certificate_information);
+ builder.setView(view);
+ builder.setPositiveButton(R.string.ok, null);
+ builder.create().show();
+ }
+
+ protected void confirmToDeleteFingerprint(final String fingerprint) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.delete_fingerprint);
+ builder.setMessage(R.string.sure_delete_fingerprint);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.delete,
+ new android.content.DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (contact.deleteOtrFingerprint(fingerprint)) {
+ populateView();
+ xmppConnectionService.syncRosterToDisk(contact.getAccount());
+ }
+ }
+
+ });
+ builder.create().show();
+ }
+
+ @Override
+ public void onBackendConnected() {
+ if ((accountJid != null) && (contactJid != null)) {
+ Account account = xmppConnectionService
+ .findAccountByJid(accountJid);
+ if (account == null) {
+ return;
+ }
+ this.contact = account.getRoster().getContact(contactJid);
+ populateView();
+ }
+ }
+
+ @Override
+ public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
+ refreshUi();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java
new file mode 100644
index 000000000..141941c6b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java
@@ -0,0 +1,2044 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.FragmentTransaction;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Typeface;
+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;
+import android.util.Pair;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.Surface;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.PopupMenu;
+import android.widget.PopupMenu.OnMenuItemClickListener;
+import android.widget.TextView;
+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;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import de.timroes.android.listview.EnhancedListView;
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Blockable;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.entities.Presence;
+import de.pixart.messenger.entities.Transferable;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
+import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate;
+import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
+import de.pixart.messenger.ui.adapter.ConversationAdapter;
+import de.pixart.messenger.utils.ExceptionHelper;
+import de.pixart.messenger.utils.FileUtils;
+import de.pixart.messenger.utils.UIHelper;
+import de.pixart.messenger.xmpp.OnUpdateBlocklist;
+import de.pixart.messenger.xmpp.chatstate.ChatState;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ConversationActivity extends XmppActivity
+ implements OnAccountUpdate, OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, View.OnClickListener {
+
+ public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW";
+ public static final String CONVERSATION = "conversationUuid";
+ public static final String EXTRA_DOWNLOAD_UUID = "eu.siacs.conversations.download_uuid";
+ public static final String TEXT = "text";
+ public static final String NICK = "nick";
+ public static final String PRIVATE_MESSAGE = "pm";
+
+ public static final int REQUEST_SEND_MESSAGE = 0x0201;
+ public static final int REQUEST_DECRYPT_PGP = 0x0202;
+ public static final int REQUEST_ENCRYPT_MESSAGE = 0x0207;
+ public static final int REQUEST_TRUST_KEYS_TEXT = 0x0208;
+ public static final int REQUEST_TRUST_KEYS_MENU = 0x0209;
+ public static final int REQUEST_START_DOWNLOAD = 0x0210;
+ public static final int ATTACHMENT_CHOICE_CHOOSE_IMAGE = 0x0301;
+ public static final int ATTACHMENT_CHOICE_TAKE_PHOTO = 0x0302;
+ public static final int ATTACHMENT_CHOICE_CHOOSE_FILE = 0x0303;
+ public static final int ATTACHMENT_CHOICE_RECORD_VOICE = 0x0304;
+ public static final int ATTACHMENT_CHOICE_LOCATION = 0x0305;
+ public static final int ATTACHMENT_CHOICE_INVALID = 0x0306;
+ private static final String STATE_OPEN_CONVERSATION = "state_open_conversation";
+ private static final String STATE_PANEL_OPEN = "state_panel_open";
+ private static final String STATE_PENDING_URI = "state_pending_uri";
+ final private List<Uri> mPendingImageUris = new ArrayList<>();
+ final private List<Uri> mPendingFileUris = new ArrayList<>();
+ private String mOpenConverstaion = null;
+ private boolean mPanelOpen = true;
+ private Uri mPendingGeoUri = null;
+ private boolean forbidProcessingPendings = false;
+ private Message mPendingDownloadableMessage = null;
+
+ private boolean conversationWasSelectedByKeyboard = false;
+
+ private View mContentView;
+
+ private List<Conversation> conversationList = new ArrayList<>();
+ private Conversation swipedConversation = null;
+ private Conversation mSelectedConversation = null;
+ private EnhancedListView listView;
+ private ConversationFragment mConversationFragment;
+
+ private ArrayAdapter<Conversation> listAdapter;
+
+ private boolean mActivityPaused = false;
+ private AtomicBoolean mRedirected = new AtomicBoolean(false);
+ private Pair<Integer, Intent> mPostponedActivityResult;
+
+ private boolean PermissionGranted = false;
+
+ FileUtils mFileUtils;
+
+ long FirstStartTime = -1;
+
+ @SuppressLint("NewApi")
+ private static List<Uri> extractUriFromIntent(final Intent intent) {
+ List<Uri> uris = new ArrayList<>();
+ if (intent == null) {
+ return uris;
+ }
+ 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) {
+ uris.add(clipData.getItemAt(i).getUri());
+ }
+ } else {
+ uris.add(uri);
+ }
+ return uris;
+ }
+
+ public Conversation getSelectedConversation() {
+ return this.mSelectedConversation;
+ }
+
+ public void setSelectedConversation(Conversation conversation) {
+ this.mSelectedConversation = conversation;
+ }
+
+ public void showConversationsOverview() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.openPane();
+ }
+ }
+
+ @Override
+ protected String getShareableUri() {
+ Conversation conversation = getSelectedConversation();
+ if (conversation != null) {
+ return conversation.getAccount().getShareableUri();
+ } else {
+ return "";
+ }
+ }
+
+ public void hideConversationsOverview() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.closePane();
+ }
+ }
+
+ public boolean isConversationsOverviewHideable() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ public boolean isConversationsOverviewVisable() {
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ return mSlidingPaneLayout.isOpen();
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mOpenConverstaion = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
+ mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
+ String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
+ if (pending != null) {
+ mPendingImageUris.clear();
+ mPendingImageUris.add(Uri.parse(pending));
+ }
+ }
+
+ setContentView(R.layout.fragment_conversations_overview);
+
+ this.mConversationFragment = new ConversationFragment();
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
+ transaction.commit();
+
+ listView = (EnhancedListView) findViewById(R.id.list);
+ this.listAdapter = new ConversationAdapter(this, conversationList);
+ listView.setAdapter(this.listAdapter);
+
+ if (getActionBar() != null) {
+ getActionBar().setDisplayHomeAsUpEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
+ }
+
+ listView.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View clickedView,
+ int position, long arg3) {
+ if (getSelectedConversation() != conversationList.get(position)) {
+ setSelectedConversation(conversationList.get(position));
+ ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
+ conversationWasSelectedByKeyboard = false;
+ }
+ hideConversationsOverview();
+ openConversation();
+ }
+ });
+
+ listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
+
+ @Override
+ public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
+
+ final int index = listView.getFirstVisiblePosition();
+ View v = listView.getChildAt(0);
+ final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
+
+ try {
+ swipedConversation = listAdapter.getItem(position);
+ } catch (IndexOutOfBoundsException e) {
+ return null;
+ }
+ listAdapter.remove(swipedConversation);
+ xmppConnectionService.markRead(swipedConversation);
+
+ final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
+ if (position == 0 && listAdapter.getCount() == 0) {
+ endConversation(swipedConversation, false, true);
+ return null;
+ } else if (formerlySelected) {
+ setSelectedConversation(listAdapter.getItem(0));
+ ConversationActivity.this.mConversationFragment
+ .reInit(getSelectedConversation());
+ }
+
+ return new EnhancedListView.Undoable() {
+
+ @Override
+ public void undo() {
+ listAdapter.insert(swipedConversation, position);
+ if (formerlySelected) {
+ setSelectedConversation(swipedConversation);
+ ConversationActivity.this.mConversationFragment
+ .reInit(getSelectedConversation());
+ }
+ swipedConversation = null;
+ listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
+ }
+
+ @Override
+ public void discard() {
+ if (!swipedConversation.isRead()
+ && swipedConversation.getMode() == Conversation.MODE_SINGLE) {
+ swipedConversation = null;
+ return;
+ }
+ endConversation(swipedConversation, false, false);
+ swipedConversation = null;
+ }
+
+ @Override
+ public String getTitle() {
+ if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
+ return getResources().getString(R.string.title_undo_swipe_out_muc);
+ } else {
+ return getResources().getString(R.string.title_undo_swipe_out_conversation);
+ }
+ }
+ };
+ }
+ });
+ //listView.enableSwipeToDismiss();
+ listView.setSwipingLayout(R.id.swipeable_item);
+ listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
+ listView.setUndoHideDelay(10000);
+ listView.setRequireTouchBeforeDismiss(false);
+ listView.setSwipeDirection(EnhancedListView.SwipeDirection.START); // swipe to left to close conversation
+
+ mContentView = findViewById(R.id.content_view_spl);
+ if (mContentView == null) {
+ mContentView = findViewById(R.id.content_view_ll);
+ }
+ if (mContentView instanceof SlidingPaneLayout) {
+ SlidingPaneLayout mSlidingPaneLayout = (SlidingPaneLayout) mContentView;
+ mSlidingPaneLayout.setParallaxDistance(150);
+ mSlidingPaneLayout
+ .setShadowResource(R.drawable.es_slidingpane_shadow);
+ mSlidingPaneLayout.setSliderFadeColor(0);
+ mSlidingPaneLayout.setPanelSlideListener(new PanelSlideListener() {
+
+ @Override
+ public void onPanelOpened(View arg0) {
+ updateActionBarTitle();
+ invalidateOptionsMenu();
+ hideKeyboard();
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.getNotificationService()
+ .setOpenConversation(null);
+ }
+ closeContextMenu();
+ }
+
+ @Override
+ public void onPanelClosed(View arg0) {
+ listView.discardUndo();
+ openConversation();
+ }
+
+ @Override
+ public void onPanelSlide(View arg0, float arg1) {
+ // TODO Auto-generated method stub
+
+ }
+ });
+ }
+ }
+
+ private boolean isPackageInstalled(String targetPackage){
+ List<ApplicationInfo> packages;
+ PackageManager pm;
+ pm = getPackageManager();
+ packages = pm.getInstalledApplications(0);
+ for (ApplicationInfo packageInfo : packages) {
+ if(packageInfo.packageName.equals(targetPackage)) return true;
+ }
+ return false;
+ }
+
+ protected void AppUpdate() {
+ String PREFS_NAME = "UpdateTimeStamp";
+ SharedPreferences UpdateTimeStamp = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ long lastUpdateTime = UpdateTimeStamp.getLong("lastUpdateTime", 0);
+
+ //detect installed plugins and deinstall them
+ PackageInfo pInfo = null;
+ try {
+ pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ //get the app version Name for display
+ final int versionCode = pInfo.versionCode;
+ // delete voice recorder and location plugin for versions >= 142 (1.12.1)
+ if (versionCode >= 142) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall plugins");
+ if (isPackageInstalled("eu.siacs.conversations.voicerecorder") || isPackageInstalled("eu.siacs.conversations.sharelocation") || isPackageInstalled("com.samwhited.opensharelocationplugin")) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this);
+ builder.setMessage(R.string.uninstall_plugins)
+ .setPositiveButton(R.string.uninstall, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //start the deinstallation of voice recorder
+ if (isPackageInstalled("eu.siacs.conversations.voicerecorder")) {
+ Uri packageURI_VR = Uri.parse("package:eu.siacs.conversations.voicerecorder");
+ Intent uninstallIntent_VR = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI_VR);
+ if (uninstallIntent_VR.resolveActivity(getPackageManager()) != null) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall voice recorder");
+ startActivity(uninstallIntent_VR);
+ }
+ }
+ //start the deinstallation of share location
+ if (isPackageInstalled("eu.siacs.conversations.sharelocation")) {
+ Uri packageURI_SL = Uri.parse("package:eu.siacs.conversations.sharelocation");
+ Intent uninstallIntent_SL = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI_SL);
+ if (uninstallIntent_SL.resolveActivity(getPackageManager()) != null) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall share location");
+ startActivity(uninstallIntent_SL);
+ }
+ }
+ //start the deinstallation of open share location
+ if (isPackageInstalled("com.samwhited.opensharelocationplugin")) {
+ Uri packageURI_SL = Uri.parse("package:com.samwhited.opensharelocationplugin");
+ Intent uninstallIntent_SL = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI_SL);
+ if (uninstallIntent_SL.resolveActivity(getPackageManager()) != null) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall open share location");
+ startActivity(uninstallIntent_SL);
+ }
+ }
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialogInterface, int i) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall cancled");
+
+ }
+ });
+ builder.create().show();
+ }
+ }
+
+ Log.d(Config.LOGTAG, "AppUpdater - LastUpdateTime: " + lastUpdateTime);
+
+ if ((lastUpdateTime + (Config.UPDATE_CHECK_TIMER * 1000)) < System.currentTimeMillis()) {
+ lastUpdateTime = System.currentTimeMillis();
+ SharedPreferences.Editor editor = UpdateTimeStamp.edit();
+ editor.putLong("lastUpdateTime", lastUpdateTime);
+ editor.commit();
+
+ // run AppUpdater
+ Log.d(Config.LOGTAG, "AppUpdater - CurrentTime: " + lastUpdateTime);
+ Intent AppUpdater = new Intent(this, UpdaterActivity.class);
+ startActivity(AppUpdater);
+ Log.d(Config.LOGTAG, "AppUpdater started");
+
+ } else {
+
+ Log.d(Config.LOGTAG, "AppUpdater stopped");
+ return;
+ }
+ }
+
+ @Override
+ public void switchToConversation(Conversation conversation) {
+ setSelectedConversation(conversation);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation());
+ openConversation();
+ }
+ });
+ }
+
+ private void updateActionBarTitle() {
+ updateActionBarTitle(isConversationsOverviewHideable() && !isConversationsOverviewVisable());
+ }
+
+ private void updateActionBarTitle(boolean titleShouldBeName) {
+ final ActionBar ab = getActionBar();
+ final Conversation conversation = getSelectedConversation();
+ if (ab != null) {
+ if (titleShouldBeName && conversation != null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ ab.setHomeButtonEnabled(true);
+ ab.setDisplayShowTitleEnabled(false);
+ ab.setDisplayShowCustomEnabled(true);
+ ab.setCustomView(R.layout.ab_title);
+ if (conversation.getMode() == Conversation.MODE_SINGLE || useSubjectToIdentifyConference()) {
+ TextView abtitle = (TextView) findViewById(android.R.id.text1);
+ abtitle.setText(conversation.getName());
+ abtitle.setOnClickListener(this);
+ if (conversation.getMode() == Conversation.MODE_SINGLE && !this.getSelectedConversation().withSelf()) {
+ if (conversation.getContact().getShownStatus() == Presence.Status.OFFLINE) {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(getString(R.string.account_status_offline));
+ absubtitle.setOnClickListener(this);
+ } else {
+ ChatState state = conversation.getIncomingChatState();
+ if (state == ChatState.COMPOSING) {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(getString(R.string.is_typing));
+ absubtitle.setTypeface(null, Typeface.BOLD_ITALIC);
+ absubtitle.setOnClickListener(this);
+ } else if (state == ChatState.PAUSED) {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(UIHelper.lastseen(getApplicationContext(), conversation.getContact().isActive(), conversation.getContact().getLastseen()));
+ absubtitle.setOnClickListener(this);
+ } else {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(UIHelper.lastseen(getApplicationContext(), conversation.getContact().isActive(), conversation.getContact().getLastseen()));
+ absubtitle.setOnClickListener(this);
+ }
+ }
+ } else if (useSubjectToIdentifyConference()) {
+ if (conversation.getParticipants() != null) {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(conversation.getParticipants());
+ absubtitle.setOnClickListener(this);
+ } else {
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(R.string.no_participants);
+ absubtitle.setOnClickListener(this);
+ }
+ }
+ } else {
+ TextView abtitle = (TextView) findViewById(android.R.id.text1);
+ abtitle.setText(conversation.getJid().toBareJid().toString());
+ abtitle.setOnClickListener(this);
+ TextView absubtitle = (TextView) findViewById(android.R.id.text2);
+ absubtitle.setText(null);
+ absubtitle.setOnClickListener(this);
+ }
+ } else {
+ ab.setDisplayHomeAsUpEnabled(false);
+ ab.setHomeButtonEnabled(false);
+ ab.setDisplayShowTitleEnabled(true);
+ ab.setDisplayShowCustomEnabled(false);
+ ab.setTitle(R.string.app_name);
+ ab.setSubtitle(null);
+ }
+ }
+ }
+
+ private void openConversation() {
+ this.updateActionBarTitle();
+ this.invalidateOptionsMenu();
+ if (xmppConnectionServiceBound) {
+ final Conversation conversation = getSelectedConversation();
+ xmppConnectionService.getNotificationService().setOpenConversation(conversation);
+ sendReadMarkerIfNecessary(conversation);
+ }
+ listAdapter.notifyDataSetChanged();
+ }
+
+ public void sendReadMarkerIfNecessary(final Conversation conversation) {
+ if (!mActivityPaused && conversation != null) {
+ xmppConnectionService.sendReadMarker(conversation);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.conversations, menu);
+ final MenuItem menuSecure = menu.findItem(R.id.action_security);
+ final MenuItem menuArchiveChat = menu.findItem(R.id.action_archive_chat);
+ final MenuItem menuArchiveMuc = menu.findItem(R.id.action_archive_muc);
+ final MenuItem menuAttach = menu.findItem(R.id.action_attach_file);
+ final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history);
+ final MenuItem menuAdd = menu.findItem(R.id.action_add);
+ final MenuItem menuInviteContact = menu.findItem(R.id.action_invite);
+ final MenuItem menuMute = menu.findItem(R.id.action_mute);
+ final MenuItem menuUnmute = menu.findItem(R.id.action_unmute);
+ final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates);
+ final MenuItem menuInviteUser = menu.findItem(R.id.action_invite_user);
+
+ if (isConversationsOverviewVisable() && isConversationsOverviewHideable()) {
+ menuArchiveChat.setVisible(false);
+ menuArchiveMuc.setVisible(false);
+ menuSecure.setVisible(false);
+ menuInviteContact.setVisible(false);
+ menuAttach.setVisible(false);
+ menuClearHistory.setVisible(false);
+ menuMute.setVisible(false);
+ menuUnmute.setVisible(false);
+ } else {
+ menuAdd.setVisible(!isConversationsOverviewHideable());
+ //hide settings, accounts and updater in all menus except in main window
+ menuUpdater.setVisible(false);
+ menuInviteUser.setVisible(false);
+
+ if (this.getSelectedConversation() != null) {
+ if (this.getSelectedConversation().getMode() == Conversation.MODE_SINGLE) {
+ menuArchiveMuc.setVisible(false);
+ } else {
+ menuArchiveChat.setVisible(false);
+ }
+ if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
+ } else {
+ menuSecure.setIcon(R.drawable.ic_action_secure);
+ }
+ }
+ if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) {
+ menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating());
+ menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite());
+ menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice
+ } else {
+ menuSecure.setVisible(Config.multipleEncryptionChoices());
+ }
+ if (this.getSelectedConversation().isMuted()) {
+ menuMute.setVisible(false);
+ } else {
+ menuUnmute.setVisible(false);
+ }
+ }
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) {
+ final Conversation conversation = getSelectedConversation();
+ final Account account = conversation.getAccount();
+ final OnPresenceSelected callback = new OnPresenceSelected() {
+
+ @Override
+ public void onPresenceSelected() {
+ Intent intent = new Intent();
+ boolean chooser = false;
+ String fallbackPackageId = null;
+ switch (attachmentChoice) {
+ 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.setType("image/*");
+ chooser = true;
+ break;
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
+ intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
+ mPendingImageUris.clear();
+ mPendingImageUris.add(uri);
+ break;
+ case ATTACHMENT_CHOICE_CHOOSE_FILE:
+ chooser = true;
+ intent.setType("*/*");
+ intent.addCategory(Intent.CATEGORY_OPENABLE);
+ intent.setAction(Intent.ACTION_GET_CONTENT);
+ break;
+ case ATTACHMENT_CHOICE_RECORD_VOICE:
+ startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class),attachmentChoice);
+ break;
+ case ATTACHMENT_CHOICE_LOCATION:
+ startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class),attachmentChoice);
+ break;
+ }
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice);
+ if (chooser) {
+ startActivityForResult(
+ Intent.createChooser(intent, getString(R.string.perform_action_with)),
+ attachmentChoice);
+ } else {
+ startActivityForResult(intent, attachmentChoice);
+ }
+ } else if (fallbackPackageId != null) {
+ startActivity(getInstallApkIntent(fallbackPackageId));
+ }
+ }
+ };
+ if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) {
+ conversation.setNextCounterpart(null);
+ callback.onPresenceSelected();
+ } else {
+ selectPresence(conversation, callback);
+ }
+ }
+
+ private Intent getInstallApkIntent(final String packageId) {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(Uri.parse("market://details?id=" + packageId));
+ if (intent.resolveActivity(getPackageManager()) != null) {
+ return intent;
+ } else {
+ intent.setData(Uri.parse("http://play.google.com/store/apps/details?id=" + packageId));
+ return intent;
+ }
+ }
+
+ public void attachFile(final int attachmentChoice) {
+ if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
+ if (!hasStoragePermission(attachmentChoice)) {
+ return;
+ }
+ }
+ if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
+ if (!hasMicPermission(attachmentChoice)) {
+ return;
+ }
+ }
+ if (attachmentChoice == ATTACHMENT_CHOICE_LOCATION) {
+ if (!hasLocationPermission(attachmentChoice)) {
+ return;
+ }
+ }
+ switch (attachmentChoice) {
+ case ATTACHMENT_CHOICE_LOCATION:
+ getPreferences().edit().putString("recently_used_quick_action", "location").apply();
+ break;
+ case ATTACHMENT_CHOICE_RECORD_VOICE:
+ getPreferences().edit().putString("recently_used_quick_action", "voice").apply();
+ break;
+ case ATTACHMENT_CHOICE_TAKE_PHOTO:
+ getPreferences().edit().putString("recently_used_quick_action", "photo").apply();
+ break;
+ case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
+ getPreferences().edit().putString("recently_used_quick_action", "picture").apply();
+ break;
+ }
+ final Conversation conversation = getSelectedConversation();
+ final int encryption = conversation.getNextEncryption();
+ final int mode = conversation.getMode();
+ if (encryption == Message.ENCRYPTION_PGP) {
+ if (hasPgp()) {
+ if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) {
+ xmppConnectionService.getPgpEngine().hasKey(
+ conversation.getContact(),
+ new UiCallback<Contact>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Contact contact) {
+ ConversationActivity.this.runIntent(pi, attachmentChoice);
+ }
+
+ @Override
+ public void success(Contact contact) {
+ selectPresenceToAttachFile(attachmentChoice, encryption);
+ }
+
+ @Override
+ public void error(int error, Contact contact) {
+ replaceToast(getString(error));
+ }
+ });
+ } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) {
+ if (!conversation.getMucOptions().everybodyHasKeys()) {
+ Toast warning = Toast
+ .makeText(this,
+ R.string.missing_public_keys,
+ Toast.LENGTH_LONG);
+ warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ warning.show();
+ }
+ selectPresenceToAttachFile(attachmentChoice, encryption);
+ } else {
+ final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (fragment != null) {
+ fragment.showNoPGPKeyDialog(false,
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ xmppConnectionService.databaseBackend
+ .updateConversation(conversation);
+ selectPresenceToAttachFile(attachmentChoice, Message.ENCRYPTION_NONE);
+ }
+ });
+ }
+ }
+ } else {
+ showInstallPgpDialog();
+ }
+ } else {
+ if (encryption != Message.ENCRYPTION_AXOLOTL || !trustKeysIfNeeded(REQUEST_TRUST_KEYS_MENU, attachmentChoice)) {
+ selectPresenceToAttachFile(attachmentChoice, encryption);
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ if (grantResults.length > 0)
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (requestCode == REQUEST_START_DOWNLOAD) {
+ if (this.mPendingDownloadableMessage != null) {
+ startDownloadable(this.mPendingDownloadableMessage);
+ }
+ } else {
+ attachFile(requestCode);
+ }
+ } else {
+ Toast.makeText(this, R.string.no_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ public void startDownloadable(Message message) {
+ if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) {
+ this.mPendingDownloadableMessage = message;
+ return;
+ }
+ Transferable transferable = message.getTransferable();
+ if (transferable != null) {
+ if (!transferable.start()) {
+ 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
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == android.R.id.home) {
+ showConversationsOverview();
+ return true;
+ } else if (item.getItemId() == R.id.action_add) {
+ startActivity(new Intent(this, StartConversationActivity.class));
+ return true;
+ } else if (getSelectedConversation() != null) {
+ switch (item.getItemId()) {
+ case R.id.action_attach_file:
+ attachFileDialog();
+ break;
+ case R.id.action_archive_chat:
+ this.endConversation(getSelectedConversation());
+ break;
+ case R.id.action_archive_muc:
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.action_end_conversation_muc));
+ builder.setMessage(getString(R.string.leave_conference_warning));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.action_end_conversation_muc),
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ endConversation(getSelectedConversation());
+ }
+ });
+ builder.create().show();
+ break;
+ case R.id.action_invite:
+ inviteToConversation(getSelectedConversation());
+ break;
+ case R.id.action_security:
+ selectEncryptionDialog(getSelectedConversation());
+ break;
+ case R.id.action_clear_history:
+ clearHistoryDialog(getSelectedConversation());
+ break;
+ case R.id.action_mute:
+ muteConversationDialog(getSelectedConversation());
+ break;
+ case R.id.action_unmute:
+ unmuteConversation(getSelectedConversation());
+ break;
+ case R.id.action_block:
+ BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation());
+ break;
+ case R.id.action_unblock:
+ BlockContactDialog.show(this, xmppConnectionService, getSelectedConversation());
+ break;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ public void endConversation(Conversation conversation) {
+ endConversation(conversation, true, true);
+ }
+
+ public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
+ if (showOverview) {
+ showConversationsOverview();
+ }
+ xmppConnectionService.archiveConversation(conversation);
+ if (reinit) {
+ if (conversationList.size() > 0) {
+ setSelectedConversation(conversationList.get(0));
+ this.mConversationFragment.reInit(getSelectedConversation());
+ } else {
+ setSelectedConversation(null);
+ if (mRedirected.compareAndSet(false, true)) {
+ Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init", true);
+ startActivity(intent);
+ finish();
+ }
+ }
+ }
+ }
+
+ @SuppressLint("InflateParams")
+ protected void clearHistoryDialog(final Conversation conversation) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.clear_conversation_history));
+ View dialogView = getLayoutInflater().inflate(
+ R.layout.dialog_clear_history, null);
+ final CheckBox endConversationCheckBox = (CheckBox) dialogView
+ .findViewById(R.id.end_conversation_checkbox);
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ endConversationCheckBox.setVisibility(View.VISIBLE);
+ }
+ builder.setView(dialogView);
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.delete_messages),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation);
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (endConversationCheckBox.isChecked()) {
+ endConversation(conversation);
+ } else {
+ updateConversationList();
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ }
+ } else {
+ updateConversationList();
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ protected void attachFileDialog() {
+ View menuAttachFile = findViewById(R.id.action_attach_file);
+ if (menuAttachFile == null) {
+ return;
+ }
+ PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile);
+ attachFilePopup.inflate(R.menu.attachment_choices);
+ if (new Intent(MediaStore.Audio.Media.RECORD_SOUND_ACTION).resolveActivity(getPackageManager()) == null) {
+ attachFilePopup.getMenu().findItem(R.id.attach_record_voice).setVisible(false);
+ }
+ if (new Intent("eu.siacs.conversations.location.request").resolveActivity(getPackageManager()) == null) {
+ attachFilePopup.getMenu().findItem(R.id.attach_location).setVisible(false);
+ }
+ attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.attach_choose_picture:
+ attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE);
+ break;
+ case R.id.attach_take_picture:
+ attachFile(ATTACHMENT_CHOICE_TAKE_PHOTO);
+ break;
+ case R.id.attach_choose_file:
+ attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE);
+ break;
+ case R.id.attach_record_voice:
+ attachFile(ATTACHMENT_CHOICE_RECORD_VOICE);
+ break;
+ case R.id.attach_location:
+ attachFile(ATTACHMENT_CHOICE_LOCATION);
+ break;
+ }
+ return false;
+ }
+ });
+ attachFilePopup.show();
+ }
+
+ public void verifyOtrSessionDialog(final Conversation conversation, View view) {
+ if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
+ Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show();
+ return;
+ }
+ if (view == null) {
+ return;
+ }
+ PopupMenu popup = new PopupMenu(this, view);
+ popup.inflate(R.menu.verification_choices);
+ popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+ @Override
+ public boolean onMenuItemClick(MenuItem menuItem) {
+ Intent intent = new Intent(ConversationActivity.this, VerifyOTRActivity.class);
+ intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
+ intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
+ intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
+ switch (menuItem.getItemId()) {
+ case R.id.scan_fingerprint:
+ intent.putExtra("mode", VerifyOTRActivity.MODE_SCAN_FINGERPRINT);
+ break;
+ case R.id.ask_question:
+ intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION);
+ break;
+ case R.id.manual_verification:
+ intent.putExtra("mode", VerifyOTRActivity.MODE_MANUAL_VERIFICATION);
+ break;
+ }
+ startActivity(intent);
+ return true;
+ }
+ });
+ popup.show();
+ }
+
+ protected void selectEncryptionDialog(final Conversation conversation) {
+ View menuItemView = findViewById(R.id.action_security);
+ if (menuItemView == null) {
+ return;
+ }
+ PopupMenu popup = new PopupMenu(this, menuItemView);
+ final ConversationFragment fragment = (ConversationFragment) getFragmentManager()
+ .findFragmentByTag("conversation");
+ if (fragment != null) {
+ popup.setOnMenuItemClickListener(new OnMenuItemClickListener() {
+
+ @Override
+ public boolean onMenuItemClick(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.encryption_choice_none:
+ conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ item.setChecked(true);
+ break;
+ case R.id.encryption_choice_otr:
+ conversation.setNextEncryption(Message.ENCRYPTION_OTR);
+ item.setChecked(true);
+ break;
+ case R.id.encryption_choice_pgp:
+ if (hasPgp()) {
+ if (conversation.getAccount().getPgpSignature() != null) {
+ conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+ item.setChecked(true);
+ } else {
+ announcePgp(conversation.getAccount(), conversation, onOpenPGPKeyPublished);
+ }
+ } else {
+ showInstallPgpDialog();
+ }
+ break;
+ case R.id.encryption_choice_axolotl:
+ Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
+ + "Enabled axolotl for Contact " + conversation.getContact().getJid());
+ conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
+ item.setChecked(true);
+ break;
+ default:
+ conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ break;
+ }
+ xmppConnectionService.databaseBackend.updateConversation(conversation);
+ fragment.updateChatMsgHint();
+ invalidateOptionsMenu();
+ refreshUi();
+ return true;
+ }
+ });
+ popup.inflate(R.menu.encryption_choices);
+ MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr);
+ MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none);
+ MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp);
+ MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl);
+ pgp.setVisible(Config.supportOpenPgp());
+ none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI);
+ otr.setVisible(Config.supportOtr());
+ axolotl.setVisible(Config.supportOmemo());
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ otr.setVisible(false);
+ }
+ if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) {
+ axolotl.setEnabled(false);
+ }
+ switch (conversation.getNextEncryption()) {
+ case Message.ENCRYPTION_NONE:
+ none.setChecked(true);
+ break;
+ case Message.ENCRYPTION_OTR:
+ otr.setChecked(true);
+ break;
+ case Message.ENCRYPTION_PGP:
+ pgp.setChecked(true);
+ break;
+ case Message.ENCRYPTION_AXOLOTL:
+ axolotl.setChecked(true);
+ break;
+ default:
+ none.setChecked(true);
+ break;
+ }
+ popup.show();
+ }
+ }
+
+ protected void muteConversationDialog(final Conversation conversation) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.disable_notifications);
+ final int[] durations = getResources().getIntArray(R.array.mute_options_durations);
+ builder.setItems(R.array.mute_options_descriptions,
+ new OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final long till;
+ if (durations[which] == -1) {
+ till = Long.MAX_VALUE;
+ } else {
+ till = System.currentTimeMillis() + (durations[which] * 1000);
+ }
+ conversation.setMutedTill(till);
+ ConversationActivity.this.xmppConnectionService.databaseBackend
+ .updateConversation(conversation);
+ updateConversationList();
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ invalidateOptionsMenu();
+ }
+ });
+ builder.create().show();
+ }
+
+ public void unmuteConversation(final Conversation conversation) {
+ conversation.setMutedTill(0);
+ this.xmppConnectionService.databaseBackend.updateConversation(conversation);
+ updateConversationList();
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (!isConversationsOverviewVisable()) {
+ showConversationsOverview();
+ } else {
+ moveTaskToBack(true);
+ }
+ }
+
+ @Override
+ public boolean onKeyUp(int key, KeyEvent event) {
+ int rotation = getWindowManager().getDefaultDisplay().getRotation();
+ final int upKey;
+ final int downKey;
+ switch (rotation) {
+ case Surface.ROTATION_90:
+ upKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ downKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ break;
+ case Surface.ROTATION_180:
+ upKey = KeyEvent.KEYCODE_DPAD_DOWN;
+ downKey = KeyEvent.KEYCODE_DPAD_UP;
+ break;
+ case Surface.ROTATION_270:
+ upKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ downKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ break;
+ default:
+ upKey = KeyEvent.KEYCODE_DPAD_UP;
+ downKey = KeyEvent.KEYCODE_DPAD_DOWN;
+ }
+ final boolean modifier = event.isCtrlPressed() || (event.getMetaState() & KeyEvent.META_ALT_LEFT_ON) != 0;
+ if (modifier && key == KeyEvent.KEYCODE_TAB && isConversationsOverviewHideable()) {
+ toggleConversationsOverview();
+ return true;
+ } else if (modifier && key == KeyEvent.KEYCODE_SPACE) {
+ startActivity(new Intent(this, StartConversationActivity.class));
+ return true;
+ } else if (modifier && key == downKey) {
+ if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
+ showConversationsOverview();
+ }
+ return selectDownConversation();
+ } else if (modifier && key == upKey) {
+ if (isConversationsOverviewHideable() && !isConversationsOverviewVisable()) {
+ showConversationsOverview();
+ }
+ return selectUpConversation();
+ } else if (modifier && key == KeyEvent.KEYCODE_1) {
+ return openConversationByIndex(0);
+ } else if (modifier && key == KeyEvent.KEYCODE_2) {
+ return openConversationByIndex(1);
+ } else if (modifier && key == KeyEvent.KEYCODE_3) {
+ return openConversationByIndex(2);
+ } else if (modifier && key == KeyEvent.KEYCODE_4) {
+ return openConversationByIndex(3);
+ } else if (modifier && key == KeyEvent.KEYCODE_5) {
+ return openConversationByIndex(4);
+ } else if (modifier && key == KeyEvent.KEYCODE_6) {
+ return openConversationByIndex(5);
+ } else if (modifier && key == KeyEvent.KEYCODE_7) {
+ return openConversationByIndex(6);
+ } else if (modifier && key == KeyEvent.KEYCODE_8) {
+ return openConversationByIndex(7);
+ } else if (modifier && key == KeyEvent.KEYCODE_9) {
+ return openConversationByIndex(8);
+ } else if (modifier && key == KeyEvent.KEYCODE_0) {
+ return openConversationByIndex(9);
+ } else {
+ return super.onKeyUp(key, event);
+ }
+ }
+
+ private void toggleConversationsOverview() {
+ if (isConversationsOverviewVisable()) {
+ hideConversationsOverview();
+ if (mConversationFragment != null) {
+ mConversationFragment.setFocusOnInputField();
+ }
+ } else {
+ showConversationsOverview();
+ }
+ }
+
+ private boolean selectUpConversation() {
+ if (this.mSelectedConversation != null) {
+ int index = this.conversationList.indexOf(this.mSelectedConversation);
+ if (index > 0) {
+ return openConversationByIndex(index - 1);
+ }
+ }
+ return false;
+ }
+
+ private boolean selectDownConversation() {
+ if (this.mSelectedConversation != null) {
+ int index = this.conversationList.indexOf(this.mSelectedConversation);
+ if (index != -1 && index < this.conversationList.size() - 1) {
+ return openConversationByIndex(index + 1);
+ }
+ }
+ return false;
+ }
+
+ private boolean openConversationByIndex(int index) {
+ try {
+ this.conversationWasSelectedByKeyboard = true;
+ setSelectedConversation(this.conversationList.get(index));
+ this.mConversationFragment.reInit(getSelectedConversation());
+ if (index > listView.getLastVisiblePosition() - 1 || index < listView.getFirstVisiblePosition() + 1) {
+ this.listView.setSelection(index);
+ }
+ openConversation();
+ return true;
+ } catch (IndexOutOfBoundsException e) {
+ return false;
+ }
+ }
+
+ @Override
+ protected void onNewIntent(final Intent intent) {
+ if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
+ mOpenConverstaion = null;
+ if (xmppConnectionServiceBound) {
+ handleViewConversationIntent(intent);
+ intent.setAction(Intent.ACTION_MAIN);
+ } else {
+ setIntent(intent);
+ }
+ }
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ this.mRedirected.set(false);
+
+ //Permission check
+ Bundle extras = getIntent().getExtras();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (extras != null && extras.containsKey("FirstStart")) {
+ FirstStartTime = extras.getLong("FirstStart");
+ Log.d(Config.LOGTAG, "Get first start time from StartUI: " + FirstStartTime);
+ }
+ } else {
+ FirstStartTime = System.currentTimeMillis();
+ Log.d(Config.LOGTAG, "Device is running Android < SDK 23, no restart required: " + FirstStartTime);
+ }
+ if (FirstStartTime == 0) {
+ Log.d(Config.LOGTAG, "First start time: " + FirstStartTime + ", restarting App");
+ //write first start timestamp to file
+ String PREFS_NAME = "FirstStart";
+ FirstStartTime = System.currentTimeMillis();
+ SharedPreferences FirstStart = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ SharedPreferences.Editor editor = FirstStart.edit();
+ editor.putLong("FirstStart", FirstStartTime);
+ editor.commit();
+ // restart
+ Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ System.exit(0);
+ }
+ // end
+ if (this.xmppConnectionServiceBound) {
+ this.onBackendConnected();
+ }
+ if (conversationList.size() >= 1) {
+ this.onConversationUpdate();
+ }
+ }
+
+ @Override
+ public void onPause() {
+ listView.discardUndo();
+ super.onPause();
+ this.mActivityPaused = true;
+ if (this.xmppConnectionServiceBound) {
+ this.xmppConnectionService.getNotificationService().setIsInForeground(false);
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ final int theme = findTheme();
+ final boolean usingEnterKey = usingEnterKey();
+ if (this.mTheme != theme || usingEnterKey != mUsingEnterKey) {
+ recreate();
+ }
+ this.mActivityPaused = false;
+ if (this.xmppConnectionServiceBound) {
+ this.xmppConnectionService.getNotificationService().setIsInForeground(true);
+ }
+
+ if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) {
+ sendReadMarkerIfNecessary(getSelectedConversation());
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle savedInstanceState) {
+ Conversation conversation = getSelectedConversation();
+ if (conversation != null) {
+ savedInstanceState.putString(STATE_OPEN_CONVERSATION, conversation.getUuid());
+ } else {
+ savedInstanceState.remove(STATE_OPEN_CONVERSATION);
+ }
+ savedInstanceState.putBoolean(STATE_PANEL_OPEN, isConversationsOverviewVisable());
+ if (this.mPendingImageUris.size() >= 1) {
+ savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
+ } else {
+ savedInstanceState.remove(STATE_PENDING_URI);
+ }
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ private void clearPending() {
+ mPendingImageUris.clear();
+ mPendingFileUris.clear();
+ mPendingGeoUri = null;
+ mPostponedActivityResult = null;
+ }
+
+ @Override
+ void onBackendConnected() {
+ this.xmppConnectionService.getNotificationService().setIsInForeground(true);
+ updateConversationList();
+
+
+ if (mPendingConferenceInvite != null) {
+ if (mPendingConferenceInvite.execute(this)) {
+ mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
+ mToast.show();
+ }
+ mPendingConferenceInvite = null;
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED
+ || checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ Intent intent = new Intent (this, StartUI.class);
+ startActivity(intent);
+ } else {
+ PermissionGranted = true;
+ }
+ } else {
+ PermissionGranted = true;
+ }
+
+ final Intent intent = getIntent();
+ if (PermissionGranted) {
+ if (xmppConnectionService.getAccounts().size() == 0) {
+ if (mRedirected.compareAndSet(false, true)) {
+ if (Config.X509_VERIFICATION) {
+ startActivity(new Intent(this, ManageAccountActivity.class));
+ } else if (Config.MAGIC_CREATE_DOMAIN != null) {
+ Log.d(Config.LOGTAG, "First start time: " + FirstStartTime);
+ startActivity(new Intent(this, WelcomeActivity.class));
+ } else {
+ startActivity(new Intent(this, EditAccountActivity.class));
+ }
+ finish();
+ }
+ } else if (conversationList.size() <= 0) {
+ if (mRedirected.compareAndSet(false, true)) {
+ Account pendingAccount = xmppConnectionService.getPendingAccount();
+ if (pendingAccount == null) {
+ Intent startConversationActivity = new Intent(this, StartConversationActivity.class);
+ intent.putExtra("init", true);
+ startActivity(startConversationActivity);
+ } else {
+ switchToAccount(pendingAccount, true);
+ }
+ finish();
+ }
+ } else if (selectConversationByUuid(mOpenConverstaion)) {
+ if (mPanelOpen) {
+ showConversationsOverview();
+ } else {
+ if (isConversationsOverviewHideable()) {
+ openConversation();
+ updateActionBarTitle(true);
+ }
+ }
+ this.mConversationFragment.reInit(getSelectedConversation());
+ mOpenConverstaion = null;
+ } else if (intent != null && ACTION_VIEW_CONVERSATION.equals(intent.getAction())) {
+ clearPending();
+ handleViewConversationIntent(intent);
+ intent.setAction(Intent.ACTION_MAIN);
+ } else if (getSelectedConversation() == null) {
+ showConversationsOverview();
+ clearPending();
+ setSelectedConversation(conversationList.get(0));
+ this.mConversationFragment.reInit(getSelectedConversation());
+ } else {
+ this.mConversationFragment.messageListAdapter.updatePreferences();
+ this.mConversationFragment.messagesView.invalidateViews();
+ this.mConversationFragment.setupIme();
+ }
+ }
+
+ if (xmppConnectionService.getAccounts().size() != 0) {
+ AppUpdate();
+ }
+
+ if (this.mPostponedActivityResult != null) {
+ this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+ }
+
+ if (!forbidProcessingPendings) {
+ int ImageUrisCount = mPendingImageUris.size();
+ if (ImageUrisCount == 1) {
+ Uri uri = mPendingImageUris.get(0);
+ attachImageToConversation(getSelectedConversation(), uri);
+ } else {
+ for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ Uri foo = i.next();
+ attachImagesToConversation(getSelectedConversation(), foo);
+ }
+ }
+
+ for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ attachFileToConversation(getSelectedConversation(), i.next());
+ }
+
+ if (mPendingGeoUri != null) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+ mPendingGeoUri = null;
+ }
+ }
+ forbidProcessingPendings = false;
+
+ if (!ExceptionHelper.checkForCrash(this, this.xmppConnectionService)) {
+ openBatteryOptimizationDialogIfNeeded();
+ }
+ }
+
+ private void handleViewConversationIntent(final Intent intent) {
+ final String uuid = intent.getStringExtra(CONVERSATION);
+ final String downloadUuid = intent.getStringExtra(EXTRA_DOWNLOAD_UUID);
+ final String text = intent.getStringExtra(TEXT);
+ final String nick = intent.getStringExtra(NICK);
+ 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);
+ this.mConversationFragment.privateMessageWith(next);
+ } catch (final InvalidJidException ignored) {
+ //do nothing
+ }
+ } else {
+ this.mConversationFragment.highlightInConference(nick);
+ }
+ } else {
+ this.mConversationFragment.appendText(text);
+ }
+ hideConversationsOverview();
+ openConversation();
+ if (mContentView instanceof SlidingPaneLayout) {
+ updateActionBarTitle(true); //fixes bug where slp isn't properly closed yet
+ }
+ if (downloadUuid != null) {
+ final Message message = mSelectedConversation.findMessageWithFileAndUuid(downloadUuid);
+ if (message != null) {
+ startDownloadable(message);
+ }
+ }
+ }
+ }
+
+ private boolean selectConversationByUuid(String uuid) {
+ if (uuid == null) {
+ return false;
+ }
+ for (Conversation aConversationList : conversationList) {
+ if (aConversationList.getUuid().equals(uuid)) {
+ setSelectedConversation(aConversationList);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected void unregisterListeners() {
+ super.unregisterListeners();
+ xmppConnectionService.getNotificationService().setOpenConversation(null);
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_DECRYPT_PGP) {
+ mConversationFragment.onActivityResult(requestCode, resultCode, data);
+ } else if (requestCode == REQUEST_CHOOSE_PGP_ID) {
+ // the user chose OpenPGP for encryption and selected his key in the PGP provider
+ if (xmppConnectionServiceBound) {
+ if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
+ // associate selected PGP keyId with the account
+ mSelectedConversation.getAccount().setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
+ // we need to announce the key as described in XEP-027
+ announcePgp(mSelectedConversation.getAccount(), null, onOpenPGPKeyPublished);
+ } else {
+ choosePgpSignId(mSelectedConversation.getAccount());
+ }
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+ } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
+ if (xmppConnectionServiceBound) {
+ announcePgp(mSelectedConversation.getAccount(), mSelectedConversation, onOpenPGPKeyPublished);
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+ } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
+ mPendingImageUris.clear();
+ mPendingImageUris.addAll(extractUriFromIntent(data));
+ int ImageUrisCount = mPendingImageUris.size();
+ if (xmppConnectionServiceBound) {
+ if (ImageUrisCount == 1) {
+ Uri uri = mPendingImageUris.get(0);
+ attachImageToConversation(getSelectedConversation(), uri);
+ } else {
+ for (Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
+ attachImagesToConversation(getSelectedConversation(), i.next());
+ }
+ }
+ }
+ } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
+ final List<Uri> uris = extractUriFromIntent(data);
+ final Conversation c = getSelectedConversation();
+ final OnPresenceSelected callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ mPendingFileUris.clear();
+ mPendingFileUris.addAll(uris);
+ if (xmppConnectionServiceBound) {
+ for (Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
+ attachFileToConversation(c, i.next());
+ }
+ }
+ }
+ };
+ if (c == null || c.getMode() == Conversation.MODE_MULTI
+ || FileBackend.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c))
+ || c.getNextEncryption() == Message.ENCRYPTION_OTR) {
+ callback.onPresenceSelected();
+ } else {
+ selectPresence(c, callback);
+ }
+ } else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
+ if (mPendingImageUris.size() == 1) {
+ Uri uri = mPendingImageUris.get(0);
+ if (xmppConnectionServiceBound) {
+ attachImagesToConversation(getSelectedConversation(), uri);
+ mPendingImageUris.clear();
+ }
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(uri);
+ sendBroadcast(intent);
+ } else {
+ 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));
+ if (xmppConnectionServiceBound) {
+ attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
+ this.mPendingGeoUri = null;
+ }
+ } else if (requestCode == REQUEST_TRUST_KEYS_TEXT || requestCode == REQUEST_TRUST_KEYS_MENU) {
+ this.forbidProcessingPendings = !xmppConnectionServiceBound;
+ if (xmppConnectionServiceBound) {
+ mConversationFragment.onActivityResult(requestCode, resultCode, data);
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+
+ }
+ } else {
+ mPendingImageUris.clear();
+ mPendingFileUris.clear();
+ if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
+ mConversationFragment.onActivityResult(requestCode, resultCode, data);
+ }
+ if (requestCode == REQUEST_BATTERY_OP) {
+ setNeverAskForBatteryOptimizationsAgain();
+ }
+ }
+ }
+
+ private long getMaxHttpUploadSize(Conversation conversation) {
+ return conversation.getAccount().getXmppConnection().getFeatures().getMaxHttpUploadSize();
+ }
+
+ private void setNeverAskForBatteryOptimizationsAgain() {
+ getPreferences().edit().putBoolean("show_battery_optimization", false).commit();
+ }
+
+ private void openBatteryOptimizationDialogIfNeeded() {
+ if (hasAccountWithoutPush()
+ && isOptimizingBattery()
+ && getPreferences().getBoolean("show_battery_optimization", 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);
+ try {
+ startActivityForResult(intent, REQUEST_BATTERY_OP);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ 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();
+ }
+ }
+
+ private boolean hasAccountWithoutPush() {
+ for(Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED
+ && !xmppConnectionService.getPushManagementService().available(account)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void attachLocationToConversation(Conversation conversation, Uri uri) {
+ if (conversation == null) {
+ return;
+ }
+ xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback<Message>() {
+
+ @Override
+ public void success(Message message) {
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(int errorCode, Message object) {
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+
+ }
+ });
+ }
+
+ private void attachFileToConversation(Conversation conversation, Uri uri) {
+ if (conversation == null) {
+ return;
+ }
+ final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_file), Toast.LENGTH_LONG);
+ prepareFileToast.show();
+ xmppConnectionService.attachFileToConversation(conversation, uri, new UiCallback<Message>() {
+ @Override
+ public void success(Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(final int errorCode, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(errorCode));
+ }
+ });
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ }
+ });
+ }
+
+ private void attachImagesToConversation(Conversation conversation, Uri uri) {
+ if (conversation == null) {
+ return;
+ }
+ final Toast prepareFileToast = Toast.makeText(getApplicationContext(),getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ prepareFileToast.show();
+ xmppConnectionService.attachImageToConversation(conversation, uri,
+ new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ hidePrepareFileToast(prepareFileToast);
+ }
+
+ @Override
+ public void success(Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(final int error, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(error));
+ }
+ });
+ }
+ });
+ }
+
+ private void attachImageToConversation(Conversation conversation, Uri uri) {
+ if (conversation == null) {
+ return;
+ }
+ final Conversation conversation_preview = conversation;
+ final Uri uri_preview = uri;
+ Bitmap bitmap = BitmapFactory.decodeFile(FileUtils.getPath(this, uri));
+ if (bitmap != null) {
+ int scaleSize = 600;
+ int originalWidth = bitmap.getWidth();
+ int originalHeight = bitmap.getHeight();
+ int newWidth = -1;
+ int newHeight = -1;
+ float multFactor;
+ if (originalHeight > originalWidth) {
+ newHeight = scaleSize;
+ multFactor = (float) originalWidth / (float) originalHeight;
+ newWidth = (int) (newHeight * multFactor);
+ } else if (originalWidth > originalHeight) {
+ newWidth = scaleSize;
+ multFactor = (float) originalHeight / (float) originalWidth;
+ newHeight = (int) (newWidth * multFactor);
+ } else if (originalHeight == originalWidth) {
+ newHeight = scaleSize;
+ newWidth = scaleSize;
+ }
+ Log.d(Config.LOGTAG, "Scaling preview image from " + originalHeight + "px x " + originalWidth + "px to " + newHeight + "px x " + newWidth + "px");
+ Bitmap preview = Bitmap.createScaledBitmap(bitmap, newWidth, newHeight, false);
+ ImageView ImagePreview = new ImageView(this);
+
+ LinearLayout.LayoutParams vp = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT);
+ ImagePreview.setLayoutParams(vp);
+ ImagePreview.setMaxWidth(newWidth);
+ ImagePreview.setMaxHeight(newHeight);
+ //ImagePreview.setScaleType(ImageView.ScaleType.FIT_XY);
+ //ImagePreview.setAdjustViewBounds(true);
+ ImagePreview.setPadding(5, 5, 5, 5);
+ ImagePreview.setImageBitmap(preview);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setView(ImagePreview);
+ builder.setTitle(R.string.send_image);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ final Toast prepareFileToast = Toast.makeText(getApplicationContext(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ prepareFileToast.show();
+ xmppConnectionService.attachImageToConversation(conversation_preview, uri_preview,
+ new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ hidePrepareFileToast(prepareFileToast);
+ }
+
+ @Override
+ public void success(Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ xmppConnectionService.sendMessage(message);
+ }
+
+ @Override
+ public void error(final int error, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(error));
+ }
+ });
+ }
+ });
+ }
+ });
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ mPendingImageUris.clear();
+ }
+ });
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
+ @Override
+ public void onDismiss(DialogInterface dialog) {
+ mPendingImageUris.clear();
+ }
+ });
+ }
+ AlertDialog alertDialog = builder.create();
+ alertDialog.show();
+ } else {
+ Toast.makeText(getApplicationContext(), getText(R.string.error_file_not_found), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void hidePrepareFileToast(final Toast prepareFileToast) {
+ if (prepareFileToast != null) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ prepareFileToast.cancel();
+ }
+ });
+ }
+ }
+
+ public void updateConversationList() {
+ xmppConnectionService
+ .populateWithOrderedConversations(conversationList);
+ if (swipedConversation != null) {
+ if (swipedConversation.isRead()) {
+ conversationList.remove(swipedConversation);
+ } else {
+ listView.discardUndo();
+ }
+ }
+ listAdapter.notifyDataSetChanged();
+ }
+
+ public void runIntent(PendingIntent pi, int requestCode) {
+ try {
+ this.startIntentSenderForResult(pi.getIntentSender(), requestCode,
+ null, 0, 0, 0);
+ } catch (final SendIntentException ignored) {
+ }
+ }
+
+ public void encryptTextMessage(Message message) {
+ xmppConnectionService.getPgpEngine().encrypt(message,
+ new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,Message message) {
+ ConversationActivity.this.runIntent(pi,ConversationActivity.REQUEST_SEND_MESSAGE);
+ }
+
+ @Override
+ public void success(Message message) {
+ message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ xmppConnectionService.sendMessage(message);
+ if (mConversationFragment != null) {
+ mConversationFragment.messageSent();
+ }
+ }
+
+ @Override
+ public void error(final int error, Message message) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConversationActivity.this,
+ R.string.unable_to_connect_to_keychain,
+ Toast.LENGTH_SHORT
+ ).show();
+ }
+ });
+ }
+ });
+ }
+
+ public boolean useSendButtonToIndicateStatus() {
+ return getPreferences().getBoolean("send_button_status", true);
+ }
+
+ public boolean indicateReceived() {
+ return getPreferences().getBoolean("indicate_received", true);
+ }
+
+ public boolean useWhiteBackground() {
+ return getPreferences().getBoolean("use_white_background", false);
+ }
+
+ protected boolean trustKeysIfNeeded(int requestCode) {
+ return trustKeysIfNeeded(requestCode, ATTACHMENT_CHOICE_INVALID);
+ }
+
+ protected boolean trustKeysIfNeeded(int requestCode, int attachmentChoice) {
+ AxolotlService axolotlService = mSelectedConversation.getAccount().getAxolotlService();
+ final List<Jid> targets = axolotlService.getCryptoTargets(mSelectedConversation);
+ boolean hasUnaccepted = !mSelectedConversation.getAcceptedCryptoTargets().containsAll(targets);
+ boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED).isEmpty();
+ boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, targets).isEmpty();
+ boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(mSelectedConversation).isEmpty();
+ boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets);
+ if(hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted) {
+ axolotlService.createSessionsIfNeeded(mSelectedConversation);
+ Intent intent = new Intent(getApplicationContext(), TrustKeysActivity.class);
+ String[] contacts = new String[targets.size()];
+ for(int i = 0; i < contacts.length; ++i) {
+ contacts[i] = targets.get(i).toString();
+ }
+ intent.putExtra("contacts", contacts);
+ intent.putExtra(EXTRA_ACCOUNT, mSelectedConversation.getAccount().getJid().toBareJid().toString());
+ intent.putExtra("choice", attachmentChoice);
+ intent.putExtra("conversation",mSelectedConversation.getUuid());
+ startActivityForResult(intent, requestCode);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ updateConversationList();
+ if (conversationList.size() > 0) {
+ if (!this.mConversationFragment.isAdded()) {
+ Log.d(Config.LOGTAG,"fragment NOT added to activity. detached="+Boolean.toString(mConversationFragment.isDetached()));
+ }
+ ConversationActivity.this.mConversationFragment.updateMessages();
+ updateActionBarTitle();
+ invalidateOptionsMenu();
+ } else {
+ Log.d(Config.LOGTAG,"not updating conversations fragment because conversations list size was 0");
+ }
+ }
+
+ @Override
+ public void onAccountUpdate() {
+ this.refreshUi();
+ }
+
+ @Override
+ public void onConversationUpdate() {
+ this.refreshUi();
+ }
+
+ @Override
+ public void onRosterUpdate() {
+ this.refreshUi();
+ }
+
+ @Override
+ public void OnUpdateBlocklist(Status status) {
+ this.refreshUi();
+ }
+
+ public void unblockConversation(final Blockable conversation) {
+ xmppConnectionService.sendUnblockRequest(conversation);
+ }
+
+ public boolean enterIsSend() {
+ return getPreferences().getBoolean("enter_is_send",false);
+ }
+
+ @Override
+ public void onShowErrorToast(final int resId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ConversationActivity.this,resId,Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ public boolean highlightSelectedConversations() {
+ return !isConversationsOverviewHideable() || this.conversationWasSelectedByKeyboard;
+ }
+
+ @Override
+ public void onClick(View view) {
+ final Conversation conversation = getSelectedConversation();
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ switchToContactDetails(getSelectedConversation().getContact());
+ } else if (conversation.getMode() == Conversation.MODE_MULTI) {
+ Intent intent = new Intent(this,
+ ConferenceDetailsActivity.class);
+ intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
+ intent.putExtra("uuid", getSelectedConversation().getUuid());
+ startActivity(intent);
+ }
+ }
+
+ public void setMessagesLoaded() {
+ if (mConversationFragment != null) {
+ mConversationFragment.setMessagesLoaded();
+ mConversationFragment.updateMessages();
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
new file mode 100644
index 000000000..1ffa52c3f
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java
@@ -0,0 +1,1420 @@
+package de.pixart.messenger.ui;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.InputType;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AbsListView;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.ImageButton;
+import android.widget.ListView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+import android.widget.Toast;
+
+import net.java.otr4j.session.SessionStatus;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.UUID;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.DownloadableFile;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.entities.Presence;
+import de.pixart.messenger.entities.Transferable;
+import de.pixart.messenger.entities.TransferablePlaceholder;
+import de.pixart.messenger.http.HttpDownloadConnection;
+import de.pixart.messenger.services.MessageArchiveService;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.XmppActivity.OnPresenceSelected;
+import de.pixart.messenger.ui.XmppActivity.OnValueEdited;
+import de.pixart.messenger.ui.adapter.MessageAdapter;
+import de.pixart.messenger.ui.adapter.MessageAdapter.OnContactPictureClicked;
+import de.pixart.messenger.ui.adapter.MessageAdapter.OnContactPictureLongClicked;
+import de.pixart.messenger.utils.GeoHelper;
+import de.pixart.messenger.utils.UIHelper;
+import de.pixart.messenger.xmpp.XmppConnection;
+import de.pixart.messenger.xmpp.chatstate.ChatState;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ConversationFragment extends Fragment implements EditMessage.KeyboardListener {
+
+ protected Conversation conversation;
+ private OnClickListener leaveMuc = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.endConversation(conversation);
+ }
+ };
+ private OnClickListener joinMuc = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.xmppConnectionService.joinMuc(conversation);
+ }
+ };
+ private OnClickListener enterPassword = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ MucOptions muc = conversation.getMucOptions();
+ String password = muc.getPassword();
+ if (password == null) {
+ password = "";
+ }
+ activity.quickPasswordEdit(password, new OnValueEdited() {
+
+ @Override
+ public void onValueEdited(String value) {
+ activity.xmppConnectionService.providePasswordForMuc(
+ conversation, value);
+ }
+ });
+ }
+ };
+ protected ListView messagesView;
+ final protected List<Message> messageList = new ArrayList<>();
+ protected MessageAdapter messageListAdapter;
+ private EditMessage mEditMessage;
+ private ImageButton mSendButton;
+ private RelativeLayout snackbar;
+ private TextView snackbarMessage;
+ private TextView snackbarAction;
+ private boolean messagesLoaded = true;
+ private Toast messageLoaderToast;
+
+ private OnScrollListener mOnScrollListener = new OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem,
+ int visibleItemCount, int totalItemCount) {
+ synchronized (ConversationFragment.this.messageList) {
+ if (firstVisibleItem < 5 && messagesLoaded && messageList.size() > 0) {
+ long timestamp;
+ if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) {
+ timestamp = messageList.get(1).getTimeSent();
+ } else {
+ timestamp = messageList.get(0).getTimeSent();
+ }
+ messagesLoaded = false;
+ activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
+ @Override
+ public void onMoreMessagesLoaded(final int c, Conversation conversation) {
+ if (ConversationFragment.this.conversation != conversation) {
+ return;
+ }
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int oldPosition = messagesView.getFirstVisiblePosition();
+ final Message message;
+ if (oldPosition < messageList.size()) {
+ message = messageList.get(oldPosition);
+ } else {
+ message = null;
+ }
+ String uuid = message != null ? message.getUuid() : null;
+ View v = messagesView.getChildAt(0);
+ final int pxOffset = (v == null) ? 0 : v.getTop();
+ ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
+ updateStatusMessages();
+ messageListAdapter.notifyDataSetChanged();
+ int pos = Math.max(getIndexOf(uuid,messageList),0);
+ messagesView.setSelectionFromTop(pos, pxOffset);
+ messagesLoaded = true;
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void informUser(final int resId) {
+
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ if (ConversationFragment.this.conversation != conversation) {
+ return;
+ }
+ messageLoaderToast = Toast.makeText(activity, resId, Toast.LENGTH_LONG);
+ messageLoaderToast.show();
+ }
+ });
+
+ }
+ });
+
+ }
+ }
+ }
+ };
+
+ private int getIndexOf(String uuid, List<Message> messages) {
+ if (uuid == null) {
+ return messages.size() - 1;
+ }
+ for(int i = 0; i < messages.size(); ++i) {
+ if (uuid.equals(messages.get(i).getUuid())) {
+ return i;
+ } else {
+ Message next = messages.get(i);
+ while(next != null && next.wasMergedIntoPrevious()) {
+ if (uuid.equals(next.getUuid())) {
+ return i;
+ }
+ next = next.next();
+ }
+
+ }
+ }
+ return -1;
+ }
+ protected OnClickListener clickToDecryptListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent();
+ if (pendingIntent != null) {
+ try {
+ activity.startIntentSenderForResult(pendingIntent.getIntentSender(),
+ ConversationActivity.REQUEST_DECRYPT_PGP,
+ null,
+ 0,
+ 0,
+ 0);
+ } catch (SendIntentException e) {
+ Toast.makeText(activity,R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show();
+ conversation.getAccount().getPgpDecryptionService().continueDecryption(true);
+ }
+ }
+ updateSnackBar(conversation);
+ }
+ };
+ protected OnClickListener clickToVerify = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.verifyOtrSessionDialog(conversation, v);
+ }
+ };
+ private OnEditorActionListener mEditorActionListener = new OnEditorActionListener() {
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (actionId == EditorInfo.IME_ACTION_SEND) {
+ InputMethodManager imm = (InputMethodManager) v.getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm.isFullscreenMode()) {
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ sendMessage();
+ return true;
+ } else {
+ return false;
+ }
+ }
+ };
+ private OnClickListener mSendButtonListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof SendButtonAction) {
+ SendButtonAction action = (SendButtonAction) tag;
+ switch (action) {
+ case TAKE_PHOTO:
+ activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_TAKE_PHOTO);
+ break;
+ case SEND_LOCATION:
+ activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_LOCATION);
+ break;
+ case RECORD_VOICE:
+ activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_RECORD_VOICE);
+ break;
+ case CHOOSE_PICTURE:
+ activity.attachFile(ConversationActivity.ATTACHMENT_CHOICE_CHOOSE_IMAGE);
+ break;
+ case CANCEL:
+ if (conversation != null) {
+ if (conversation.getCorrectingMessage() != null) {
+ conversation.setCorrectingMessage(null);
+ mEditMessage.getEditableText().clear();
+ }
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setNextCounterpart(null);
+ }
+ updateChatMsgHint();
+ updateSendButton();
+ }
+ break;
+ default:
+ sendMessage();
+ }
+ } else {
+ sendMessage();
+ }
+ }
+ };
+ private View.OnLongClickListener mSendButtonLongListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ final String body = mEditMessage.getText().toString();
+ if (body.length() == 0) {
+ mEditMessage.getText().insert(0, "/me ");
+ }
+ return true;
+ }
+ };
+ private OnClickListener clickToMuc = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class);
+ intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
+ intent.putExtra("uuid", conversation.getUuid());
+ startActivity(intent);
+ }
+ };
+ private ConversationActivity activity;
+ private Message selectedMessage;
+
+ public void setMessagesLoaded() {
+ this.messagesLoaded = true;
+ }
+
+ private void sendMessage() {
+ final String body = mEditMessage.getText().toString();
+ if (body.length() == 0 || this.conversation == null) {
+ return;
+ }
+ final Message message;
+ if (conversation.getCorrectingMessage() == null) {
+ message = new Message(conversation, body, conversation.getNextEncryption());
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ if (conversation.getNextCounterpart() != null) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ message.setType(Message.TYPE_PRIVATE);
+ }
+ }
+ } else {
+ message = conversation.getCorrectingMessage();
+ message.setBody(body);
+ message.setEdited(message.getUuid());
+ message.setUuid(UUID.randomUUID().toString());
+ conversation.setCorrectingMessage(null);
+ }
+ switch (conversation.getNextEncryption()) {
+ case Message.ENCRYPTION_OTR:
+ sendOtrMessage(message);
+ break;
+ case Message.ENCRYPTION_PGP:
+ sendPgpMessage(message);
+ break;
+ case Message.ENCRYPTION_AXOLOTL:
+ if(!activity.trustKeysIfNeeded(ConversationActivity.REQUEST_TRUST_KEYS_TEXT)) {
+ sendAxolotlMessage(message);
+ }
+ break;
+ default:
+ sendPlainTextMessage(message);
+ }
+ }
+
+ public void updateChatMsgHint() {
+ final boolean multi = conversation.getMode() == Conversation.MODE_MULTI;
+ if (conversation.getCorrectingMessage() != null) {
+ this.mEditMessage.setHint(R.string.send_corrected_message);
+ } else if (multi && conversation.getNextCounterpart() != null) {
+ this.mEditMessage.setHint(getString(
+ R.string.send_private_message_to,
+ conversation.getNextCounterpart().getResourcepart()));
+ } else if (multi && !conversation.getMucOptions().participating()) {
+ this.mEditMessage.setHint(R.string.you_are_not_participating);
+ } else {
+ switch (conversation.getNextEncryption()) {
+ case Message.ENCRYPTION_NONE:
+ if (Config.multipleEncryptionChoices()) {
+ mEditMessage.setHint(getString(R.string.send_unencrypted_message));
+ } else {
+ mEditMessage.setHint(getString(R.string.send_message_to_x,conversation.getName()));
+ }
+ break;
+ case Message.ENCRYPTION_OTR:
+ mEditMessage.setHint(getString(R.string.send_otr_message));
+ break;
+ case Message.ENCRYPTION_AXOLOTL:
+ AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
+ if (axolotlService != null && axolotlService.trustedSessionVerified(conversation)) {
+ mEditMessage.setHint(getString(R.string.send_omemo_x509_message));
+ } else {
+ mEditMessage.setHint(getString(R.string.send_omemo_message));
+ }
+ break;
+ case Message.ENCRYPTION_PGP:
+ mEditMessage.setHint(getString(R.string.send_pgp_message));
+ break;
+ default:
+ break;
+ }
+ getActivity().invalidateOptionsMenu();
+ }
+ }
+
+ public void setupIme() {
+ if (activity == null) {
+ return;
+ } else if (activity.usingEnterKey() && activity.enterIsSend()) {
+ mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE));
+ mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
+ } else if (activity.usingEnterKey()) {
+ mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE));
+ } else {
+ mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE);
+ mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE);
+ }
+ }
+
+ @Override
+ public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ final View view = inflater.inflate(R.layout.fragment_conversation, container, false);
+ view.setOnClickListener(null);
+ mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
+ mEditMessage.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (activity != null) {
+ activity.hideConversationsOverview();
+ }
+ }
+ });
+ mEditMessage.setOnEditorActionListener(mEditorActionListener);
+
+ mSendButton = (ImageButton) view.findViewById(R.id.textSendButton);
+ mSendButton.setOnClickListener(this.mSendButtonListener);
+ mSendButton.setOnLongClickListener(this.mSendButtonLongListener);
+
+ snackbar = (RelativeLayout) view.findViewById(R.id.snackbar);
+ snackbarMessage = (TextView) view.findViewById(R.id.snackbar_message);
+ snackbarAction = (TextView) view.findViewById(R.id.snackbar_action);
+
+ messagesView = (ListView) view.findViewById(R.id.messages_view);
+ messagesView.setOnScrollListener(mOnScrollListener);
+ messagesView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL);
+ messageListAdapter = new MessageAdapter((ConversationActivity) getActivity(), this.messageList);
+ messageListAdapter.setOnContactPictureClicked(new OnContactPictureClicked() {
+
+ @Override
+ public void onContactPictureClicked(Message message) {
+ if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
+ Jid user = message.getCounterpart();
+ if (user != null && !user.isBareJid()) {
+ if (!message.getConversation().getMucOptions().isUserInRoom(user)) {
+ Toast.makeText(activity,activity.getString(R.string.user_has_left_conference,user.getResourcepart()),Toast.LENGTH_SHORT).show();
+ }
+ highlightInConference(user.getResourcepart());
+ }
+ } else {
+ if (!message.getContact().isSelf()) {
+ String fingerprint;
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ fingerprint = "pgp";
+ } else {
+ fingerprint = message.getFingerprint();
+ }
+ activity.switchToContactDetails(message.getContact(), fingerprint);
+ }
+ }
+ } else {
+ Account account = message.getConversation().getAccount();
+ Intent intent;
+ if (activity.manuallyChangePresence()) {
+ intent = new Intent(activity, SetPresenceActivity.class);
+ intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
+ } else {
+ intent = new Intent(activity, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().toBareJid().toString());
+ String fingerprint;
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ fingerprint = "pgp";
+ } else {
+ fingerprint = message.getFingerprint();
+ }
+ intent.putExtra("fingerprint", fingerprint);
+ }
+ startActivity(intent);
+ }
+ }
+ });
+ messageListAdapter
+ .setOnContactPictureLongClicked(new OnContactPictureLongClicked() {
+
+ @Override
+ public void onContactPictureLongClicked(Message message) {
+ if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
+ Jid user = message.getCounterpart();
+ if (user != null && !user.isBareJid()) {
+ if (message.getConversation().getMucOptions().isUserInRoom(user)) {
+ privateMessageWith(user);
+ } else {
+ Toast.makeText(activity, activity.getString(R.string.user_has_left_conference, user.getResourcepart()), Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ } else {
+ activity.showQrCode();
+ }
+ }
+ });
+ messagesView.setAdapter(messageListAdapter);
+
+ registerForContextMenu(messagesView);
+
+ return view;
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ synchronized (this.messageList) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
+ this.selectedMessage = this.messageList.get(acmi.position);
+ populateContextMenu(menu);
+ }
+ }
+
+ private void populateContextMenu(ContextMenu menu) {
+ final Message m = this.selectedMessage;
+ final Transferable t = m.getTransferable();
+ Message relevantForCorrection = m;
+ while(relevantForCorrection.mergeable(relevantForCorrection.next())) {
+ relevantForCorrection = relevantForCorrection.next();
+ }
+ if (m.getType() != Message.TYPE_STATUS) {
+ final boolean treatAsFile = m.getType() != Message.TYPE_TEXT
+ && m.getType() != Message.TYPE_PRIVATE
+ && t == null;
+ activity.getMenuInflater().inflate(R.menu.message_context, menu);
+ menu.setHeaderTitle(R.string.message_options);
+ MenuItem copyText = menu.findItem(R.id.copy_text);
+ MenuItem retryDecryption = menu.findItem(R.id.retry_decryption);
+ MenuItem correctMessage = menu.findItem(R.id.correct_message);
+ MenuItem shareWith = menu.findItem(R.id.share_with);
+ MenuItem sendAgain = menu.findItem(R.id.send_again);
+ MenuItem copyUrl = menu.findItem(R.id.copy_url);
+ MenuItem downloadFile = menu.findItem(R.id.download_file);
+ MenuItem cancelTransmission = menu.findItem(R.id.cancel_transmission);
+ MenuItem deleteFile = menu.findItem(R.id.delete_file);
+ if (!treatAsFile
+ && !GeoHelper.isGeoUri(m.getBody())
+ && m.treatAsDownloadable() != Message.Decision.MUST) {
+ copyText.setVisible(true);
+ }
+ if (m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
+ retryDecryption.setVisible(true);
+ }
+ if (relevantForCorrection.getType() == Message.TYPE_TEXT
+ && relevantForCorrection.isLastCorrectableMessage()) {
+ correctMessage.setVisible(true);
+ }
+ if (treatAsFile || (GeoHelper.isGeoUri(m.getBody()))) {
+ shareWith.setVisible(true);
+ }
+ if (m.getStatus() == Message.STATUS_SEND_FAILED) {
+ sendAgain.setVisible(true);
+ }
+ if (m.hasFileOnRemoteHost()
+ || GeoHelper.isGeoUri(m.getBody())
+ || m.treatAsDownloadable() == Message.Decision.MUST
+ || (t != null && t instanceof HttpDownloadConnection)) {
+ copyUrl.setVisible(true);
+ }
+ if ((m.getType() == Message.TYPE_TEXT && t == null && m.treatAsDownloadable() != Message.Decision.NEVER)
+ || (m.isFileOrImage() && t instanceof TransferablePlaceholder && m.hasFileOnRemoteHost())){
+ downloadFile.setVisible(true);
+ downloadFile.setTitle(activity.getString(R.string.download_x_file,UIHelper.getFileDescriptionString(activity, m)));
+ }
+ if ((t != null && !(t instanceof TransferablePlaceholder))
+ || (m.isFileOrImage() && (m.getStatus() == Message.STATUS_WAITING
+ || m.getStatus() == Message.STATUS_OFFERED))) {
+ cancelTransmission.setVisible(true);
+ }
+ if (treatAsFile) {
+ deleteFile.setVisible(true);
+ deleteFile.setTitle(activity.getString(R.string.delete_x_file,UIHelper.getFileDescriptionString(activity, m)));
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.share_with:
+ shareWith(selectedMessage);
+ return true;
+ case R.id.copy_text:
+ copyText(selectedMessage);
+ return true;
+ case R.id.correct_message:
+ correctMessage(selectedMessage);
+ return true;
+ case R.id.send_again:
+ resendMessage(selectedMessage);
+ return true;
+ case R.id.copy_url:
+ copyUrl(selectedMessage);
+ return true;
+ case R.id.download_file:
+ downloadFile(selectedMessage);
+ return true;
+ case R.id.cancel_transmission:
+ cancelTransmission(selectedMessage);
+ return true;
+ case R.id.retry_decryption:
+ retryDecryption(selectedMessage);
+ return true;
+ case R.id.delete_file:
+ deleteFile(selectedMessage);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ private void shareWith(Message message) {
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ shareIntent.putExtra(Intent.EXTRA_TEXT, message.getBody());
+ shareIntent.setType("text/plain");
+ } else {
+ shareIntent.putExtra(Intent.EXTRA_STREAM,
+ activity.xmppConnectionService.getFileBackend()
+ .getJingleFileUri(message));
+ shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ String mime = message.getMimeType();
+ if (mime == null) {
+ mime = "*/*";
+ }
+ shareIntent.setType(mime);
+ }
+ try {
+ activity.startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
+ } catch (ActivityNotFoundException e) {
+ //This should happen only on faulty androids because normally chooser is always available
+ Toast.makeText(activity,R.string.no_application_found_to_open_file,Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void copyText(Message message) {
+ if (activity.copyTextToClipboard(message.getMergedBody(),
+ R.string.message_text)) {
+ Toast.makeText(activity, R.string.message_copied_to_clipboard,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void deleteFile(Message message) {
+ if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+ activity.updateConversationList();
+ updateMessages();
+ }
+ }
+
+ private void resendMessage(Message message) {
+ if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) {
+ DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ if (!file.exists()) {
+ Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
+ message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED));
+ activity.updateConversationList();
+ updateMessages();
+ return;
+ }
+ }
+ activity.xmppConnectionService.resendFailedMessages(message);
+ }
+
+ private void copyUrl(Message message) {
+ final String url;
+ final int resId;
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ resId = R.string.location;
+ url = message.getBody();
+ } else if (message.hasFileOnRemoteHost()) {
+ resId = R.string.file_url;
+ url = message.getFileParams().url.toString();
+ } else {
+ url = message.getBody().trim();
+ resId = R.string.file_url;
+ }
+ if (activity.copyTextToClipboard(url, resId)) {
+ Toast.makeText(activity, R.string.url_copied_to_clipboard,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void downloadFile(Message message) {
+ activity.xmppConnectionService.getHttpConnectionManager()
+ .createNewDownloadConnection(message,true);
+ }
+
+ private void cancelTransmission(Message message) {
+ Transferable transferable = message.getTransferable();
+ if (transferable != null) {
+ transferable.cancel();
+ } else {
+ activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
+ }
+ }
+
+ private void retryDecryption(Message message) {
+ message.setEncryption(Message.ENCRYPTION_PGP);
+ activity.updateConversationList();
+ updateMessages();
+ conversation.getAccount().getPgpDecryptionService().decrypt(message, false);
+ }
+
+ protected void privateMessageWith(final Jid counterpart) {
+ this.mEditMessage.setText("");
+ this.conversation.setNextCounterpart(counterpart);
+ updateChatMsgHint();
+ updateSendButton();
+ }
+
+ private void correctMessage(Message message) {
+ while(message.mergeable(message.next())) {
+ message = message.next();
+ }
+ this.conversation.setCorrectingMessage(message);
+ this.mEditMessage.getEditableText().clear();
+ this.mEditMessage.getEditableText().append(message.getBody());
+
+ }
+
+ protected void highlightInConference(String nick) {
+ String oldString = mEditMessage.getText().toString().trim();
+ if (oldString.isEmpty() || mEditMessage.getSelectionStart() == 0) {
+ mEditMessage.getText().insert(0, nick + ": ");
+ } else {
+ if (mEditMessage.getText().charAt(
+ mEditMessage.getSelectionStart() - 1) != ' ') {
+ nick = " " + nick;
+ }
+ mEditMessage.getText().insert(mEditMessage.getSelectionStart(),
+ nick + " ");
+ }
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ if (this.conversation != null) {
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ updateChatState(this.conversation, msg);
+ }
+ }
+
+ private void updateChatState(final Conversation conversation, final String msg) {
+ ChatState state = msg.length() == 0 ? Config.DEFAULT_CHATSTATE : ChatState.PAUSED;
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(state)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ public void reInit(Conversation conversation) {
+ if (conversation == null) {
+ return;
+ }
+ this.activity = (ConversationActivity) getActivity();
+ setupIme();
+ if (this.conversation != null) {
+ final String msg = mEditMessage.getText().toString();
+ this.conversation.setNextMessage(msg);
+ if (this.conversation != conversation) {
+ updateChatState(this.conversation, msg);
+ }
+ this.conversation.trim();
+ }
+
+ this.conversation = conversation;
+ boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating();
+ this.mEditMessage.setEnabled(canWrite);
+ this.mSendButton.setEnabled(canWrite);
+ this.mEditMessage.setKeyboardListener(null);
+ this.mEditMessage.setText("");
+ this.mEditMessage.append(this.conversation.getNextMessage());
+ this.mEditMessage.setKeyboardListener(this);
+ messageListAdapter.updatePreferences();
+ this.messagesView.setAdapter(messageListAdapter);
+ updateMessages();
+ this.messagesLoaded = true;
+ synchronized (this.messageList) {
+ final Message first = conversation.getFirstUnreadMessage();
+ final int bottom = Math.max(0, this.messageList.size() - 1);
+ final int pos;
+ if (first == null) {
+ pos = bottom;
+ } else {
+ int i = getIndexOf(first.getUuid(), this.messageList);
+ pos = i < 0 ? bottom : i;
+ }
+ messagesView.setSelection(pos);
+ }
+ }
+
+ private OnClickListener mEnableAccountListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Account account = conversation == null ? null : conversation.getAccount();
+ if (account != null) {
+ account.setOption(Account.OPTION_DISABLED, false);
+ activity.xmppConnectionService.updateAccount(account);
+ }
+ }
+ };
+
+ private OnClickListener mUnblockClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ v.post(new Runnable() {
+ @Override
+ public void run() {
+ v.setVisibility(View.INVISIBLE);
+ }
+ });
+ if (conversation.isDomainBlocked()) {
+ BlockContactDialog.show(activity, activity.xmppConnectionService, conversation);
+ } else {
+ activity.unblockConversation(conversation);
+ }
+ }
+ };
+
+ private OnClickListener mAddBackClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Contact contact = conversation == null ? null : conversation.getContact();
+ if (contact != null) {
+ activity.xmppConnectionService.createContact(contact);
+ activity.switchToContactDetails(contact);
+ }
+ }
+ };
+
+ private OnClickListener mAnswerSmpClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Intent intent = new Intent(activity, VerifyOTRActivity.class);
+ intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
+ intent.putExtra("contact", conversation.getContact().getJid().toBareJid().toString());
+ intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
+ intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION);
+ startActivity(intent);
+ }
+ };
+
+ private void updateSnackBar(final Conversation conversation) {
+ final Account account = conversation.getAccount();
+ final Contact contact = conversation.getContact();
+ final int mode = conversation.getMode();
+ if (account.getStatus() == Account.State.DISABLED) {
+ showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener);
+ } else if (conversation.isBlocked()) {
+ showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
+ } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener);
+ } else if (mode == Conversation.MODE_MULTI
+ && !conversation.getMucOptions().online()
+ && account.getStatus() == Account.State.ONLINE) {
+ switch (conversation.getMucOptions().getError()) {
+ case NICK_IN_USE:
+ showSnackbar(R.string.nick_in_use, R.string.edit, clickToMuc);
+ break;
+ case NO_RESPONSE:
+ showSnackbar(R.string.joining_conference, 0, null);
+ break;
+ case PASSWORD_REQUIRED:
+ showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword);
+ break;
+ case BANNED:
+ showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc);
+ break;
+ case MEMBERS_ONLY:
+ showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc);
+ break;
+ case KICKED:
+ showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
+ break;
+ case UNKNOWN:
+ activity.xmppConnectionService.joinMuc(conversation);
+ //showSnackbar(R.string.conference_unknown_error, R.string.join, joinMuc);
+ break;
+ case SHUTDOWN:
+ showSnackbar(R.string.conference_shutdown, R.string.join, joinMuc);
+ break;
+ default:
+ break;
+ }
+ } else if (account.hasPendingPgpIntent(conversation)) {
+ showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
+ } else if (mode == Conversation.MODE_SINGLE
+ && conversation.smpRequested()) {
+ showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener);
+ } else if (mode == Conversation.MODE_SINGLE
+ && conversation.hasValidOtrSession()
+ && (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
+ && (!conversation.isOtrFingerprintVerified())) {
+ showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
+ } else {
+ hideSnackbar();
+ }
+ }
+
+ public void updateMessages() {
+ synchronized (this.messageList) {
+ if (getView() == null) {
+ return;
+ }
+ final ConversationActivity activity = (ConversationActivity) getActivity();
+ if (this.conversation != null) {
+ conversation.populateWithMessages(ConversationFragment.this.messageList);
+ updateSnackBar(conversation);
+ updateStatusMessages();
+ this.messageListAdapter.notifyDataSetChanged();
+ updateChatMsgHint();
+ if (!activity.isConversationsOverviewVisable() || !activity.isConversationsOverviewHideable()) {
+ activity.sendReadMarkerIfNecessary(conversation);
+ }
+ this.updateSendButton();
+ }
+ }
+ }
+
+ protected void messageSent() {
+ mEditMessage.setText("");
+ updateChatMsgHint();
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ int size = messageList.size();
+ messagesView.setSelection(size - 1);
+ }
+ });
+ }
+
+ public void setFocusOnInputField() {
+ mEditMessage.requestFocus();
+ }
+
+ enum SendButtonAction {TEXT, TAKE_PHOTO, SEND_LOCATION, RECORD_VOICE, CANCEL, CHOOSE_PICTURE}
+
+ private int getSendButtonImageResource(SendButtonAction action, Presence.Status status) {
+ switch (action) {
+ case TEXT:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_text_online;
+ case AWAY:
+ return R.drawable.ic_send_text_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_text_dnd;
+ default:
+ return R.drawable.ic_send_text_offline;
+ }
+ case TAKE_PHOTO:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_photo_online;
+ case AWAY:
+ return R.drawable.ic_send_photo_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_photo_dnd;
+ default:
+ return R.drawable.ic_send_photo_offline;
+ }
+ case RECORD_VOICE:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_voice_online;
+ case AWAY:
+ return R.drawable.ic_send_voice_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_voice_dnd;
+ default:
+ return R.drawable.ic_send_voice_offline;
+ }
+ case SEND_LOCATION:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_location_online;
+ case AWAY:
+ return R.drawable.ic_send_location_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_location_dnd;
+ default:
+ return R.drawable.ic_send_location_offline;
+ }
+ case CANCEL:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_cancel_online;
+ case AWAY:
+ return R.drawable.ic_send_cancel_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_cancel_dnd;
+ default:
+ return R.drawable.ic_send_cancel_offline;
+ }
+ case CHOOSE_PICTURE:
+ switch (status) {
+ case CHAT:
+ case ONLINE:
+ return R.drawable.ic_send_picture_online;
+ case AWAY:
+ return R.drawable.ic_send_picture_away;
+ case XA:
+ case DND:
+ return R.drawable.ic_send_picture_dnd;
+ default:
+ return R.drawable.ic_send_picture_offline;
+ }
+ }
+ return R.drawable.ic_send_text_offline;
+ }
+
+ public void updateSendButton() {
+ final Conversation c = this.conversation;
+ final SendButtonAction action;
+ final Presence.Status status;
+ final String text = this.mEditMessage == null ? "" : this.mEditMessage.getText().toString();
+ final boolean empty = text.length() == 0;
+ final boolean conference = c.getMode() == Conversation.MODE_MULTI;
+ if (c.getCorrectingMessage() != null && (empty || text.equals(c.getCorrectingMessage().getBody()))) {
+ action = SendButtonAction.CANCEL;
+ } else if (conference && !c.getAccount().httpUploadAvailable()) {
+ if (empty && c.getNextCounterpart() != null) {
+ action = SendButtonAction.CANCEL;
+ } else {
+ action = SendButtonAction.TEXT;
+ }
+ } else {
+ if (empty) {
+ if (conference && c.getNextCounterpart() != null) {
+ action = SendButtonAction.CANCEL;
+ } else {
+ String setting = activity.getPreferences().getString("quick_action", "recent");
+ if (!setting.equals("none") && UIHelper.receivedLocationQuestion(conversation.getLatestMessage())) {
+ setting = "location";
+ } else if (setting.equals("recent")) {
+ setting = activity.getPreferences().getString("recently_used_quick_action", "text");
+ }
+ switch (setting) {
+ case "photo":
+ action = SendButtonAction.TAKE_PHOTO;
+ break;
+ case "location":
+ action = SendButtonAction.SEND_LOCATION;
+ break;
+ case "voice":
+ action = SendButtonAction.RECORD_VOICE;
+ break;
+ case "picture":
+ action = SendButtonAction.CHOOSE_PICTURE;
+ break;
+ default:
+ action = SendButtonAction.TEXT;
+ break;
+ }
+ }
+ } else {
+ action = SendButtonAction.TEXT;
+ }
+ }
+ if (activity.useSendButtonToIndicateStatus() && c != null
+ && c.getAccount().getStatus() == Account.State.ONLINE) {
+ if (c.getMode() == Conversation.MODE_SINGLE) {
+ status = c.getContact().getShownStatus();
+ } else {
+ status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE;
+ }
+ } else {
+ status = Presence.Status.OFFLINE;
+ }
+ this.mSendButton.setTag(action);
+ this.mSendButton.setImageResource(getSendButtonImageResource(action, status));
+ }
+
+ protected void updateStatusMessages() {
+ synchronized (this.messageList) {
+ if (showLoadMoreMessages(conversation)) {
+ this.messageList.add(0, Message.createLoadMoreMessage(conversation));
+ }
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ ChatState state = conversation.getIncomingChatState();
+ if (state == ChatState.COMPOSING) {
+ //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName())));
+ } else if (state == ChatState.PAUSED) {
+ //this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName())));
+ } else {
+ for (int i = this.messageList.size() - 1; i >= 0; --i) {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_RECEIVED) {
+ return;
+ } else {
+ if (this.messageList.get(i).getStatus() == Message.STATUS_SEND_DISPLAYED) {
+// this.messageList.add(i + 1,
+// Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
+// return;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private boolean showLoadMoreMessages(final Conversation c) {
+ final boolean mam = hasMamSupport(c);
+ final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService();
+ return mam && (c.getLastClearHistory() != 0 || (c.countMessages() == 0 && c.hasMessagesLeftOnServer() && !service.queryInProgress(c)));
+ }
+
+ private boolean hasMamSupport(final Conversation c) {
+ if (c.getMode() == Conversation.MODE_SINGLE) {
+ final XmppConnection connection = c.getAccount().getXmppConnection();
+ return connection != null && connection.getFeatures().mam();
+ } else {
+ return c.getMucOptions().mamSupport();
+ }
+ }
+
+ protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) {
+ snackbar.setVisibility(View.VISIBLE);
+ snackbar.setOnClickListener(null);
+ snackbarMessage.setText(message);
+ snackbarMessage.setOnClickListener(null);
+ snackbarAction.setVisibility(clickListener == null ? View.GONE : View.VISIBLE);
+ if (action != 0) {
+ snackbarAction.setText(action);
+ }
+ snackbarAction.setOnClickListener(clickListener);
+ }
+
+ protected void hideSnackbar() {
+ snackbar.setVisibility(View.GONE);
+ }
+
+ protected void sendPlainTextMessage(Message message) {
+ ConversationActivity activity = (ConversationActivity) getActivity();
+ activity.xmppConnectionService.sendMessage(message);
+ messageSent();
+ }
+
+ protected void sendPgpMessage(final Message message) {
+ final ConversationActivity activity = (ConversationActivity) getActivity();
+ final XmppConnectionService xmppService = activity.xmppConnectionService;
+ final Contact contact = message.getConversation().getContact();
+ if (!activity.hasPgp()) {
+ activity.showInstallPgpDialog();
+ return;
+ }
+ if (conversation.getAccount().getPgpSignature() == null) {
+ activity.announcePgp(conversation.getAccount(), conversation, activity.onOpenPGPKeyPublished);
+ return;
+ }
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ if (contact.getPgpKeyId() != 0) {
+ xmppService.getPgpEngine().hasKey(contact,
+ new UiCallback<Contact>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi,
+ Contact contact) {
+ activity.runIntent(
+ pi,
+ ConversationActivity.REQUEST_ENCRYPT_MESSAGE);
+ }
+
+ @Override
+ public void success(Contact contact) {
+ activity.encryptTextMessage(message);
+ }
+
+ @Override
+ public void error(int error, Contact contact) {
+ activity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(activity,
+ R.string.unable_to_connect_to_keychain,
+ Toast.LENGTH_SHORT
+ ).show();
+ }
+ });
+ }
+ });
+
+ } else {
+ showNoPGPKeyDialog(false,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ xmppService.databaseBackend
+ .updateConversation(conversation);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+ });
+ }
+ } else {
+ if (conversation.getMucOptions().pgpKeysInUse()) {
+ if (!conversation.getMucOptions().everybodyHasKeys()) {
+ Toast warning = Toast
+ .makeText(getActivity(),
+ R.string.missing_public_keys,
+ Toast.LENGTH_LONG);
+ warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
+ warning.show();
+ }
+ activity.encryptTextMessage(message);
+ } else {
+ showNoPGPKeyDialog(true,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ conversation
+ .setNextEncryption(Message.ENCRYPTION_NONE);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.databaseBackend
+ .updateConversation(conversation);
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+ });
+ }
+ }
+ }
+
+ public void showNoPGPKeyDialog(boolean plural,
+ DialogInterface.OnClickListener listener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ if (plural) {
+ builder.setTitle(getString(R.string.no_pgp_keys));
+ builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
+ } else {
+ builder.setTitle(getString(R.string.no_pgp_key));
+ builder.setMessage(getText(R.string.contact_has_no_pgp_key));
+ }
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.send_unencrypted),
+ listener);
+ builder.create().show();
+ }
+
+ protected void sendAxolotlMessage(final Message message) {
+ final ConversationActivity activity = (ConversationActivity) getActivity();
+ final XmppConnectionService xmppService = activity.xmppConnectionService;
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+
+ protected void sendOtrMessage(final Message message) {
+ final ConversationActivity activity = (ConversationActivity) getActivity();
+ final XmppConnectionService xmppService = activity.xmppConnectionService;
+ activity.selectPresence(message.getConversation(),
+ new OnPresenceSelected() {
+
+ @Override
+ public void onPresenceSelected() {
+ message.setCounterpart(conversation.getNextCounterpart());
+ xmppService.sendMessage(message);
+ messageSent();
+ }
+ });
+ }
+
+ public void appendText(String text) {
+ if (text == null) {
+ return;
+ }
+ String previous = this.mEditMessage.getText().toString();
+ if (previous.length() != 0 && !previous.endsWith(" ")) {
+ text = " " + text;
+ }
+ this.mEditMessage.append(text);
+ }
+
+ @Override
+ public boolean onEnterPressed() {
+ if (activity.enterIsSend()) {
+ sendMessage();
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onTypingStarted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ activity.hideConversationsOverview();
+ updateSendButton();
+ }
+
+ @Override
+ public void onTypingStopped() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.PAUSED)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ }
+
+ @Override
+ public void onTextDeleted() {
+ Account.State status = conversation.getAccount().getStatus();
+ if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
+ activity.xmppConnectionService.sendChatState(conversation);
+ }
+ updateSendButton();
+ }
+
+ @Override
+ public void onTextChanged() {
+ if (conversation != null && conversation.getCorrectingMessage() != null) {
+ updateSendButton();
+ }
+ }
+
+ private int completionIndex = 0;
+ private int lastCompletionLength = 0;
+ private String incomplete;
+ private int lastCompletionCursor;
+ private boolean firstWord = false;
+
+ @Override
+ public boolean onTabPressed(boolean repeated) {
+ if (conversation == null || conversation.getMode() == Conversation.MODE_SINGLE) {
+ return false;
+ }
+ if (repeated) {
+ completionIndex++;
+ } else {
+ lastCompletionLength = 0;
+ completionIndex = 0;
+ final String content = mEditMessage.getText().toString();
+ lastCompletionCursor = mEditMessage.getSelectionEnd();
+ int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ",lastCompletionCursor-1) + 1 : 0;
+ firstWord = start == 0;
+ incomplete = content.substring(start,lastCompletionCursor);
+ }
+ List<String> completions = new ArrayList<>();
+ for(MucOptions.User user : conversation.getMucOptions().getUsers()) {
+ String name = user.getName();
+ if (name != null && name.startsWith(incomplete)) {
+ completions.add(name+(firstWord ? ": " : " "));
+ }
+ }
+ Collections.sort(completions);
+ if (completions.size() > completionIndex) {
+ String completion = completions.get(completionIndex).substring(incomplete.length());
+ mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength);
+ mEditMessage.getEditableText().insert(lastCompletionCursor, completion);
+ lastCompletionLength = completion.length();
+ } else {
+ completionIndex = -1;
+ mEditMessage.getEditableText().delete(lastCompletionCursor,lastCompletionCursor + lastCompletionLength);
+ lastCompletionLength = 0;
+ }
+ return true;
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode,
+ final Intent data) {
+ if (resultCode == Activity.RESULT_OK) {
+ if (requestCode == ConversationActivity.REQUEST_DECRYPT_PGP) {
+ activity.getSelectedConversation().getAccount().getPgpDecryptionService().continueDecryption(true);
+ } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_TEXT) {
+ final String body = mEditMessage.getText().toString();
+ Message message = new Message(conversation, body, conversation.getNextEncryption());
+ sendAxolotlMessage(message);
+ } else if (requestCode == ConversationActivity.REQUEST_TRUST_KEYS_MENU) {
+ int choice = data.getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID);
+ activity.selectPresenceToAttachFile(choice, conversation.getNextEncryption());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
new file mode 100644
index 000000000..330e3e59a
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java
@@ -0,0 +1,1070 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+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;
+import android.text.TextWatcher;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.AutoCompleteTextView;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
+import de.pixart.messenger.services.XmppConnectionService.OnCaptchaRequested;
+import de.pixart.messenger.ui.adapter.KnownHostsAdapter;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.utils.UIHelper;
+import de.pixart.messenger.xml.Element;
+import de.pixart.messenger.xmpp.OnKeyStatusUpdated;
+import de.pixart.messenger.xmpp.XmppConnection;
+import de.pixart.messenger.xmpp.XmppConnection.Features;
+import de.pixart.messenger.xmpp.forms.Data;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+import de.pixart.messenger.xmpp.pep.Avatar;
+
+public class EditAccountActivity extends XmppActivity implements OnAccountUpdate,
+ OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
+
+ 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;
+ private TextView mServerInfoMam;
+ private TextView mServerInfoCSI;
+ private TextView mServerInfoBlocking;
+ private TextView mServerInfoPep;
+ private TextView mServerInfoHttpUpload;
+ private TextView mServerInfoPush;
+ private TextView mSessionEst;
+ private TextView mOtrFingerprint;
+ private TextView mAxolotlFingerprint;
+ private TextView mOwnFingerprintDesc;
+ private TextView mAccountJidLabel;
+ private ImageView mAvatar;
+ private RelativeLayout mOtrFingerprintBox;
+ private RelativeLayout mAxolotlFingerprintBox;
+ private ImageButton mOtrFingerprintToClipboardButton;
+ private ImageButton mAxolotlFingerprintToClipboardButton;
+ private ImageButton mRegenerateAxolotlKeyButton;
+ private LinearLayout keys;
+ private LinearLayout keysCard;
+ private LinearLayout mNamePort;
+ private EditText mHostname;
+ private EditText mPort;
+ private AlertDialog mCaptchaDialog = null;
+
+ private Jid jidToEdit;
+ private boolean mInitMode = false;
+ private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
+ private boolean mShowOptions = false;
+ private Account mAccount;
+ private String messageFingerprint;
+
+ private boolean mFetchingAvatar = false;
+
+ private final OnClickListener mSaveButtonClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ final String password = mPassword.getText().toString();
+ final String passwordConfirm = mPasswordConfirm.getText().toString();
+
+ if (!mInitMode && passwordChangedInMagicCreateMode()) {
+ gotoChangePassword(password);
+ return;
+ }
+ if (mInitMode && mAccount != null) {
+ mAccount.setOption(Account.OPTION_DISABLED, false);
+ }
+ if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
+ mAccount.setOption(Account.OPTION_DISABLED, false);
+ xmppConnectionService.updateAccount(mAccount);
+ return;
+ }
+ final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI;
+ if (mUsernameMode && mAccountJid.getText().toString().contains("@")) {
+ mAccountJid.setError(getString(R.string.invalid_username));
+ mAccountJid.requestFocus();
+ return;
+ }
+ final Jid jid;
+ try {
+ if (mUsernameMode) {
+ jid = Jid.fromParts(mAccountJid.getText().toString(), getUserModeDomain(), null);
+ } else {
+ jid = Jid.fromString(mAccountJid.getText().toString());
+ }
+ } catch (final InvalidJidException e) {
+ if (mUsernameMode) {
+ mAccountJid.setError(getString(R.string.invalid_username));
+ } else {
+ mAccountJid.setError(getString(R.string.invalid_jid));
+ }
+ mAccountJid.requestFocus();
+ return;
+ }
+ String hostname = null;
+ int numericPort = 5222;
+ if (mShowOptions) {
+ hostname = mHostname.getText().toString().replaceAll("\\s","");
+ final String port = mPort.getText().toString().replaceAll("\\s","");
+ if (hostname.contains(" ")) {
+ mHostname.setError(getString(R.string.not_valid_hostname));
+ mHostname.requestFocus();
+ return;
+ }
+ try {
+ numericPort = Integer.parseInt(port);
+ if (numericPort < 0 || numericPort > 65535) {
+ mPort.setError(getString(R.string.not_a_valid_port));
+ mPort.requestFocus();
+ return;
+ }
+
+ } catch (NumberFormatException e) {
+ mPort.setError(getString(R.string.not_a_valid_port));
+ mPort.requestFocus();
+ return;
+ }
+ }
+
+ if (jid.isDomainJid()) {
+ if (mUsernameMode) {
+ mAccountJid.setError(getString(R.string.invalid_username));
+ } else {
+ mAccountJid.setError(getString(R.string.invalid_jid));
+ }
+ mAccountJid.requestFocus();
+ return;
+ }
+ if (registerNewAccount) {
+ if (!password.equals(passwordConfirm)) {
+ mPasswordConfirm.setError(getString(R.string.passwords_do_not_match));
+ mPasswordConfirm.requestFocus();
+ return;
+ }
+ }
+ if (mAccount != null) {
+ if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
+ mAccount.setOption(Account.OPTION_MAGIC_CREATE, mAccount.getPassword().contains(password));
+ }
+ mAccount.setJid(jid);
+ mAccount.setPort(numericPort);
+ mAccount.setHostname(hostname);
+ mAccountJid.setError(null);
+ mPasswordConfirm.setError(null);
+ mAccount.setPassword(password);
+ mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+ xmppConnectionService.updateAccount(mAccount);
+ } else {
+ if (xmppConnectionService.findAccountByJid(jid) != null) {
+ mAccountJid.setError(getString(R.string.account_already_exists));
+ mAccountJid.requestFocus();
+ return;
+ }
+ mAccount = new Account(jid.toBareJid(), password);
+ mAccount.setPort(numericPort);
+ mAccount.setHostname(hostname);
+ mAccount.setOption(Account.OPTION_USETLS, true);
+ mAccount.setOption(Account.OPTION_USECOMPRESSION, true);
+ mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount);
+ xmppConnectionService.createAccount(mAccount);
+ }
+ mHostname.setError(null);
+ mPort.setError(null);
+ if (!mAccount.isOptionSet(Account.OPTION_DISABLED)
+ && !registerNewAccount
+ && !mInitMode) {
+ finish();
+ } else {
+ updateSaveButton();
+ updateAccountInformation(true);
+ }
+
+ }
+ };
+ private final OnClickListener mCancelButtonClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ deleteMagicCreatedAccountAndReturnIfNecessary();
+ finish();
+ }
+ };
+ private Toast mFetchingMamPrefsToast;
+ private TableRow mPushRow;
+
+ public void refreshUiReal() {
+ invalidateOptionsMenu();
+ if (mAccount != null
+ && mAccount.getStatus() != Account.State.ONLINE
+ && mFetchingAvatar) {
+ startActivity(new Intent(getApplicationContext(),
+ ManageAccountActivity.class));
+ finish();
+ } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) {
+ if (!mFetchingAvatar) {
+ mFetchingAvatar = true;
+ xmppConnectionService.checkForAvatar(mAccount, mAvatarFetchCallback);
+ }
+ }
+ if (mAccount != null) {
+ updateAccountInformation(false);
+ }
+ updateSaveButton();
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ deleteMagicCreatedAccountAndReturnIfNecessary();
+ return super.onNavigateUp();
+ }
+
+ @Override
+ public void onBackPressed() {
+ deleteMagicCreatedAccountAndReturnIfNecessary();
+ super.onBackPressed();
+ }
+
+ private void deleteMagicCreatedAccountAndReturnIfNecessary() {
+ if (Config.MAGIC_CREATE_DOMAIN != null
+ && mAccount != null
+ && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
+ && mAccount.isOptionSet(Account.OPTION_REGISTER)
+ && xmppConnectionService.getAccounts().size() == 1) {
+ xmppConnectionService.deleteAccount(mAccount);
+ startActivity(new Intent(EditAccountActivity.this, WelcomeActivity.class));
+ }
+ }
+
+ @Override
+ public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ private final UiCallback<Avatar> mAvatarFetchCallback = new UiCallback<Avatar>() {
+
+ @Override
+ public void userInputRequried(final PendingIntent pi, final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+
+ @Override
+ public void success(final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+
+ @Override
+ public void error(final int errorCode, final Avatar avatar) {
+ finishInitialSetup(avatar);
+ }
+ };
+ private final TextWatcher mTextWatcher = new TextWatcher() {
+
+ @Override
+ public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
+ updateSaveButton();
+ }
+
+ @Override
+ public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
+ }
+
+ @Override
+ public void afterTextChanged(final Editable s) {
+
+ }
+ };
+
+ private final OnClickListener mAvatarClickListener = new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ if (mAccount != null) {
+ final Intent intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString());
+ startActivity(intent);
+ }
+ }
+ };
+
+ protected void finishInitialSetup(final Avatar avatar) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ final Intent intent;
+ final XmppConnection connection = mAccount.getXmppConnection();
+ final boolean wasFirstAccount = xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1;
+ if (avatar != null || (connection != null && !connection.getFeatures().pep())) {
+ intent = new Intent(getApplicationContext(), StartConversationActivity.class);
+ if (wasFirstAccount) {
+ intent.putExtra("init", true);
+ }
+ } else {
+ intent = new Intent(getApplicationContext(), PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toBareJid().toString());
+ intent.putExtra("setup", true);
+ }
+ if (wasFirstAccount) {
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ }
+ startActivity(intent);
+ finish();
+ }
+ });
+ }
+
+ @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() {
+ boolean accountInfoEdited = accountInfoEdited();
+
+ if (!mInitMode && passwordChangedInMagicCreateMode()) {
+ this.mSaveButton.setText(R.string.change_password);
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ } else if (accountInfoEdited && !mInitMode) {
+ this.mSaveButton.setText(R.string.save);
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ } else if (mAccount != null
+ && (mAccount.getStatus() == Account.State.CONNECTING || mAccount.getStatus() == Account.State.REGISTRATION_SUCCESSFUL|| mFetchingAvatar)) {
+ this.mSaveButton.setEnabled(false);
+ this.mSaveButton.setTextColor(getSecondaryTextColor());
+ this.mSaveButton.setText(R.string.account_status_connecting);
+ } else if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !mInitMode) {
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ this.mSaveButton.setText(R.string.enable);
+ } else {
+ this.mSaveButton.setEnabled(true);
+ this.mSaveButton.setTextColor(getPrimaryTextColor());
+ if (!mInitMode) {
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ this.mSaveButton.setText(R.string.save);
+ if (!accountInfoEdited) {
+ this.mSaveButton.setEnabled(false);
+ this.mSaveButton.setTextColor(getSecondaryTextColor());
+ }
+ } else {
+ this.mSaveButton.setText(R.string.connect);
+ }
+ } else {
+ this.mSaveButton.setText(R.string.next);
+ }
+ }
+ }
+
+ protected boolean accountInfoEdited() {
+ if (this.mAccount == null) {
+ return false;
+ }
+ return jidEdited() ||
+ !this.mAccount.getPassword().equals(this.mPassword.getText().toString()) ||
+ !this.mAccount.getHostname().equals(this.mHostname.getText().toString()) ||
+ !String.valueOf(this.mAccount.getPort()).equals(this.mPort.getText().toString());
+ }
+
+ protected boolean jidEdited() {
+ final String unmodified;
+ if (mUsernameMode) {
+ unmodified = this.mAccount.getJid().getLocalpart();
+ } else {
+ unmodified = this.mAccount.getJid().toBareJid().toString();
+ }
+ return !unmodified.equals(this.mAccountJid.getText().toString());
+ }
+
+ protected boolean passwordChangedInMagicCreateMode() {
+ return mAccount != null
+ && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
+ && !this.mAccount.getPassword().equals(this.mPassword.getText().toString())
+ && !this.jidEdited()
+ && mAccount.isOnlineAndConnected();
+ }
+
+ @Override
+ protected String getShareableUri() {
+ if (mAccount != null) {
+ return mAccount.getShareableUri();
+ } else {
+ return "";
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_edit_account);
+ this.mAccountJid = (AutoCompleteTextView) findViewById(R.id.account_jid);
+ this.mAccountJid.addTextChangedListener(this.mTextWatcher);
+ this.mAccountJidLabel = (TextView) findViewById(R.id.account_jid_label);
+ this.mPassword = (EditText) findViewById(R.id.account_password);
+ this.mPassword.addTextChangedListener(this.mTextWatcher);
+ this.mPasswordConfirm = (EditText) findViewById(R.id.account_password_confirm);
+ this.mAvatar = (ImageView) findViewById(R.id.avater);
+ 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);
+ try {
+ startActivityForResult(intent, REQUEST_BATTERY_OP);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ 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);
+ this.mServerInfoMam = (TextView) findViewById(R.id.server_info_mam);
+ this.mServerInfoCSI = (TextView) findViewById(R.id.server_info_csi);
+ this.mServerInfoBlocking = (TextView) findViewById(R.id.server_info_blocking);
+ this.mServerInfoSm = (TextView) findViewById(R.id.server_info_sm);
+ this.mServerInfoPep = (TextView) findViewById(R.id.server_info_pep);
+ this.mServerInfoHttpUpload = (TextView) findViewById(R.id.server_info_http_upload);
+ this.mPushRow = (TableRow) findViewById(R.id.push_row);
+ this.mServerInfoPush = (TextView) findViewById(R.id.server_info_push);
+ this.mOtrFingerprint = (TextView) findViewById(R.id.otr_fingerprint);
+ this.mOtrFingerprintBox = (RelativeLayout) findViewById(R.id.otr_fingerprint_box);
+ this.mOtrFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_to_clipboard);
+ this.mAxolotlFingerprint = (TextView) findViewById(R.id.axolotl_fingerprint);
+ this.mAxolotlFingerprintBox = (RelativeLayout) findViewById(R.id.axolotl_fingerprint_box);
+ this.mAxolotlFingerprintToClipboardButton = (ImageButton) findViewById(R.id.action_copy_axolotl_to_clipboard);
+ this.mRegenerateAxolotlKeyButton = (ImageButton) findViewById(R.id.action_regenerate_omemo_key);
+ this.mOwnFingerprintDesc = (TextView) findViewById(R.id.own_fingerprint_desc);
+ this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
+ this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
+ this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
+ this.mHostname = (EditText) findViewById(R.id.hostname);
+ this.mHostname.addTextChangedListener(mTextWatcher);
+ this.mPort = (EditText) findViewById(R.id.port);
+ this.mPort.setText("5222");
+ this.mPort.addTextChangedListener(mTextWatcher);
+ this.mSaveButton = (Button) findViewById(R.id.save_button);
+ this.mCancelButton = (Button) findViewById(R.id.cancel_button);
+ this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
+ this.mCancelButton.setOnClickListener(this.mCancelButtonClickListener);
+ this.mMoreTable = (TableLayout) findViewById(R.id.server_info_more);
+ final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(final CompoundButton buttonView,
+ final boolean isChecked) {
+ if (isChecked) {
+ mPasswordConfirm.setVisibility(View.VISIBLE);
+ } else {
+ mPasswordConfirm.setVisibility(View.GONE);
+ }
+ updateSaveButton();
+ }
+ };
+ this.mRegisterNew.setOnCheckedChangeListener(OnCheckedShowConfirmPassword);
+ if (Config.DISALLOW_REGISTRATION_IN_UI) {
+ this.mRegisterNew.setVisibility(View.GONE);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.editaccount, menu);
+ final MenuItem showQrCode = menu.findItem(R.id.action_show_qr_code);
+ final MenuItem showBlocklist = menu.findItem(R.id.action_show_block_list);
+ final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
+ final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
+ final MenuItem showPassword = menu.findItem(R.id.action_show_password);
+ final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
+ final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
+ final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
+ final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
+ renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
+
+ if (mAccount != null && mAccount.isOnlineAndConnected()) {
+ if (!mAccount.getXmppConnection().getFeatures().blocking()) {
+ showBlocklist.setVisible(false);
+ }
+ if (!mAccount.getXmppConnection().getFeatures().register()) {
+ changePassword.setVisible(false);
+ }
+ mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
+ Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
+ if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) {
+ clearDevices.setVisible(false);
+ }
+ changePresence.setVisible(manuallyChangePresence());
+ } else {
+ showQrCode.setVisible(false);
+ showBlocklist.setVisible(false);
+ showMoreInfo.setVisible(false);
+ changePassword.setVisible(false);
+ clearDevices.setVisible(false);
+ mamPrefs.setVisible(false);
+ changePresence.setVisible(false);
+ }
+
+ if (mAccount != null) {
+ showPassword.setVisible(mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)
+ && !mAccount.isOptionSet(Account.OPTION_REGISTER));
+ } else {
+ showPassword.setVisible(false);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (getIntent() != null) {
+ try {
+ this.jidToEdit = Jid.fromString(getIntent().getStringExtra("jid"));
+ } catch (final InvalidJidException | NullPointerException ignored) {
+ this.jidToEdit = null;
+ }
+ this.mInitMode = getIntent().getBooleanExtra("init", false) || this.jidToEdit == null;
+ this.messageFingerprint = getIntent().getStringExtra("fingerprint");
+ if (!mInitMode) {
+ this.mRegisterNew.setVisibility(View.GONE);
+ if (getActionBar() != null) {
+ getActionBar().setTitle(getString(R.string.account_details));
+ }
+ } else {
+ this.mAvatar.setVisibility(View.GONE);
+ if (getActionBar() != null) {
+ getActionBar().setTitle(R.string.action_add_account);
+ }
+ }
+ }
+ SharedPreferences preferences = getPreferences();
+ boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false);
+ this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false);
+ mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example);
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ if (this.jidToEdit != null) {
+ this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit);
+ if (this.mAccount != null) {
+ this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER);
+ this.mUsernameMode |= mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && mAccount.isOptionSet(Account.OPTION_REGISTER);
+ if (this.mAccount.getPrivateKeyAlias() != null) {
+ this.mPassword.setHint(R.string.authenticate_with_certificate);
+ if (this.mInitMode) {
+ this.mPassword.requestFocus();
+ }
+ }
+ updateAccountInformation(true);
+ }
+ }
+ if (Config.MAGIC_CREATE_DOMAIN == null && this.xmppConnectionService.getAccounts().size() == 0) {
+ this.mCancelButton.setEnabled(false);
+ this.mCancelButton.setTextColor(getSecondaryTextColor());
+ }
+ if (mUsernameMode) {
+ this.mAccountJidLabel.setText(R.string.username);
+ this.mAccountJid.setHint(R.string.username_hint);
+ } else {
+ final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
+ R.layout.simple_list_item,
+ xmppConnectionService.getKnownHosts());
+ this.mAccountJid.setAdapter(mKnownHostsAdapter);
+ }
+ updateSaveButton();
+ invalidateOptionsMenu();
+ }
+
+ private String getUserModeDomain() {
+ if (mAccount != null) {
+ return mAccount.getJid().getDomainpart();
+ } else {
+ return Config.DOMAIN_LOCK;
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mgmt_account_reconnect:
+ if (xmppConnectionServiceBound) {
+ unbindService(mConnection);
+ xmppConnectionServiceBound = false;
+ }
+ stopService(new Intent(EditAccountActivity.this,
+ XmppConnectionService.class));
+ finish();
+ break;
+ case R.id.action_show_block_list:
+ final Intent showBlocklistIntent = new Intent(this, BlocklistActivity.class);
+ showBlocklistIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
+ startActivity(showBlocklistIntent);
+ break;
+ case R.id.action_server_info_show_more:
+ mMoreTable.setVisibility(item.isChecked() ? View.GONE : View.VISIBLE);
+ item.setChecked(!item.isChecked());
+ break;
+ case R.id.action_change_password_on_server:
+ gotoChangePassword(null);
+ break;
+ case R.id.action_mam_prefs:
+ editMamPrefs();
+ break;
+ case R.id.action_clear_devices:
+ showWipePepDialog();
+ break;
+ case R.id.action_renew_certificate:
+ renewCertificate();
+ break;
+ case R.id.action_change_presence:
+ changePresence();
+ break;
+ case R.id.action_show_password:
+ showPassword();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ private void gotoChangePassword(String newPassword) {
+ final Intent changePasswordIntent = new Intent(this, ChangePasswordActivity.class);
+ changePasswordIntent.putExtra(EXTRA_ACCOUNT, mAccount.getJid().toString());
+ if (newPassword != null) {
+ changePasswordIntent.putExtra("password", newPassword);
+ }
+ startActivity(changePasswordIntent);
+ }
+
+ private void renewCertificate() {
+ KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+ }
+
+ private void changePresence() {
+ Intent intent = new Intent(this, SetPresenceActivity.class);
+ intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,mAccount.getJid().toBareJid().toString());
+ startActivity(intent);
+ }
+
+ @Override
+ public void alias(String alias) {
+ if (alias != null) {
+ xmppConnectionService.updateKeyInAccount(mAccount, alias);
+ }
+ }
+
+ private void updateAccountInformation(boolean init) {
+ if (init) {
+ this.mAccountJid.getEditableText().clear();
+ if (mUsernameMode) {
+ this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart());
+ } else {
+ this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString());
+ }
+ this.mPassword.setText(this.mAccount.getPassword());
+ this.mHostname.setText("");
+ this.mHostname.getEditableText().append(this.mAccount.getHostname());
+ this.mPort.setText("");
+ this.mPort.getEditableText().append(String.valueOf(this.mAccount.getPort()));
+ this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE);
+
+ }
+
+ if (!mInitMode) {
+ this.mAvatar.setVisibility(View.VISIBLE);
+ this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(Config.AVATAR_SIZE)));
+ } else {
+ this.mAvatar.setVisibility(View.GONE);
+ }
+ if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) {
+ this.mRegisterNew.setVisibility(View.VISIBLE);
+ this.mRegisterNew.setChecked(true);
+ this.mPasswordConfirm.setText(this.mAccount.getPassword());
+ } else {
+ this.mRegisterNew.setVisibility(View.GONE);
+ this.mRegisterNew.setChecked(false);
+ }
+ if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) {
+ Features features = this.mAccount.getXmppConnection().getFeatures();
+ this.mStats.setVisibility(View.VISIBLE);
+ boolean showOptimizingWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery();
+ this.mBatteryOptimizations.setVisibility(showOptimizingWarning ? View.VISIBLE : View.GONE);
+ this.mSessionEst.setText(UIHelper.readableTimeDifferenceFull(this, this.mAccount.getXmppConnection()
+ .getLastSessionEstablished()));
+ if (features.rosterVersioning()) {
+ this.mServerInfoRosterVersion.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoRosterVersion.setText(R.string.server_info_unavailable);
+ }
+ if (features.carbons()) {
+ this.mServerInfoCarbons.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoCarbons
+ .setText(R.string.server_info_unavailable);
+ }
+ if (features.mam()) {
+ this.mServerInfoMam.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoMam.setText(R.string.server_info_unavailable);
+ }
+ if (features.csi()) {
+ this.mServerInfoCSI.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoCSI.setText(R.string.server_info_unavailable);
+ }
+ if (features.blocking()) {
+ this.mServerInfoBlocking.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoBlocking.setText(R.string.server_info_unavailable);
+ }
+ if (features.sm()) {
+ this.mServerInfoSm.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoSm.setText(R.string.server_info_unavailable);
+ }
+ if (features.pep()) {
+ AxolotlService axolotlService = this.mAccount.getAxolotlService();
+ if (axolotlService != null && axolotlService.isPepBroken()) {
+ this.mServerInfoPep.setText(R.string.server_info_broken);
+ } else {
+ this.mServerInfoPep.setText(R.string.server_info_available);
+ }
+ } else {
+ this.mServerInfoPep.setText(R.string.server_info_unavailable);
+ }
+ if (features.httpUpload(0)) {
+ this.mServerInfoHttpUpload.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoHttpUpload.setText(R.string.server_info_unavailable);
+ }
+
+ this.mPushRow.setVisibility(xmppConnectionService.getPushManagementService().isStub() ? View.GONE : View.VISIBLE);
+
+ if (xmppConnectionService.getPushManagementService().available(mAccount)) {
+ this.mServerInfoPush.setText(R.string.server_info_available);
+ } else {
+ this.mServerInfoPush.setText(R.string.server_info_unavailable);
+ }
+ final String otrFingerprint = this.mAccount.getOtrFingerprint();
+ if (otrFingerprint != null && Config.supportOtr()) {
+ this.mOtrFingerprintBox.setVisibility(View.VISIBLE);
+ this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
+ this.mOtrFingerprintToClipboardButton
+ .setVisibility(View.VISIBLE);
+ this.mOtrFingerprintToClipboardButton
+ .setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+
+ if (copyTextToClipboard(otrFingerprint, R.string.otr_fingerprint)) {
+ Toast.makeText(
+ EditAccountActivity.this,
+ R.string.toast_message_otr_fingerprint,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ } else {
+ this.mOtrFingerprintBox.setVisibility(View.GONE);
+ }
+ final String ownAxolotlFingerprint = this.mAccount.getAxolotlService().getOwnFingerprint();
+ if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
+ this.mAxolotlFingerprintBox.setVisibility(View.VISIBLE);
+ if (ownAxolotlFingerprint.equals(messageFingerprint)) {
+ this.mOwnFingerprintDesc.setTextColor(getResources().getColor(R.color.accent));
+ } else {
+ this.mOwnFingerprintDesc.setTextColor(getSecondaryTextColor());
+ }
+ this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
+ this.mAxolotlFingerprintToClipboardButton
+ .setVisibility(View.VISIBLE);
+ this.mAxolotlFingerprintToClipboardButton
+ .setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+
+ if (copyTextToClipboard(ownAxolotlFingerprint.substring(2), R.string.omemo_fingerprint)) {
+ Toast.makeText(
+ EditAccountActivity.this,
+ R.string.toast_message_omemo_fingerprint,
+ Toast.LENGTH_SHORT).show();
+ }
+ }
+ });
+ if (Config.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON) {
+ this.mRegenerateAxolotlKeyButton
+ .setVisibility(View.VISIBLE);
+ this.mRegenerateAxolotlKeyButton
+ .setOnClickListener(new View.OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ showRegenerateAxolotlKeyDialog();
+ }
+ });
+ }
+ } else {
+ this.mAxolotlFingerprintBox.setVisibility(View.GONE);
+ }
+ boolean hasKeys = false;
+ keys.removeAllViews();
+ for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
+ if (ownAxolotlFingerprint.equals(fingerprint)) {
+ continue;
+ }
+ boolean highlight = fingerprint.equals(messageFingerprint);
+ hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight, null);
+ }
+ if (hasKeys && Config.supportOmemo()) {
+ keysCard.setVisibility(View.VISIBLE);
+ } else {
+ keysCard.setVisibility(View.GONE);
+ }
+ } else {
+ if (this.mAccount.errorStatus()) {
+ final EditText errorTextField;
+ if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) {
+ errorTextField = this.mPassword;
+ } else if (mShowOptions
+ && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND
+ && this.mHostname.getText().length() > 0) {
+ errorTextField = this.mHostname;
+ } else {
+ errorTextField = this.mAccountJid;
+ }
+ errorTextField.setError(getString(this.mAccount.getStatus().getReadableId()));
+ if (init || !accountInfoEdited()) {
+ errorTextField.requestFocus();
+ }
+ } else {
+ this.mAccountJid.setError(null);
+ this.mPassword.setError(null);
+ this.mHostname.setError(null);
+ }
+ this.mStats.setVisibility(View.GONE);
+ }
+ }
+
+ public void showRegenerateAxolotlKeyDialog() {
+ Builder builder = new Builder(this);
+ builder.setTitle("Regenerate Key");
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage("Are you sure you want to regenerate your Identity Key? (This will also wipe all established sessions and contact Identity Keys)");
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton("Yes",
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAccount.getAxolotlService().regenerateKeys(false);
+ }
+ });
+ builder.create().show();
+ }
+
+ public void showWipePepDialog() {
+ Builder builder = new Builder(this);
+ builder.setTitle(getString(R.string.clear_other_devices));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getString(R.string.clear_other_devices_desc));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.accept),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ mAccount.getAxolotlService().wipeOtherPepDevices();
+ }
+ });
+ builder.create().show();
+ }
+
+ private void editMamPrefs() {
+ this.mFetchingMamPrefsToast = Toast.makeText(this, R.string.fetching_mam_prefs, Toast.LENGTH_LONG);
+ this.mFetchingMamPrefsToast.show();
+ xmppConnectionService.fetchMamPreferences(mAccount, this);
+ }
+
+ private void showPassword() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View view = getLayoutInflater().inflate(R.layout.dialog_show_password, null);
+ TextView password = (TextView) view.findViewById(R.id.password);
+ password.setText(mAccount.getPassword());
+ builder.setTitle(R.string.password);
+ builder.setView(view);
+ builder.setPositiveButton(R.string.cancel, null);
+ builder.create().show();
+ }
+
+ @Override
+ public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
+ refreshUi();
+ }
+
+ @Override
+ public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) {
+ mCaptchaDialog.dismiss();
+ }
+ final AlertDialog.Builder builder = new AlertDialog.Builder(EditAccountActivity.this);
+ final View view = getLayoutInflater().inflate(R.layout.captcha, null);
+ final ImageView imageView = (ImageView) view.findViewById(R.id.captcha);
+ final EditText input = (EditText) view.findViewById(R.id.input);
+ imageView.setImageBitmap(captcha);
+
+ builder.setTitle(getString(R.string.captcha_required));
+ builder.setView(view);
+
+ builder.setPositiveButton(getString(R.string.ok),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String rc = input.getText().toString();
+ data.put("username", account.getUsername());
+ data.put("password", account.getPassword());
+ data.put("ocr", rc);
+ data.submit();
+
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(
+ account, id, data);
+ }
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ }
+ });
+
+ builder.setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ if (xmppConnectionService != null) {
+ xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null);
+ }
+ }
+ });
+ mCaptchaDialog = builder.create();
+ mCaptchaDialog.show();
+ }
+ });
+ }
+
+ public void onShowErrorToast(final int resId) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(EditAccountActivity.this, resId, Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
+ @Override
+ public void onPreferencesFetched(final Element prefs) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ AlertDialog.Builder builder = new Builder(EditAccountActivity.this);
+ builder.setTitle(R.string.server_side_mam_prefs);
+ String defaultAttr = prefs.getAttribute("default");
+ final List<String> defaults = Arrays.asList("never", "roster", "always");
+ final AtomicInteger choice = new AtomicInteger(Math.max(0,defaults.indexOf(defaultAttr)));
+ builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ choice.set(which);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ prefs.setAttribute("default",defaults.get(choice.get()));
+ xmppConnectionService.pushMamPreferences(mAccount, prefs);
+ }
+ });
+ builder.create().show();
+ }
+ });
+ }
+
+ @Override
+ public void onPreferencesFetchFailed() {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mFetchingMamPrefsToast != null) {
+ mFetchingMamPrefsToast.cancel();
+ }
+ Toast.makeText(EditAccountActivity.this,R.string.unable_to_fetch_mam_prefs,Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/EditMessage.java b/src/main/java/de/pixart/messenger/ui/EditMessage.java
new file mode 100644
index 000000000..ee08cd47e
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/EditMessage.java
@@ -0,0 +1,92 @@
+package de.pixart.messenger.ui;
+
+import android.content.Context;
+import android.os.Handler;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.widget.EditText;
+
+import de.pixart.messenger.Config;
+
+public class EditMessage extends EditText {
+
+ public EditMessage(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public EditMessage(Context context) {
+ super(context);
+ }
+
+ protected Handler mTypingHandler = new Handler();
+
+ protected Runnable mTypingTimeout = new Runnable() {
+ @Override
+ public void run() {
+ if (isUserTyping && keyboardListener != null) {
+ keyboardListener.onTypingStopped();
+ isUserTyping = false;
+ }
+ }
+ };
+
+ private boolean isUserTyping = false;
+
+ private boolean lastInputWasTab = false;
+
+ protected KeyboardListener keyboardListener;
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent e) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && !e.isShiftPressed()) {
+ lastInputWasTab = false;
+ if (keyboardListener != null && keyboardListener.onEnterPressed()) {
+ return true;
+ }
+ } else if (keyCode == KeyEvent.KEYCODE_TAB && !e.isAltPressed() && !e.isCtrlPressed()) {
+ if (keyboardListener != null && keyboardListener.onTabPressed(this.lastInputWasTab)) {
+ lastInputWasTab = true;
+ return true;
+ }
+ } else {
+ lastInputWasTab = false;
+ }
+ return super.onKeyDown(keyCode, e);
+ }
+
+ @Override
+ public void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
+ super.onTextChanged(text,start,lengthBefore,lengthAfter);
+ lastInputWasTab = false;
+ if (this.mTypingHandler != null && this.keyboardListener != null) {
+ this.mTypingHandler.removeCallbacks(mTypingTimeout);
+ this.mTypingHandler.postDelayed(mTypingTimeout, Config.TYPING_TIMEOUT * 1000);
+ final int length = text.length();
+ if (!isUserTyping && length > 0) {
+ this.isUserTyping = true;
+ this.keyboardListener.onTypingStarted();
+ } else if (length == 0) {
+ this.isUserTyping = false;
+ this.keyboardListener.onTextDeleted();
+ }
+ this.keyboardListener.onTextChanged();
+ }
+ }
+
+ public void setKeyboardListener(KeyboardListener listener) {
+ this.keyboardListener = listener;
+ if (listener != null) {
+ this.isUserTyping = false;
+ }
+ }
+
+ public interface KeyboardListener {
+ boolean onEnterPressed();
+ void onTypingStarted();
+ void onTypingStopped();
+ void onTextDeleted();
+ void onTextChanged();
+ boolean onTabPressed(boolean repeated);
+ }
+
+}
diff --git a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java
new file mode 100644
index 000000000..086594951
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java
@@ -0,0 +1,127 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.ui.adapter.KnownHostsAdapter;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class EnterJidDialog {
+ public interface OnEnterJidDialogPositiveListener {
+ boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError;
+ }
+
+ public static class JidError extends Exception {
+ final String msg;
+
+ public JidError(final String msg) {
+ this.msg = msg;
+ }
+
+ public String toString() {
+ return msg;
+ }
+ }
+
+ protected final AlertDialog dialog;
+ protected View.OnClickListener dialogOnClick;
+ protected OnEnterJidDialogPositiveListener listener = null;
+
+ public EnterJidDialog(
+ final Context context, List<String> knownHosts, final List<String> activatedAccounts,
+ final String title, final String positiveButton,
+ final String prefilledJid, final String account, boolean allowEditJid
+ ) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(context);
+ builder.setTitle(title);
+ View dialogView = LayoutInflater.from(context).inflate(R.layout.enter_jid_dialog, null);
+ final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
+ jabberIdDesc.setText(R.string.account_settings_jabber_id);
+ final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
+ final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
+ jid.setAdapter(new KnownHostsAdapter(context, R.layout.simple_list_item, knownHosts));
+ if (prefilledJid != null) {
+ jid.append(prefilledJid);
+ if (!allowEditJid) {
+ jid.setFocusable(false);
+ jid.setFocusableInTouchMode(false);
+ jid.setClickable(false);
+ jid.setCursorVisible(false);
+ }
+ }
+
+ jid.setHint(R.string.account_settings_example_jabber_id);
+
+ if (account == null) {
+ StartConversationActivity.populateAccountSpinner(context, activatedAccounts, spinner);
+ } else {
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
+ R.layout.simple_list_item,
+ new String[] { account });
+ spinner.setEnabled(false);
+ adapter.setDropDownViewResource(R.layout.simple_list_item);
+ spinner.setAdapter(adapter);
+ }
+
+ builder.setView(dialogView);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(positiveButton, null);
+ this.dialog = builder.create();
+
+ this.dialogOnClick = new View.OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ final Jid accountJid;
+ if (!spinner.isEnabled() && account == null) {
+ return;
+ }
+ try {
+ if (Config.DOMAIN_LOCK != null) {
+ accountJid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
+ } else {
+ accountJid = Jid.fromString((String) spinner.getSelectedItem());
+ }
+ } catch (final InvalidJidException e) {
+ return;
+ }
+ final Jid contactJid;
+ try {
+ contactJid = Jid.fromString(jid.getText().toString());
+ } catch (final InvalidJidException e) {
+ jid.setError(context.getString(R.string.invalid_jid));
+ return;
+ }
+
+ if(listener != null) {
+ try {
+ if(listener.onEnterJidDialogPositive(accountJid, contactJid)) {
+ dialog.dismiss();
+ }
+ } catch(JidError error) {
+ jid.setError(error.toString());
+ }
+ }
+ }
+ };
+ }
+
+ public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
+ this.listener = listener;
+ }
+
+ public void show() {
+ this.dialog.show();
+ this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(this.dialogOnClick);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ExportLogsPreference.java b/src/main/java/de/pixart/messenger/ui/ExportLogsPreference.java
new file mode 100644
index 000000000..e7c57a033
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ExportLogsPreference.java
@@ -0,0 +1,36 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.preference.Preference;
+import android.util.AttributeSet;
+
+import de.pixart.messenger.services.ExportLogsService;
+
+public class ExportLogsPreference extends Preference {
+
+ public ExportLogsPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public ExportLogsPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ExportLogsPreference(Context context) {
+ super(context);
+ }
+
+ protected void onClick() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+ && getContext().checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ final Intent startIntent = new Intent(getContext(), ExportLogsService.class);
+ getContext().startService(startIntent);
+ super.onClick();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java
new file mode 100644
index 000000000..6927c9732
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java
@@ -0,0 +1,116 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.security.SecureRandom;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class MagicCreateActivity extends XmppActivity implements TextWatcher {
+
+ private TextView mFullJidDisplay;
+ private EditText mUsername;
+ private SecureRandom mRandom;
+
+ private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780+-/#$!?";
+ private static final int PW_LENGTH = 10;
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.magic_create);
+ mFullJidDisplay = (TextView) findViewById(R.id.full_jid);
+ mUsername = (EditText) findViewById(R.id.username);
+ mRandom = new SecureRandom();
+ Button next = (Button) findViewById(R.id.create_account);
+ next.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ String username = mUsername.getText().toString();
+ if (username.contains("@") || username.length() < 3) {
+ mUsername.setError(getString(R.string.invalid_username));
+ mUsername.requestFocus();
+ } else {
+ mUsername.setError(null);
+ try {
+ Jid jid = Jid.fromParts(username.toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
+ Account account = xmppConnectionService.findAccountByJid(jid);
+ if (account == null) {
+ account = new Account(jid, createPassword());
+ account.setOption(Account.OPTION_REGISTER, true);
+ account.setOption(Account.OPTION_DISABLED, true);
+ account.setOption(Account.OPTION_MAGIC_CREATE, true);
+ xmppConnectionService.createAccount(account);
+ }
+ Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().toBareJid().toString());
+ intent.putExtra("init", true);
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
+ startActivity(intent);
+ } catch (InvalidJidException e) {
+ mUsername.setError(getString(R.string.invalid_username));
+ mUsername.requestFocus();
+ }
+ }
+ }
+ });
+ mUsername.addTextChangedListener(this);
+ }
+
+ private String createPassword() {
+ StringBuilder builder = new StringBuilder(PW_LENGTH);
+ for(int i = 0; i < PW_LENGTH; ++i) {
+ builder.append(CHARS.charAt(mRandom.nextInt(CHARS.length() - 1)));
+ }
+ return builder.toString();
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ if (s.toString().trim().length() > 0) {
+ try {
+ mFullJidDisplay.setVisibility(View.VISIBLE);
+ Jid jid = Jid.fromParts(s.toString().toLowerCase(), Config.MAGIC_CREATE_DOMAIN, null);
+ mFullJidDisplay.setText(getString(R.string.your_full_jid_will_be, jid.toString()));
+ } catch (InvalidJidException e) {
+ mFullJidDisplay.setVisibility(View.INVISIBLE);
+ }
+
+ } else {
+ mFullJidDisplay.setVisibility(View.INVISIBLE);
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java
new file mode 100644
index 000000000..81e553bd5
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java
@@ -0,0 +1,383 @@
+package de.pixart.messenger.ui;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.os.Bundle;
+import android.security.KeyChain;
+import android.security.KeyChainAliasCallback;
+import android.util.Pair;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import org.openintents.openpgp.util.OpenPgpApi;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.services.XmppConnectionService.OnAccountUpdate;
+import de.pixart.messenger.ui.adapter.AccountAdapter;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated {
+
+ private final String STATE_SELECTED_ACCOUNT = "selected_account";
+
+ protected Account selectedAccount = null;
+ protected Jid selectedAccountJid = null;
+
+ protected final List<Account> accountList = new ArrayList<>();
+ protected ListView accountListView;
+ protected AccountAdapter mAccountAdapter;
+ protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
+
+ protected Pair<Integer, Intent> mPostponedActivityResult = null;
+
+ @Override
+ public void onAccountUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ synchronized (this.accountList) {
+ accountList.clear();
+ accountList.addAll(xmppConnectionService.getAccounts());
+ }
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
+ actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
+ }
+ invalidateOptionsMenu();
+ mAccountAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.manage_accounts);
+
+ if (savedInstanceState != null) {
+ String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
+ if (jid != null) {
+ try {
+ this.selectedAccountJid = Jid.fromString(jid);
+ } catch (InvalidJidException e) {
+ this.selectedAccountJid = null;
+ }
+ }
+ }
+
+ accountListView = (ListView) findViewById(R.id.account_list);
+ this.mAccountAdapter = new AccountAdapter(this, accountList);
+ accountListView.setAdapter(this.mAccountAdapter);
+ accountListView.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View view,
+ int position, long arg3) {
+ switchToAccount(accountList.get(position));
+ }
+ });
+ registerForContextMenu(accountListView);
+ }
+
+ @Override
+ public void onSaveInstanceState(final Bundle savedInstanceState) {
+ if (selectedAccount != null) {
+ savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().toBareJid().toString());
+ }
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ @Override
+ public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ ManageAccountActivity.this.getMenuInflater().inflate(
+ R.menu.manageaccounts_context, menu);
+ AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
+ this.selectedAccount = accountList.get(acmi.position);
+ if (this.selectedAccount.isOptionSet(Account.OPTION_DISABLED)) {
+ menu.findItem(R.id.mgmt_account_reconnect).setVisible(false);
+ menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
+ menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
+ menu.findItem(R.id.mgmt_account_change_presence).setVisible(false);
+ } else {
+ menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
+ menu.findItem(R.id.mgmt_account_change_presence).setVisible(manuallyChangePresence());
+ }
+ menu.setHeaderTitle(this.selectedAccount.getJid().toBareJid().toString());
+ }
+
+ @Override
+ void onBackendConnected() {
+ if (selectedAccountJid != null) {
+ this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
+ }
+ refreshUiReal();
+ if (this.mPostponedActivityResult != null) {
+ this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+ }
+ if (Config.X509_VERIFICATION && this.accountList.size() == 0) {
+ if (mInvokedAddAccount.compareAndSet(false, true)) {
+ addAccountFromKey();
+ }
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.manageaccounts, menu);
+ MenuItem addAccount = menu.findItem(R.id.action_add_account);
+ MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
+
+ if (Config.X509_VERIFICATION) {
+ addAccount.setVisible(false);
+ addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
+ } else {
+ addAccount.setVisible(!Config.SINGLE_ACCOUNT);
+ }
+ addAccountWithCertificate.setVisible(!Config.SINGLE_ACCOUNT);
+ return true;
+ }
+
+ @Override
+ public boolean onContextItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.mgmt_account_publish_avatar:
+ publishAvatar(selectedAccount);
+ return true;
+ case R.id.mgmt_account_reconnect:
+ disableAccount(selectedAccount);
+ enableAccount(selectedAccount);
+ return true;
+ case R.id.mgmt_account_delete:
+ deleteAccount(selectedAccount);
+ return true;
+ case R.id.mgmt_account_announce_pgp:
+ publishOpenPGPPublicKey(selectedAccount);
+ return true;
+ case R.id.mgmt_account_change_presence:
+ changePresence(selectedAccount);
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_add_account:
+ startActivity(new Intent(getApplicationContext(),
+ EditAccountActivity.class));
+ break;
+ case R.id.action_add_account_with_cert:
+ addAccountFromKey();
+ break;
+ default:
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onNavigateUp() {
+ if (xmppConnectionService.getConversations().size() == 0) {
+ Intent contactsIntent = new Intent(this,
+ StartConversationActivity.class);
+ contactsIntent.setFlags(
+ // if activity exists in stack, pop the stack and go back to it
+ Intent.FLAG_ACTIVITY_CLEAR_TOP |
+ // otherwise, make a new task for it
+ Intent.FLAG_ACTIVITY_NEW_TASK |
+ // don't use the new activity animation; finish
+ // animation runs instead
+ Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(contactsIntent);
+ finish();
+ return true;
+ } else {
+ return super.onNavigateUp();
+ }
+ }
+
+ private void changePresence(Account account) {
+ Intent intent = new Intent(this, SetPresenceActivity.class);
+ intent.putExtra(SetPresenceActivity.EXTRA_ACCOUNT,account.getJid().toBareJid().toString());
+ startActivity(intent);
+ }
+
+ public void onClickTglAccountState(Account account, boolean enable) {
+ if (enable) {
+ enableAccount(account);
+ } else {
+ disableAccount(account);
+ }
+ }
+
+ private void addAccountFromKey() {
+ try {
+ KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
+ }
+ }
+
+ private void publishAvatar(Account account) {
+ Intent intent = new Intent(getApplicationContext(),
+ PublishProfilePictureActivity.class);
+ intent.putExtra(EXTRA_ACCOUNT, account.getJid().toString());
+ startActivity(intent);
+ }
+
+ private void disableAllAccounts() {
+ List<Account> list = new ArrayList<>();
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ list.add(account);
+ }
+ }
+ }
+ for (Account account : list) {
+ disableAccount(account);
+ }
+ }
+
+ private boolean accountsLeftToDisable() {
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private boolean accountsLeftToEnable() {
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (account.isOptionSet(Account.OPTION_DISABLED)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ private void enableAllAccounts() {
+ List<Account> list = new ArrayList<>();
+ synchronized (this.accountList) {
+ for (Account account : this.accountList) {
+ if (account.isOptionSet(Account.OPTION_DISABLED)) {
+ list.add(account);
+ }
+ }
+ }
+ for (Account account : list) {
+ enableAccount(account);
+ }
+ }
+
+ private void disableAccount(Account account) {
+ account.setOption(Account.OPTION_DISABLED, true);
+ xmppConnectionService.updateAccount(account);
+ }
+
+ private void enableAccount(Account account) {
+ account.setOption(Account.OPTION_DISABLED, false);
+ xmppConnectionService.updateAccount(account);
+ }
+
+ private void publishOpenPGPPublicKey(Account account) {
+ if (ManageAccountActivity.this.hasPgp()) {
+ announcePgp(selectedAccount, null, onOpenPGPKeyPublished);
+ } else {
+ this.showInstallPgpDialog();
+ }
+ }
+
+ private void deleteAccount(final Account account) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ ManageAccountActivity.this);
+ builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
+ builder.setPositiveButton(getString(R.string.delete),
+ new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ xmppConnectionService.deleteAccount(account);
+ selectedAccount = null;
+ }
+ });
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.create().show();
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ if (xmppConnectionServiceBound) {
+ if (requestCode == REQUEST_CHOOSE_PGP_ID) {
+ if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
+ selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
+ announcePgp(selectedAccount, null, onOpenPGPKeyPublished);
+ } else {
+ choosePgpSignId(selectedAccount);
+ }
+ } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
+ announcePgp(selectedAccount, null, onOpenPGPKeyPublished);
+ }
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+ }
+ }
+
+ @Override
+ public void alias(String alias) {
+ if (alias != null) {
+ xmppConnectionService.createAccountFromKey(alias, this);
+ }
+ }
+
+ @Override
+ public void onAccountCreated(Account account) {
+ switchToAccount(account, true);
+ }
+
+ @Override
+ public void informUser(final int r) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java
new file mode 100644
index 000000000..187317f86
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/PublishProfilePictureActivity.java
@@ -0,0 +1,335 @@
+
+package de.pixart.messenger.ui;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.soundcloud.android.crop.Crop;
+
+import java.io.File;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.utils.FileUtils;
+import de.pixart.messenger.utils.PhoneHelper;
+import de.pixart.messenger.xmpp.pep.Avatar;
+
+public class PublishProfilePictureActivity extends XmppActivity {
+
+ private static final int REQUEST_CHOOSE_FILE_AND_CROP = 0xac23;
+ private static final int REQUEST_CHOOSE_FILE = 0xac24;
+ private ImageView avatar;
+ private TextView accountTextView;
+ private TextView hintOrWarning;
+ private TextView secondaryHint;
+ private Button cancelButton;
+ private Button publishButton;
+ private Uri avatarUri;
+ private Uri defaultUri;
+ private Account account;
+ private boolean support = false;
+ private OnLongClickListener backToDefaultListener = new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ avatarUri = defaultUri;
+ loadImageIntoPreview(defaultUri);
+ return true;
+ }
+ };
+ private boolean mInitialAccountSetup;
+ private UiCallback<Avatar> avatarPublication = new UiCallback<Avatar>() {
+
+ @Override
+ public void success(Avatar object) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ if (mInitialAccountSetup) {
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ intent.putExtra("init", true);
+ startActivity(intent);
+ }
+ Toast.makeText(PublishProfilePictureActivity.this,
+ R.string.avatar_has_been_published,
+ Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ });
+ }
+
+ @Override
+ public void error(final int errorCode, Avatar object) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ hintOrWarning.setText(errorCode);
+ hintOrWarning.setTextColor(getWarningTextColor());
+ publishButton.setText(R.string.publish);
+ enablePublishButton();
+ }
+ });
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Avatar object) {
+ }
+ };
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_publish_profile_picture);
+ this.avatar = (ImageView) findViewById(R.id.account_image);
+ this.cancelButton = (Button) findViewById(R.id.cancel_button);
+ this.publishButton = (Button) findViewById(R.id.publish_button);
+ this.accountTextView = (TextView) findViewById(R.id.account);
+ this.hintOrWarning = (TextView) findViewById(R.id.hint_or_warning);
+ this.secondaryHint = (TextView) findViewById(R.id.secondary_hint);
+ this.publishButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (avatarUri != null) {
+ publishButton.setText(R.string.publishing);
+ disablePublishButton();
+ xmppConnectionService.publishAvatar(account, avatarUri,
+ avatarPublication);
+ }
+ }
+ });
+ this.cancelButton.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (mInitialAccountSetup) {
+ Intent intent = new Intent(getApplicationContext(),
+ StartConversationActivity.class);
+ if (xmppConnectionService != null && xmppConnectionService.getAccounts().size() == 1) {
+ intent.putExtra("init", true);
+ }
+ startActivity(intent);
+ }
+ finish();
+ }
+ });
+ this.avatar.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (hasStoragePermission(REQUEST_CHOOSE_FILE)) {
+ chooseAvatar(false);
+ }
+
+ }
+ });
+ this.defaultUri = PhoneHelper.getSefliUri(getApplicationContext());
+ }
+
+ private void chooseAvatar(boolean crop) {
+ 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, crop ? REQUEST_CHOOSE_FILE_AND_CROP : 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_AND_CROP) {
+ chooseAvatar(true);
+ } else if (requestCode == REQUEST_CHOOSE_FILE) {
+ chooseAvatar(false);
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.publish_avatar, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.action_crop_image) {
+ if (hasStoragePermission(REQUEST_CHOOSE_FILE_AND_CROP)) {
+ chooseAvatar(true);
+ }
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ Uri source = data.getData();
+ switch (requestCode) {
+ case REQUEST_CHOOSE_FILE_AND_CROP:
+ if (FileBackend.weOwnFile(this, source)) {
+ Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show();
+ return;
+ }
+ String original = FileUtils.getPath(this, source);
+ if (original != null) {
+ source = Uri.parse("file://"+original);
+ }
+ Uri destination = Uri.fromFile(new File(getCacheDir(), "croppedAvatar"));
+ final int size = getPixel(Config.AVATAR_SIZE);
+ Crop.of(source, destination).asSquare().withMaxSize(size, size).start(this);
+ break;
+ case REQUEST_CHOOSE_FILE:
+ if (FileBackend.weOwnFile(this, source)) {
+ Toast.makeText(this,R.string.security_error_invalid_file_access,Toast.LENGTH_SHORT).show();
+ return;
+ }
+ this.avatarUri = source;
+ if (xmppConnectionServiceBound) {
+ loadImageIntoPreview(this.avatarUri);
+ }
+ break;
+ case Crop.REQUEST_CROP:
+ this.avatarUri = Uri.fromFile(new File(getCacheDir(), "croppedAvatar"));
+ if (xmppConnectionServiceBound) {
+ loadImageIntoPreview(this.avatarUri);
+ }
+ break;
+ }
+ } else {
+ if (requestCode == Crop.REQUEST_CROP && data != null) {
+ Throwable throwable = Crop.getError(data);
+ if (throwable != null && throwable instanceof OutOfMemoryError) {
+ Toast.makeText(this,R.string.selection_too_large, Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ this.account = extractAccount(getIntent());
+ if (this.account != null) {
+ if (this.account.getXmppConnection() != null) {
+ this.support = this.account.getXmppConnection().getFeatures().pep();
+ }
+ if (this.avatarUri == null) {
+ if (this.account.getAvatar() != null
+ || this.defaultUri == null) {
+ this.avatar.setImageBitmap(avatarService().get(account, getPixel(Config.AVATAR_SIZE)));
+ if (this.defaultUri != null) {
+ this.avatar
+ .setOnLongClickListener(this.backToDefaultListener);
+ } else {
+ this.secondaryHint.setVisibility(View.INVISIBLE);
+ }
+ if (!support) {
+ this.hintOrWarning
+ .setTextColor(getWarningTextColor());
+ this.hintOrWarning
+ .setText(R.string.error_publish_avatar_no_server_support);
+ }
+ } else {
+ this.avatarUri = this.defaultUri;
+ loadImageIntoPreview(this.defaultUri);
+ this.secondaryHint.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ loadImageIntoPreview(avatarUri);
+ }
+ String account;
+ if (Config.DOMAIN_LOCK != null) {
+ account = this.account.getJid().getLocalpart();
+ } else {
+ account = this.account.getJid().toBareJid().toString();
+ }
+ this.accountTextView.setText(account);
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (getIntent() != null) {
+ this.mInitialAccountSetup = getIntent().getBooleanExtra("setup", false);
+ }
+ if (this.mInitialAccountSetup) {
+ this.cancelButton.setText(R.string.skip);
+ }
+ }
+
+ protected void loadImageIntoPreview(Uri uri) {
+ Bitmap bm = null;
+ try {
+ bm = xmppConnectionService.getFileBackend().cropCenterSquare(uri, getPixel(Config.AVATAR_SIZE));
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ if (bm == null) {
+ disablePublishButton();
+ this.hintOrWarning.setTextColor(getWarningTextColor());
+ this.hintOrWarning
+ .setText(R.string.error_publish_avatar_converting);
+ return;
+ }
+ this.avatar.setImageBitmap(bm);
+ if (support) {
+ enablePublishButton();
+ this.publishButton.setText(R.string.publish);
+ this.hintOrWarning.setText(R.string.publish_avatar_explanation);
+ this.hintOrWarning.setTextColor(getPrimaryTextColor());
+ } else {
+ disablePublishButton();
+ this.hintOrWarning.setTextColor(getWarningTextColor());
+ this.hintOrWarning
+ .setText(R.string.error_publish_avatar_no_server_support);
+ }
+ if (this.defaultUri != null && uri.equals(this.defaultUri)) {
+ this.secondaryHint.setVisibility(View.INVISIBLE);
+ this.avatar.setOnLongClickListener(null);
+ } else if (this.defaultUri != null) {
+ this.secondaryHint.setVisibility(View.VISIBLE);
+ this.avatar.setOnLongClickListener(this.backToDefaultListener);
+ }
+ }
+
+ protected void enablePublishButton() {
+ this.publishButton.setEnabled(true);
+ this.publishButton.setTextColor(getPrimaryTextColor());
+ }
+
+ protected void disablePublishButton() {
+ this.publishButton.setEnabled(false);
+ this.publishButton.setTextColor(getSecondaryTextColor());
+ }
+
+ public void refreshUiReal() {
+ //nothing to do. This Activity doesn't implement any listeners
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java
new file mode 100644
index 000000000..a9ebccb8c
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java
@@ -0,0 +1,157 @@
+package de.pixart.messenger.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.persistance.FileBackend;
+
+public class RecordingActivity extends Activity implements View.OnClickListener {
+
+ private TextView mTimerTextView;
+ private Button mCancelButton;
+ private Button mStopButton;
+
+ private MediaRecorder mRecorder;
+ private long mStartTime = 0;
+
+ private int[] amplitudes = new int[100];
+ private int i = 0;
+
+ private Handler mHandler = new Handler();
+ private Runnable mTickExecutor = new Runnable() {
+ @Override
+ public void run() {
+ tick();
+ mHandler.postDelayed(mTickExecutor,100);
+ }
+ };
+ private File mOutputFile;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_recording);
+ this.mTimerTextView = (TextView) this.findViewById(R.id.timer);
+ this.mCancelButton = (Button) this.findViewById(R.id.cancel_button);
+ this.mCancelButton.setOnClickListener(this);
+ this.mStopButton = (Button) this.findViewById(R.id.share_button);
+ this.mStopButton.setOnClickListener(this);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Log.d(Config.LOGTAG, "output: " + getOutputFile());
+ startRecording();
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mRecorder != null) {
+ stopRecording(false);
+ }
+ }
+
+ private void startRecording() {
+ mRecorder = new MediaRecorder();
+ mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ mRecorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mRecorder.setAudioEncodingBitRate(48000);
+ } else {
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mRecorder.setAudioEncodingBitRate(48000);
+ }
+ mRecorder.setAudioSamplingRate(48000);
+ mOutputFile = getOutputFile();
+ mOutputFile.getParentFile().mkdirs();
+ mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
+
+ try {
+ mRecorder.prepare();
+ mRecorder.start();
+ mStartTime = SystemClock.elapsedRealtime();
+ mHandler.postDelayed(mTickExecutor, 100);
+ Log.d(Config.LOGTAG,"started recording to "+mOutputFile.getAbsolutePath());
+ } catch (IOException e) {
+ Log.e(Config.LOGTAG, "prepare() failed "+e.getMessage());
+ }
+ }
+
+ protected void stopRecording(boolean saveFile) {
+ mRecorder.stop();
+ mRecorder.release();
+ mRecorder = null;
+ mStartTime = 0;
+ mHandler.removeCallbacks(mTickExecutor);
+ if (!saveFile && mOutputFile != null) {
+ mOutputFile.delete();
+ }
+ }
+
+ private File getOutputFile() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+ return new File(FileBackend.getConversationsAudioDirectory() + "/"
+ + dateFormat.format(new Date())
+ + ".m4a");
+ }
+
+ private void tick() {
+ long time = (mStartTime < 0) ? 0 : (SystemClock.elapsedRealtime() - mStartTime);
+ int minutes = (int) (time / 60000);
+ int seconds = (int) (time / 1000) % 60;
+ int milliseconds = (int) (time / 100) % 10;
+ mTimerTextView.setText(minutes+":"+(seconds < 10 ? "0"+seconds : seconds)+"."+milliseconds);
+ if (mRecorder != null) {
+ amplitudes[i] = mRecorder.getMaxAmplitude();
+ //Log.d(Config.LOGTAG,"amplitude: "+(amplitudes[i] * 100 / 32767));
+ if (i >= amplitudes.length -1) {
+ i = 0;
+ } else {
+ ++i;
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View view) {
+ switch (view.getId()) {
+ case R.id.cancel_button:
+ stopRecording(false);
+ setResult(RESULT_CANCELED);
+ finish();
+ break;
+ case R.id.share_button:
+ stopRecording(true);
+ Uri uri = Uri.parse("file://"+mOutputFile.getAbsolutePath());
+ Intent scanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ scanIntent.setData(uri);
+ sendBroadcast(scanIntent);
+ setResult(Activity.RESULT_OK, new Intent().setData(uri));
+ finish();
+ break;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java
new file mode 100644
index 000000000..7214e560b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java
@@ -0,0 +1,232 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.Spinner;
+import android.widget.TextView;
+
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.entities.Presence;
+import de.pixart.messenger.entities.PresenceTemplate;
+import de.pixart.messenger.utils.UIHelper;
+
+public class SetPresenceActivity extends XmppActivity implements View.OnClickListener {
+
+ //data
+ protected Account mAccount;
+ private List<PresenceTemplate> mTemplates;
+
+ //UI Elements
+ protected ScrollView mScrollView;
+ protected EditText mStatusMessage;
+ protected Spinner mShowSpinner;
+ protected CheckBox mAllAccounts;
+ protected LinearLayout mTemplatesView;
+ private Pair<Integer, Intent> mPostponedActivityResult;
+
+ private Runnable onPresenceChanged = new Runnable() {
+ @Override
+ public void run() {
+ finish();
+ }
+ };
+
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_set_presence);
+ mScrollView = (ScrollView) findViewById(R.id.scroll_view);
+ mShowSpinner = (Spinner) findViewById(R.id.presence_show);
+ ArrayAdapter adapter = ArrayAdapter.createFromResource(this,
+ R.array.presence_show_options,
+ R.layout.simple_list_item);
+ mShowSpinner.setAdapter(adapter);
+ mShowSpinner.setSelection(1);
+ mStatusMessage = (EditText) findViewById(R.id.presence_status_message);
+ mAllAccounts = (CheckBox) findViewById(R.id.all_accounts);
+ mTemplatesView = (LinearLayout) findViewById(R.id.templates);
+ final Button changePresence = (Button) findViewById(R.id.change_presence);
+ changePresence.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ executeChangePresence();
+ }
+ });
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.change_presence, menu);
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ if (item.getItemId() == R.id.action_account_details) {
+ if (mAccount != null) {
+ switchToAccount(mAccount);
+ }
+ return true;
+ } else {
+ return super.onOptionsItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == RESULT_OK) {
+ if (xmppConnectionServiceBound && mAccount != null) {
+ if (requestCode == REQUEST_ANNOUNCE_PGP) {
+ announcePgp(mAccount, null, onPresenceChanged);
+ }
+ this.mPostponedActivityResult = null;
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, data);
+ }
+ }
+ }
+
+ private void executeChangePresence() {
+ Presence.Status status = getStatusFromSpinner();
+ boolean allAccounts = mAllAccounts.isChecked();
+ String statusMessage = mStatusMessage.getText().toString().trim();
+ if (allAccounts && noAccountUsesPgp()) {
+ xmppConnectionService.changeStatus(status, statusMessage);
+ finish();
+ } else if (mAccount != null) {
+ if (mAccount.getPgpId() == 0 || !hasPgp()) {
+ xmppConnectionService.changeStatus(mAccount, status, statusMessage, true);
+ finish();
+ } else {
+ xmppConnectionService.changeStatus(mAccount, status, statusMessage, false);
+ announcePgp(mAccount, null, onPresenceChanged);
+ }
+ }
+ }
+
+ private Presence.Status getStatusFromSpinner() {
+ switch (mShowSpinner.getSelectedItemPosition()) {
+ case 0:
+ return Presence.Status.CHAT;
+ case 2:
+ return Presence.Status.AWAY;
+ case 3:
+ return Presence.Status.XA;
+ case 4:
+ return Presence.Status.DND;
+ default:
+ return Presence.Status.ONLINE;
+ }
+ }
+
+ private void setStatusInSpinner(Presence.Status status) {
+ switch(status) {
+ case AWAY:
+ mShowSpinner.setSelection(2);
+ break;
+ case XA:
+ mShowSpinner.setSelection(3);
+ break;
+ case CHAT:
+ mShowSpinner.setSelection(0);
+ break;
+ case DND:
+ mShowSpinner.setSelection(4);
+ break;
+ default:
+ mShowSpinner.setSelection(1);
+ break;
+ }
+ }
+
+ @Override
+ protected void refreshUiReal() {
+
+ }
+
+ @Override
+ void onBackendConnected() {
+ mAccount = extractAccount(getIntent());
+ if (mAccount != null) {
+ setStatusInSpinner(mAccount.getPresenceStatus());
+ String message = mAccount.getPresenceStatusMessage();
+ if (mStatusMessage.getText().length() == 0 && message != null) {
+ mStatusMessage.append(message);
+ }
+ mTemplates = xmppConnectionService.getPresenceTemplates(mAccount);
+ if (this.mPostponedActivityResult != null) {
+ this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+ }
+ boolean e = noAccountUsesPgp();
+ mAllAccounts.setEnabled(e);
+ mAllAccounts.setTextColor(e ? getPrimaryTextColor() : getSecondaryTextColor());
+ }
+ redrawTemplates();
+ }
+
+ private void redrawTemplates() {
+ if (mTemplates == null || mTemplates.size() == 0) {
+ mTemplatesView.setVisibility(View.GONE);
+ } else {
+ mTemplatesView.removeAllViews();
+ mTemplatesView.setVisibility(View.VISIBLE);
+ LayoutInflater inflater = getLayoutInflater();
+ for (PresenceTemplate template : mTemplates) {
+ View templateLayout = inflater.inflate(R.layout.presence_template, mTemplatesView, false);
+ templateLayout.setTag(template);
+ setListItemBackgroundOnView(templateLayout);
+ templateLayout.setOnClickListener(this);
+ TextView message = (TextView) templateLayout.findViewById(R.id.presence_status_message);
+ TextView status = (TextView) templateLayout.findViewById(R.id.status);
+ ImageButton button = (ImageButton) templateLayout.findViewById(R.id.delete_button);
+ button.setTag(template);
+ button.setOnClickListener(this);
+ ListItem.Tag tag = UIHelper.getTagForStatus(this, template.getStatus());
+ status.setText(tag.getName());
+ status.setBackgroundColor(tag.getColor());
+ message.setText(template.getStatusMessage());
+ mTemplatesView.addView(templateLayout);
+ }
+ }
+ }
+
+ @Override
+ public void onClick(View v) {
+ PresenceTemplate template = (PresenceTemplate) v.getTag();
+ if (template == null) {
+ return;
+ }
+ if (v.getId() == R.id.presence_template) {
+ setStatusInSpinner(template.getStatus());
+ mStatusMessage.getEditableText().clear();
+ mStatusMessage.getEditableText().append(template.getStatusMessage());
+ new Handler().post(new Runnable() {
+ @Override
+ public void run() {
+ mScrollView.smoothScrollTo(0,0);
+ }
+ });
+ } else if (v.getId() == R.id.delete_button) {
+ xmppConnectionService.databaseBackend.deletePresenceTemplate(template);
+ mTemplates.remove(template);
+ redrawTemplates();
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
new file mode 100644
index 000000000..150a1d779
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java
@@ -0,0 +1,235 @@
+package de.pixart.messenger.ui;
+
+import android.app.AlertDialog;
+import android.app.FragmentManager;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.os.Bundle;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceCategory;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.widget.Toast;
+
+import java.security.KeyStoreException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+
+import de.duenndns.ssl.MemorizingTrustManager;
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.services.ExportLogsService;
+import de.pixart.messenger.xmpp.XmppConnection;
+
+public class SettingsActivity extends XmppActivity implements
+ OnSharedPreferenceChangeListener {
+
+ public static final int REQUEST_WRITE_LOGS = 0xbf8701;
+ private SettingsFragment mSettingsFragment;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ FragmentManager fm = getFragmentManager();
+ mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content);
+ if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
+ mSettingsFragment = new SettingsFragment();
+ fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit();
+ }
+ }
+
+ @Override
+ void onBackendConnected() {
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
+ ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
+ if (resources != null) {
+ ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
+ if (!entries.contains(Build.MODEL)) {
+ entries.add(0, Build.MODEL);
+ resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
+ resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
+ }
+ }
+
+ if (Config.FORCE_ORBOT) {
+ PreferenceCategory connectionOptions = (PreferenceCategory) mSettingsFragment.findPreference("connection_options");
+ PreferenceScreen expert = (PreferenceScreen) mSettingsFragment.findPreference("expert");
+ if (connectionOptions != null) {
+ expert.removePreference(connectionOptions);
+ }
+ }
+
+ final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
+ removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
+ final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
+ if (aliases.size() == 0) {
+ displayToast(getString(R.string.toast_no_trusted_certs));
+ return true;
+ }
+ final ArrayList selectedItems = new ArrayList<Integer>();
+ final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
+ dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
+ dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int indexSelected,
+ boolean isChecked) {
+ if (isChecked) {
+ selectedItems.add(indexSelected);
+ } else if (selectedItems.contains(indexSelected)) {
+ selectedItems.remove(Integer.valueOf(indexSelected));
+ }
+ if (selectedItems.size() > 0)
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+ else {
+ ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+ }
+ }
+ });
+
+ dialogBuilder.setPositiveButton(
+ getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ int count = selectedItems.size();
+ if (count > 0) {
+ for (int i = 0; i < count; i++) {
+ try {
+ Integer item = Integer.valueOf(selectedItems.get(i).toString());
+ String alias = aliases.get(item);
+ mtm.deleteCertificate(alias);
+ } catch (KeyStoreException e) {
+ e.printStackTrace();
+ displayToast("Error: " + e.getLocalizedMessage());
+ }
+ }
+ if (xmppConnectionServiceBound) {
+ reconnectAccounts();
+ }
+ displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
+ }
+ }
+ });
+ dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
+ AlertDialog removeCertsDialog = dialogBuilder.create();
+ removeCertsDialog.show();
+ removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
+ return true;
+ }
+ });
+
+ final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs");
+ exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ hasStoragePermission(REQUEST_WRITE_LOGS);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences preferences, String name) {
+ final List<String> resendPresence = Arrays.asList(
+ "confirm_messages",
+ "xa_on_silent_mode",
+ "away_when_screen_off",
+ "allow_message_correction",
+ "treat_vibrate_as_silent",
+ "manually_change_presence",
+ "last_activity");
+ if (name.equals("resource")) {
+ String resource = preferences.getString("resource", "mobile")
+ .toLowerCase(Locale.US);
+ if (xmppConnectionServiceBound) {
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.setResource(resource)) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ XmppConnection connection = account.getXmppConnection();
+ if (connection != null) {
+ connection.resetStreamId();
+ }
+ xmppConnectionService.reconnectAccountInBackground(account);
+ }
+ }
+ }
+ }
+ } else if (resendPresence.contains(name)) {
+ if (xmppConnectionServiceBound) {
+ if (name.equals("away_when_screen_off")
+ || name.equals("manually_change_presence")) {
+ xmppConnectionService.toggleScreenEventReceiver();
+ }
+ if (name.equals("manually_change_presence") && !noAccountUsesPgp()) {
+ Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show();
+ }
+ xmppConnectionService.refreshAllPresences();
+ }
+ } else if (name.equals("dont_trust_system_cas")) {
+ xmppConnectionService.updateMemorizingTrustmanager();
+ reconnectAccounts();
+ } else if (name.equals("use_tor")) {
+ reconnectAccounts();
+ }
+
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ if (grantResults.length > 0)
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (requestCode == REQUEST_WRITE_LOGS) {
+ getApplicationContext().startService(new Intent(getApplicationContext(), ExportLogsService.class));
+ }
+ } else {
+ Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ private void displayToast(final String msg) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
+ }
+ });
+ }
+
+ private void reconnectAccounts() {
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ xmppConnectionService.reconnectAccountInBackground(account);
+ }
+ }
+ }
+
+ public void refreshUiReal() {
+ //nothing to do. This Activity doesn't implement any listeners
+ }
+
+}
diff --git a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java
new file mode 100644
index 000000000..c9e6dc51a
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java
@@ -0,0 +1,65 @@
+package de.pixart.messenger.ui;
+
+import android.app.Dialog;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceFragment;
+import android.preference.PreferenceScreen;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+
+import de.pixart.messenger.R;
+
+public class SettingsFragment extends PreferenceFragment {
+
+ //http://stackoverflow.com/questions/16374820/action-bar-home-button-not-functional-with-nested-preferencescreen/16800527#16800527
+ private void initializeActionBar(PreferenceScreen preferenceScreen) {
+ final Dialog dialog = preferenceScreen.getDialog();
+
+ if (dialog != null) {
+ View homeBtn = dialog.findViewById(android.R.id.home);
+
+ if (homeBtn != null) {
+ View.OnClickListener dismissDialogClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ dialog.dismiss();
+ }
+ };
+
+ ViewParent homeBtnContainer = homeBtn.getParent();
+
+ if (homeBtnContainer instanceof FrameLayout) {
+ ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent();
+ if (containerParent instanceof LinearLayout) {
+ ((LinearLayout) containerParent).setOnClickListener(dismissDialogClickListener);
+ } else {
+ ((FrameLayout) homeBtnContainer).setOnClickListener(dismissDialogClickListener);
+ }
+ } else {
+ homeBtn.setOnClickListener(dismissDialogClickListener);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Load the preferences from an XML resource
+ addPreferencesFromResource(R.xml.preferences);
+ }
+
+ @Override
+ public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+ super.onPreferenceTreeClick(preferenceScreen, preference);
+ if (preference instanceof PreferenceScreen) {
+ initializeActionBar((PreferenceScreen) preference);
+ }
+ return false;
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java
new file mode 100644
index 000000000..5e059386c
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java
@@ -0,0 +1,240 @@
+package de.pixart.messenger.ui;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.location.Address;
+import android.location.Geocoder;
+import android.location.Location;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.api.GoogleApiClient;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+import com.google.android.gms.location.LocationServices;
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.MapFragment;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.model.LatLng;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Locale;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+
+public class ShareLocationActivity extends Activity implements OnMapReadyCallback,
+ GoogleApiClient.ConnectionCallbacks,
+ GoogleApiClient.OnConnectionFailedListener,
+ LocationListener{
+
+ private GoogleMap mGoogleMap;
+ private GoogleApiClient mGoogleApiClient;
+ private LocationRequest mLocationRequest;
+ private Location mLastLocation;
+ private Button mCancelButton;
+ private Button mShareButton;
+ private RelativeLayout mSnackbar;
+ private RelativeLayout mLocationInfo;
+ private TextView mSnackbarLocation;
+ private TextView mSnackbarAction;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_share_locaction);
+ MapFragment fragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map_fragment);
+ fragment.getMapAsync(this);
+ mGoogleApiClient = new GoogleApiClient.Builder(this)
+ .addApi(LocationServices.API)
+ .addConnectionCallbacks(this)
+ .addOnConnectionFailedListener(this)
+ .build();
+ mCancelButton = (Button) findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ });
+ mShareButton = (Button) findViewById(R.id.share_button);
+ mShareButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mLastLocation != null) {
+ Intent result = new Intent();
+ result.putExtra("latitude",mLastLocation.getLatitude());
+ result.putExtra("longitude",mLastLocation.getLongitude());
+ result.putExtra("altitude",mLastLocation.getAltitude());
+ result.putExtra("accuracy",(int) mLastLocation.getAccuracy());
+ setResult(RESULT_OK, result);
+ finish();
+ }
+ }
+ });
+ mSnackbar = (RelativeLayout) findViewById(R.id.snackbar);
+ mLocationInfo = (RelativeLayout) findViewById(R.id.snackbar_location);
+ mSnackbarLocation = (TextView) findViewById(R.id.snackbar_location_message);
+ mSnackbarAction = (TextView) findViewById(R.id.snackbar_action);
+ mSnackbarAction.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
+ }
+ });
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ this.mLastLocation = null;
+ if (isLocationEnabled()) {
+ this.mSnackbar.setVisibility(View.GONE);
+ } else {
+ this.mSnackbar.setVisibility(View.VISIBLE);
+ }
+ mShareButton.setEnabled(false);
+ mShareButton.setTextColor(0x8a000000);
+ mShareButton.setText(R.string.locating);
+ mGoogleApiClient.connect();
+ }
+
+ @Override
+ protected void onPause() {
+ mGoogleApiClient.disconnect();
+ super.onPause();
+ }
+
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ this.mGoogleMap = googleMap;
+ this.mGoogleMap.setMyLocationEnabled(true);
+ }
+
+ private void centerOnLocation(LatLng location) {
+ this.mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(location, Config.DEFAULT_ZOOM));
+ }
+
+ @Override
+ public void onConnected(Bundle bundle) {
+ mLocationRequest = LocationRequest.create();
+ mLocationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
+ mLocationRequest.setInterval(1000);
+
+ LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
+ }
+
+ @Override
+ public void onConnectionSuspended(int i) {
+
+ }
+
+ @Override
+ public void onConnectionFailed(ConnectionResult connectionResult) {
+
+ }
+
+ @Override
+ public void onLocationChanged(Location location) {
+ double longitude = location.getLongitude();
+ double latitude = location.getLatitude();
+
+ if (this.mLastLocation == null) {
+ centerOnLocation(new LatLng(location.getLatitude(), location.getLongitude()));
+ this.mShareButton.setEnabled(true);
+ this.mShareButton.setTextColor(0xde000000);
+ this.mShareButton.setText(R.string.share);
+ this.mLocationInfo.setVisibility(View.VISIBLE);
+ }
+ this.mLastLocation = location;
+ if (latitude != 0 && longitude != 0) {
+ Double[] lat_long = new Double[]{latitude, longitude};
+ new ReverseGeocodingTask(getBaseContext()).execute(lat_long);
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ private boolean isLocationEnabledKitkat() {
+ try {
+ int locationMode = Settings.Secure.getInt(getContentResolver(), Settings.Secure.LOCATION_MODE);
+ return locationMode != Settings.Secure.LOCATION_MODE_OFF;
+ } catch (Settings.SettingNotFoundException e) {
+ return false;
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean isLocationEnabledLegacy() {
+ String locationProviders = Settings.Secure.getString(getContentResolver(), Settings.Secure.LOCATION_PROVIDERS_ALLOWED);
+ return !TextUtils.isEmpty(locationProviders);
+ }
+
+ private boolean isLocationEnabled() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT){
+ return isLocationEnabledKitkat();
+ }else{
+ return isLocationEnabledLegacy();
+ }
+ }
+
+ private class ReverseGeocodingTask extends AsyncTask<Double, Void, String> {
+ Context mContext;
+
+ public ReverseGeocodingTask(Context context){
+ super();
+ mContext = context;
+ }
+
+ @Override
+ protected String doInBackground(Double... params) {
+ Geocoder geocoder = new Geocoder(mContext, Locale.getDefault());
+
+ double latitude = params[0].doubleValue();
+ double longitude = params[1].doubleValue();
+
+ List<Address> addresses = null;
+ String address="";
+
+ try {
+ addresses = geocoder.getFromLocation(latitude, longitude,1);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ if (addresses != null) {
+ Address Address = addresses.get(0);
+ StringBuilder strAddress = new StringBuilder("");
+
+ for (int i = 0; i < Address.getMaxAddressLineIndex(); i++) {
+ strAddress.append(Address.getAddressLine(i)).append("\n");
+ }
+ address = strAddress.toString();
+ address = address.substring(0, address.length()-1); //trim last \n
+ }
+
+ return address;
+
+ }
+
+ @Override
+ protected void onPostExecute(String address) {
+ // Setting address of the touched Position
+ mSnackbarLocation.setText(address);
+ Log.d(Config.LOGTAG,"Location: Address = "+ address);
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
new file mode 100644
index 000000000..d0a840af8
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java
@@ -0,0 +1,349 @@
+package de.pixart.messenger.ui;
+
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.persistance.FileBackend;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.ui.adapter.ConversationAdapter;
+import de.pixart.messenger.xmpp.XmppConnection;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
+
+ private boolean mReturnToPrevious = false;
+
+ @Override
+ public void onConversationUpdate() {
+ refreshUi();
+ }
+
+ private class Share {
+ public List<Uri> uris = new ArrayList<>();
+ public boolean image;
+ public String account;
+ public String contact;
+ public String text;
+ public String uuid;
+ public boolean multiple = false;
+ }
+
+ private Share share;
+
+ private static final int REQUEST_START_NEW_CONVERSATION = 0x0501;
+ private ListView mListView;
+ private ConversationAdapter mAdapter;
+ private List<Conversation> mConversations = new ArrayList<>();
+ private Toast mToast;
+ private AtomicInteger attachmentCounter = new AtomicInteger(0);
+
+ private UiCallback<Message> attachFileCallback = new UiCallback<Message>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Message object) {
+ // TODO Auto-generated method stub
+
+ }
+
+ @Override
+ public void success(final Message message) {
+ xmppConnectionService.sendMessage(message);
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (attachmentCounter.decrementAndGet() <=0 ) {
+ int resId;
+ if (share.image && share.multiple) {
+ resId = R.string.shared_images_with_x;
+ } else if (share.image) {
+ resId = R.string.shared_image_with_x;
+ } else {
+ resId = R.string.shared_file_with_x;
+ }
+ replaceToast(getString(resId, message.getConversation().getName()));
+ if (mReturnToPrevious) {
+ finish();
+ } else {
+ switchToConversation(message.getConversation());
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void error(final int errorCode, Message object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(errorCode));
+ if (attachmentCounter.decrementAndGet() <=0 ) {
+ finish();
+ }
+ }
+ });
+ }
+ };
+
+ protected void hideToast() {
+ if (mToast != null) {
+ mToast.cancel();
+ }
+ }
+
+ protected void replaceToast(String msg) {
+ hideToast();
+ mToast = Toast.makeText(this, msg ,Toast.LENGTH_LONG);
+ mToast.show();
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_START_NEW_CONVERSATION
+ && resultCode == RESULT_OK) {
+ share.contact = data.getStringExtra("contact");
+ share.account = data.getStringExtra(EXTRA_ACCOUNT);
+ }
+ if (xmppConnectionServiceBound
+ && share != null
+ && share.contact != null
+ && share.account != null) {
+ share();
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (getActionBar() != null) {
+ getActionBar().setDisplayHomeAsUpEnabled(false);
+ getActionBar().setHomeButtonEnabled(false);
+ }
+
+ setContentView(R.layout.share_with);
+ setTitle(getString(R.string.title_activity_sharewith));
+
+ mListView = (ListView) findViewById(R.id.choose_conversation_list);
+ mAdapter = new ConversationAdapter(this, this.mConversations);
+ mListView.setAdapter(mAdapter);
+ mListView.setOnItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1, int position, long arg3) {
+ share(mConversations.get(position));
+ }
+ });
+
+ this.share = new Share();
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.share_with, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_add:
+ final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
+ startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION);
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ Intent intent = getIntent();
+ if (intent == null) {
+ return;
+ }
+ this.mReturnToPrevious = getPreferences().getBoolean("return_to_previous", false);
+ final String type = intent.getType();
+ final String action = intent.getAction();
+ Log.d(Config.LOGTAG, "action: "+action+ ", type:"+type);
+ share.uuid = intent.getStringExtra("uuid");
+ if (Intent.ACTION_SEND.equals(action)) {
+ final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
+ final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
+ if (type != null && uri != null && (text == null || !type.equals("text/plain"))) {
+ this.share.uris.clear();
+ this.share.uris.add(uri);
+ this.share.image = type.startsWith("image/") || isImage(uri);
+ } else {
+ this.share.text = text;
+ }
+ } else if (Intent.ACTION_SEND_MULTIPLE.equals(action)) {
+ this.share.image = type != null && type.startsWith("image/");
+ if (!this.share.image) {
+ return;
+ }
+ this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ }
+ if (xmppConnectionServiceBound) {
+ if (share.uuid != null) {
+ share();
+ } else {
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0);
+ }
+ }
+
+ }
+
+ protected boolean isImage(Uri uri) {
+ try {
+ String guess = URLConnection.guessContentTypeFromName(uri.toString());
+ return (guess != null && guess.startsWith("image/"));
+ } catch (final StringIndexOutOfBoundsException ignored) {
+ return false;
+ }
+ }
+
+ @Override
+ void onBackendConnected() {
+ if (xmppConnectionServiceBound && share != null
+ && ((share.contact != null && share.account != null) || share.uuid != null)) {
+ share();
+ return;
+ }
+ refreshUiReal();
+ }
+
+ private void share() {
+ final Conversation conversation;
+ if (share.uuid != null) {
+ conversation = xmppConnectionService.findConversationByUuid(share.uuid);
+ if (conversation == null) {
+ return;
+ }
+ }else{
+ Account account;
+ try {
+ account = xmppConnectionService.findAccountByJid(Jid.fromString(share.account));
+ } catch (final InvalidJidException e) {
+ account = null;
+ }
+ if (account == null) {
+ return;
+ }
+
+ try {
+ conversation = xmppConnectionService
+ .findOrCreateConversation(account, Jid.fromString(share.contact), false);
+ } catch (final InvalidJidException e) {
+ return;
+ }
+ }
+ share(conversation);
+ }
+
+ private void share(final Conversation conversation) {
+ final Account account = conversation.getAccount();
+ final XmppConnection connection = account.getXmppConnection();
+ final long max = connection == null ? -1 : connection.getFeatures().getMaxHttpUploadSize();
+ mListView.setEnabled(false);
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP && !hasPgp()) {
+ if (share.uuid == null) {
+ showInstallPgpDialog();
+ } else {
+ Toast.makeText(this,R.string.openkeychain_not_installed,Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ return;
+ }
+ if (share.uris.size() != 0) {
+ OnPresenceSelected callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ attachmentCounter.set(share.uris.size());
+ if (share.image) {
+ share.multiple = share.uris.size() > 1;
+ replaceToast(getString(share.multiple ? R.string.preparing_images : R.string.preparing_image));
+ for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
+ ShareWithActivity.this.xmppConnectionService
+ .attachImageToConversation(conversation, i.next(),
+ attachFileCallback);
+ }
+ } else {
+ replaceToast(getString(R.string.preparing_file));
+ ShareWithActivity.this.xmppConnectionService
+ .attachFileToConversation(conversation, share.uris.get(0),
+ attachFileCallback);
+ }
+ }
+ };
+ if (account.httpUploadAvailable()
+ && ((share.image && !neverCompressPictures())
+ || conversation.getMode() == Conversation.MODE_MULTI
+ || FileBackend.allFilesUnderSize(this, share.uris, max))
+ && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) {
+ callback.onPresenceSelected();
+ } else {
+ selectPresence(conversation, callback);
+ }
+ } else {
+ if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty() ) {
+ final OnPresenceSelected callback = new OnPresenceSelected() {
+ @Override
+ public void onPresenceSelected() {
+ Message message = new Message(conversation,share.text, conversation.getNextEncryption());
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
+ message.setCounterpart(conversation.getNextCounterpart());
+ }
+ xmppConnectionService.sendMessage(message);
+ replaceToast(getString(R.string.shared_text_with_x, conversation.getName()));
+ finish();
+ }
+ };
+ if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) {
+ selectPresence(conversation, callback);
+ } else {
+ callback.onPresenceSelected();
+ }
+ } else {
+ switchToConversation(conversation, this.share.text, true);
+ }
+ }
+
+ }
+
+ public void refreshUiReal() {
+ xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0);
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (attachmentCounter.get() >= 1) {
+ replaceToast(getString(R.string.sharing_files_please_wait));
+ } else {
+ super.onBackPressed();
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java
new file mode 100644
index 000000000..8d2df66f6
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java
@@ -0,0 +1,176 @@
+package de.pixart.messenger.ui;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.View;
+import android.view.Window;
+import android.view.WindowManager;
+import android.webkit.MimeTypeMap;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.animation.GlideAnimation;
+import com.bumptech.glide.request.target.BitmapImageViewTarget;
+import com.github.rtoshiro.view.video.FullscreenVideoLayout;
+
+import java.io.File;
+import java.io.IOException;
+
+import de.pixart.messenger.R;
+import uk.co.senab.photoview.PhotoView;
+import uk.co.senab.photoview.PhotoViewAttacher;
+
+public class ShowFullscreenMessageActivity extends Activity {
+
+ private ConversationActivity activity;
+ PhotoView mImage;
+ FullscreenVideoLayout mVideo;
+ ImageView mFullscreenbutton;
+ Uri mFileUri;
+ File mFile;
+ ImageButton mFAB;
+
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ WindowManager.LayoutParams layout = getWindow().getAttributes();
+ layout.screenBrightness = 1;
+ getWindow().setAttributes(layout);
+ getWindow().requestFeature(Window.FEATURE_ACTION_BAR);
+ getWindow().addFlags(layout.FLAG_KEEP_SCREEN_ON);
+ getActionBar().hide();
+ if (Build.VERSION.SDK_INT < 16) {
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN);
+ } else {
+ View decorView = getWindow().getDecorView();
+ int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
+ decorView.setSystemUiVisibility(uiOptions);
+ }
+ setContentView(R.layout.activity_fullscreen_message);
+ mImage = (PhotoView) findViewById(R.id.message_image_view);
+ mVideo = (FullscreenVideoLayout) findViewById(R.id.message_video_view);
+ mFullscreenbutton = (ImageView) findViewById(R.id.vcv_img_fullscreen);
+
+ mFAB = (ImageButton) findViewById(R.id.imageButton);
+ mFAB.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mVideo.reset();
+ shareWith(mFileUri);
+ }
+ });
+ }
+
+ private void shareWith(Uri mFileUri) {
+ Intent share = new Intent(Intent.ACTION_SEND);
+ share.setType(getMimeType(mFileUri.toString()));
+ share.putExtra(Intent.EXTRA_STREAM, Uri.parse(mFileUri.toString()));
+ startActivity(Intent.createChooser(share, getString(R.string.share_with)));
+ }
+
+ public static String getMimeType(String path) {
+ try {
+ String type = null;
+ String extension = path.substring(path.lastIndexOf(".") + 1, path.length());
+ if (extension != null) {
+ type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+ return type;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ Intent intent = getIntent();
+
+ if (intent != null) {
+ if (intent.hasExtra("image")) {
+ mFileUri = intent.getParcelableExtra("image");
+ mFile = new File(mFileUri.getPath());
+ if (mFileUri != null) {
+ DisplayImage(mFile);
+ } else {
+ Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show();
+ }
+ } else if (intent.hasExtra("video")) {
+ mFileUri = intent.getParcelableExtra("video");
+ if (mFileUri != null) {
+ DisplayVideo(mFileUri);
+ } else {
+ Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+ }
+
+ private void DisplayImage(File file) {
+ final PhotoViewAttacher mAttacher = new PhotoViewAttacher(mImage);
+ mImage.setVisibility(View.VISIBLE);
+ try {
+ Glide.with(this)
+ .load(file)
+ .asBitmap()
+ .into(new BitmapImageViewTarget(mImage) {
+ @Override
+ public void onResourceReady(Bitmap resource, GlideAnimation<? super Bitmap> glideAnimation) {
+ super.onResourceReady(resource, glideAnimation);
+ mAttacher.update();
+ }
+ });
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void DisplayVideo(Uri uri) {
+ try {
+ mVideo.setVisibility(View.VISIBLE);
+ mVideo.setVideoURI(uri);
+ mFullscreenbutton.setVisibility(View.INVISIBLE);
+ mVideo.setShouldAutoplay(true);
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onResume() {
+ WindowManager.LayoutParams layout = getWindow().getAttributes();
+ layout.screenBrightness = 1;
+ mVideo.setShouldAutoplay(true);
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ mVideo.reset();
+ super.onPause();
+ }
+
+ public void onStop () {
+ WindowManager.LayoutParams layout = getWindow().getAttributes();
+ layout.screenBrightness = -1;
+ getWindow().setAttributes(layout);
+ getWindow().clearFlags(layout.FLAG_KEEP_SCREEN_ON);
+ super.onStop();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java
new file mode 100644
index 000000000..4b13fd515
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java
@@ -0,0 +1,156 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.location.Address;
+import android.location.Geocoder;
+import android.os.Build;
+import android.os.Bundle;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.TextView;
+
+import com.google.android.gms.maps.CameraUpdateFactory;
+import com.google.android.gms.maps.GoogleMap;
+import com.google.android.gms.maps.MapFragment;
+import com.google.android.gms.maps.OnMapReadyCallback;
+import com.google.android.gms.maps.model.LatLng;
+import com.google.android.gms.maps.model.Marker;
+import com.google.android.gms.maps.model.MarkerOptions;
+
+import java.util.List;
+import java.util.Locale;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+
+public class ShowLocationActivity extends Activity implements OnMapReadyCallback {
+
+ private GoogleMap mGoogleMap;
+ private LatLng mLocation;
+ private String mLocationName;
+
+ class InfoWindowAdapter implements GoogleMap.InfoWindowAdapter {
+
+ private final View InfoWindow;
+
+ InfoWindowAdapter() {
+ InfoWindow = getLayoutInflater().inflate(R.layout.show_location_infowindow, null);
+ }
+
+ @Override
+ public View getInfoWindow(Marker marker) {
+ return null;
+ }
+
+ @Override
+ public View getInfoContents(Marker marker) {
+
+ TextView Title = ((TextView) InfoWindow.findViewById(R.id.title));
+ Title.setText(marker.getTitle());
+ TextView Snippet = ((TextView) InfoWindow.findViewById(R.id.snippet));
+ Snippet.setText(marker.getSnippet());
+
+ return InfoWindow;
+ }
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ActionBar actionBar = getActionBar();
+ if (actionBar != null) {
+ actionBar.setDisplayHomeAsUpEnabled(true);
+ }
+
+ setContentView(R.layout.activity_show_locaction);
+ MapFragment fragment = (MapFragment) getFragmentManager().findFragmentById(R.id.map_fragment);
+ fragment.getMapAsync(this);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case android.R.id.home:
+ finish();
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ Intent intent = getIntent();
+
+ this.mLocationName = intent != null ? intent.getStringExtra("name") : null;
+
+ if (intent != null && intent.hasExtra("longitude") && intent.hasExtra("latitude")) {
+ double longitude = intent.getDoubleExtra("longitude",0);
+ double latitude = intent.getDoubleExtra("latitude",0);
+ this.mLocation = new LatLng(latitude,longitude);
+ if (this.mGoogleMap != null) {
+ markAndCenterOnLocation(this.mLocation, this.mLocationName);
+ }
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ }
+
+ @Override
+ public void onMapReady(GoogleMap googleMap) {
+ this.mGoogleMap = googleMap;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED
+ || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
+ this.mGoogleMap.setMyLocationEnabled(true);
+ }
+ }
+ if (this.mLocation != null) {
+ this.markAndCenterOnLocation(this.mLocation,this.mLocationName);
+ }
+ }
+
+ private void markAndCenterOnLocation(LatLng location, String name) {
+ this.mGoogleMap.clear();
+ MarkerOptions options = new MarkerOptions();
+ options.position(location);
+ double longitude = mLocation.longitude;
+ double latitude = mLocation.latitude;
+ if (latitude != 0 && longitude != 0) {
+ Geocoder geoCoder = new Geocoder(getBaseContext(), Locale.getDefault());
+ try {
+ List<Address> addresses = geoCoder.getFromLocation(latitude, longitude, 1);
+
+ String address = "";
+ if (addresses != null) {
+ Address Address = addresses.get(0);
+ StringBuilder strAddress = new StringBuilder("");
+
+ for (int i = 0; i < Address.getMaxAddressLineIndex(); i++) {
+ strAddress.append(Address.getAddressLine(i)).append("\n");
+ }
+ address = strAddress.toString();
+ address = address.substring(0, address.length()-1); //trim last \n
+ options.snippet(address);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ if (name != null) {
+ options.title(name);
+ }
+ this.mGoogleMap.setInfoWindowAdapter(new InfoWindowAdapter());
+ this.mGoogleMap.addMarker(options).showInfoWindow();
+ this.mGoogleMap.animateCamera(CameraUpdateFactory.newLatLngZoom(location, Config.DEFAULT_ZOOM));
+ }
+
+}
diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
new file mode 100644
index 000000000..931a2dbae
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java
@@ -0,0 +1,1039 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.ActionBar.Tab;
+import android.app.ActionBar.TabListener;
+import android.app.AlertDialog;
+import android.app.Fragment;
+import android.app.FragmentTransaction;
+import android.app.ListFragment;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.v13.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.util.Log;
+import android.util.Pair;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView;
+import android.widget.AdapterView.AdapterContextMenuInfo;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.ArrayAdapter;
+import android.widget.AutoCompleteTextView;
+import android.widget.CheckBox;
+import android.widget.Checkable;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.Spinner;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Blockable;
+import de.pixart.messenger.entities.Bookmark;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.entities.Presence;
+import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate;
+import de.pixart.messenger.ui.adapter.KnownHostsAdapter;
+import de.pixart.messenger.ui.adapter.ListItemAdapter;
+import de.pixart.messenger.utils.XmppUri;
+import de.pixart.messenger.xmpp.OnUpdateBlocklist;
+import de.pixart.messenger.xmpp.XmppConnection;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist {
+
+ public int conference_context_id;
+ public int contact_context_id;
+ private Tab mContactsTab;
+ private Tab mConferencesTab;
+ private ViewPager mViewPager;
+ private MyListFragment mContactsListFragment = new MyListFragment();
+ private List<ListItem> contacts = new ArrayList<>();
+ private ArrayAdapter<ListItem> mContactsAdapter;
+ private MyListFragment mConferenceListFragment = new MyListFragment();
+ private List<ListItem> conferences = new ArrayList<ListItem>();
+ private ArrayAdapter<ListItem> mConferenceAdapter;
+ private List<String> mActivatedAccounts = new ArrayList<String>();
+ private List<String> mKnownHosts;
+ private List<String> mKnownConferenceHosts;
+ private Invite mPendingInvite = null;
+ private EditText mSearchEditText;
+ private AtomicBoolean mRequestedContactsPermission = new AtomicBoolean(false);
+ private final int REQUEST_SYNC_CONTACTS = 0x3b28cf;
+ private final int REQUEST_CREATE_CONFERENCE = 0x3b39da;
+
+ private MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
+
+ @Override
+ public boolean onMenuItemActionExpand(MenuItem item) {
+ mSearchEditText.post(new Runnable() {
+
+ @Override
+ public void run() {
+ mSearchEditText.requestFocus();
+ InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(mSearchEditText,
+ InputMethodManager.SHOW_IMPLICIT);
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onMenuItemActionCollapse(MenuItem item) {
+ hideKeyboard();
+ mSearchEditText.setText("");
+ filter(null);
+ return true;
+ }
+ };
+ private boolean mHideOfflineContacts = false;
+ private TabListener mTabListener = new TabListener() {
+
+ @Override
+ public void onTabUnselected(Tab tab, FragmentTransaction ft) {
+ return;
+ }
+
+ @Override
+ public void onTabSelected(Tab tab, FragmentTransaction ft) {
+ mViewPager.setCurrentItem(tab.getPosition());
+ onTabChanged();
+ }
+
+ @Override
+ public void onTabReselected(Tab tab, FragmentTransaction ft) {
+ return;
+ }
+ };
+ private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() {
+ @Override
+ public void onPageSelected(int position) {
+ if (getActionBar() != null) {
+ getActionBar().setSelectedNavigationItem(position);
+ }
+ onTabChanged();
+ }
+ };
+ private TextWatcher mSearchTextWatcher = new TextWatcher() {
+
+ @Override
+ public void afterTextChanged(Editable editable) {
+ filter(editable.toString());
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count,
+ int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before,
+ int count) {
+ }
+ };
+
+ private TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (getActionBar().getSelectedNavigationIndex() == 0) {
+ if (contacts.size() == 1) {
+ openConversationForContact((Contact) contacts.get(0));
+ } else {
+ hideKeyboard();
+ mContactsListFragment.getListView().requestFocus();
+ }
+ } else {
+ if (conferences.size() == 1) {
+ openConversationsForBookmark((Bookmark) conferences.get(0));
+ } else {
+ hideKeyboard();
+ mConferenceListFragment.getListView().requestFocus();
+ }
+ }
+ return true;
+ }
+ };
+ private MenuItem mMenuSearchView;
+ private ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
+ @Override
+ public void onTagClicked(String tag) {
+ if (mMenuSearchView != null) {
+ mMenuSearchView.expandActionView();
+ mSearchEditText.setText("");
+ mSearchEditText.append(tag);
+ filter(tag);
+ }
+ }
+ };
+ private String mInitialJid;
+ private Pair<Integer, Intent> mPostponedActivityResult;
+ private UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
+ @Override
+ public void success(final Conversation conversation) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ hideToast();
+ switchToConversation(conversation);
+ }
+ });
+ }
+
+ @Override
+ public void error(final int errorCode, Conversation object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(errorCode));
+ }
+ });
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Conversation object) {
+
+ }
+ };
+ private Toast mToast;
+
+ protected void hideToast() {
+ if (mToast != null) {
+ mToast.cancel();
+ }
+ }
+
+ protected void replaceToast(String msg) {
+ hideToast();
+ mToast = Toast.makeText(this, msg ,Toast.LENGTH_LONG);
+ mToast.show();
+ }
+
+ @Override
+ public void onRosterUpdate() {
+ this.refreshUi();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_start_conversation);
+ mViewPager = (ViewPager) findViewById(R.id.start_conversation_view_pager);
+ ActionBar actionBar = getActionBar();
+ actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
+
+ mContactsTab = actionBar.newTab().setText(R.string.contacts)
+ .setTabListener(mTabListener);
+ mConferencesTab = actionBar.newTab().setText(R.string.conferences)
+ .setTabListener(mTabListener);
+ actionBar.addTab(mContactsTab);
+ actionBar.addTab(mConferencesTab);
+
+ mViewPager.setOnPageChangeListener(mOnPageChangeListener);
+ mViewPager.setAdapter(new FragmentPagerAdapter(getFragmentManager()) {
+
+ @Override
+ public int getCount() {
+ return 2;
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ if (position == 0) {
+ return mContactsListFragment;
+ } else {
+ return mConferenceListFragment;
+ }
+ }
+ });
+
+ mConferenceAdapter = new ListItemAdapter(this, conferences);
+ mConferenceListFragment.setListAdapter(mConferenceAdapter);
+ mConferenceListFragment.setContextMenu(R.menu.conference_context);
+ mConferenceListFragment
+ .setOnListItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1,
+ int position, long arg3) {
+ openConversationForBookmark(position);
+ }
+ });
+
+ mContactsAdapter = new ListItemAdapter(this, contacts);
+ ((ListItemAdapter) mContactsAdapter).setOnTagClickedListener(this.mOnTagClickedListener);
+ mContactsListFragment.setListAdapter(mContactsAdapter);
+ mContactsListFragment.setContextMenu(R.menu.contact_context);
+ mContactsListFragment
+ .setOnListItemClickListener(new OnItemClickListener() {
+
+ @Override
+ public void onItemClick(AdapterView<?> arg0, View arg1,
+ int position, long arg3) {
+ openConversationForContact(position);
+ }
+ });
+
+ this.mHideOfflineContacts = getPreferences().getBoolean("hide_offline", false);
+
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ askForContactsPermissions();
+ }
+
+ protected void openConversationForContact(int position) {
+ Contact contact = (Contact) contacts.get(position);
+ openConversationForContact(contact);
+ }
+
+ protected void openConversationForContact(Contact contact) {
+ Conversation conversation = xmppConnectionService
+ .findOrCreateConversation(contact.getAccount(),
+ contact.getJid(), false);
+ switchToConversation(conversation);
+ }
+
+ protected void openConversationForContact() {
+ int position = contact_context_id;
+ openConversationForContact(position);
+ }
+
+ protected void openConversationForBookmark() {
+ openConversationForBookmark(conference_context_id);
+ }
+
+ protected void openConversationForBookmark(int position) {
+ Bookmark bookmark = (Bookmark) conferences.get(position);
+ openConversationsForBookmark(bookmark);
+ }
+
+ protected void openConversationsForBookmark(Bookmark bookmark) {
+ Jid jid = bookmark.getJid();
+ if (jid == null) {
+ Toast.makeText(this,R.string.invalid_jid,Toast.LENGTH_SHORT).show();
+ return;
+ }
+ Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(),jid, true);
+ conversation.setBookmark(bookmark);
+ if (!conversation.getMucOptions().online()) {
+ xmppConnectionService.joinMuc(conversation);
+ }
+ if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) {
+ bookmark.setAutojoin(true);
+ xmppConnectionService.pushBookmarks(bookmark.getAccount());
+ }
+ switchToConversation(conversation);
+ }
+
+ protected void openDetailsForContact() {
+ int position = contact_context_id;
+ Contact contact = (Contact) contacts.get(position);
+ switchToContactDetails(contact);
+ }
+
+ protected void toggleContactBlock() {
+ final int position = contact_context_id;
+ BlockContactDialog.show(this, xmppConnectionService, (Contact) contacts.get(position));
+ }
+
+ protected void deleteContact() {
+ final int position = contact_context_id;
+ final Contact contact = (Contact) contacts.get(position);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setTitle(R.string.action_delete_contact);
+ builder.setMessage(getString(R.string.remove_contact_text,
+ contact.getJid()));
+ builder.setPositiveButton(R.string.delete, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ xmppConnectionService.deleteContactOnServer(contact);
+ filter(mSearchEditText.getText().toString());
+ }
+ });
+ builder.create().show();
+ }
+
+ protected void deleteConference() {
+ int position = conference_context_id;
+ final Bookmark bookmark = (Bookmark) conferences.get(position);
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setTitle(R.string.delete_bookmark);
+ builder.setMessage(getString(R.string.remove_bookmark_text,
+ bookmark.getJid()));
+ builder.setPositiveButton(R.string.delete, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ bookmark.unregisterConversation();
+ Account account = bookmark.getAccount();
+ account.getBookmarks().remove(bookmark);
+ xmppConnectionService.pushBookmarks(account);
+ filter(mSearchEditText.getText().toString());
+ }
+ });
+ builder.create().show();
+
+ }
+
+ @SuppressLint("InflateParams")
+ protected void showCreateContactDialog(final String prefilledJid, final String fingerprint) {
+ EnterJidDialog dialog = new EnterJidDialog(
+ this, mKnownHosts, mActivatedAccounts,
+ getString(R.string.create_contact), getString(R.string.create),
+ prefilledJid, null, fingerprint == null
+ );
+
+ dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
+ @Override
+ public boolean onEnterJidDialogPositive(Jid accountJid, Jid contactJid) throws EnterJidDialog.JidError {
+ if (!xmppConnectionServiceBound) {
+ return false;
+ }
+
+ final Account account = xmppConnectionService.findAccountByJid(accountJid);
+ if (account == null) {
+ return true;
+ }
+
+ final Contact contact = account.getRoster().getContact(contactJid);
+ if (contact.showInRoster()) {
+ throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
+ } else {
+ contact.addOtrFingerprint(fingerprint);
+ xmppConnectionService.createContact(contact);
+ switchToConversation(contact);
+ return true;
+ }
+ }
+ });
+
+ dialog.show();
+ }
+
+ @SuppressLint("InflateParams")
+ protected void showJoinConferenceDialog(final String prefilledJid) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.join_conference);
+ final View dialogView = getLayoutInflater().inflate(R.layout.join_conference_dialog, null);
+ final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
+ final AutoCompleteTextView jid = (AutoCompleteTextView) dialogView.findViewById(R.id.jid);
+ final TextView jabberIdDesc = (TextView) dialogView.findViewById(R.id.jabber_id);
+ jabberIdDesc.setText(R.string.conference_address);
+ jid.setHint(R.string.conference_address_example);
+ jid.setAdapter(new KnownHostsAdapter(this, R.layout.simple_list_item, mKnownConferenceHosts));
+ if (prefilledJid != null) {
+ jid.append(prefilledJid);
+ }
+ populateAccountSpinner(this, mActivatedAccounts, spinner);
+ final Checkable bookmarkCheckBox = (CheckBox) dialogView
+ .findViewById(R.id.bookmark);
+ builder.setView(dialogView);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.join, null);
+ final AlertDialog dialog = builder.create();
+ dialog.show();
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(
+ new View.OnClickListener() {
+
+ @Override
+ public void onClick(final View v) {
+ if (!xmppConnectionServiceBound) {
+ return;
+ }
+ final Account account = getSelectedAccount(spinner);
+ if (account == null) {
+ return;
+ }
+ final Jid conferenceJid;
+ try {
+ conferenceJid = Jid.fromString(jid.getText().toString());
+ } catch (final InvalidJidException e) {
+ jid.setError(getString(R.string.invalid_jid));
+ return;
+ }
+
+ if (bookmarkCheckBox.isChecked()) {
+ if (account.hasBookmarkFor(conferenceJid)) {
+ jid.setError(getString(R.string.bookmark_already_exists));
+ } else {
+ final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid());
+ bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true));
+ String nick = conferenceJid.getResourcepart();
+ if (nick != null && !nick.isEmpty()) {
+ bookmark.setNick(nick);
+ }
+ account.getBookmarks().add(bookmark);
+ xmppConnectionService.pushBookmarks(account);
+ final Conversation conversation = xmppConnectionService
+ .findOrCreateConversation(account,
+ conferenceJid, true);
+ conversation.setBookmark(bookmark);
+ if (!conversation.getMucOptions().online()) {
+ xmppConnectionService.joinMuc(conversation);
+ }
+ dialog.dismiss();
+ switchToConversation(conversation);
+ }
+ } else {
+ final Conversation conversation = xmppConnectionService
+ .findOrCreateConversation(account,
+ conferenceJid, true);
+ if (!conversation.getMucOptions().online()) {
+ xmppConnectionService.joinMuc(conversation);
+ }
+ dialog.dismiss();
+ switchToConversation(conversation);
+ }
+ }
+ });
+ }
+
+ private void showCreateConferenceDialog() {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.create_conference);
+ final View dialogView = getLayoutInflater().inflate(R.layout.create_conference_dialog, null);
+ final Spinner spinner = (Spinner) dialogView.findViewById(R.id.account);
+ final EditText subject = (EditText) dialogView.findViewById(R.id.subject);
+ populateAccountSpinner(this, mActivatedAccounts, spinner);
+ builder.setView(dialogView);
+ builder.setPositiveButton(R.string.choose_participants, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (!xmppConnectionServiceBound) {
+ return;
+ }
+ final Account account = getSelectedAccount(spinner);
+ if (account == null) {
+ return;
+ }
+ Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
+ intent.putExtra("multiple", true);
+ intent.putExtra("show_enter_jid", true);
+ intent.putExtra("subject", subject.getText().toString());
+ intent.putExtra(EXTRA_ACCOUNT, account.getJid().toBareJid().toString());
+ intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID,R.string.choose_participants);
+ startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ }
+
+ private Account getSelectedAccount(Spinner spinner) {
+ if (!spinner.isEnabled()) {
+ return null;
+ }
+ Jid jid;
+ try {
+ if (Config.DOMAIN_LOCK != null) {
+ jid = Jid.fromParts((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
+ } else {
+ jid = Jid.fromString((String) spinner.getSelectedItem());
+ }
+ } catch (final InvalidJidException e) {
+ return null;
+ }
+ return xmppConnectionService.findAccountByJid(jid);
+ }
+
+ protected void switchToConversation(Contact contact) {
+ Conversation conversation = xmppConnectionService
+ .findOrCreateConversation(contact.getAccount(),
+ contact.getJid(), false);
+ switchToConversation(conversation);
+ }
+
+ public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
+ if (accounts.size() > 0) {
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
+ adapter.setDropDownViewResource(R.layout.simple_list_item);
+ spinner.setAdapter(adapter);
+ spinner.setEnabled(true);
+ } else {
+ ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
+ R.layout.simple_list_item,
+ Arrays.asList(new String[]{context.getString(R.string.no_accounts)}));
+ adapter.setDropDownViewResource(R.layout.simple_list_item);
+ spinner.setAdapter(adapter);
+ spinner.setEnabled(false);
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.start_conversation, menu);
+ MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact);
+ MenuItem menuCreateConference = menu.findItem(R.id.action_conference);
+ MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
+ menuHideOffline.setChecked(this.mHideOfflineContacts);
+ mMenuSearchView = menu.findItem(R.id.action_search);
+ mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
+ View mSearchView = mMenuSearchView.getActionView();
+ mSearchEditText = (EditText) mSearchView
+ .findViewById(R.id.search_field);
+ mSearchEditText.addTextChangedListener(mSearchTextWatcher);
+ mSearchEditText.setOnEditorActionListener(mSearchDone);
+ if (getActionBar().getSelectedNavigationIndex() == 0) {
+ menuCreateConference.setVisible(false);
+ } else {
+ menuCreateContact.setVisible(false);
+ }
+ if (mInitialJid != null) {
+ mMenuSearchView.expandActionView();
+ mSearchEditText.append(mInitialJid);
+ filter(mInitialJid);
+ }
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_create_contact:
+ showCreateContactDialog(null,null);
+ return true;
+ case R.id.action_join_conference:
+ showJoinConferenceDialog(null);
+ return true;
+ case R.id.action_create_conference:
+ showCreateConferenceDialog();
+ return true;
+ case R.id.action_scan_qr_code:
+ new IntentIntegrator(this).initiateScan();
+ return true;
+ case R.id.action_hide_offline:
+ mHideOfflineContacts = !item.isChecked();
+ getPreferences().edit().putBoolean("hide_offline", mHideOfflineContacts).commit();
+ if (mSearchEditText != null) {
+ filter(mSearchEditText.getText().toString());
+ }
+ invalidateOptionsMenu();
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_SEARCH && !event.isLongPress()) {
+ openSearch();
+ return true;
+ }
+ int c = event.getUnicodeChar();
+ if (c > 32) {
+ if (mSearchEditText != null && !mSearchEditText.isFocused()) {
+ openSearch();
+ mSearchEditText.append(Character.toString((char) c));
+ return true;
+ }
+ }
+ return super.onKeyUp(keyCode, event);
+ }
+
+ private void openSearch() {
+ if (mMenuSearchView != null) {
+ mMenuSearchView.expandActionView();
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
+ IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ if (scanResult != null && scanResult.getFormatName() != null) {
+ String data = scanResult.getContents();
+ Invite invite = new Invite(data);
+ if (xmppConnectionServiceBound) {
+ invite.invite();
+ } else if (invite.getJid() != null) {
+ this.mPendingInvite = invite;
+ } else {
+ this.mPendingInvite = null;
+ }
+ }
+ } else if (resultCode == RESULT_OK) {
+ if (xmppConnectionServiceBound) {
+ this.mPostponedActivityResult = null;
+ if (requestCode == REQUEST_CREATE_CONFERENCE) {
+ Account account = extractAccount(intent);
+ final String subject = intent.getStringExtra("subject");
+ List<Jid> jids = new ArrayList<>();
+ if (intent.getBooleanExtra("multiple", false)) {
+ String[] toAdd = intent.getStringArrayExtra("contacts");
+ for (String item : toAdd) {
+ try {
+ jids.add(Jid.fromString(item));
+ } catch (InvalidJidException e) {
+ //ignored
+ }
+ }
+ } else {
+ try {
+ jids.add(Jid.fromString(intent.getStringExtra("contact")));
+ } catch (Exception e) {
+ //ignored
+ }
+ }
+ if (account != null && jids.size() > 0) {
+ xmppConnectionService.createAdhocConference(account, subject, jids, mAdhocConferenceCallback);
+ mToast = Toast.makeText(this, R.string.creating_conference,Toast.LENGTH_LONG);
+ mToast.show();
+ }
+ }
+ } else {
+ this.mPostponedActivityResult = new Pair<>(requestCode, intent);
+ }
+ }
+ super.onActivityResult(requestCode, requestCode, intent);
+ }
+
+ private void askForContactsPermissions() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ if (mRequestedContactsPermission.compareAndSet(false, true)) {
+ if (shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.sync_with_contacts);
+ builder.setMessage(R.string.sync_with_contacts_long);
+ builder.setPositiveButton(R.string.next, new OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
+ }
+ }
+ });
+ builder.create().show();
+ } else {
+ requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, 0);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
+ if (grantResults.length > 0)
+ if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) {
+ xmppConnectionService.loadPhoneContacts();
+ }
+ }
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ if (mPostponedActivityResult != null) {
+ onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
+ this.mPostponedActivityResult = null;
+ }
+ this.mActivatedAccounts.clear();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ if (Config.DOMAIN_LOCK != null) {
+ this.mActivatedAccounts.add(account.getJid().getLocalpart());
+ } else {
+ this.mActivatedAccounts.add(account.getJid().toBareJid().toString());
+ }
+ }
+ }
+ final Intent intent = getIntent();
+ final ActionBar ab = getActionBar();
+ if (intent != null && intent.getBooleanExtra("init",false) && ab != null) {
+ ab.setDisplayShowHomeEnabled(false);
+ ab.setDisplayHomeAsUpEnabled(false);
+ ab.setHomeButtonEnabled(false);
+ }
+ this.mKnownHosts = xmppConnectionService.getKnownHosts();
+ this.mKnownConferenceHosts = xmppConnectionService.getKnownConferenceHosts();
+ if (this.mPendingInvite != null) {
+ mPendingInvite.invite();
+ this.mPendingInvite = null;
+ } else if (!handleIntent(getIntent())) {
+ if (mSearchEditText != null) {
+ filter(mSearchEditText.getText().toString());
+ } else {
+ filter(null);
+ }
+ }
+ setIntent(null);
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ Invite getInviteJellyBean(NdefRecord record) {
+ return new Invite(record.toUri());
+ }
+
+ protected boolean handleIntent(Intent intent) {
+ if (intent == null || intent.getAction() == null) {
+ return false;
+ }
+ switch (intent.getAction()) {
+ case Intent.ACTION_SENDTO:
+ case Intent.ACTION_VIEW:
+ Log.d(Config.LOGTAG, "received uri=" + intent.getData());
+ return new Invite(intent.getData()).invite();
+ case NfcAdapter.ACTION_NDEF_DISCOVERED:
+ for (Parcelable message : getIntent().getParcelableArrayExtra(NfcAdapter.EXTRA_NDEF_MESSAGES)) {
+ if (message instanceof NdefMessage) {
+ Log.d(Config.LOGTAG, "received message=" + message);
+ for (NdefRecord record : ((NdefMessage) message).getRecords()) {
+ switch (record.getTnf()) {
+ case NdefRecord.TNF_WELL_KNOWN:
+ if (Arrays.equals(record.getType(), NdefRecord.RTD_URI)) {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
+ return getInviteJellyBean(record).invite();
+ } else {
+ byte[] payload = record.getPayload();
+ if (payload[0] == 0) {
+ return new Invite(Uri.parse(new String(Arrays.copyOfRange(
+ payload, 1, payload.length)))).invite();
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ private boolean handleJid(Invite invite) {
+ List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid());
+ if (invite.isMuc()) {
+ Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
+ if (muc != null) {
+ switchToConversation(muc);
+ return true;
+ } else {
+ showJoinConferenceDialog(invite.getJid().toBareJid().toString());
+ return false;
+ }
+ } else if (contacts.size() == 0) {
+ showCreateContactDialog(invite.getJid().toString(),invite.getFingerprint());
+ return false;
+ } else if (contacts.size() == 1) {
+ Contact contact = contacts.get(0);
+ if (invite.getFingerprint() != null) {
+ if (contact.addOtrFingerprint(invite.getFingerprint())) {
+ Log.d(Config.LOGTAG,"added new fingerprint");
+ xmppConnectionService.syncRosterToDisk(contact.getAccount());
+ }
+ }
+ switchToConversation(contact);
+ return true;
+ } else {
+ if (mMenuSearchView != null) {
+ mMenuSearchView.expandActionView();
+ mSearchEditText.setText("");
+ mSearchEditText.append(invite.getJid().toString());
+ filter(invite.getJid().toString());
+ } else {
+ mInitialJid = invite.getJid().toString();
+ }
+ return true;
+ }
+ }
+
+ protected void filter(String needle) {
+ if (xmppConnectionServiceBound) {
+ this.filterContacts(needle);
+ this.filterConferences(needle);
+ }
+ }
+
+ protected void filterContacts(String needle) {
+ this.contacts.clear();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ for (Contact contact : account.getRoster().getContacts()) {
+ Presence.Status s = contact.getShownStatus();
+ if (contact.showInRoster() && contact.match(this, needle)
+ && (!this.mHideOfflineContacts
+ || (needle != null && !needle.trim().isEmpty())
+ || s.compareTo(Presence.Status.OFFLINE) < 0)) {
+ this.contacts.add(contact);
+ }
+ }
+ }
+ }
+ Collections.sort(this.contacts);
+ mContactsAdapter.notifyDataSetChanged();
+ }
+
+ protected void filterConferences(String needle) {
+ this.conferences.clear();
+ for (Account account : xmppConnectionService.getAccounts()) {
+ if (account.getStatus() != Account.State.DISABLED) {
+ for (Bookmark bookmark : account.getBookmarks()) {
+ if (bookmark.match(this, needle)) {
+ this.conferences.add(bookmark);
+ }
+ }
+ }
+ }
+ Collections.sort(this.conferences);
+ mConferenceAdapter.notifyDataSetChanged();
+ }
+
+ private void onTabChanged() {
+ invalidateOptionsMenu();
+ }
+
+ @Override
+ public void OnUpdateBlocklist(final Status status) {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ if (mSearchEditText != null) {
+ filter(mSearchEditText.getText().toString());
+ }
+ }
+
+ public static class MyListFragment extends ListFragment {
+ private AdapterView.OnItemClickListener mOnItemClickListener;
+ private int mResContextMenu;
+
+ public void setContextMenu(final int res) {
+ this.mResContextMenu = res;
+ }
+
+ @Override
+ public void onListItemClick(final ListView l, final View v, final int position, final long id) {
+ if (mOnItemClickListener != null) {
+ mOnItemClickListener.onItemClick(l, v, position, id);
+ }
+ }
+
+ public void setOnListItemClickListener(AdapterView.OnItemClickListener l) {
+ this.mOnItemClickListener = l;
+ }
+
+ @Override
+ public void onViewCreated(final View view, final Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ registerForContextMenu(getListView());
+ getListView().setFastScrollEnabled(true);
+ }
+
+ @Override
+ public void onCreateContextMenu(final ContextMenu menu, final View v,
+ final ContextMenuInfo menuInfo) {
+ super.onCreateContextMenu(menu, v, menuInfo);
+ final StartConversationActivity activity = (StartConversationActivity) getActivity();
+ activity.getMenuInflater().inflate(mResContextMenu, menu);
+ final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
+ if (mResContextMenu == R.menu.conference_context) {
+ activity.conference_context_id = acmi.position;
+ } else if (mResContextMenu == R.menu.contact_context){
+ activity.contact_context_id = acmi.position;
+ final Blockable contact = (Contact) activity.contacts.get(acmi.position);
+ final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
+ XmppConnection xmpp = contact.getAccount().getXmppConnection();
+ if (xmpp != null && xmpp.getFeatures().blocking()) {
+ if (contact.isBlocked()) {
+ blockUnblockItem.setTitle(R.string.unblock_contact);
+ } else {
+ blockUnblockItem.setTitle(R.string.block_contact);
+ }
+ } else {
+ blockUnblockItem.setVisible(false);
+ }
+ }
+ }
+
+ @Override
+ public boolean onContextItemSelected(final MenuItem item) {
+ StartConversationActivity activity = (StartConversationActivity) getActivity();
+ switch (item.getItemId()) {
+ case R.id.context_start_conversation:
+ activity.openConversationForContact();
+ break;
+ case R.id.context_contact_details:
+ activity.openDetailsForContact();
+ break;
+ case R.id.context_contact_block_unblock:
+ activity.toggleContactBlock();
+ break;
+ case R.id.context_delete_contact:
+ activity.deleteContact();
+ break;
+ case R.id.context_join_conference:
+ activity.openConversationForBookmark();
+ break;
+ case R.id.context_delete_conference:
+ activity.deleteConference();
+ }
+ return true;
+ }
+ }
+
+ private class Invite extends XmppUri {
+
+ public Invite(final Uri uri) {
+ super(uri);
+ }
+
+ public Invite(final String uri) {
+ super(uri);
+ }
+
+ boolean invite() {
+ if (getJid() != null) {
+ return handleJid(this);
+ }
+ return false;
+ }
+
+ public boolean isMuc() {
+ return muc;
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/StartUI.java b/src/main/java/de/pixart/messenger/ui/StartUI.java
new file mode 100644
index 000000000..5c9e4c324
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/StartUI.java
@@ -0,0 +1,109 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.support.v7.app.AlertDialog;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import pub.devrel.easypermissions.AfterPermissionGranted;
+import pub.devrel.easypermissions.EasyPermissions;
+
+public class StartUI extends AppCompatActivity
+ implements EasyPermissions.PermissionCallbacks {
+
+ private static final int NeededPermissions = 1000;
+
+ String[] perms = {Manifest.permission.READ_EXTERNAL_STORAGE,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_start_ui);
+ requestNeededPermissions();
+ }
+
+ @AfterPermissionGranted(NeededPermissions)
+ private void requestNeededPermissions() {
+ String PREFS_NAME = "FirstStart";
+ SharedPreferences FirstStart = getApplicationContext().getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
+ long FirstStartTime = FirstStart.getLong("FirstStart", 0);
+ if (EasyPermissions.hasPermissions(this, perms)) {
+ // Already have permission, start ConversationsActivity
+ Log.d(Config.LOGTAG, "All permissions granted, starting "+getString(R.string.app_name) + "(" +FirstStartTime + ")");
+ Intent intent = new Intent (this, ConversationActivity.class);
+ intent.putExtra("FirstStart", FirstStartTime);
+ startActivity(intent);
+ finish();
+ } else {
+ // set first start to 0 if there are permissions to request
+ Log.d(Config.LOGTAG, "Requesting required permissions");
+ SharedPreferences.Editor editor = FirstStart.edit();
+ editor.putLong("FirstStart", 0);
+ editor.commit();
+ // Do not have permissions, request them now
+ EasyPermissions.requestPermissions(this, getString(R.string.request_permissions_message),
+ NeededPermissions, perms);
+ }
+ }
+
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ // Forward results to EasyPermissions
+ EasyPermissions.onRequestPermissionsResult(requestCode, permissions, grantResults, this);
+ }
+
+ @Override
+ public void onPermissionsGranted(int requestCode, List<String> list) {
+ Log.d(Config.LOGTAG, "Permissions granted:" + requestCode);
+ }
+
+ @Override
+ public void onPermissionsDenied(int requestCode, List<String> list) {
+ Log.d(Config.LOGTAG, "Permissions denied:" + requestCode);
+ AlertDialog dialog = new AlertDialog.Builder(this)
+ .setMessage(getString(R.string.request_permissions_message_again))
+ .setPositiveButton(getString(R.string.yes), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ Uri uri = Uri.fromParts("package", getPackageName(), null);
+ intent.setData(uri);
+ startActivity(intent);
+ }
+ })
+ .setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ finish();
+ }
+ })
+ .create();
+ dialog.show();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/TimePreference.java b/src/main/java/de/pixart/messenger/ui/TimePreference.java
new file mode 100644
index 000000000..1c8c8c0f1
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/TimePreference.java
@@ -0,0 +1,105 @@
+package de.pixart.messenger.ui;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.preference.DialogPreference;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TimePicker;
+
+import java.text.DateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
+public class TimePreference extends DialogPreference implements Preference.OnPreferenceChangeListener {
+ private TimePicker picker = null;
+ public final static long DEFAULT_VALUE = 0;
+
+ public TimePreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs, 0);
+ this.setOnPreferenceChangeListener(this);
+ }
+
+ protected void setTime(final long time) {
+ persistLong(time);
+ notifyDependencyChange(shouldDisableDependents());
+ notifyChanged();
+ }
+
+ protected void updateSummary(final long time) {
+ final DateFormat dateFormat = android.text.format.DateFormat.getTimeFormat(getContext());
+ final Date date = new Date(time);
+ setSummary(dateFormat.format(date.getTime()));
+ }
+
+ @Override
+ protected View onCreateDialogView() {
+ picker = new TimePicker(getContext());
+ picker.setIs24HourView(android.text.format.DateFormat.is24HourFormat(getContext()));
+ return picker;
+ }
+
+ protected Calendar getPersistedTime() {
+ final Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(getPersistedLong(DEFAULT_VALUE));
+
+ return c;
+ }
+
+ @SuppressWarnings("NullableProblems")
+ @Override
+ protected void onBindDialogView(final View v) {
+ super.onBindDialogView(v);
+ final Calendar c = getPersistedTime();
+
+ picker.setCurrentHour(c.get(Calendar.HOUR_OF_DAY));
+ picker.setCurrentMinute(c.get(Calendar.MINUTE));
+ }
+
+ @Override
+ protected void onDialogClosed(final boolean positiveResult) {
+ super.onDialogClosed(positiveResult);
+
+ if (positiveResult) {
+ final Calendar c = Calendar.getInstance();
+ c.set(Calendar.MINUTE, picker.getCurrentMinute());
+ c.set(Calendar.HOUR_OF_DAY, picker.getCurrentHour());
+
+
+ if (!callChangeListener(c.getTimeInMillis())) {
+ return;
+ }
+
+ setTime(c.getTimeInMillis());
+ }
+ }
+
+ @Override
+ protected Object onGetDefaultValue(final TypedArray a, final int index) {
+ return a.getInteger(index, 0);
+ }
+
+ @Override
+ protected void onSetInitialValue(final boolean restorePersistedValue, final Object defaultValue) {
+ long time;
+ if (defaultValue == null) {
+ time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE;
+ } else if (defaultValue instanceof Long) {
+ time = restorePersistedValue ? getPersistedLong((Long) defaultValue) : (Long) defaultValue;
+ } else if (defaultValue instanceof Calendar) {
+ time = restorePersistedValue ? getPersistedLong(((Calendar)defaultValue).getTimeInMillis()) : ((Calendar)defaultValue).getTimeInMillis();
+ } else {
+ time = restorePersistedValue ? getPersistedLong(DEFAULT_VALUE) : DEFAULT_VALUE;
+ }
+
+ setTime(time);
+ updateSummary(time);
+ }
+
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+ ((TimePreference) preference).updateSummary((Long)newValue);
+ return true;
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java
new file mode 100644
index 000000000..5b274d102
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java
@@ -0,0 +1,337 @@
+package de.pixart.messenger.ui;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.whispersystems.libaxolotl.IdentityKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.AxolotlService;
+import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.xmpp.OnKeyStatusUpdated;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class TrustKeysActivity extends XmppActivity implements OnKeyStatusUpdated {
+ private List<Jid> contactJids;
+
+ private Account mAccount;
+ private Conversation mConversation;
+ private TextView keyErrorMessage;
+ private LinearLayout keyErrorMessageCard;
+ private TextView ownKeysTitle;
+ private LinearLayout ownKeys;
+ private LinearLayout ownKeysCard;
+ private LinearLayout foreignKeys;
+ private Button mSaveButton;
+ private Button mCancelButton;
+
+ private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS;
+
+ private final Map<String, Boolean> ownKeysToTrust = new HashMap<>();
+ private final Map<Jid,Map<String, Boolean>> foreignKeysToTrust = new HashMap<>();
+
+ private final OnClickListener mSaveButtonListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ commitTrusts();
+ finishOk();
+ }
+ };
+
+ private final OnClickListener mCancelButtonListener = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ setResult(RESULT_CANCELED);
+ finish();
+ }
+ };
+
+ @Override
+ protected void refreshUiReal() {
+ invalidateOptionsMenu();
+ populateView();
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_trust_keys);
+ this.contactJids = new ArrayList<>();
+ for(String jid : getIntent().getStringArrayExtra("contacts")) {
+ try {
+ this.contactJids.add(Jid.fromString(jid));
+ } catch (InvalidJidException e) {
+ e.printStackTrace();
+ }
+ }
+
+ keyErrorMessageCard = (LinearLayout) findViewById(R.id.key_error_message_card);
+ keyErrorMessage = (TextView) findViewById(R.id.key_error_message);
+ ownKeysTitle = (TextView) findViewById(R.id.own_keys_title);
+ ownKeys = (LinearLayout) findViewById(R.id.own_keys_details);
+ ownKeysCard = (LinearLayout) findViewById(R.id.own_keys_card);
+ foreignKeys = (LinearLayout) findViewById(R.id.foreign_keys);
+ mCancelButton = (Button) findViewById(R.id.cancel_button);
+ mCancelButton.setOnClickListener(mCancelButtonListener);
+ mSaveButton = (Button) findViewById(R.id.save_button);
+ mSaveButton.setOnClickListener(mSaveButtonListener);
+
+
+ if (getActionBar() != null) {
+ getActionBar().setHomeButtonEnabled(true);
+ getActionBar().setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ private void populateView() {
+ setTitle(getString(R.string.trust_omemo_fingerprints));
+ ownKeys.removeAllViews();
+ foreignKeys.removeAllViews();
+ boolean hasOwnKeys = false;
+ boolean hasForeignKeys = false;
+ for(final String fingerprint : ownKeysToTrust.keySet()) {
+ hasOwnKeys = true;
+ addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false,
+ XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)), false,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ ownKeysToTrust.put(fingerprint, isChecked);
+ // own fingerprints have no impact on locked status.
+ }
+ },
+ null,
+ null
+ );
+ }
+
+ synchronized (this.foreignKeysToTrust) {
+ for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
+ hasForeignKeys = true;
+ final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
+ final Jid jid = entry.getKey();
+ final TextView header = (TextView) layout.findViewById(R.id.foreign_keys_title);
+ final LinearLayout keysContainer = (LinearLayout) layout.findViewById(R.id.foreign_keys_details);
+ final TextView informNoKeys = (TextView) layout.findViewById(R.id.no_keys_to_accept);
+ header.setText(jid.toString());
+ final Map<String, Boolean> fingerprints = entry.getValue();
+ for (final String fingerprint : fingerprints.keySet()) {
+ addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false,
+ XmppAxolotlSession.Trust.fromBoolean(fingerprints.get(fingerprint)), false,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ fingerprints.put(fingerprint, isChecked);
+ lockOrUnlockAsNeeded();
+ }
+ },
+ null,
+ null
+ );
+ }
+ if (fingerprints.size() == 0) {
+ informNoKeys.setVisibility(View.VISIBLE);
+ informNoKeys.setText(getString(R.string.no_keys_just_confirm,mAccount.getRoster().getContact(jid).getDisplayName()));
+ } else {
+ informNoKeys.setVisibility(View.GONE);
+ }
+ foreignKeys.addView(layout);
+ }
+ }
+
+ ownKeysTitle.setText(mAccount.getJid().toBareJid().toString());
+ ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE);
+ foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE);
+ if(hasPendingKeyFetches()) {
+ setFetching();
+ lock();
+ } else {
+ if (!hasForeignKeys && hasNoOtherTrustedKeys()) {
+ keyErrorMessageCard.setVisibility(View.VISIBLE);
+ if (lastFetchReport == AxolotlService.FetchStatus.ERROR
+ || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) {
+ 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();
+ foreignKeys.setVisibility(View.GONE);
+ }
+ lockOrUnlockAsNeeded();
+ setDone();
+ }
+ }
+
+ private boolean reloadFingerprints() {
+ List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
+ ownKeysToTrust.clear();
+ AxolotlService service = this.mAccount.getAxolotlService();
+ Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED);
+ for(final IdentityKey identityKey : ownKeysSet) {
+ if(!ownKeysToTrust.containsKey(identityKey)) {
+ ownKeysToTrust.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
+ }
+ }
+ synchronized (this.foreignKeysToTrust) {
+ foreignKeysToTrust.clear();
+ for (Jid jid : contactJids) {
+ Set<IdentityKey> foreignKeysSet = service.getKeysWithTrust(XmppAxolotlSession.Trust.UNDECIDED, jid);
+ if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) {
+ foreignKeysSet.addAll(service.getKeysWithTrust(XmppAxolotlSession.Trust.UNTRUSTED, jid));
+ }
+ Map<String, Boolean> foreignFingerprints = new HashMap<>();
+ for (final IdentityKey identityKey : foreignKeysSet) {
+ if (!foreignFingerprints.containsKey(identityKey)) {
+ foreignFingerprints.put(identityKey.getFingerprint().replaceAll("\\s", ""), false);
+ }
+ }
+ if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) {
+ foreignKeysToTrust.put(jid, foreignFingerprints);
+ }
+ }
+ }
+ return ownKeysSet.size() + foreignKeysToTrust.size() > 0;
+ }
+
+ @Override
+ public void onBackendConnected() {
+ Intent intent = getIntent();
+ this.mAccount = extractAccount(intent);
+ if (this.mAccount != null && intent != null) {
+ String uuid = intent.getStringExtra("conversation");
+ this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
+ reloadFingerprints();
+ populateView();
+ }
+ }
+
+ private boolean hasNoOtherTrustedKeys() {
+ return mAccount == null || mAccount.getAxolotlService().anyTargetHasNoTrustedKeys(contactJids);
+ }
+
+ private boolean hasNoOtherTrustedKeys(Jid contact) {
+ return mAccount == null || mAccount.getAxolotlService().getNumTrustedKeys(contact) == 0;
+ }
+
+ private boolean hasPendingKeyFetches() {
+ return mAccount != null && mAccount.getAxolotlService().hasPendingKeyFetches(mAccount, contactJids);
+ }
+
+
+ @Override
+ public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
+ if (report != null) {
+ lastFetchReport = report;
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ switch (report) {
+ case ERROR:
+ Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
+ break;
+ case SUCCESS_VERIFIED:
+ Toast.makeText(TrustKeysActivity.this,R.string.verified_omemo_key_with_certificate,Toast.LENGTH_LONG).show();
+ break;
+ }
+ }
+ });
+
+ }
+ boolean keysToTrust = reloadFingerprints();
+ if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
+ refreshUi();
+ } else {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ finishOk();
+ }
+ });
+
+ }
+ }
+
+ private void finishOk() {
+ Intent data = new Intent();
+ data.putExtra("choice", getIntent().getIntExtra("choice", ConversationActivity.ATTACHMENT_CHOICE_INVALID));
+ setResult(RESULT_OK, data);
+ finish();
+ }
+
+ private void commitTrusts() {
+ for(final String fingerprint :ownKeysToTrust.keySet()) {
+ mAccount.getAxolotlService().setFingerprintTrust(
+ fingerprint,
+ XmppAxolotlSession.Trust.fromBoolean(ownKeysToTrust.get(fingerprint)));
+ }
+ List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets();
+ synchronized (this.foreignKeysToTrust) {
+ for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
+ Jid jid = entry.getKey();
+ Map<String, Boolean> value = entry.getValue();
+ if (!acceptedTargets.contains(jid)) {
+ acceptedTargets.add(jid);
+ }
+ for (final String fingerprint : value.keySet()) {
+ mAccount.getAxolotlService().setFingerprintTrust(
+ fingerprint,
+ XmppAxolotlSession.Trust.fromBoolean(value.get(fingerprint)));
+ }
+ }
+ }
+ if (mConversation != null && mConversation.getMode() == Conversation.MODE_MULTI) {
+ mConversation.setAcceptedCryptoTargets(acceptedTargets);
+ xmppConnectionService.updateConversation(mConversation);
+ }
+ }
+
+ private void unlock() {
+ mSaveButton.setEnabled(true);
+ mSaveButton.setTextColor(getPrimaryTextColor());
+ }
+
+ private void lock() {
+ mSaveButton.setEnabled(false);
+ mSaveButton.setTextColor(getSecondaryTextColor());
+ }
+
+ private void lockOrUnlockAsNeeded() {
+ synchronized (this.foreignKeysToTrust) {
+ for (Jid jid : contactJids) {
+ Map<String, Boolean> fingerprints = foreignKeysToTrust.get(jid);
+ if (hasNoOtherTrustedKeys(jid) && (fingerprints == null || !fingerprints.values().contains(true))) {
+ lock();
+ return;
+ }
+ }
+ }
+ unlock();
+
+ }
+
+ private void setDone() {
+ mSaveButton.setText(getString(R.string.done));
+ }
+
+ private void setFetching() {
+ mSaveButton.setText(getString(R.string.fetching_keys));
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/UiCallback.java b/src/main/java/de/pixart/messenger/ui/UiCallback.java
new file mode 100644
index 000000000..07377c879
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/UiCallback.java
@@ -0,0 +1,11 @@
+package de.pixart.messenger.ui;
+
+import android.app.PendingIntent;
+
+public interface UiCallback<T> {
+ void success(T object);
+
+ void error(int errorCode, T object);
+
+ void userInputRequried(PendingIntent pi, T object);
+}
diff --git a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java
new file mode 100644
index 000000000..0a35288d7
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java
@@ -0,0 +1,336 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.support.v4.app.ActivityCompat;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.persistance.DatabaseBackend;
+import de.pixart.messenger.services.UpdaterWebService;
+
+public class UpdaterActivity extends Activity {
+
+ String appURI = "";
+ private UpdateReceiver receiver = null;
+ private int versionCode = 0;
+ private DownloadManager downloadManager;
+ private long downloadReference;
+ private String FileName = "update.apk";
+ //broadcast receiver to get notification about ongoing downloads
+
+ BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ //check if the broadcast message is for our Enqueued download
+ long referenceId = intent.getExtras().getLong(DownloadManager.EXTRA_DOWNLOAD_ID);
+ if (downloadReference == referenceId) {
+ File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS), FileName);
+ //start the installation of the latest version
+ Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE);
+ installIntent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive");
+ installIntent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true);
+ installIntent.putExtra(Intent.EXTRA_RETURN_RESULT, true);
+ installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(installIntent);
+
+ UpdaterActivity.this.finish();
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ //set activity
+ setContentView(R.layout.activity_updater);
+ TextView textView = (TextView) findViewById(R.id.updater);
+ textView.setText(R.string.update_info);
+
+ //Broadcast receiver for our Web Request
+ IntentFilter filter = new IntentFilter(UpdateReceiver.PROCESS_RESPONSE);
+ filter.addCategory(Intent.CATEGORY_DEFAULT);
+ receiver = new UpdateReceiver();
+ registerReceiver(receiver, filter);
+
+ //Broadcast receiver for the download manager (download complete)
+ registerReceiver(downloadReceiver, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
+
+ //check of internet is available before making a web service request
+ if (isNetworkAvailable(this)) {
+ Intent msgIntent = new Intent(this, UpdaterWebService.class);
+ msgIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ msgIntent.putExtra(UpdaterWebService.REQUEST_STRING, Config.UPDATE_URL);
+
+ Toast.makeText(getApplicationContext(),
+ getText(R.string.checking_for_updates),
+ Toast.LENGTH_SHORT).show();
+ startService(msgIntent);
+ }
+ }
+
+ private void ExportDatabase() throws IOException {
+
+ // Get hold of the db:
+ InputStream myInput = new FileInputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
+
+ // Set the output folder on the SDcard
+ File directory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/.Database/");
+
+ // Create the folder if it doesn't exist:
+ if (!directory.exists()) {
+ directory.mkdirs();
+ }
+
+ // Set the output file stream up:
+ OutputStream myOutput = new FileOutputStream(directory.getPath() + "/Database.bak");
+
+ // Transfer bytes from the input file to the output file
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = myInput.read(buffer)) > 0) {
+ myOutput.write(buffer, 0, length);
+ }
+
+ // Close and clear the streams
+ myOutput.flush();
+ myOutput.close();
+ myInput.close();
+ }
+
+
+ @Override
+ public void onDestroy() {
+ //unregister your receivers
+ this.unregisterReceiver(receiver);
+ this.unregisterReceiver(downloadReceiver);
+ super.onDestroy();
+ //enable touch events
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle savedInstanceState) {
+ super.onSaveInstanceState(savedInstanceState);
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle savedInstanceState) {
+ super.onRestoreInstanceState(savedInstanceState);
+ }
+
+ //check for internet connection
+ private boolean isNetworkAvailable(Context context) {
+ ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ if (connectivity != null) {
+ NetworkInfo[] info = connectivity.getAllNetworkInfo();
+ if (info != null) {
+ for (int i = 0; i < info.length; i++) {
+ if (info[i].getState() == NetworkInfo.State.CONNECTED) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ public boolean isStoragePermissionGranted() {
+ if (Build.VERSION.SDK_INT >= 23) {
+ if (checkSelfPermission(android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ } else {
+
+ ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1);
+ return false;
+ }
+ } else { //permission is automatically granted on sdk<23 upon installation
+ return true;
+ }
+ }
+
+ //show warning on back pressed
+ @Override
+ public void onBackPressed() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.cancel_update)
+ .setCancelable(false)
+ .setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ UpdaterActivity.this.finish();
+ }
+ })
+ .setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ //broadcast receiver to get notification when the web request finishes
+ public class UpdateReceiver extends BroadcastReceiver {
+
+ public static final String PROCESS_RESPONSE = "de.pixart.messenger.intent.action.PROCESS_RESPONSE";
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+
+ //disable touch events
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
+ String responseMessage = intent.getStringExtra(UpdaterWebService.RESPONSE_MESSAGE);
+
+ if (responseMessage == "" || responseMessage.isEmpty() || responseMessage == null) {
+ Toast.makeText(getApplicationContext(),
+ getText(R.string.failed),
+ Toast.LENGTH_LONG).show();
+ UpdaterActivity.this.finish();
+ } else {
+ //parse the JSON reponse
+ JSONObject reponseObj;
+
+ try {
+ //if the response was successful check further
+ reponseObj = new JSONObject(responseMessage);
+ boolean success = reponseObj.getBoolean("success");
+ if (success) {
+ if (isStoragePermissionGranted()) {
+ //start backing up database
+ try {
+ ExportDatabase();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ //Overall information about the contents of a package
+ //This corresponds to all of the information collected from AndroidManifest.xml.
+ PackageInfo pInfo = null;
+ try {
+ pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+ } catch (PackageManager.NameNotFoundException e) {
+ e.printStackTrace();
+ }
+ //get the app version Name for display
+ final String versionName = pInfo.versionName;
+ final int versionCode = pInfo.versionCode;
+ //get the latest version from the JSON string
+ int latestVersionCode = reponseObj.getInt("latestVersionCode");
+ String latestVersion = reponseObj.getString("latestVersion");
+ String filesize = reponseObj.getString("filesize");
+ String changelog = reponseObj.getString("changelog");
+ //get the lastest application URI from the JSON string
+ appURI = reponseObj.getString("appURI");
+ //check if we need to upgrade?
+ if (latestVersionCode > versionCode) {
+ //delete old downloaded version files
+ File dir = new File(getExternalFilesDir(null), Environment.DIRECTORY_DOWNLOADS);
+ if (dir.isDirectory()) {
+ String[] children = dir.list();
+ for (int i = 0; i < children.length; i++) {
+ new File(dir, children[i]).delete();
+ }
+ }
+ //enable touch events
+ getWindow().clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+
+ //oh yeah we do need an upgrade, let the user know send an alert message
+ AlertDialog.Builder builder = new AlertDialog.Builder(UpdaterActivity.this);
+ builder.setCancelable(false);
+
+ String UpdateMessageInfo = getResources().getString(R.string.update_available);
+ builder.setMessage(String.format(UpdateMessageInfo, latestVersion, filesize, versionName, changelog))
+ .setPositiveButton(R.string.update, new DialogInterface.OnClickListener() {
+ //if the user agrees to upgrade
+ public void onClick(DialogInterface dialog, int id) {
+ //disable touch events
+ getWindow().setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
+ WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE);
+ //ask for permissions on devices >= SDK 23
+ if (isStoragePermissionGranted()) {
+ //start downloading the file using the download manager
+ downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+ Uri Download_Uri = Uri.parse(appURI);
+ DownloadManager.Request request = new DownloadManager.Request(Download_Uri);
+ //request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_WIFI);
+ //request.setAllowedOverRoaming(false);
+ request.setTitle("Pix-Art Messenger Update");
+ request.setDestinationInExternalFilesDir(UpdaterActivity.this, Environment.DIRECTORY_DOWNLOADS, FileName);
+ downloadReference = downloadManager.enqueue(request);
+ Toast.makeText(getApplicationContext(),
+ getText(R.string.download_started),
+ Toast.LENGTH_LONG).show();
+ }
+ }
+ })
+ .setNeutralButton(R.string.changelog, new DialogInterface.OnClickListener() {
+ //open link to changelog
+ public void onClick(DialogInterface dialog, int id) {
+ Uri uri = Uri.parse("https://github.com/kriztan/Conversations/blob/development/CHANGELOG.md"); // missing 'http://' will cause crashed
+ Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ //restart updater to show dialog again after coming back after opening changelog
+ recreate();
+ }
+ })
+ .setNegativeButton(R.string.remind_later, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int id) {
+ // User cancelled the dialog
+ UpdaterActivity.this.finish();
+ }
+ });
+ //show the alert message
+ builder.create().show();
+ } else {
+ Toast.makeText(getApplicationContext(),
+ getText(R.string.no_update_available),
+ Toast.LENGTH_SHORT).show();
+ UpdaterActivity.this.finish();
+ }
+ } else {
+ Toast.makeText(getApplicationContext(),
+ getText(R.string.failed),
+ Toast.LENGTH_LONG).show();
+ UpdaterActivity.this.finish();
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java
new file mode 100644
index 000000000..9ef4ca171
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java
@@ -0,0 +1,445 @@
+package de.pixart.messenger.ui;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
+import net.java.otr4j.OtrException;
+import net.java.otr4j.session.Session;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.utils.XmppUri;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class VerifyOTRActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
+
+ public static final String ACTION_VERIFY_CONTACT = "verify_contact";
+ public static final int MODE_SCAN_FINGERPRINT = - 0x0502;
+ public static final int MODE_ASK_QUESTION = 0x0503;
+ public static final int MODE_ANSWER_QUESTION = 0x0504;
+ public static final int MODE_MANUAL_VERIFICATION = 0x0505;
+
+ private LinearLayout mManualVerificationArea;
+ private LinearLayout mSmpVerificationArea;
+ private TextView mRemoteFingerprint;
+ private TextView mYourFingerprint;
+ private TextView mVerificationExplain;
+ private TextView mStatusMessage;
+ private TextView mSharedSecretHint;
+ private EditText mSharedSecretHintEditable;
+ private EditText mSharedSecretSecret;
+ private Button mLeftButton;
+ private Button mRightButton;
+ private Account mAccount;
+ private Conversation mConversation;
+ private int mode = MODE_MANUAL_VERIFICATION;
+ private XmppUri mPendingUri = null;
+
+ private DialogInterface.OnClickListener mVerifyFingerprintListener = new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialogInterface, int click) {
+ mConversation.verifyOtrFingerprint();
+ xmppConnectionService.syncRosterToDisk(mConversation.getAccount());
+ Toast.makeText(VerifyOTRActivity.this,R.string.verified,Toast.LENGTH_SHORT).show();
+ finish();
+ }
+ };
+
+ private View.OnClickListener mCreateSharedSecretListener = new View.OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ if (isAccountOnline()) {
+ final String question = mSharedSecretHintEditable.getText().toString();
+ final String secret = mSharedSecretSecret.getText().toString();
+ if (question.trim().isEmpty()) {
+ mSharedSecretHintEditable.requestFocus();
+ mSharedSecretHintEditable.setError(getString(R.string.shared_secret_hint_should_not_be_empty));
+ } else if (secret.trim().isEmpty()) {
+ mSharedSecretSecret.requestFocus();
+ mSharedSecretSecret.setError(getString(R.string.shared_secret_can_not_be_empty));
+ } else {
+ mSharedSecretSecret.setError(null);
+ mSharedSecretHintEditable.setError(null);
+ initSmp(question, secret);
+ updateView();
+ }
+ }
+ }
+ };
+ private View.OnClickListener mCancelSharedSecretListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (isAccountOnline()) {
+ abortSmp();
+ updateView();
+ }
+ }
+ };
+ private View.OnClickListener mRespondSharedSecretListener = new View.OnClickListener() {
+
+ @Override
+ public void onClick(View view) {
+ if (isAccountOnline()) {
+ final String question = mSharedSecretHintEditable.getText().toString();
+ final String secret = mSharedSecretSecret.getText().toString();
+ respondSmp(question, secret);
+ updateView();
+ }
+ }
+ };
+ private View.OnClickListener mRetrySharedSecretListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+ mConversation.smp().hint = null;
+ mConversation.smp().secret = null;
+ updateView();
+ }
+ };
+ private View.OnClickListener mFinishListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+ finish();
+ }
+ };
+
+ protected boolean initSmp(final String question, final String secret) {
+ final Session session = mConversation.getOtrSession();
+ if (session!=null) {
+ try {
+ session.initSmp(question, secret);
+ mConversation.smp().status = Conversation.Smp.STATUS_WE_REQUESTED;
+ mConversation.smp().secret = secret;
+ mConversation.smp().hint = question;
+ return true;
+ } catch (OtrException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ protected boolean abortSmp() {
+ final Session session = mConversation.getOtrSession();
+ if (session!=null) {
+ try {
+ session.abortSmp();
+ mConversation.smp().status = Conversation.Smp.STATUS_NONE;
+ mConversation.smp().hint = null;
+ mConversation.smp().secret = null;
+ return true;
+ } catch (OtrException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ protected boolean respondSmp(final String question, final String secret) {
+ final Session session = mConversation.getOtrSession();
+ if (session!=null) {
+ try {
+ session.respondSmp(question,secret);
+ return true;
+ } catch (OtrException e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ protected boolean verifyWithUri(XmppUri uri) {
+ Contact contact = mConversation.getContact();
+ if (this.mConversation.getContact().getJid().equals(uri.getJid()) && uri.getFingerprint() != null) {
+ contact.addOtrFingerprint(uri.getFingerprint());
+ Toast.makeText(this,R.string.verified,Toast.LENGTH_SHORT).show();
+ updateView();
+ xmppConnectionService.syncRosterToDisk(contact.getAccount());
+ return true;
+ } else {
+ Toast.makeText(this,R.string.could_not_verify_fingerprint,Toast.LENGTH_SHORT).show();
+ return false;
+ }
+ }
+
+ protected boolean isAccountOnline() {
+ if (this.mAccount.getStatus() != Account.State.ONLINE) {
+ Toast.makeText(this,R.string.not_connected_try_again,Toast.LENGTH_SHORT).show();
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ protected boolean handleIntent(Intent intent) {
+ if (intent != null && intent.getAction().equals(ACTION_VERIFY_CONTACT)) {
+ this.mAccount = extractAccount(intent);
+ if (this.mAccount == null) {
+ return false;
+ }
+ try {
+ this.mConversation = this.xmppConnectionService.find(this.mAccount,Jid.fromString(intent.getExtras().getString("contact")));
+ if (this.mConversation == null) {
+ return false;
+ }
+ } catch (final InvalidJidException ignored) {
+ return false;
+ }
+ this.mode = intent.getIntExtra("mode", MODE_MANUAL_VERIFICATION);
+ if (this.mode == MODE_SCAN_FINGERPRINT) {
+ new IntentIntegrator(this).initiateScan();
+ return false;
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ if ((requestCode & 0xFFFF) == IntentIntegrator.REQUEST_CODE) {
+ IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ if (scanResult != null && scanResult.getFormatName() != null) {
+ String data = scanResult.getContents();
+ XmppUri uri = new XmppUri(data);
+ if (xmppConnectionServiceBound) {
+ verifyWithUri(uri);
+ finish();
+ } else {
+ this.mPendingUri = uri;
+ }
+ } else {
+ finish();
+ }
+ }
+ super.onActivityResult(requestCode, requestCode, intent);
+ }
+
+ @Override
+ protected void onBackendConnected() {
+ if (handleIntent(getIntent())) {
+ updateView();
+ } else if (mPendingUri!=null) {
+ verifyWithUri(mPendingUri);
+ finish();
+ mPendingUri = null;
+ }
+ setIntent(null);
+ }
+
+ protected void updateView() {
+ if (this.mConversation != null && this.mConversation.hasValidOtrSession()) {
+ final ActionBar actionBar = getActionBar();
+ this.mVerificationExplain.setText(R.string.no_otr_session_found);
+ invalidateOptionsMenu();
+ switch(this.mode) {
+ case MODE_ASK_QUESTION:
+ if (actionBar != null ) {
+ actionBar.setTitle(R.string.ask_question);
+ }
+ this.updateViewAskQuestion();
+ break;
+ case MODE_ANSWER_QUESTION:
+ if (actionBar != null ) {
+ actionBar.setTitle(R.string.smp_requested);
+ }
+ this.updateViewAnswerQuestion();
+ break;
+ case MODE_MANUAL_VERIFICATION:
+ default:
+ if (actionBar != null ) {
+ actionBar.setTitle(R.string.manually_verify);
+ }
+ this.updateViewManualVerification();
+ break;
+ }
+ } else {
+ this.mManualVerificationArea.setVisibility(View.GONE);
+ this.mSmpVerificationArea.setVisibility(View.GONE);
+ }
+ }
+
+ protected void updateViewManualVerification() {
+ this.mVerificationExplain.setText(R.string.manual_verification_explanation);
+ this.mManualVerificationArea.setVisibility(View.VISIBLE);
+ this.mSmpVerificationArea.setVisibility(View.GONE);
+ this.mYourFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mAccount.getOtrFingerprint()));
+ this.mRemoteFingerprint.setText(CryptoHelper.prettifyFingerprint(this.mConversation.getOtrFingerprint()));
+ if (this.mConversation.isOtrFingerprintVerified()) {
+ deactivateButton(this.mRightButton,R.string.verified);
+ activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
+ } else {
+ activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
+ activateButton(this.mRightButton,R.string.verify, new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ showManuallyVerifyDialog();
+ }
+ });
+ }
+ }
+
+ protected void updateViewAskQuestion() {
+ this.mManualVerificationArea.setVisibility(View.GONE);
+ this.mSmpVerificationArea.setVisibility(View.VISIBLE);
+ this.mVerificationExplain.setText(R.string.smp_explain_question);
+ final int smpStatus = this.mConversation.smp().status;
+ switch (smpStatus) {
+ case Conversation.Smp.STATUS_WE_REQUESTED:
+ this.mStatusMessage.setVisibility(View.GONE);
+ this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
+ this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+ this.mSharedSecretHintEditable.setText(this.mConversation.smp().hint);
+ this.mSharedSecretSecret.setText(this.mConversation.smp().secret);
+ this.activateButton(this.mLeftButton, R.string.cancel, this.mCancelSharedSecretListener);
+ this.deactivateButton(this.mRightButton, R.string.in_progress);
+ break;
+ case Conversation.Smp.STATUS_FAILED:
+ this.mStatusMessage.setVisibility(View.GONE);
+ this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
+ this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+ this.mSharedSecretSecret.requestFocus();
+ this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
+ this.deactivateButton(this.mLeftButton, R.string.cancel);
+ this.activateButton(this.mRightButton, R.string.try_again, this.mRetrySharedSecretListener);
+ break;
+ case Conversation.Smp.STATUS_VERIFIED:
+ this.mSharedSecretHintEditable.setText("");
+ this.mSharedSecretHintEditable.setVisibility(View.GONE);
+ this.mSharedSecretSecret.setText("");
+ this.mSharedSecretSecret.setVisibility(View.GONE);
+ this.mStatusMessage.setVisibility(View.VISIBLE);
+ this.deactivateButton(this.mLeftButton, R.string.cancel);
+ this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
+ break;
+ default:
+ this.mStatusMessage.setVisibility(View.GONE);
+ this.mSharedSecretHintEditable.setVisibility(View.VISIBLE);
+ this.mSharedSecretSecret.setVisibility(View.VISIBLE);
+ this.activateButton(this.mLeftButton,R.string.cancel,this.mFinishListener);
+ this.activateButton(this.mRightButton, R.string.ask_question, this.mCreateSharedSecretListener);
+ break;
+ }
+ }
+
+ protected void updateViewAnswerQuestion() {
+ this.mManualVerificationArea.setVisibility(View.GONE);
+ this.mSmpVerificationArea.setVisibility(View.VISIBLE);
+ this.mVerificationExplain.setText(R.string.smp_explain_answer);
+ this.mSharedSecretHintEditable.setVisibility(View.GONE);
+ this.mSharedSecretHint.setVisibility(View.VISIBLE);
+ this.deactivateButton(this.mLeftButton, R.string.cancel);
+ final int smpStatus = this.mConversation.smp().status;
+ switch (smpStatus) {
+ case Conversation.Smp.STATUS_CONTACT_REQUESTED:
+ this.mStatusMessage.setVisibility(View.GONE);
+ this.mSharedSecretHint.setText(this.mConversation.smp().hint);
+ this.activateButton(this.mRightButton,R.string.respond,this.mRespondSharedSecretListener);
+ break;
+ case Conversation.Smp.STATUS_VERIFIED:
+ this.mSharedSecretHintEditable.setText("");
+ this.mSharedSecretHintEditable.setVisibility(View.GONE);
+ this.mSharedSecretHint.setVisibility(View.GONE);
+ this.mSharedSecretSecret.setText("");
+ this.mSharedSecretSecret.setVisibility(View.GONE);
+ this.mStatusMessage.setVisibility(View.VISIBLE);
+ this.activateButton(this.mRightButton, R.string.finish, this.mFinishListener);
+ break;
+ case Conversation.Smp.STATUS_FAILED:
+ default:
+ this.mSharedSecretSecret.requestFocus();
+ this.mSharedSecretSecret.setError(getString(R.string.secrets_do_not_match));
+ this.activateButton(this.mRightButton,R.string.finish,this.mFinishListener);
+ break;
+ }
+ }
+
+ protected void activateButton(Button button, int text, View.OnClickListener listener) {
+ button.setEnabled(true);
+ button.setTextColor(getPrimaryTextColor());
+ button.setText(text);
+ button.setOnClickListener(listener);
+ }
+
+ protected void deactivateButton(Button button, int text) {
+ button.setEnabled(false);
+ button.setTextColor(getSecondaryTextColor());
+ button.setText(text);
+ button.setOnClickListener(null);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_verify_otr);
+ this.mRemoteFingerprint = (TextView) findViewById(R.id.remote_fingerprint);
+ this.mYourFingerprint = (TextView) findViewById(R.id.your_fingerprint);
+ this.mLeftButton = (Button) findViewById(R.id.left_button);
+ this.mRightButton = (Button) findViewById(R.id.right_button);
+ this.mVerificationExplain = (TextView) findViewById(R.id.verification_explanation);
+ this.mStatusMessage = (TextView) findViewById(R.id.status_message);
+ this.mSharedSecretSecret = (EditText) findViewById(R.id.shared_secret_secret);
+ this.mSharedSecretHintEditable = (EditText) findViewById(R.id.shared_secret_hint_editable);
+ this.mSharedSecretHint = (TextView) findViewById(R.id.shared_secret_hint);
+ this.mManualVerificationArea = (LinearLayout) findViewById(R.id.manual_verification_area);
+ this.mSmpVerificationArea = (LinearLayout) findViewById(R.id.smp_verification_area);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(final Menu menu) {
+ super.onCreateOptionsMenu(menu);
+ getMenuInflater().inflate(R.menu.verify_otr, menu);
+ return true;
+ }
+
+ private void showManuallyVerifyDialog() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(R.string.manually_verify);
+ builder.setMessage(R.string.are_you_sure_verify_fingerprint);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.verify, mVerifyFingerprintListener);
+ builder.create().show();
+ }
+
+ @Override
+ protected String getShareableUri() {
+ if (mAccount!=null) {
+ return mAccount.getShareableUri();
+ } else {
+ return "";
+ }
+ }
+
+ public void onConversationUpdate() {
+ refreshUi();
+ }
+
+ @Override
+ protected void refreshUiReal() {
+ updateView();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
new file mode 100644
index 000000000..c92bb8cd4
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java
@@ -0,0 +1,208 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.persistance.DatabaseBackend;
+
+public class WelcomeActivity extends Activity {
+
+ private static final int REQUEST_READ_EXTERNAL_STORAGE = 0XD737;
+ boolean dbExist = false;
+ boolean backup_existing = false;
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.welcome);
+
+ //check if there is a backed up database --
+ if (hasStoragePermission(REQUEST_READ_EXTERNAL_STORAGE)) {
+ dbExist = checkDatabase();
+ }
+
+ if (dbExist) {
+ backup_existing = true;
+ }
+
+
+ final Button ImportDatabase = (Button) findViewById(R.id.import_database);
+ final TextView ImportText = (TextView) findViewById(R.id.import_text);
+
+ if (backup_existing) {
+ ImportDatabase.setVisibility(View.VISIBLE);
+ ImportText.setVisibility(View.VISIBLE);
+ }
+
+ ImportDatabase.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ try {
+ ImportDatabase();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ });
+
+ final Button createAccount = (Button) findViewById(R.id.create_account);
+ createAccount.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(WelcomeActivity.this, MagicCreateActivity.class);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
+ startActivity(intent);
+ }
+ });
+ final Button useOwnProvider = (Button) findViewById(R.id.use_existing_account);
+ useOwnProvider.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ startActivity(new Intent(WelcomeActivity.this, EditAccountActivity.class));
+ }
+ });
+
+ }
+
+ private boolean checkDatabase() {
+
+ SQLiteDatabase checkDB = null;
+ String DB_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/.Database/";
+ String DB_NAME = "Database.bak";
+ int DB_Version = DatabaseBackend.DATABASE_VERSION;
+ int Backup_DB_Version = 0;
+
+ try {
+ String dbPath = DB_PATH + DB_NAME;
+ checkDB = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READONLY);
+ Backup_DB_Version = checkDB.getVersion();
+ Log.d(Config.LOGTAG, "Backup found: " + checkDB + " Version: " + checkDB.getVersion());
+
+ } catch (SQLiteException e) {
+ //database does't exist yet.
+ Log.d(Config.LOGTAG, "No backup found: " + checkDB);
+ }
+
+ if (checkDB != null) {
+ checkDB.close();
+ }
+ if (checkDB != null && Backup_DB_Version <= DB_Version) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private void ImportDatabase() throws IOException {
+
+ // Set location for the db:
+ OutputStream myOutput = new FileOutputStream(this.getDatabasePath(DatabaseBackend.DATABASE_NAME));
+
+ // Set the folder on the SDcard
+ File directory = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pix-Art Messenger/.Database/");
+
+ // Set the input file stream up:
+ InputStream myInput = new FileInputStream(directory.getPath() + "/Database.bak");
+
+ // Transfer bytes from the input file to the output file
+ byte[] buffer = new byte[1024];
+ int length;
+ while ((length = myInput.read(buffer)) > 0) {
+ myOutput.write(buffer, 0, length);
+ }
+ Log.d(Config.LOGTAG,"Starting import of backup");
+
+ // Close and clear the streams
+ myOutput.flush();
+ myOutput.close();
+ myInput.close();
+
+ Log.d(Config.LOGTAG, "New Features - Uninstall old version of Pix-Art Messenger");
+ if (isPackageInstalled("eu.siacs.conversations")) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(R.string.uninstall_app_text)
+ .setPositiveButton(R.string.uninstall, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialogInterface, int i) {
+ //start the deinstallation of old version
+ if (isPackageInstalled("eu.siacs.conversations")) {
+ Uri packageURI_VR = Uri.parse("package:eu.siacs.conversations");
+ Intent uninstallIntent_VR = new Intent(Intent.ACTION_UNINSTALL_PACKAGE, packageURI_VR);
+ if (uninstallIntent_VR.resolveActivity(getPackageManager()) != null) {
+ startActivity(uninstallIntent_VR);
+ }
+ }
+ }
+ })
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialogInterface, int i) {
+ Log.d(Config.LOGTAG, "New Features - Uninstall cancled");
+ restart();
+ }
+ });
+ builder.create().show();
+ } else {
+ restart();
+ }
+
+ }
+
+ private void restart() {
+ //restart app
+ Log.d(Config.LOGTAG, "Restarting " + getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName()));
+ Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
+ intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ System.exit(0);
+ }
+
+ private boolean isPackageInstalled(String targetPackage) {
+ List<ApplicationInfo> packages;
+ PackageManager pm;
+ pm = getPackageManager();
+ packages = pm.getInstalledApplications(0);
+ for (ApplicationInfo packageInfo : packages) {
+ if (packageInfo.packageName.equals(targetPackage)) return true;
+ }
+ return false;
+ }
+
+ public boolean hasStoragePermission(int requestCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, requestCode);
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
new file mode 100644
index 000000000..8f6192d04
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java
@@ -0,0 +1,1389 @@
+package de.pixart.messenger.ui;
+
+import android.Manifest;
+import android.annotation.SuppressLint;
+import android.annotation.TargetApi;
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.AlertDialog.Builder;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
+import android.content.ClipData;
+import android.content.ClipboardManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.IntentSender.SendIntentException;
+import android.content.ServiceConnection;
+import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.nfc.NdefMessage;
+import android.nfc.NdefRecord;
+import android.nfc.NfcAdapter;
+import android.nfc.NfcEvent;
+import android.os.AsyncTask;
+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;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.CompoundButton;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.WriterException;
+import com.google.zxing.common.BitMatrix;
+import com.google.zxing.qrcode.QRCodeWriter;
+import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
+
+import net.java.otr4j.session.SessionID;
+
+import java.io.FileNotFoundException;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Contact;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.entities.MucOptions;
+import de.pixart.messenger.entities.Presences;
+import de.pixart.messenger.services.AvatarService;
+import de.pixart.messenger.services.XmppConnectionService;
+import de.pixart.messenger.services.XmppConnectionService.XmppConnectionBinder;
+import de.pixart.messenger.ui.widget.Switch;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.utils.ExceptionHelper;
+import de.pixart.messenger.xmpp.OnKeyStatusUpdated;
+import de.pixart.messenger.xmpp.OnUpdateBlocklist;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+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 static final String EXTRA_ACCOUNT = "account";
+
+ public XmppConnectionService xmppConnectionService;
+ public boolean xmppConnectionServiceBound = false;
+ protected boolean registeredListeners = false;
+
+ protected int mPrimaryTextColor;
+ protected int mSecondaryTextColor;
+ protected int mTertiaryTextColor;
+ protected int mPrimaryBackgroundColor;
+ protected int mSecondaryBackgroundColor;
+ protected int mColorRed;
+ protected int mColorOrange;
+ protected int mColorGreen;
+ protected int mPrimaryColor;
+
+ protected boolean mUseSubject = true;
+
+ private DisplayMetrics metrics;
+ protected int mTheme;
+ protected boolean mUsingEnterKey = false;
+
+ protected Toast mToast;
+
+ protected void hideToast() {
+ if (mToast != null) {
+ mToast.cancel();
+ }
+ }
+
+ protected void replaceToast(String msg) {
+ replaceToast(msg, true);
+ }
+
+ protected void replaceToast(String msg, boolean showlong) {
+ hideToast();
+ mToast = Toast.makeText(this, msg ,showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT);
+ mToast.show();
+ }
+
+ protected Runnable onOpenPGPKeyPublished = new Runnable() {
+ @Override
+ public void run() {
+ Toast.makeText(XmppActivity.this,R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show();
+ }
+ };
+
+ private long mLastUiRefresh = 0;
+ private Handler mRefreshUiHandler = new Handler();
+ private Runnable mRefreshUiRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mLastUiRefresh = SystemClock.elapsedRealtime();
+ refreshUiReal();
+ }
+ };
+
+ protected ConferenceInvite mPendingConferenceInvite = null;
+
+
+ protected final void refreshUi() {
+ final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh;
+ if (diff > Config.REFRESH_UI_INTERVAL) {
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ runOnUiThread(mRefreshUiRunnable);
+ } else {
+ final long next = Config.REFRESH_UI_INTERVAL - diff;
+ mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable);
+ mRefreshUiHandler.postDelayed(mRefreshUiRunnable,next);
+ }
+ }
+
+ abstract protected void refreshUiReal();
+
+ protected interface OnValueEdited {
+ public void onValueEdited(String value);
+ }
+
+ public interface OnPresenceSelected {
+ public void onPresenceSelected();
+ }
+
+ protected ServiceConnection mConnection = new ServiceConnection() {
+
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ XmppConnectionBinder binder = (XmppConnectionBinder) service;
+ xmppConnectionService = binder.getService();
+ xmppConnectionServiceBound = true;
+ if (!registeredListeners && shouldRegisterListeners()) {
+ registerListeners();
+ registeredListeners = true;
+ }
+ onBackendConnected();
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName arg0) {
+ xmppConnectionServiceBound = false;
+ }
+ };
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ if (!xmppConnectionServiceBound) {
+ connectToBackend();
+ } else {
+ if (!registeredListeners) {
+ this.registerListeners();
+ this.registeredListeners = true;
+ }
+ this.onBackendConnected();
+ }
+ }
+
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
+ protected boolean shouldRegisterListeners() {
+ if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+ return !isDestroyed() && !isFinishing();
+ } else {
+ return !isFinishing();
+ }
+ }
+
+ public void connectToBackend() {
+ Intent intent = new Intent(this, XmppConnectionService.class);
+ intent.setAction("ui");
+ startService(intent);
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (xmppConnectionServiceBound) {
+ if (registeredListeners) {
+ this.unregisterListeners();
+ this.registeredListeners = false;
+ }
+ unbindService(mConnection);
+ xmppConnectionServiceBound = false;
+ }
+ }
+
+ protected void hideKeyboard() {
+ InputMethodManager inputManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
+
+ View focus = getCurrentFocus();
+
+ if (focus != null) {
+
+ inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
+ InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ }
+
+ public boolean hasPgp() {
+ return xmppConnectionService.getPgpEngine() != null;
+ }
+
+ public void showInstallPgpDialog() {
+ Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.openkeychain_required));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getText(R.string.openkeychain_required_long));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setNeutralButton(getString(R.string.restart),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (xmppConnectionServiceBound) {
+ unbindService(mConnection);
+ xmppConnectionServiceBound = false;
+ }
+ stopService(new Intent(XmppActivity.this,
+ XmppConnectionService.class));
+ finish();
+ }
+ });
+ builder.setPositiveButton(getString(R.string.install),
+ new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ Uri uri = Uri
+ .parse("market://details?id=org.sufficientlysecure.keychain");
+ Intent marketIntent = new Intent(Intent.ACTION_VIEW,
+ uri);
+ PackageManager manager = getApplicationContext()
+ .getPackageManager();
+ List<ResolveInfo> infos = manager
+ .queryIntentActivities(marketIntent, 0);
+ if (infos.size() > 0) {
+ startActivity(marketIntent);
+ } else {
+ uri = Uri.parse("http://www.openkeychain.org/");
+ Intent browserIntent = new Intent(
+ Intent.ACTION_VIEW, uri);
+ startActivity(browserIntent);
+ }
+ finish();
+ }
+ });
+ builder.create().show();
+ }
+
+ abstract void onBackendConnected();
+
+ protected void registerListeners() {
+ if (this instanceof XmppConnectionService.OnConversationUpdate) {
+ this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this);
+ }
+ if (this instanceof XmppConnectionService.OnAccountUpdate) {
+ this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this);
+ }
+ if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+ this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this);
+ }
+ if (this instanceof XmppConnectionService.OnRosterUpdate) {
+ this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this);
+ }
+ if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
+ this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this);
+ }
+ if (this instanceof OnUpdateBlocklist) {
+ this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this);
+ }
+ if (this instanceof XmppConnectionService.OnShowErrorToast) {
+ this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this);
+ }
+ if (this instanceof OnKeyStatusUpdated) {
+ this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this);
+ }
+ }
+
+ protected void unregisterListeners() {
+ if (this instanceof XmppConnectionService.OnConversationUpdate) {
+ this.xmppConnectionService.removeOnConversationListChangedListener();
+ }
+ if (this instanceof XmppConnectionService.OnAccountUpdate) {
+ this.xmppConnectionService.removeOnAccountListChangedListener();
+ }
+ if (this instanceof XmppConnectionService.OnCaptchaRequested) {
+ this.xmppConnectionService.removeOnCaptchaRequestedListener();
+ }
+ if (this instanceof XmppConnectionService.OnRosterUpdate) {
+ this.xmppConnectionService.removeOnRosterUpdateListener();
+ }
+ if (this instanceof XmppConnectionService.OnMucRosterUpdate) {
+ this.xmppConnectionService.removeOnMucRosterUpdateListener();
+ }
+ if (this instanceof OnUpdateBlocklist) {
+ this.xmppConnectionService.removeOnUpdateBlocklistListener();
+ }
+ if (this instanceof XmppConnectionService.OnShowErrorToast) {
+ this.xmppConnectionService.removeOnShowErrorToastListener();
+ }
+ if (this instanceof OnKeyStatusUpdated) {
+ this.xmppConnectionService.removeOnNewKeysAvailableListener();
+ }
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(final MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_invite_user:
+ inviteUser();
+ break;
+ case R.id.action_settings:
+ startActivity(new Intent(this, SettingsActivity.class));
+ break;
+ case R.id.action_check_updates:
+ startActivity(new Intent(this, UpdaterActivity.class));
+ break;
+ case R.id.action_accounts:
+ final Intent intent = new Intent(getApplicationContext(), EditAccountActivity.class);
+ Account mAccount = xmppConnectionService.getAccounts().get(0);
+ intent.putExtra("jid", mAccount.getJid().toBareJid().toString());
+ intent.putExtra("init", false);
+ startActivity(intent);
+ break;
+ case android.R.id.home:
+ finish();
+ break;
+ case R.id.action_show_qr_code:
+ showQrCode();
+ break;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ metrics = getResources().getDisplayMetrics();
+ ExceptionHelper.init(getApplicationContext());
+ mPrimaryTextColor = getResources().getColor(R.color.black87);
+ mSecondaryTextColor = getResources().getColor(R.color.black54);
+ mTertiaryTextColor = getResources().getColor(R.color.black12);
+ mColorRed = getResources().getColor(R.color.red800);
+ mColorOrange = getResources().getColor(R.color.orange500);
+ mColorGreen = getResources().getColor(R.color.realgreen);
+ mPrimaryColor = getResources().getColor(R.color.primary);
+ mPrimaryBackgroundColor = getResources().getColor(R.color.grey50);
+ mSecondaryBackgroundColor = getResources().getColor(R.color.grey200);
+ this.mTheme = findTheme();
+ setTheme(this.mTheme);
+ this.mUsingEnterKey = usingEnterKey();
+ mUseSubject = getPreferences().getBoolean("use_subject", true);
+ final ActionBar ab = getActionBar();
+ if (ab!=null) {
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
+ }
+
+ protected boolean isOptimizingBattery() {
+ 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);
+ }
+
+ protected SharedPreferences getPreferences() {
+ return PreferenceManager
+ .getDefaultSharedPreferences(getApplicationContext());
+ }
+
+ public boolean useSubjectToIdentifyConference() {
+ return mUseSubject;
+ }
+
+ public void switchToConversation(Conversation conversation) {
+ switchToConversation(conversation, null, false);
+ }
+
+ public void switchToConversation(Conversation conversation, String text,
+ boolean newTask) {
+ switchToConversation(conversation,text,null,false,newTask);
+ }
+
+ public void highlightInMuc(Conversation conversation, String nick) {
+ switchToConversation(conversation, null, nick, false, false);
+ }
+
+ public void privateMsgInMuc(Conversation conversation, String nick) {
+ switchToConversation(conversation, null, nick, true, false);
+ }
+
+ private void switchToConversation(Conversation conversation, String text, String nick, boolean pm, boolean newTask) {
+ Intent viewConversationIntent = new Intent(this,
+ ConversationActivity.class);
+ viewConversationIntent.setAction(ConversationActivity.ACTION_VIEW_CONVERSATION);
+ viewConversationIntent.putExtra(ConversationActivity.CONVERSATION,
+ conversation.getUuid());
+ if (text != null) {
+ viewConversationIntent.putExtra(ConversationActivity.TEXT, text);
+ }
+ if (nick != null) {
+ viewConversationIntent.putExtra(ConversationActivity.NICK, nick);
+ viewConversationIntent.putExtra(ConversationActivity.PRIVATE_MESSAGE,pm);
+ }
+ if (newTask) {
+ viewConversationIntent.setFlags(viewConversationIntent.getFlags()
+ | Intent.FLAG_ACTIVITY_NEW_TASK
+ | Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ } else {
+ viewConversationIntent.setFlags(viewConversationIntent.getFlags()
+ | Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ }
+ startActivity(viewConversationIntent);
+ finish();
+ }
+
+ public void switchToContactDetails(Contact contact) {
+ switchToContactDetails(contact, null);
+ }
+
+ public void switchToContactDetails(Contact contact, String messageFingerprint) {
+ Intent intent = new Intent(this, ContactDetailsActivity.class);
+ intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT);
+ intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().toBareJid().toString());
+ intent.putExtra("contact", contact.getJid().toString());
+ intent.putExtra("fingerprint", messageFingerprint);
+ startActivity(intent);
+ }
+
+ public void switchToAccount(Account account) {
+ switchToAccount(account, false);
+ }
+
+ public void switchToAccount(Account account, boolean init) {
+ Intent intent = new Intent(this, EditAccountActivity.class);
+ intent.putExtra("jid", account.getJid().toBareJid().toString());
+ intent.putExtra("init", init);
+ startActivity(intent);
+ }
+
+ protected void inviteToConversation(Conversation conversation) {
+ Intent intent = new Intent(getApplicationContext(),
+ ChooseContactActivity.class);
+ List<String> contacts = new ArrayList<>();
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) {
+ Jid jid = user.getRealJid();
+ if (jid != null) {
+ contacts.add(jid.toBareJid().toString());
+ }
+ }
+ } else {
+ contacts.add(conversation.getJid().toBareJid().toString());
+ }
+ intent.putExtra("filter_contacts", contacts.toArray(new String[contacts.size()]));
+ intent.putExtra("conversation", conversation.getUuid());
+ intent.putExtra("multiple", true);
+ intent.putExtra("show_enter_jid", true);
+ intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().toBareJid().toString());
+ startActivityForResult(intent, REQUEST_INVITE_TO_CONVERSATION);
+ }
+
+ protected void announcePgp(Account account, final Conversation conversation, final Runnable onSuccess) {
+ if (account.getPgpId() == 0) {
+ choosePgpSignId(account);
+ } else {
+ String status = null;
+ if (manuallyChangePresence()) {
+ status = account.getPresenceStatusMessage();
+ }
+ if (status == null) {
+ status = "";
+ }
+ xmppConnectionService.getPgpEngine().generateSignature(account, status, new UiCallback<Account>() {
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Account account) {
+ try {
+ startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0);
+ } catch (final SendIntentException ignored) {
+ }
+ }
+
+ @Override
+ public void success(Account account) {
+ xmppConnectionService.databaseBackend.updateAccount(account);
+ xmppConnectionService.sendPresence(account);
+ if (conversation != null) {
+ conversation.setNextEncryption(Message.ENCRYPTION_PGP);
+ xmppConnectionService.databaseBackend.updateConversation(conversation);
+ refreshUi();
+ }
+ if (onSuccess != null) {
+ runOnUiThread(onSuccess);
+ }
+ }
+
+ @Override
+ public void error(int error, Account account) {
+ displayErrorDialog(error);
+ }
+ });
+ }
+ }
+
+ protected boolean noAccountUsesPgp() {
+ if (!hasPgp()) {
+ return true;
+ }
+ for(Account account : xmppConnectionService.getAccounts()) {
+ if (account.getPgpId() != 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @SuppressWarnings("deprecation")
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
+ protected void setListItemBackgroundOnView(View view) {
+ int sdk = android.os.Build.VERSION.SDK_INT;
+ if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) {
+ view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground));
+ } else {
+ view.setBackground(getResources().getDrawable(R.drawable.greybackground));
+ }
+ }
+
+ protected void choosePgpSignId(Account account) {
+ xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<Account>() {
+ @Override
+ public void success(Account account1) {
+ }
+
+ @Override
+ public void error(int errorCode, Account object) {
+
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Account object) {
+ try {
+ startIntentSenderForResult(pi.getIntentSender(),
+ REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0);
+ } catch (final SendIntentException ignored) {
+ }
+ }
+ });
+ }
+
+ protected void displayErrorDialog(final int errorCode) {
+ runOnUiThread(new Runnable() {
+
+ @Override
+ public void run() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(
+ XmppActivity.this);
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setTitle(getString(R.string.error));
+ builder.setMessage(errorCode);
+ builder.setNeutralButton(R.string.accept, null);
+ builder.create().show();
+ }
+ });
+
+ }
+
+ protected void showAddToRosterDialog(final Conversation conversation) {
+ showAddToRosterDialog(conversation.getContact());
+ }
+
+ protected void showAddToRosterDialog(final Contact contact) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(contact.getJid().toString());
+ builder.setMessage(getString(R.string.not_in_roster));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.add_contact),
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ final Jid jid = contact.getJid();
+ Account account = contact.getAccount();
+ Contact contact = account.getRoster().getContact(jid);
+ xmppConnectionService.createContact(contact);
+ }
+ });
+ builder.create().show();
+ }
+
+ private void showAskForPresenceDialog(final Contact contact) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(contact.getJid().toString());
+ builder.setMessage(R.string.request_presence_updates);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.request_now,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ if (xmppConnectionServiceBound) {
+ xmppConnectionService.sendPresencePacket(contact
+ .getAccount(), xmppConnectionService
+ .getPresenceGenerator()
+ .requestPresenceUpdatesFrom(contact));
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ private void warnMutalPresenceSubscription(final Conversation conversation,
+ final OnPresenceSelected listener) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(conversation.getContact().getJid().toString());
+ builder.setMessage(R.string.without_mutual_presence_updates);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ignore, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ conversation.setNextCounterpart(null);
+ if (listener != null) {
+ listener.onPresenceSelected();
+ }
+ }
+ });
+ builder.create().show();
+ }
+
+ protected void quickEdit(String previousValue, int hint, OnValueEdited callback) {
+ quickEdit(previousValue, callback, hint, false);
+ }
+
+ protected void quickPasswordEdit(String previousValue, OnValueEdited callback) {
+ quickEdit(previousValue, callback, R.string.password, true);
+ }
+
+ @SuppressLint("InflateParams")
+ private void quickEdit(final String previousValue,
+ final OnValueEdited callback,
+ final int hint,
+ boolean password) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ View view = getLayoutInflater().inflate(R.layout.quickedit, null);
+ final EditText editor = (EditText) view.findViewById(R.id.editor);
+ OnClickListener mClickListener = new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ String value = editor.getText().toString();
+ if (!value.equals(previousValue) && value.trim().length() > 0) {
+ callback.onValueEdited(value);
+ }
+ }
+ };
+ if (password) {
+ editor.setInputType(InputType.TYPE_CLASS_TEXT
+ | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ builder.setPositiveButton(R.string.accept, mClickListener);
+ } else {
+ builder.setPositiveButton(R.string.edit, mClickListener);
+ }
+ if (hint != 0) {
+ editor.setHint(hint);
+ }
+ editor.requestFocus();
+ editor.setText("");
+ if (previousValue != null) {
+ editor.getText().append(previousValue);
+ }
+ builder.setView(view);
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.create().show();
+ }
+
+ protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight, View.OnClickListener onKeyClickedListener) {
+ final XmppAxolotlSession.Trust trust = account.getAxolotlService()
+ .getFingerprintTrust(fingerprint);
+ if (trust == null) {
+ return false;
+ }
+ return addFingerprintRowWithListeners(keys, account, fingerprint, highlight, trust, true,
+ new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ account.getAxolotlService().setFingerprintTrust(fingerprint,
+ (isChecked) ? XmppAxolotlSession.Trust.TRUSTED :
+ XmppAxolotlSession.Trust.UNTRUSTED);
+ }
+ },
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ account.getAxolotlService().setFingerprintTrust(fingerprint,
+ XmppAxolotlSession.Trust.UNTRUSTED);
+ v.setEnabled(true);
+ }
+ },
+ onKeyClickedListener
+
+ );
+ }
+
+ protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
+ final String fingerprint,
+ boolean highlight,
+ XmppAxolotlSession.Trust trust,
+ boolean showTag,
+ CompoundButton.OnCheckedChangeListener
+ onCheckedChangeListener,
+ View.OnClickListener onClickListener,
+ View.OnClickListener onKeyClickedListener) {
+ if (trust == XmppAxolotlSession.Trust.COMPROMISED) {
+ return false;
+ }
+ View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
+ TextView key = (TextView) view.findViewById(R.id.key);
+ key.setOnClickListener(onKeyClickedListener);
+ TextView keyType = (TextView) view.findViewById(R.id.key_type);
+ keyType.setOnClickListener(onKeyClickedListener);
+ Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
+ trustToggle.setVisibility(View.VISIBLE);
+ trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
+ trustToggle.setOnClickListener(onClickListener);
+ final View.OnLongClickListener purge = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ showPurgeKeyDialog(account, fingerprint);
+ return true;
+ }
+ };
+ boolean active = true;
+ view.setOnLongClickListener(purge);
+ key.setOnLongClickListener(purge);
+ keyType.setOnLongClickListener(purge);
+ boolean x509 = Config.X509_VERIFICATION
+ && (trust == XmppAxolotlSession.Trust.TRUSTED_X509 || trust == XmppAxolotlSession.Trust.INACTIVE_TRUSTED_X509);
+ switch (trust) {
+ case UNTRUSTED:
+ case TRUSTED:
+ case TRUSTED_X509:
+ trustToggle.setChecked(trust.trusted(), false);
+ trustToggle.setEnabled(!Config.X509_VERIFICATION || trust != XmppAxolotlSession.Trust.TRUSTED_X509);
+ if (Config.X509_VERIFICATION && trust == XmppAxolotlSession.Trust.TRUSTED_X509) {
+ trustToggle.setOnClickListener(null);
+ }
+ key.setTextColor(getPrimaryTextColor());
+ keyType.setTextColor(getSecondaryTextColor());
+ break;
+ case UNDECIDED:
+ trustToggle.setChecked(false, false);
+ trustToggle.setEnabled(false);
+ key.setTextColor(getPrimaryTextColor());
+ keyType.setTextColor(getSecondaryTextColor());
+ break;
+ case INACTIVE_UNTRUSTED:
+ case INACTIVE_UNDECIDED:
+ trustToggle.setOnClickListener(null);
+ trustToggle.setChecked(false, false);
+ trustToggle.setEnabled(false);
+ key.setTextColor(getTertiaryTextColor());
+ keyType.setTextColor(getTertiaryTextColor());
+ active = false;
+ break;
+ case INACTIVE_TRUSTED:
+ case INACTIVE_TRUSTED_X509:
+ trustToggle.setOnClickListener(null);
+ trustToggle.setChecked(true, false);
+ trustToggle.setEnabled(false);
+ key.setTextColor(getTertiaryTextColor());
+ keyType.setTextColor(getTertiaryTextColor());
+ active = false;
+ break;
+ }
+
+ if (showTag) {
+ keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
+ } else {
+ keyType.setVisibility(View.GONE);
+ }
+ if (highlight) {
+ keyType.setTextColor(getResources().getColor(R.color.accent));
+ keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
+ } else {
+ keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
+ }
+
+ key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
+
+ final View.OnClickListener toast;
+ if (!active) {
+ toast = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
+ }
+ };
+ trustToggle.setOnClickListener(toast);
+ } else {
+ toast = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ hideToast();
+ }
+ };
+ }
+ view.setOnClickListener(toast);
+ key.setOnClickListener(toast);
+ keyType.setOnClickListener(toast);
+
+ keys.addView(view);
+ return true;
+ }
+
+ public void showPurgeKeyDialog(final Account account, final String fingerprint) {
+ Builder builder = new Builder(this);
+ builder.setTitle(getString(R.string.purge_key));
+ builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ builder.setMessage(getString(R.string.purge_key_desc_part1)
+ + "\n\n" + CryptoHelper.prettifyFingerprint(fingerprint.substring(2))
+ + "\n\n" + getString(R.string.purge_key_desc_part2));
+ builder.setNegativeButton(getString(R.string.cancel), null);
+ builder.setPositiveButton(getString(R.string.purge_key),
+ new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ account.getAxolotlService().purgeKey(fingerprint);
+ refreshUi();
+ }
+ });
+ 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 boolean hasMicPermission(int requestCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.RECORD_AUDIO}, requestCode);
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ public boolean hasLocationPermission(int requestCode) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
+ requestPermissions(new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, requestCode);
+ requestPermissions(new String[]{Manifest.permission.ACCESS_COARSE_LOCATION}, requestCode);
+ return false;
+ } else {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ }
+
+ public void selectPresence(final Conversation conversation,
+ final OnPresenceSelected listener) {
+ final Contact contact = conversation.getContact();
+ if (conversation.hasValidOtrSession()) {
+ SessionID id = conversation.getOtrSession().getSessionID();
+ Jid jid;
+ try {
+ jid = Jid.fromString(id.getAccountID() + "/" + id.getUserID());
+ } catch (InvalidJidException e) {
+ jid = null;
+ }
+ conversation.setNextCounterpart(jid);
+ listener.onPresenceSelected();
+ } else if (!contact.showInRoster()) {
+ showAddToRosterDialog(conversation);
+ } else {
+ Presences presences = contact.getPresences();
+ if (presences.size() == 0) {
+ if (!contact.getOption(Contact.Options.TO)
+ && !contact.getOption(Contact.Options.ASKING)
+ && contact.getAccount().getStatus() == Account.State.ONLINE) {
+ showAskForPresenceDialog(contact);
+ } else if (!contact.getOption(Contact.Options.TO)
+ || !contact.getOption(Contact.Options.FROM)) {
+ warnMutalPresenceSubscription(conversation, listener);
+ } else {
+ conversation.setNextCounterpart(null);
+ listener.onPresenceSelected();
+ }
+ } else if (presences.size() == 1) {
+ String presence = presences.asStringArray()[0];
+ try {
+ conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence));
+ } catch (InvalidJidException e) {
+ conversation.setNextCounterpart(null);
+ }
+ listener.onPresenceSelected();
+ } else {
+ final StringBuilder presence = new StringBuilder();
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setTitle(getString(R.string.choose_presence));
+ final String[] presencesArray = presences.asStringArray();
+ int preselectedPresence = 0;
+ for (int i = 0; i < presencesArray.length; ++i) {
+ if (presencesArray[i].equals(contact.getLastPresence())) {
+ preselectedPresence = i;
+ break;
+ }
+ }
+ presence.append(presencesArray[preselectedPresence]);
+ builder.setSingleChoiceItems(presencesArray,
+ preselectedPresence,
+ new DialogInterface.OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog,
+ int which) {
+ presence.delete(0, presence.length());
+ presence.append(presencesArray[which]);
+ }
+ });
+ builder.setNegativeButton(R.string.cancel, null);
+ builder.setPositiveButton(R.string.ok, new OnClickListener() {
+
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ try {
+ conversation.setNextCounterpart(Jid.fromParts(contact.getJid().getLocalpart(),contact.getJid().getDomainpart(),presence.toString()));
+ } catch (InvalidJidException e) {
+ conversation.setNextCounterpart(null);
+ }
+ listener.onPresenceSelected();
+ }
+ });
+ builder.create().show();
+ }
+ }
+ }
+
+ protected void onActivityResult(int requestCode, int resultCode,
+ final Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) {
+ mPendingConferenceInvite = ConferenceInvite.parse(data);
+ if (xmppConnectionServiceBound && mPendingConferenceInvite != null) {
+ if (mPendingConferenceInvite.execute(this)) {
+ mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG);
+ mToast.show();
+ }
+ mPendingConferenceInvite = null;
+ }
+ }
+ }
+
+
+ private UiCallback<Conversation> adhocCallback = new UiCallback<Conversation>() {
+ @Override
+ public void success(final Conversation conversation) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ switchToConversation(conversation);
+ hideToast();
+ }
+ });
+ }
+
+ @Override
+ public void error(final int errorCode, Conversation object) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ replaceToast(getString(errorCode));
+ }
+ });
+ }
+
+ @Override
+ public void userInputRequried(PendingIntent pi, Conversation object) {
+
+ }
+ };
+
+ public int getTertiaryTextColor() {
+ return this.mTertiaryTextColor;
+ }
+
+ public int getSecondaryTextColor() {
+ return this.mSecondaryTextColor;
+ }
+
+ public int getPrimaryTextColor() {
+ return this.mPrimaryTextColor;
+ }
+
+ public int getWarningTextColor() {
+ return this.mColorRed;
+ }
+
+ public int getOnlineColor() {
+ return this.mColorGreen;
+ }
+
+ public int getPrimaryBackgroundColor() {
+ return this.mPrimaryBackgroundColor;
+ }
+
+ public int getSecondaryBackgroundColor() {
+ return this.mSecondaryBackgroundColor;
+ }
+
+ public int getPixel(int dp) {
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ return ((int) (dp * metrics.density));
+ }
+
+ public boolean copyTextToClipboard(String text, int labelResId) {
+ ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+ String label = getResources().getString(labelResId);
+ if (mClipBoardManager != null) {
+ ClipData mClipData = ClipData.newPlainText(label, text);
+ mClipBoardManager.setPrimaryClip(mClipData);
+ return true;
+ }
+ return false;
+ }
+
+ protected void registerNdefPushMessageCallback() {
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ if (nfcAdapter != null && nfcAdapter.isEnabled()) {
+ nfcAdapter.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
+ @Override
+ public NdefMessage createNdefMessage(NfcEvent nfcEvent) {
+ return new NdefMessage(new NdefRecord[]{
+ NdefRecord.createUri(getShareableUri()),
+ NdefRecord.createApplicationRecord("eu.siacs.conversations")
+ });
+ }
+ }, this);
+ }
+ }
+
+ protected boolean neverCompressPictures() {
+ return getPreferences().getString("picture_compression", "auto").equals("never");
+ }
+
+ protected boolean manuallyChangePresence() {
+ return getPreferences().getBoolean("manually_change_presence", true);
+ }
+
+ protected void unregisterNdefPushMessageCallback() {
+ NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+ if (nfcAdapter != null && nfcAdapter.isEnabled()) {
+ nfcAdapter.setNdefPushMessageCallback(null,this);
+ }
+ }
+
+ protected String getShareableUri() {
+ return null;
+ }
+
+ private void inviteUser() {
+ Account mAccount = xmppConnectionService.getAccounts().get(0);
+ String user = mAccount.getJid().getLocalpart().toString();
+ String domain = mAccount.getJid().getDomainpart().toString();
+ String inviteURL = Config.inviteUserURL + user + "/" + domain;
+ String inviteText = getString(R.string.InviteText, user);
+ Intent intent = new Intent(android.content.Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ intent.putExtra(Intent.EXTRA_SUBJECT, user + " " + getString(R.string.inviteUser_Subject) + " " + getString(R.string.app_name));
+ intent.putExtra(Intent.EXTRA_TEXT, inviteText + "\n\n" + inviteURL);
+ startActivity(Intent.createChooser(intent, getString(R.string.invite_contact)));
+ }
+
+ protected void shareUri() {
+ String uri = getShareableUri();
+ if (uri == null || uri.isEmpty()) {
+ return;
+ }
+ Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri());
+ shareIntent.setType("text/plain");
+ try {
+ startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with)));
+ } catch (ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ if (this.getShareableUri()!=null) {
+ this.registerNdefPushMessageCallback();
+ }
+ }
+
+ protected int findTheme() {
+ if (getPreferences().getBoolean("use_larger_font", false)) {
+ return R.style.ConversationsTheme_LargerText;
+ } else {
+ return R.style.ConversationsTheme;
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ this.unregisterNdefPushMessageCallback();
+ }
+
+ protected void showQrCode() {
+ String uri = getShareableUri();
+ if (uri!=null) {
+ Point size = new Point();
+ getWindowManager().getDefaultDisplay().getSize(size);
+ final int width = (size.x < size.y ? size.x : size.y);
+ Bitmap bitmap = createQrCodeBitmap(uri, width);
+ ImageView view = new ImageView(this);
+ view.setImageBitmap(bitmap);
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setView(view);
+ builder.create().show();
+ }
+ }
+
+ protected Bitmap createQrCodeBitmap(String input, int size) {
+ Log.d(Config.LOGTAG,"qr code requested size: "+size);
+ try {
+ final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
+ final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
+ hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
+ final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
+ final int width = result.getWidth();
+ final int height = result.getHeight();
+ final int[] pixels = new int[width * height];
+ for (int y = 0; y < height; y++) {
+ final int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
+ }
+ }
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ } catch (final WriterException e) {
+ return null;
+ }
+ }
+
+ protected Account extractAccount(Intent intent) {
+ String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
+ try {
+ return jid != null ? xmppConnectionService.findAccountByJid(Jid.fromString(jid)) : null;
+ } catch (InvalidJidException e) {
+ return null;
+ }
+ }
+
+ public static class ConferenceInvite {
+ private String uuid;
+ private List<Jid> jids = new ArrayList<>();
+
+ public static ConferenceInvite parse(Intent data) {
+ ConferenceInvite invite = new ConferenceInvite();
+ invite.uuid = data.getStringExtra("conversation");
+ if (invite.uuid == null) {
+ return null;
+ }
+ try {
+ if (data.getBooleanExtra("multiple", false)) {
+ String[] toAdd = data.getStringArrayExtra("contacts");
+ for (String item : toAdd) {
+ invite.jids.add(Jid.fromString(item));
+ }
+ } else {
+ invite.jids.add(Jid.fromString(data.getStringExtra("contact")));
+ }
+ } catch (final InvalidJidException ignored) {
+ return null;
+ }
+ return invite;
+ }
+
+ public boolean execute(XmppActivity activity) {
+ XmppConnectionService service = activity.xmppConnectionService;
+ Conversation conversation = service.findConversationByUuid(this.uuid);
+ if (conversation == null) {
+ return false;
+ }
+ if (conversation.getMode() == Conversation.MODE_MULTI) {
+ for (Jid jid : jids) {
+ service.invite(conversation, jid);
+ }
+ return false;
+ } else {
+ jids.add(conversation.getJid().toBareJid());
+ service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback);
+ return true;
+ }
+ }
+ }
+
+ public AvatarService avatarService() {
+ return xmppConnectionService.getAvatarService();
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Message message = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Message... params) {
+ if (isCancelled()) {
+ return null;
+ }
+ message = params[0];
+ try {
+ return xmppConnectionService.getFileBackend().getThumbnail(
+ message, (int) (metrics.density * 288), false);
+ } catch (FileNotFoundException e) {
+ return null;
+ }
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadBitmap(Message message, ImageView imageView) {
+ Bitmap bm;
+ try {
+ bm = xmppConnectionService.getFileBackend().getThumbnail(message,
+ (int) (metrics.density * 288), true);
+ } catch (FileNotFoundException e) {
+ bm = null;
+ }
+ if (bm != null) {
+ cancelPotentialWork(message, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ if (cancelPotentialWork(message, imageView)) {
+ imageView.setBackgroundColor(0xff333333);
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(
+ getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(message);
+ } catch (final RejectedExecutionException ignored) {
+ ignored.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(Message message, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Message oldMessage = bitmapWorkerTask.message;
+ if (oldMessage == null || message != oldMessage) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap,
+ BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(
+ bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
new file mode 100644
index 000000000..812c41ace
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java
@@ -0,0 +1,59 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.List;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.ui.XmppActivity;
+
+public class AccountAdapter extends ArrayAdapter<Account> {
+
+ private XmppActivity activity;
+
+ public AccountAdapter(XmppActivity activity, List<Account> objects) {
+ super(activity, 0, objects);
+ this.activity = activity;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ final Account account = getItem(position);
+ if (view == null) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ view = inflater.inflate(R.layout.account_row, parent, false);
+ }
+ TextView jid = (TextView) view.findViewById(R.id.account_jid);
+ if (Config.DOMAIN_LOCK != null) {
+ jid.setText(account.getJid().getLocalpart());
+ } else {
+ jid.setText(account.getJid().toBareJid().toString());
+ }
+ TextView statusView = (TextView) view.findViewById(R.id.account_status);
+ ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
+ imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(56)));
+ statusView.setText(getContext().getString(account.getStatus().getReadableId()));
+ switch (account.getStatus()) {
+ case ONLINE:
+ statusView.setTextColor(activity.getOnlineColor());
+ break;
+ case DISABLED:
+ case CONNECTING:
+ statusView.setTextColor(activity.getSecondaryTextColor());
+ break;
+ default:
+ statusView.setTextColor(activity.getWarningTextColor());
+ break;
+ }
+ return view;
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
new file mode 100644
index 000000000..f19dfa43b
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java
@@ -0,0 +1,231 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.ui.ConversationActivity;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.utils.UIHelper;
+import de.pixart.messenger.xmpp.chatstate.ChatState;
+
+public class ConversationAdapter extends ArrayAdapter<Conversation> {
+
+ private XmppActivity activity;
+
+ public ConversationAdapter(XmppActivity activity,
+ List<Conversation> conversations) {
+ super(activity, 0, conversations);
+ this.activity = activity;
+ }
+
+ public static boolean cancelPotentialWork(Conversation conversation, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Conversation oldConversation = bitmapWorkerTask.conversation;
+ if (oldConversation == null || conversation != oldConversation) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ if (view == null) {
+ LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ view = inflater.inflate(R.layout.conversation_list_row, parent, false);
+ }
+ Conversation conversation = getItem(position);
+ if (this.activity instanceof ConversationActivity) {
+ View swipeableItem = view.findViewById(R.id.swipeable_item);
+ ConversationActivity a = (ConversationActivity) this.activity;
+ int c = a.highlightSelectedConversations() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
+ swipeableItem.setBackgroundColor(c);
+ }
+ TextView convName = (TextView) view.findViewById(R.id.conversation_name);
+ if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
+ convName.setText(conversation.getName());
+ } else {
+ convName.setText(conversation.getJid().toBareJid().toString());
+ }
+ TextView mLastMessage = (TextView) view.findViewById(R.id.conversation_lastmsg);
+ TextView mTimestamp = (TextView) view.findViewById(R.id.conversation_lastupdate);
+ ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
+ ImageView notificationStatus = (ImageView) view.findViewById(R.id.notification_status);
+
+ Message message = conversation.getLatestMessage();
+ String mimeType = message.getMimeType();
+
+ if (!conversation.isRead()) {
+ convName.setTypeface(null, Typeface.BOLD);
+ } else {
+ convName.setTypeface(null, Typeface.NORMAL);
+ }
+
+
+ /*if (message.getTransferable() == null
+ || message.getTransferable().getStatus() != Transferable.STATUS_DELETED) {
+ if (mimeType != null && message.getMimeType().startsWith("video/")) {
+ mLastMessage.setVisibility(View.GONE);
+ imagePreview.setVisibility(View.VISIBLE);
+ activity.loadVideoPreview(message, imagePreview);
+ } else if (message.getFileParams().width > 0) {
+ mLastMessage.setVisibility(View.GONE);
+ imagePreview.setVisibility(View.VISIBLE);
+ activity.loadBitmap(message, imagePreview);
+ } else {
+ Pair<String, Boolean> preview = UIHelper.getMessagePreview(activity, message);
+ mLastMessage.setVisibility(View.VISIBLE);
+ imagePreview.setVisibility(View.GONE);
+ mLastMessage.setText(preview.first);
+ if (preview.second) {
+ if (conversation.isRead()) {
+ mLastMessage.setTypeface(null, Typeface.ITALIC);
+ } else {
+ mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
+ }
+ } else {
+ if (conversation.isRead()) {
+ mLastMessage.setTypeface(null, Typeface.NORMAL);
+ } else {
+ mLastMessage.setTypeface(null, Typeface.BOLD);
+ }
+ }
+ }
+ } else {*/
+ Pair<String, Boolean> preview = UIHelper.getMessagePreview(activity, message);
+ mLastMessage.setVisibility(View.VISIBLE);
+ imagePreview.setVisibility(View.GONE);
+ mLastMessage.setText(preview.first);
+ if (preview.second) {
+ if (conversation.isRead()) {
+ mLastMessage.setTypeface(null, Typeface.ITALIC);
+ } else {
+ mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
+ }
+ } else {
+ if (conversation.isRead()) {
+ mLastMessage.setTypeface(null, Typeface.NORMAL);
+ } else {
+ mLastMessage.setTypeface(null, Typeface.BOLD);
+ }
+ }
+ //}
+
+ long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
+ if (muted_till == Long.MAX_VALUE) {
+ notificationStatus.setVisibility(View.VISIBLE);
+ notificationStatus.setImageResource(R.drawable.ic_notifications_off_grey600_24dp);
+ } else if (muted_till >= System.currentTimeMillis()) {
+ notificationStatus.setVisibility(View.VISIBLE);
+ notificationStatus.setImageResource(R.drawable.ic_notifications_paused_grey600_24dp);
+ } else if (conversation.alwaysNotify()) {
+ notificationStatus.setVisibility(View.GONE);
+ } else {
+ notificationStatus.setVisibility(View.VISIBLE);
+ notificationStatus.setImageResource(R.drawable.ic_notifications_none_grey600_24dp);
+ }
+
+ mTimestamp.setText(UIHelper.readableTimeDifference(activity, conversation.getLatestMessage().getTimeSent()));
+ ImageView profilePicture = (ImageView) view.findViewById(R.id.conversation_image);
+ loadAvatar(conversation, profilePicture);
+
+ if (conversation.getIncomingChatState().equals(ChatState.COMPOSING)) {
+ mLastMessage.setText(R.string.is_typing);
+ mLastMessage.setTypeface(null, Typeface.BOLD_ITALIC);
+ }
+ return view;
+ }
+
+ public void loadAvatar(Conversation conversation, ImageView imageView) {
+ if (cancelPotentialWork(conversation, imageView)) {
+ final Bitmap bm = activity.avatarService().get(conversation, activity.getPixel(56), true);
+ if (bm != null) {
+ cancelPotentialWork(conversation, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(conversation.getName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(conversation);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Conversation, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Conversation conversation = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Conversation... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(56));
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java
new file mode 100644
index 000000000..2906f16ba
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/KnownHostsAdapter.java
@@ -0,0 +1,70 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.Context;
+import android.widget.ArrayAdapter;
+import android.widget.Filter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+public class KnownHostsAdapter extends ArrayAdapter<String> {
+ private ArrayList<String> domains;
+ private Filter domainFilter = new Filter() {
+
+ @Override
+ protected FilterResults performFiltering(CharSequence constraint) {
+ if (constraint != null) {
+ ArrayList<String> suggestions = new ArrayList<>();
+ final String[] split = constraint.toString().split("@");
+ if (split.length == 1) {
+ for (String domain : domains) {
+ suggestions.add(split[0].toLowerCase(Locale
+ .getDefault()) + "@" + domain);
+ }
+ } else if (split.length == 2) {
+ for (String domain : domains) {
+ if (domain.contentEquals(split[1])) {
+ suggestions.clear();
+ break;
+ } else if (domain.contains(split[1])) {
+ suggestions.add(split[0].toLowerCase(Locale
+ .getDefault()) + "@" + domain);
+ }
+ }
+ } else {
+ return new FilterResults();
+ }
+ FilterResults filterResults = new FilterResults();
+ filterResults.values = suggestions;
+ filterResults.count = suggestions.size();
+ return filterResults;
+ } else {
+ return new FilterResults();
+ }
+ }
+
+ @Override
+ protected void publishResults(CharSequence constraint,
+ FilterResults results) {
+ ArrayList filteredList = (ArrayList) results.values;
+ if (results != null && results.count > 0) {
+ clear();
+ for (Object c : filteredList) {
+ add((String) c);
+ }
+ notifyDataSetChanged();
+ }
+ }
+ };
+
+ public KnownHostsAdapter(Context context, int viewResourceId, List<String> mKnownHosts) {
+ super(context, viewResourceId, new ArrayList<String>());
+ domains = new ArrayList<>(mKnownHosts);
+ }
+
+ @Override
+ public Filter getFilter() {
+ return domainFilter;
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
new file mode 100644
index 000000000..f338f7170
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java
@@ -0,0 +1,182 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.preference.PreferenceManager;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.entities.ListItem;
+import de.pixart.messenger.ui.XmppActivity;
+import de.pixart.messenger.utils.UIHelper;
+
+public class ListItemAdapter extends ArrayAdapter<ListItem> {
+
+ protected XmppActivity activity;
+ protected boolean showDynamicTags = false;
+ private View.OnClickListener onTagTvClick = new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (view instanceof TextView && mOnTagClickedListener != null) {
+ TextView tv = (TextView) view;
+ final String tag = tv.getText().toString();
+ mOnTagClickedListener.onTagClicked(tag);
+ }
+ }
+ };
+ private OnTagClickedListener mOnTagClickedListener = null;
+
+ public ListItemAdapter(XmppActivity activity, List<ListItem> objects) {
+ super(activity, 0, objects);
+ this.activity = activity;
+ SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
+ this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
+ }
+
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ LayoutInflater inflater = (LayoutInflater) getContext()
+ .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ ListItem item = getItem(position);
+ if (view == null) {
+ view = inflater.inflate(R.layout.contact, parent, false);
+ }
+ TextView tvName = (TextView) view.findViewById(R.id.contact_display_name);
+ TextView tvJid = (TextView) view.findViewById(R.id.contact_jid);
+ ImageView picture = (ImageView) view.findViewById(R.id.contact_photo);
+ LinearLayout tagLayout = (LinearLayout) view.findViewById(R.id.tags);
+
+ List<ListItem.Tag> tags = item.getTags(activity);
+ if (tags.size() == 0 || !this.showDynamicTags) {
+ tagLayout.setVisibility(View.GONE);
+ } else {
+ tagLayout.setVisibility(View.VISIBLE);
+ tagLayout.removeAllViewsInLayout();
+ for(ListItem.Tag tag : tags) {
+ TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tagLayout,false);
+ tv.setText(tag.getName());
+ tv.setBackgroundColor(tag.getColor());
+ tv.setOnClickListener(this.onTagTvClick);
+ tagLayout.addView(tv);
+ }
+ }
+ final String jid = item.getDisplayJid();
+ if (jid != null) {
+ tvJid.setVisibility(View.VISIBLE);
+ tvJid.setText(jid);
+ } else {
+ tvJid.setVisibility(View.GONE);
+ }
+ tvName.setText(item.getDisplayName());
+ loadAvatar(item,picture);
+ return view;
+ }
+
+ public void setOnTagClickedListener(OnTagClickedListener listener) {
+ this.mOnTagClickedListener = listener;
+ }
+
+ public interface OnTagClickedListener {
+ void onTagClicked(String tag);
+ }
+
+ class BitmapWorkerTask extends AsyncTask<ListItem, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private ListItem item = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(ListItem... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled());
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(ListItem item, ImageView imageView) {
+ if (cancelPotentialWork(item, imageView)) {
+ final Bitmap bm = activity.avatarService().get(item,activity.getPixel(48),true);
+ if (bm != null) {
+ cancelPotentialWork(item, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(item.getDisplayName()));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(item);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(ListItem item, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final ListItem oldItem = bitmapWorkerTask.item;
+ if (oldItem == null || item != oldItem) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+
+}
diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
new file mode 100644
index 000000000..0480ca2b4
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java
@@ -0,0 +1,919 @@
+package de.pixart.messenger.ui.adapter;
+
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Typeface;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.text.Spannable;
+import android.text.SpannableString;
+import android.text.Spanned;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StyleSpan;
+import android.text.util.Linkify;
+import android.util.DisplayMetrics;
+import android.util.Patterns;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import java.lang.ref.WeakReference;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.RejectedExecutionException;
+import java.util.regex.MatchResult;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import de.pixart.messenger.Config;
+import de.pixart.messenger.R;
+import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession;
+import de.pixart.messenger.entities.Account;
+import de.pixart.messenger.entities.Conversation;
+import de.pixart.messenger.entities.DownloadableFile;
+import de.pixart.messenger.entities.Message;
+import de.pixart.messenger.entities.Message.FileParams;
+import de.pixart.messenger.entities.Transferable;
+import de.pixart.messenger.ui.ConversationActivity;
+import de.pixart.messenger.ui.ShowFullscreenMessageActivity;
+import de.pixart.messenger.utils.CryptoHelper;
+import de.pixart.messenger.utils.GeoHelper;
+import de.pixart.messenger.utils.UIHelper;
+import nl.changer.audiowife.AudioWife;
+
+public class MessageAdapter extends ArrayAdapter<Message> {
+
+ private static final int SENT = 0;
+ private static final int RECEIVED = 1;
+ private static final int STATUS = 2;
+ private static final Pattern XMPP_PATTERN = Pattern
+ .compile("xmpp\\:(?:(?:["
+ + Patterns.GOOD_IRI_CHAR
+ + "\\;\\/\\?\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])"
+ + "|(?:\\%[a-fA-F0-9]{2}))+");
+
+ private ConversationActivity activity;
+
+ private DisplayMetrics metrics;
+
+ private OnContactPictureClicked mOnContactPictureClickedListener;
+ private OnContactPictureLongClicked mOnContactPictureLongClickedListener;
+
+ private OnLongClickListener openContextMenu = new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ v.showContextMenu();
+ return true;
+ }
+ };
+ private boolean mIndicateReceived = false;
+ private HashMap<Integer, AudioWife> audioPlayer;
+ private boolean mUseWhiteBackground = false;
+
+ public MessageAdapter(ConversationActivity activity, List<Message> messages) {
+ super(activity, 0, messages);
+ this.activity = activity;
+ metrics = getContext().getResources().getDisplayMetrics();
+ updatePreferences();
+ }
+
+ public void setOnContactPictureClicked(OnContactPictureClicked listener) {
+ this.mOnContactPictureClickedListener = listener;
+ }
+
+ public void setOnContactPictureLongClicked(
+ OnContactPictureLongClicked listener) {
+ this.mOnContactPictureLongClickedListener = listener;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 3;
+ }
+
+ public int getItemViewType(Message message) {
+ if (message.getType() == Message.TYPE_STATUS) {
+ return STATUS;
+ } else if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ return RECEIVED;
+ }
+
+ return SENT;
+ }
+
+ @Override
+ public int getItemViewType(int position) {
+ return getItemViewType(getItem(position));
+ }
+
+ private int getMessageTextColor(boolean onDark, boolean primary) {
+ if (onDark) {
+ return activity.getResources().getColor(primary ? R.color.dark : R.color.primary);
+ } else {
+ return activity.getResources().getColor(primary ? R.color.dark : R.color.primary);
+ }
+ }
+
+ private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) {
+ String filesize = null;
+ String info = null;
+ boolean error = false;
+ if (viewHolder.indicatorReceived != null) {
+ viewHolder.indicatorReceived.setVisibility(View.GONE);
+ viewHolder.indicatorRead.setVisibility(View.GONE);
+ }
+
+ if (viewHolder.edit_indicator != null) {
+ if (message.edited()) {
+ viewHolder.edit_indicator.setVisibility(View.VISIBLE);
+ } else {
+ viewHolder.edit_indicator.setVisibility(View.GONE);
+ }
+ }
+ boolean multiReceived = message.getConversation().getMode() == Conversation.MODE_MULTI
+ && message.getMergedStatus() <= Message.STATUS_RECEIVED;
+ if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE || message.getTransferable() != null) {
+ FileParams params = message.getFileParams();
+ if (params.size > (1.5 * 1024 * 1024)) {
+ filesize = params.size / (1024 * 1024)+ " MiB";
+ } else if (params.size > 0) {
+ filesize = params.size / 1024 + " KiB";
+ }
+ if (message.getTransferable() != null && message.getTransferable().getStatus() == Transferable.STATUS_FAILED) {
+ error = true;
+ }
+ }
+ switch (message.getMergedStatus()) {
+ case Message.STATUS_WAITING:
+ info = getContext().getString(R.string.waiting);
+ break;
+ case Message.STATUS_UNSEND:
+ Transferable d = message.getTransferable();
+ if (d!=null) {
+ info = getContext().getString(R.string.sending_file,d.getProgress());
+ } else {
+ info = getContext().getString(R.string.sending);
+ }
+ break;
+ case Message.STATUS_OFFERED:
+ info = getContext().getString(R.string.offering);
+ break;
+ case Message.STATUS_SEND_RECEIVED:
+ if (mIndicateReceived) {
+ viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
+ }
+ break;
+ case Message.STATUS_SEND_DISPLAYED:
+ if (mIndicateReceived) {
+ viewHolder.indicatorReceived.setVisibility(View.VISIBLE);
+ viewHolder.indicatorRead.setVisibility(View.VISIBLE);
+ }
+ break;
+ case Message.STATUS_SEND_FAILED:
+ info = getContext().getString(R.string.send_failed);
+ error = true;
+ break;
+ default:
+ if (multiReceived) {
+ info = UIHelper.getMessageDisplayName(message);
+ }
+ break;
+ }
+ if (error && type == SENT) {
+ viewHolder.time.setTextColor(activity.getWarningTextColor());
+ } else {
+ viewHolder.time.setTextColor(this.getMessageTextColor(darkBackground,false));
+ }
+ if (message.getEncryption() == Message.ENCRYPTION_NONE) {
+ viewHolder.indicator.setVisibility(View.GONE);
+ } else {
+ viewHolder.indicator.setVisibility(View.VISIBLE);
+ if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) {
+ XmppAxolotlSession.Trust trust = message.getConversation()
+ .getAccount().getAxolotlService().getFingerprintTrust(
+ message.getFingerprint());
+
+ if(trust == null || (!trust.trusted() && !trust.trustedInactive())) {
+ viewHolder.indicator.setColorFilter(activity.getWarningTextColor());
+ viewHolder.indicator.setAlpha(1.0f);
+ } else {
+ viewHolder.indicator.clearColorFilter();
+ if (darkBackground) {
+ viewHolder.indicator.setAlpha(0.7f);
+ } else {
+ viewHolder.indicator.setAlpha(0.57f);
+ }
+ }
+ } else {
+ viewHolder.indicator.clearColorFilter();
+ if (darkBackground) {
+ viewHolder.indicator.setAlpha(0.7f);
+ } else {
+ viewHolder.indicator.setAlpha(0.57f);
+ }
+ }
+ }
+
+ String formatedTime = UIHelper.readableTimeDifferenceFull(getContext(),
+ message.getMergedTimeSent());
+ if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ if ((filesize != null) && (info != null)) {
+ viewHolder.time.setText(formatedTime + " \u00B7 " + filesize +" \u00B7 " + info);
+ } else if ((filesize == null) && (info != null)) {
+ viewHolder.time.setText(formatedTime + " \u00B7 " + info);
+ } else if ((filesize != null) && (info == null)) {
+ viewHolder.time.setText(formatedTime + " \u00B7 " + filesize);
+ } else {
+ viewHolder.time.setText(formatedTime);
+ }
+ } else {
+ if ((filesize != null) && (info != null)) {
+ viewHolder.time.setText(filesize + " \u00B7 " + info);
+ } else if ((filesize == null) && (info != null)) {
+ if (error) {
+ viewHolder.time.setText(info + " \u00B7 " + formatedTime);
+ } else {
+ viewHolder.time.setText(info);
+ }
+ } else if ((filesize != null) && (info == null)) {
+ viewHolder.time.setText(filesize + " \u00B7 " + formatedTime);
+ } else {
+ viewHolder.time.setText(formatedTime);
+ }
+ }
+ }
+
+ private void displayInfoMessage(ViewHolder viewHolder, String text, boolean darkBackground) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setText(text);
+ viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
+ viewHolder.messageBody.setTypeface(null, Typeface.ITALIC);
+ viewHolder.messageBody.setTextIsSelectable(false);
+ }
+
+ private void displayDecryptionFailed(ViewHolder viewHolder, boolean darkBackground) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setText(getContext().getString(
+ R.string.decryption_failed));
+ viewHolder.messageBody.setTextColor(getMessageTextColor(darkBackground, false));
+ viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setTextIsSelectable(false);
+ }
+
+ private void displayHeartMessage(final ViewHolder viewHolder, final String body) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setIncludeFontPadding(false);
+ Spannable span = new SpannableString(body);
+ span.setSpan(new RelativeSizeSpan(4.0f), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ span.setSpan(new ForegroundColorSpan(activity.getWarningTextColor()), 0, body.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ viewHolder.messageBody.setText(span);
+ }
+
+ private void displayTextMessage(final ViewHolder viewHolder, final Message message, boolean darkBackground) {
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.VISIBLE);
+ viewHolder.messageBody.setIncludeFontPadding(true);
+ if (message.getBody() != null) {
+ final String nick = UIHelper.getMessageDisplayName(message);
+ String body;
+ try {
+ body = message.getMergedBody().replaceAll("^" + Message.ME_COMMAND, nick + " ");
+ } catch (ArrayIndexOutOfBoundsException e) {
+ body = message.getMergedBody();
+ }
+ if (body.length() > Config.MAX_DISPLAY_MESSAGE_CHARS) {
+ body = body.substring(0, Config.MAX_DISPLAY_MESSAGE_CHARS)+"\u2026";
+ }
+ final SpannableString formattedBody = new SpannableString(body);
+ int i = body.indexOf(Message.MERGE_SEPARATOR);
+ while(i >= 0) {
+ final int end = i + Message.MERGE_SEPARATOR.length();
+ formattedBody.setSpan(new RelativeSizeSpan(0.3f),i,end,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ i = body.indexOf(Message.MERGE_SEPARATOR,end);
+ }
+ if (message.getType() != Message.TYPE_PRIVATE) {
+ if (message.hasMeCommand()) {
+ final Spannable span = new SpannableString(formattedBody);
+ span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), 0, nick.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ viewHolder.messageBody.setText(span);
+ } else {
+ viewHolder.messageBody.setText(formattedBody);
+ }
+ } else {
+ String privateMarker;
+ if (message.getStatus() <= Message.STATUS_RECEIVED) {
+ privateMarker = activity
+ .getString(R.string.private_message);
+ } else {
+ final String to;
+ if (message.getCounterpart() != null) {
+ to = message.getCounterpart().getResourcepart();
+ } else {
+ to = "";
+ }
+ privateMarker = activity.getString(R.string.private_message_to, to);
+ }
+ final Spannable span = new SpannableString(privateMarker + " "
+ + formattedBody);
+ span.setSpan(new ForegroundColorSpan(getMessageTextColor(darkBackground,false)), 0, privateMarker
+ .length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ span.setSpan(new StyleSpan(Typeface.BOLD), 0,
+ privateMarker.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ if (message.hasMeCommand()) {
+ span.setSpan(new StyleSpan(Typeface.BOLD_ITALIC), privateMarker.length() + 1,
+ privateMarker.length() + 1 + nick.length(),
+ Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ viewHolder.messageBody.setText(span);
+ }
+ int urlCount = 0;
+ final Matcher matcher = Patterns.WEB_URL.matcher(body);
+ int beginWebURL = Integer.MAX_VALUE;
+ int endWebURL = 0;
+ while (matcher.find()) {
+ MatchResult result = matcher.toMatchResult();
+ beginWebURL = result.start();
+ endWebURL = result.end();
+ urlCount++;
+ }
+ final Matcher geoMatcher = GeoHelper.GEO_URI.matcher(body);
+ while (geoMatcher.find()) {
+ urlCount++;
+ }
+ final Matcher xmppMatcher = XMPP_PATTERN.matcher(body);
+ while (xmppMatcher.find()) {
+ MatchResult result = xmppMatcher.toMatchResult();
+ if (beginWebURL < result.start() || endWebURL > result.end()) {
+ urlCount++;
+ }
+ }
+ viewHolder.messageBody.setTextIsSelectable(urlCount <= 1);
+ viewHolder.messageBody.setAutoLinkMask(0);
+ Linkify.addLinks(viewHolder.messageBody, Linkify.WEB_URLS);
+ Linkify.addLinks(viewHolder.messageBody, XMPP_PATTERN, "xmpp");
+ Linkify.addLinks(viewHolder.messageBody, GeoHelper.GEO_URI, "geo");
+ } else {
+ viewHolder.messageBody.setText("");
+ viewHolder.messageBody.setTextIsSelectable(false);
+ }
+ viewHolder.messageBody.setTextColor(this.getMessageTextColor(darkBackground, true));
+ viewHolder.messageBody.setLinkTextColor(this.getMessageTextColor(darkBackground, true));
+ viewHolder.messageBody.setHighlightColor(activity.getResources().getColor(darkBackground ? R.color.grey800 : R.color.grey500));
+ viewHolder.messageBody.setTypeface(null, Typeface.NORMAL);
+ viewHolder.messageBody.setOnLongClickListener(openContextMenu);
+ }
+
+ private void displayDownloadableMessage(ViewHolder viewHolder,
+ final Message message, String text) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.download_button.setVisibility(View.VISIBLE);
+ viewHolder.download_button.setText(text);
+ viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.startDownloadable(message);
+ }
+ });
+ viewHolder.download_button.setOnLongClickListener(openContextMenu);
+ }
+
+ private void displayAudioMessage(ViewHolder viewHolder, final Message message, int position) {
+ if (audioPlayer == null) audioPlayer = new HashMap<>();
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) viewHolder.download_button.setVisibility(View.GONE);
+ viewHolder.aw_player.setVisibility(View.VISIBLE);
+ Uri audioFile = Uri.fromFile(activity.xmppConnectionService.getFileBackend().getFile(message));
+
+ AudioWife audioWife = audioPlayer.get(position);
+ if (audioWife == null) {
+ audioWife = new AudioWife();
+ audioWife.init(getContext(), audioFile);
+ audioPlayer.put(position, audioWife);
+ RelativeLayout vg = new RelativeLayout(activity);
+ LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ audioWife.useDefaultUi(vg, layoutInflater);
+ viewHolder.aw_player.addView(audioWife.getPlayerUi());
+ } else {
+ audioWife.cleanPlayerUi();
+ viewHolder.aw_player.addView(audioWife.getPlayerUi());
+ }
+ }
+
+ private void displayOpenableMessage(ViewHolder viewHolder,final Message message) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.download_button.setVisibility(View.VISIBLE);
+ viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message)));
+ viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ openDownloadable(message);
+ }
+ });
+ viewHolder.download_button.setOnLongClickListener(openContextMenu);
+ }
+
+ private void displayLocationMessage(ViewHolder viewHolder, final Message message) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.GONE);
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.download_button.setVisibility(View.VISIBLE);
+ viewHolder.download_button.setText(R.string.show_location);
+ viewHolder.download_button.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ showLocation(message);
+ }
+ });
+ viewHolder.download_button.setOnLongClickListener(openContextMenu);
+ }
+
+ private void displayImageMessage(ViewHolder viewHolder,
+ final Message message) {
+ viewHolder.aw_player.setVisibility(View.GONE);
+ if (viewHolder.download_button != null) {
+ viewHolder.download_button.setVisibility(View.GONE);
+ }
+ viewHolder.messageBody.setVisibility(View.GONE);
+ viewHolder.image.setVisibility(View.VISIBLE);
+ FileParams params = message.getFileParams();
+ double target = metrics.density * 200;
+ int scalledW;
+ int scalledH;
+ if (params.width <= params.height) {
+ scalledW = (int) (params.width / ((double) params.height / target));
+ scalledH = (int) target;
+ } else {
+ scalledW = (int) target;
+ scalledH = (int) (params.height / ((double) params.width / target));
+ }
+ LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(scalledW, scalledH);
+ layoutParams.setMargins(0, (int) (metrics.density * 4), 0, (int) (metrics.density * 4));
+ viewHolder.image.setLayoutParams(layoutParams);
+ activity.loadBitmap(message, viewHolder.image);
+ viewHolder.image.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ openDownloadable(message);
+ }
+ });
+ viewHolder.image.setOnLongClickListener(openContextMenu);
+ }
+
+ private void loadMoreMessages(Conversation conversation) {
+ conversation.setLastClearHistory(0);
+ conversation.setHasMessagesLeftOnServer(true);
+ conversation.setFirstMamReference(null);
+ long timestamp = conversation.getLastMessageTransmitted();
+ if (timestamp == 0) {
+ timestamp = System.currentTimeMillis();
+ }
+ activity.setMessagesLoaded();
+ activity.xmppConnectionService.getMessageArchiveService().query(conversation, 0, timestamp);
+ Toast.makeText(activity, R.string.fetching_history_from_server,Toast.LENGTH_LONG).show();
+ }
+
+ @Override
+ public View getView(int position, View unused, ViewGroup parent) {
+ final Message message = getItem(position);
+ final boolean isInValidSession = message.isValidInSession();
+ final Conversation conversation = message.getConversation();
+ final Account account = conversation.getAccount();
+ final int type = getItemViewType(position);
+ ViewHolder viewHolder;
+ View view;
+ viewHolder = new ViewHolder();
+ switch (type) {
+ case SENT:
+ view = activity.getLayoutInflater().inflate(
+ R.layout.message_sent, parent, false);
+ viewHolder.message_box = (LinearLayout) view
+ .findViewById(R.id.message_box);
+ viewHolder.contact_picture = (ImageView) view
+ .findViewById(R.id.message_photo);
+ viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player);
+ viewHolder.download_button = (Button) view
+ .findViewById(R.id.download_button);
+ viewHolder.indicator = (ImageView) view
+ .findViewById(R.id.security_indicator);
+ viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
+ viewHolder.image = (ImageView) view
+ .findViewById(R.id.message_image);
+ viewHolder.messageBody = (TextView) view
+ .findViewById(R.id.message_body);
+ viewHolder.time = (TextView) view
+ .findViewById(R.id.message_time);
+ viewHolder.indicatorReceived = (ImageView) view
+ .findViewById(R.id.indicator_received);
+ viewHolder.indicatorRead = (ImageView) view
+ .findViewById(R.id.indicator_read);
+ break;
+ case RECEIVED:
+ view = activity.getLayoutInflater().inflate(
+ R.layout.message_received, parent, false);
+ viewHolder.message_box = (LinearLayout) view
+ .findViewById(R.id.message_box);
+ viewHolder.contact_picture = (ImageView) view
+ .findViewById(R.id.message_photo);
+ viewHolder.aw_player = (ViewGroup) view.findViewById(R.id.aw_player);
+ viewHolder.download_button = (Button) view
+ .findViewById(R.id.download_button);
+ viewHolder.indicator = (ImageView) view
+ .findViewById(R.id.security_indicator);
+ viewHolder.edit_indicator = (ImageView) view.findViewById(R.id.edit_indicator);
+ viewHolder.image = (ImageView) view
+ .findViewById(R.id.message_image);
+ viewHolder.messageBody = (TextView) view
+ .findViewById(R.id.message_body);
+ viewHolder.time = (TextView) view
+ .findViewById(R.id.message_time);
+ viewHolder.indicatorReceived = (ImageView) view
+ .findViewById(R.id.indicator_received);
+ viewHolder.encryption = (TextView) view.findViewById(R.id.message_encryption);
+ break;
+ case STATUS:
+ view = activity.getLayoutInflater().inflate(R.layout.message_status, parent, false);
+ viewHolder.contact_picture = (ImageView) view.findViewById(R.id.message_photo);
+ viewHolder.status_message = (TextView) view.findViewById(R.id.status_message);
+ viewHolder.load_more_messages = (Button) view.findViewById(R.id.load_more_messages);
+ break;
+ default:
+ view = new View(getContext());
+ viewHolder = null;
+ break;
+ }
+ view.setTag(viewHolder);
+ if (viewHolder == null) {
+ return view;
+ }
+
+
+ boolean darkBackground = (type == SENT && (!isInValidSession || !mUseWhiteBackground));
+
+ if (type == STATUS) {
+ if ("LOAD_MORE".equals(message.getBody())) {
+ viewHolder.status_message.setVisibility(View.GONE);
+ viewHolder.contact_picture.setVisibility(View.GONE);
+ viewHolder.load_more_messages.setVisibility(View.VISIBLE);
+ viewHolder.load_more_messages.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ loadMoreMessages(message.getConversation());
+ }
+ });
+ } else {
+ viewHolder.status_message.setVisibility(View.VISIBLE);
+ viewHolder.contact_picture.setVisibility(View.VISIBLE);
+ viewHolder.load_more_messages.setVisibility(View.GONE);
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ viewHolder.contact_picture.setImageBitmap(activity
+ .avatarService().get(conversation.getContact(),
+ activity.getPixel(32)));
+ viewHolder.contact_picture.setAlpha(0.5f);
+ }
+ viewHolder.status_message.setText(message.getBody());
+ }
+ return view;
+ } else {
+ loadAvatar(message, viewHolder.contact_picture);
+ }
+
+ viewHolder.contact_picture
+ .setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ if (MessageAdapter.this.mOnContactPictureClickedListener != null) {
+ MessageAdapter.this.mOnContactPictureClickedListener
+ .onContactPictureClicked(message);
+ }
+
+ }
+ });
+ viewHolder.contact_picture
+ .setOnLongClickListener(new OnLongClickListener() {
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (MessageAdapter.this.mOnContactPictureLongClickedListener != null) {
+ MessageAdapter.this.mOnContactPictureLongClickedListener
+ .onContactPictureLongClicked(message);
+ return true;
+ } else {
+ return false;
+ }
+ }
+ });
+
+ final Transferable transferable = message.getTransferable();
+ String mimeType = message.getMimeType();
+ if (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING) {
+ if (transferable.getStatus() == Transferable.STATUS_OFFER) {
+ displayDownloadableMessage(viewHolder,message,activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, message)));
+ } else if (transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
+ displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)));
+ } else {
+ displayInfoMessage(viewHolder, UIHelper.getMessagePreview(activity, message).first,darkBackground);
+ }
+ } else if (message.getType() == Message.TYPE_IMAGE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
+ displayImageMessage(viewHolder, message);
+ } else if (message.getType() == Message.TYPE_FILE && message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_DECRYPTION_FAILED) {
+ if (message.getFileParams().width > 0) {
+ displayImageMessage(viewHolder,message);
+ } else {
+ if (mimeType != null) {
+ if (message.getMimeType().startsWith("audio/")) {
+ displayAudioMessage(viewHolder, message, position);
+ } else displayOpenableMessage(viewHolder, message);
+ } else displayOpenableMessage(viewHolder, message);
+ }
+ } else if (message.getEncryption() == Message.ENCRYPTION_PGP) {
+ if (account.isPgpDecryptionServiceConnected()) {
+ if (!account.hasPendingPgpIntent(conversation)) {
+ displayInfoMessage(viewHolder, activity.getString(R.string.message_decrypting), darkBackground);
+ } else {
+ displayInfoMessage(viewHolder, activity.getString(R.string.pgp_message), darkBackground);
+ }
+ } else {
+ displayInfoMessage(viewHolder,activity.getString(R.string.install_openkeychain),darkBackground);
+ if (viewHolder != null) {
+ viewHolder.message_box
+ .setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ activity.showInstallPgpDialog();
+ }
+ });
+ }
+ }
+ } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) {
+ displayDecryptionFailed(viewHolder,darkBackground);
+ } else {
+ if (GeoHelper.isGeoUri(message.getBody())) {
+ displayLocationMessage(viewHolder,message);
+ } else if (message.bodyIsHeart()) {
+ displayHeartMessage(viewHolder, message.getBody().trim());
+ } else if (message.treatAsDownloadable() == Message.Decision.MUST ||
+ message.treatAsDownloadable() == Message.Decision.SHOULD) {
+ try {
+ URL url = new URL(message.getBody());
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize_on_host,
+ UIHelper.getFileDescriptionString(activity, message),
+ url.getHost()));
+ } catch (Exception e) {
+ displayDownloadableMessage(viewHolder,
+ message,
+ activity.getString(R.string.check_x_filesize,
+ UIHelper.getFileDescriptionString(activity, message)));
+ }
+ } else {
+ displayTextMessage(viewHolder, message, darkBackground);
+ }
+ }
+
+ if (type == RECEIVED) {
+ if(isInValidSession) {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received);
+ viewHolder.encryption.setVisibility(View.GONE);
+ } else {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
+ viewHolder.encryption.setVisibility(View.VISIBLE);
+ viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
+ }
+ }
+
+ if (type == SENT) {
+ if (mUseWhiteBackground) {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent_white);
+ } else {
+ viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_sent);
+ }
+ }
+
+ displayStatus(viewHolder, message, type, darkBackground);
+
+ return view;
+ }
+
+ public void openDownloadable(Message message) {
+ DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ if (!file.exists()) {
+ Toast.makeText(activity, R.string.file_deleted, Toast.LENGTH_SHORT).show();
+ return;
+ }
+ String mime = file.getMimeType();
+
+ if (mime.startsWith("image/")) {
+ Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class);
+ intent.putExtra("image", Uri.fromFile(file));
+ try {
+ activity.startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException e) {
+ //ignored
+ }
+ } else if (mime.startsWith("video/")) {
+ Intent intent = new Intent(getContext(), ShowFullscreenMessageActivity.class);
+ intent.putExtra("video", Uri.fromFile(file));
+ try {
+ activity.startActivity(intent);
+ return;
+ } catch (ActivityNotFoundException e) {
+ //ignored
+ }
+ }
+ Intent openIntent = new Intent(Intent.ACTION_VIEW);
+ if (mime == null) {
+ mime = "*/*";
+ }
+ openIntent.setDataAndType(Uri.fromFile(file), mime);
+ PackageManager manager = activity.getPackageManager();
+ List<ResolveInfo> infos = manager.queryIntentActivities(openIntent, 0);
+ if (infos.size() == 0) {
+ openIntent.setDataAndType(Uri.fromFile(file), "*/*");
+ }
+ try {
+ getContext().startActivity(openIntent);
+ return;
+ } catch (ActivityNotFoundException e) {
+ //ignored
+ }
+ Toast.makeText(activity, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
+
+ }
+
+ public void showLocation(Message message) {
+ for(Intent intent : GeoHelper.createGeoIntentsFromMessage(message)) {
+ if (intent.resolveActivity(getContext().getPackageManager()) != null) {
+ getContext().startActivity(intent);
+ return;
+ }
+ }
+ Toast.makeText(activity,R.string.no_application_found_to_display_location,Toast.LENGTH_SHORT).show();
+ }
+
+ public void updatePreferences() {
+ this.mIndicateReceived = activity.indicateReceived();
+ this.mUseWhiteBackground = activity.useWhiteBackground();
+ }
+
+ public interface OnContactPictureClicked {
+ void onContactPictureClicked(Message message);
+ }
+
+ public interface OnContactPictureLongClicked {
+ void onContactPictureLongClicked(Message message);
+ }
+
+ private static class ViewHolder {
+
+ protected LinearLayout message_box;
+ protected Button download_button;
+ protected ViewGroup aw_player;
+ protected ImageView image;
+ protected ImageView indicator;
+ protected ImageView indicatorReceived;
+ protected ImageView indicatorRead;
+ protected TextView time;
+ protected TextView messageBody;
+ protected ImageView contact_picture;
+ protected TextView status_message;
+ protected TextView encryption;
+ public Button load_more_messages;
+ public ImageView edit_indicator;
+ }
+
+ class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
+ private final WeakReference<ImageView> imageViewReference;
+ private Message message = null;
+
+ public BitmapWorkerTask(ImageView imageView) {
+ imageViewReference = new WeakReference<>(imageView);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Message... params) {
+ return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled());
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (bitmap != null && !isCancelled()) {
+ final ImageView imageView = imageViewReference.get();
+ if (imageView != null) {
+ imageView.setImageBitmap(bitmap);
+ imageView.setBackgroundColor(0x00000000);
+ }
+ }
+ }
+ }
+
+ public void loadAvatar(Message message, ImageView imageView) {
+ if (cancelPotentialWork(message, imageView)) {
+ final Bitmap bm = activity.avatarService().get(message, activity.getPixel(48), true);
+ if (bm != null) {
+ cancelPotentialWork(message, imageView);
+ imageView.setImageBitmap(bm);
+ imageView.setBackgroundColor(0x00000000);
+ } else {
+ imageView.setBackgroundColor(UIHelper.getColorForName(UIHelper.getMessageDisplayName(message)));
+ imageView.setImageDrawable(null);
+ final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
+ final AsyncDrawable asyncDrawable = new AsyncDrawable(activity.getResources(), null, task);
+ imageView.setImageDrawable(asyncDrawable);
+ try {
+ task.execute(message);
+ } catch (final RejectedExecutionException ignored) {
+ }
+ }
+ }
+ }
+
+ public static boolean cancelPotentialWork(Message message, ImageView imageView) {
+ final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
+
+ if (bitmapWorkerTask != null) {
+ final Message oldMessage = bitmapWorkerTask.message;
+ if (oldMessage == null || message != oldMessage) {
+ bitmapWorkerTask.cancel(true);
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
+ if (imageView != null) {
+ final Drawable drawable = imageView.getDrawable();
+ if (drawable instanceof AsyncDrawable) {
+ final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
+ return asyncDrawable.getBitmapWorkerTask();
+ }
+ }
+ return null;
+ }
+
+ static class AsyncDrawable extends BitmapDrawable {
+ private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+
+ public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
+ super(res, bitmap);
+ bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
+ }
+
+ public BitmapWorkerTask getBitmapWorkerTask() {
+ return bitmapWorkerTaskReference.get();
+ }
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java
new file mode 100644
index 000000000..2593ac204
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormBooleanFieldWrapper.java
@@ -0,0 +1,80 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.xmpp.forms.Field;
+
+public class FormBooleanFieldWrapper extends FormFieldWrapper {
+
+ protected CheckBox checkBox;
+
+ protected FormBooleanFieldWrapper(Context context, Field field) {
+ super(context, field);
+ checkBox = (CheckBox) view.findViewById(R.id.field);
+ checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ checkBox.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+ });
+ }
+
+ @Override
+ protected void setLabel(String label, boolean required) {
+ CheckBox checkBox = (CheckBox) view.findViewById(R.id.field);
+ checkBox.setText(createSpannableLabelString(label, required));
+ }
+
+ @Override
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ values.add(Boolean.toString(checkBox.isChecked()));
+ return values;
+ }
+
+ @Override
+ protected void setValues(List<String> values) {
+ if (values.size() == 0) {
+ checkBox.setChecked(false);
+ } else {
+ checkBox.setChecked(Boolean.parseBoolean(values.get(0)));
+ }
+ }
+
+ @Override
+ public boolean validates() {
+ if (checkBox.isChecked() || !field.isRequired()) {
+ return true;
+ } else {
+ checkBox.setError(context.getString(R.string.this_field_is_required));
+ checkBox.requestFocus();
+ return false;
+ }
+ }
+
+ @Override
+ public boolean edited() {
+ if (field.getValues().size() == 0) {
+ return checkBox.isChecked();
+ } else {
+ return super.edited();
+ }
+ }
+
+ @Override
+ protected int getLayoutResource() {
+ return R.layout.form_boolean;
+ }
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ checkBox.setEnabled(!readOnly);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java
new file mode 100644
index 000000000..c1318b9f1
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldFactory.java
@@ -0,0 +1,30 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+
+import java.util.Hashtable;
+
+import de.pixart.messenger.xmpp.forms.Field;
+
+
+
+public class FormFieldFactory {
+
+ private static final Hashtable<String, Class> typeTable = new Hashtable<>();
+
+ static {
+ typeTable.put("text-single", FormTextFieldWrapper.class);
+ typeTable.put("text-multi", FormTextFieldWrapper.class);
+ typeTable.put("text-private", FormTextFieldWrapper.class);
+ typeTable.put("jid-single", FormJidSingleFieldWrapper.class);
+ typeTable.put("boolean", FormBooleanFieldWrapper.class);
+ }
+
+ protected static FormFieldWrapper createFromField(Context context, Field field) {
+ Class clazz = typeTable.get(field.getType());
+ if (clazz == null) {
+ clazz = FormTextFieldWrapper.class;
+ }
+ return FormFieldWrapper.createFromField(clazz, context, field);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java
new file mode 100644
index 000000000..5182f3e7a
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormFieldWrapper.java
@@ -0,0 +1,93 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.view.LayoutInflater;
+import android.view.View;
+
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.xmpp.forms.Field;
+
+public abstract class FormFieldWrapper {
+
+ protected final Context context;
+ protected final Field field;
+ protected final View view;
+ protected OnFormFieldValuesEdited onFormFieldValuesEditedListener;
+
+ protected FormFieldWrapper(Context context, Field field) {
+ this.context = context;
+ this.field = field;
+ LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ this.view = inflater.inflate(getLayoutResource(), null);
+ String label = field.getLabel();
+ if (label == null) {
+ label = field.getFieldName();
+ }
+ setLabel(label, field.isRequired());
+ }
+
+ public final void submit() {
+ this.field.setValues(getValues());
+ }
+
+ public final View getView() {
+ return view;
+ }
+
+ protected abstract void setLabel(String label, boolean required);
+
+ abstract List<String> getValues();
+
+ protected abstract void setValues(List<String> values);
+
+ abstract boolean validates();
+
+ abstract protected int getLayoutResource();
+
+ abstract void setReadOnly(boolean readOnly);
+
+ protected SpannableString createSpannableLabelString(String label, boolean required) {
+ SpannableString spannableString = new SpannableString(label + (required ? " *" : ""));
+ if (required) {
+ int start = label.length();
+ int end = label.length() + 2;
+ spannableString.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), start, end, 0);
+ spannableString.setSpan(new ForegroundColorSpan(context.getResources().getColor(R.color.accent)), start, end, 0);
+ }
+ return spannableString;
+ }
+
+ protected void invokeOnFormFieldValuesEdited() {
+ if (this.onFormFieldValuesEditedListener != null) {
+ this.onFormFieldValuesEditedListener.onFormFieldValuesEdited();
+ }
+ }
+
+ public boolean edited() {
+ return !field.getValues().equals(getValues());
+ }
+
+ public void setOnFormFieldValuesEditedListener(OnFormFieldValuesEdited listener) {
+ this.onFormFieldValuesEditedListener = listener;
+ }
+
+ protected static <F extends FormFieldWrapper> FormFieldWrapper createFromField(Class<F> c, Context context, Field field) {
+ try {
+ F fieldWrapper = c.getDeclaredConstructor(Context.class, Field.class).newInstance(context,field);
+ fieldWrapper.setValues(field.getValues());
+ return fieldWrapper;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ public interface OnFormFieldValuesEdited {
+ void onFormFieldValuesEdited();
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java
new file mode 100644
index 000000000..8d37f259f
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormJidSingleFieldWrapper.java
@@ -0,0 +1,44 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+import android.text.InputType;
+
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.xmpp.forms.Field;
+import de.pixart.messenger.xmpp.jid.InvalidJidException;
+import de.pixart.messenger.xmpp.jid.Jid;
+
+public class FormJidSingleFieldWrapper extends FormTextFieldWrapper {
+
+ protected FormJidSingleFieldWrapper(Context context, Field field) {
+ super(context, field);
+ editText.setInputType(InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
+ editText.setHint(R.string.account_settings_example_jabber_id);
+ }
+
+ @Override
+ public boolean validates() {
+ String value = getValue();
+ if (!value.isEmpty()) {
+ try {
+ Jid.fromString(value);
+ } catch (InvalidJidException e) {
+ editText.setError(context.getString(R.string.invalid_jid));
+ editText.requestFocus();
+ return false;
+ }
+ }
+ return super.validates();
+ }
+
+ @Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(String value : values) {
+ builder.append(value);
+ }
+ editText.setText(builder.toString());
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java
new file mode 100644
index 000000000..cccfd1608
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormTextFieldWrapper.java
@@ -0,0 +1,97 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+import android.text.Editable;
+import android.text.InputType;
+import android.text.TextWatcher;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.pixart.messenger.R;
+import de.pixart.messenger.xmpp.forms.Field;
+
+public class FormTextFieldWrapper extends FormFieldWrapper {
+
+ protected EditText editText;
+
+ protected FormTextFieldWrapper(Context context, Field field) {
+ super(context, field);
+ editText = (EditText) view.findViewById(R.id.field);
+ editText.setSingleLine(!"text-multi".equals(field.getType()));
+ if ("text-private".equals(field.getType())) {
+ editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
+ }
+ editText.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ editText.setError(null);
+ invokeOnFormFieldValuesEdited();
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+ }
+ });
+ }
+
+ @Override
+ protected void setLabel(String label, boolean required) {
+ TextView textView = (TextView) view.findViewById(R.id.label);
+ textView.setText(createSpannableLabelString(label, required));
+ }
+
+ protected String getValue() {
+ return editText.getText().toString();
+ }
+
+ @Override
+ public List<String> getValues() {
+ List<String> values = new ArrayList<>();
+ for (String line : getValue().split("\\n")) {
+ if (line.length() > 0) {
+ values.add(line);
+ }
+ }
+ return values;
+ }
+
+ @Override
+ protected void setValues(List<String> values) {
+ StringBuilder builder = new StringBuilder("");
+ for(int i = 0; i < values.size(); ++i) {
+ builder.append(values.get(i));
+ if (i < values.size() - 1 && "text-multi".equals(field.getType())) {
+ builder.append("\n");
+ }
+ }
+ editText.setText(builder.toString());
+ }
+
+ @Override
+ public boolean validates() {
+ if (getValue().trim().length() > 0 || !field.isRequired()) {
+ return true;
+ } else {
+ editText.setError(context.getString(R.string.this_field_is_required));
+ editText.requestFocus();
+ return false;
+ }
+ }
+
+ @Override
+ protected int getLayoutResource() {
+ return R.layout.form_text;
+ }
+
+ @Override
+ void setReadOnly(boolean readOnly) {
+ editText.setEnabled(!readOnly);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java b/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java
new file mode 100644
index 000000000..05fa922b5
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/forms/FormWrapper.java
@@ -0,0 +1,72 @@
+package de.pixart.messenger.ui.forms;
+
+import android.content.Context;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import de.pixart.messenger.xmpp.forms.Data;
+import de.pixart.messenger.xmpp.forms.Field;
+
+public class FormWrapper {
+
+ private final LinearLayout layout;
+
+ private final Data form;
+
+ private final List<FormFieldWrapper> fieldWrappers = new ArrayList<>();
+
+ private FormWrapper(Context context, LinearLayout linearLayout, Data form) {
+ this.form = form;
+ this.layout = linearLayout;
+ this.layout.removeAllViews();
+ for(Field field : form.getFields()) {
+ FormFieldWrapper fieldWrapper = FormFieldFactory.createFromField(context,field);
+ if (fieldWrapper != null) {
+ layout.addView(fieldWrapper.getView());
+ fieldWrappers.add(fieldWrapper);
+ }
+ }
+ }
+
+ public Data submit() {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.submit();
+ }
+ this.form.submit();
+ return this.form;
+ }
+
+ public boolean validates() {
+ boolean validates = true;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ validates &= fieldWrapper.validates();
+ }
+ return validates;
+ }
+
+ public void setOnFormFieldValuesEditedListener(FormFieldWrapper.OnFormFieldValuesEdited listener) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setOnFormFieldValuesEditedListener(listener);
+ }
+ }
+
+ public void setReadOnly(boolean b) {
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ fieldWrapper.setReadOnly(b);
+ }
+ }
+
+ public boolean edited() {
+ boolean edited = false;
+ for(FormFieldWrapper fieldWrapper : fieldWrappers) {
+ edited |= fieldWrapper.edited();
+ }
+ return edited;
+ }
+
+ public static FormWrapper createInLayout(Context context, LinearLayout layout, Data form) {
+ return new FormWrapper(context, layout, form);
+ }
+}
diff --git a/src/main/java/de/pixart/messenger/ui/widget/Switch.java b/src/main/java/de/pixart/messenger/ui/widget/Switch.java
new file mode 100644
index 000000000..2bd4eda3d
--- /dev/null
+++ b/src/main/java/de/pixart/messenger/ui/widget/Switch.java
@@ -0,0 +1,68 @@
+package de.pixart.messenger.ui.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.ViewConfiguration;
+
+import com.kyleduo.switchbutton.SwitchButton;
+
+public class Switch extends SwitchButton {
+
+ private int mTouchSlop;
+ private int mClickTimeout;
+ private float mStartX;
+ private float mStartY;
+ private OnClickListener mOnClickListener;
+
+ public Switch(Context context) {
+ super(context);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
+ }
+
+ public Switch(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
+ }
+
+ public Switch(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout();
+ }
+
+ @Override
+ public void setOnClickListener(OnClickListener onClickListener) {
+ this.mOnClickListener = onClickListener;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ if (!isEnabled()) {
+ float deltaX = event.getX() - mStartX;
+ float deltaY = event.getY() - mStartY;
+ int action = event.getAction();
+ switch (action) {
+ case MotionEvent.ACTION_DOWN:
+ mStartX = event.getX();
+ mStartY = event.getY();
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ float time = event.getEventTime() - event.getDownTime();
+ if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) {
+ if (mOnClickListener != null) {
+ this.mOnClickListener.onClick(this);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ return true;
+ }
+ return super.onTouchEvent(event);
+ }
+}