1package eu.siacs.conversations.ui;
2
3import android.app.AlertDialog;
4import android.app.PendingIntent;
5import android.content.Context;
6import android.content.DialogInterface;
7import android.content.Intent;
8import android.content.IntentSender.SendIntentException;
9import android.content.SharedPreferences;
10import android.net.Uri;
11import android.os.Bundle;
12import android.preference.PreferenceManager;
13import android.provider.ContactsContract;
14import android.provider.ContactsContract.CommonDataKinds;
15import android.provider.ContactsContract.Contacts;
16import android.provider.ContactsContract.Intents;
17import android.view.LayoutInflater;
18import android.view.Menu;
19import android.view.MenuItem;
20import android.view.View;
21import android.view.View.OnClickListener;
22import android.widget.Button;
23import android.widget.CheckBox;
24import android.widget.CompoundButton;
25import android.widget.CompoundButton.OnCheckedChangeListener;
26import android.widget.ImageButton;
27import android.widget.LinearLayout;
28import android.widget.QuickContactBadge;
29import android.widget.TextView;
30
31import org.openintents.openpgp.util.OpenPgpUtils;
32import org.whispersystems.libaxolotl.IdentityKey;
33
34import java.util.List;
35
36import eu.siacs.conversations.R;
37import eu.siacs.conversations.crypto.PgpEngine;
38import eu.siacs.conversations.entities.Account;
39import eu.siacs.conversations.entities.Contact;
40import eu.siacs.conversations.entities.ListItem;
41import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
42import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
43import eu.siacs.conversations.utils.CryptoHelper;
44import eu.siacs.conversations.utils.UIHelper;
45import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
46import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
47import eu.siacs.conversations.xmpp.XmppConnection;
48import eu.siacs.conversations.xmpp.jid.InvalidJidException;
49import eu.siacs.conversations.xmpp.jid.Jid;
50
51public class ContactDetailsActivity extends XmppActivity implements OnAccountUpdate, OnRosterUpdate, OnUpdateBlocklist, OnKeyStatusUpdated {
52 public static final String ACTION_VIEW_CONTACT = "view_contact";
53
54 private Contact contact;
55 private DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
56
57 @Override
58 public void onClick(DialogInterface dialog, int which) {
59 xmppConnectionService.deleteContactOnServer(contact);
60 }
61 };
62 private OnCheckedChangeListener mOnSendCheckedChange = new OnCheckedChangeListener() {
63
64 @Override
65 public void onCheckedChanged(CompoundButton buttonView,
66 boolean isChecked) {
67 if (isChecked) {
68 if (contact
69 .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
70 xmppConnectionService.sendPresencePacket(contact
71 .getAccount(),
72 xmppConnectionService.getPresenceGenerator()
73 .sendPresenceUpdatesTo(contact));
74 } else {
75 contact.setOption(Contact.Options.PREEMPTIVE_GRANT);
76 }
77 } else {
78 contact.resetOption(Contact.Options.PREEMPTIVE_GRANT);
79 xmppConnectionService.sendPresencePacket(contact.getAccount(),
80 xmppConnectionService.getPresenceGenerator()
81 .stopPresenceUpdatesTo(contact));
82 }
83 }
84 };
85 private OnCheckedChangeListener mOnReceiveCheckedChange = new OnCheckedChangeListener() {
86
87 @Override
88 public void onCheckedChanged(CompoundButton buttonView,
89 boolean isChecked) {
90 if (isChecked) {
91 xmppConnectionService.sendPresencePacket(contact.getAccount(),
92 xmppConnectionService.getPresenceGenerator()
93 .requestPresenceUpdatesFrom(contact));
94 } else {
95 xmppConnectionService.sendPresencePacket(contact.getAccount(),
96 xmppConnectionService.getPresenceGenerator()
97 .stopPresenceUpdatesFrom(contact));
98 }
99 }
100 };
101 private Jid accountJid;
102 private Jid contactJid;
103 private TextView contactJidTv;
104 private TextView accountJidTv;
105 private TextView lastseen;
106 private CheckBox send;
107 private CheckBox receive;
108 private Button addContactButton;
109 private QuickContactBadge badge;
110 private LinearLayout keys;
111 private LinearLayout tags;
112 private boolean showDynamicTags;
113 private String messageFingerprint;
114
115 private DialogInterface.OnClickListener addToPhonebook = new DialogInterface.OnClickListener() {
116
117 @Override
118 public void onClick(DialogInterface dialog, int which) {
119 Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
120 intent.setType(Contacts.CONTENT_ITEM_TYPE);
121 intent.putExtra(Intents.Insert.IM_HANDLE, contact.getJid().toString());
122 intent.putExtra(Intents.Insert.IM_PROTOCOL,
123 CommonDataKinds.Im.PROTOCOL_JABBER);
124 intent.putExtra("finishActivityOnSaveCompleted", true);
125 ContactDetailsActivity.this.startActivityForResult(intent, 0);
126 }
127 };
128
129 private OnClickListener onBadgeClick = new OnClickListener() {
130
131 @Override
132 public void onClick(View v) {
133 if (contact.getSystemAccount() == null) {
134 AlertDialog.Builder builder = new AlertDialog.Builder(
135 ContactDetailsActivity.this);
136 builder.setTitle(getString(R.string.action_add_phone_book));
137 builder.setMessage(getString(R.string.add_phone_book_text,
138 contact.getJid()));
139 builder.setNegativeButton(getString(R.string.cancel), null);
140 builder.setPositiveButton(getString(R.string.add), addToPhonebook);
141 builder.create().show();
142 } else {
143 String[] systemAccount = contact.getSystemAccount().split("#");
144 long id = Long.parseLong(systemAccount[0]);
145 Uri uri = ContactsContract.Contacts.getLookupUri(id, systemAccount[1]);
146 Intent intent = new Intent(Intent.ACTION_VIEW);
147 intent.setData(uri);
148 startActivity(intent);
149 }
150 }
151 };
152
153 @Override
154 public void onRosterUpdate() {
155 refreshUi();
156 }
157
158 @Override
159 public void onAccountUpdate() {
160 refreshUi();
161 }
162
163 @Override
164 public void OnUpdateBlocklist(final Status status) {
165 refreshUi();
166 }
167
168 @Override
169 protected void refreshUiReal() {
170 invalidateOptionsMenu();
171 populateView();
172 }
173
174 @Override
175 protected String getShareableUri() {
176 if (contact != null) {
177 return contact.getShareableUri();
178 } else {
179 return "";
180 }
181 }
182
183 @Override
184 protected void onCreate(final Bundle savedInstanceState) {
185 super.onCreate(savedInstanceState);
186 if (getIntent().getAction().equals(ACTION_VIEW_CONTACT)) {
187 try {
188 this.accountJid = Jid.fromString(getIntent().getExtras().getString("account"));
189 } catch (final InvalidJidException ignored) {
190 }
191 try {
192 this.contactJid = Jid.fromString(getIntent().getExtras().getString("contact"));
193 } catch (final InvalidJidException ignored) {
194 }
195 }
196 this.messageFingerprint = getIntent().getStringExtra("fingerprint");
197 setContentView(R.layout.activity_contact_details);
198
199 contactJidTv = (TextView) findViewById(R.id.details_contactjid);
200 accountJidTv = (TextView) findViewById(R.id.details_account);
201 lastseen = (TextView) findViewById(R.id.details_lastseen);
202 send = (CheckBox) findViewById(R.id.details_send_presence);
203 receive = (CheckBox) findViewById(R.id.details_receive_presence);
204 badge = (QuickContactBadge) findViewById(R.id.details_contact_badge);
205 addContactButton = (Button) findViewById(R.id.add_contact_button);
206 addContactButton.setOnClickListener(new OnClickListener() {
207 @Override
208 public void onClick(View view) {
209 showAddToRosterDialog(contact);
210 }
211 });
212 keys = (LinearLayout) findViewById(R.id.details_contact_keys);
213 tags = (LinearLayout) findViewById(R.id.tags);
214 if (getActionBar() != null) {
215 getActionBar().setHomeButtonEnabled(true);
216 getActionBar().setDisplayHomeAsUpEnabled(true);
217 }
218
219 final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
220 this.showDynamicTags = preferences.getBoolean("show_dynamic_tags",false);
221 }
222
223 @Override
224 public boolean onOptionsItemSelected(final MenuItem menuItem) {
225 final AlertDialog.Builder builder = new AlertDialog.Builder(this);
226 builder.setNegativeButton(getString(R.string.cancel), null);
227 switch (menuItem.getItemId()) {
228 case android.R.id.home:
229 finish();
230 break;
231 case R.id.action_delete_contact:
232 builder.setTitle(getString(R.string.action_delete_contact))
233 .setMessage(
234 getString(R.string.remove_contact_text,
235 contact.getJid()))
236 .setPositiveButton(getString(R.string.delete),
237 removeFromRoster).create().show();
238 break;
239 case R.id.action_edit_contact:
240 if (contact.getSystemAccount() == null) {
241 quickEdit(contact.getDisplayName(), new OnValueEdited() {
242
243 @Override
244 public void onValueEdited(String value) {
245 contact.setServerName(value);
246 ContactDetailsActivity.this.xmppConnectionService
247 .pushContactToServer(contact);
248 populateView();
249 }
250 });
251 } else {
252 Intent intent = new Intent(Intent.ACTION_EDIT);
253 String[] systemAccount = contact.getSystemAccount().split("#");
254 long id = Long.parseLong(systemAccount[0]);
255 Uri uri = Contacts.getLookupUri(id, systemAccount[1]);
256 intent.setDataAndType(uri, Contacts.CONTENT_ITEM_TYPE);
257 intent.putExtra("finishActivityOnSaveCompleted", true);
258 startActivity(intent);
259 }
260 break;
261 case R.id.action_block:
262 BlockContactDialog.show(this, xmppConnectionService, contact);
263 break;
264 case R.id.action_unblock:
265 BlockContactDialog.show(this, xmppConnectionService, contact);
266 break;
267 }
268 return super.onOptionsItemSelected(menuItem);
269 }
270
271 @Override
272 public boolean onCreateOptionsMenu(final Menu menu) {
273 getMenuInflater().inflate(R.menu.contact_details, menu);
274 MenuItem block = menu.findItem(R.id.action_block);
275 MenuItem unblock = menu.findItem(R.id.action_unblock);
276 MenuItem edit = menu.findItem(R.id.action_edit_contact);
277 MenuItem delete = menu.findItem(R.id.action_delete_contact);
278 if (contact == null) {
279 return true;
280 }
281 final XmppConnection connection = contact.getAccount().getXmppConnection();
282 if (connection != null && connection.getFeatures().blocking()) {
283 if (this.contact.isBlocked()) {
284 block.setVisible(false);
285 } else {
286 unblock.setVisible(false);
287 }
288 } else {
289 unblock.setVisible(false);
290 block.setVisible(false);
291 }
292 if (!contact.showInRoster()) {
293 edit.setVisible(false);
294 delete.setVisible(false);
295 }
296 return true;
297 }
298
299 private void populateView() {
300 invalidateOptionsMenu();
301 setTitle(contact.getDisplayName());
302 if (contact.showInRoster()) {
303 send.setVisibility(View.VISIBLE);
304 receive.setVisibility(View.VISIBLE);
305 addContactButton.setVisibility(View.GONE);
306 send.setOnCheckedChangeListener(null);
307 receive.setOnCheckedChangeListener(null);
308
309 if (contact.getOption(Contact.Options.FROM)) {
310 send.setText(R.string.send_presence_updates);
311 send.setChecked(true);
312 } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
313 send.setChecked(false);
314 send.setText(R.string.send_presence_updates);
315 } else {
316 send.setText(R.string.preemptively_grant);
317 if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) {
318 send.setChecked(true);
319 } else {
320 send.setChecked(false);
321 }
322 }
323 if (contact.getOption(Contact.Options.TO)) {
324 receive.setText(R.string.receive_presence_updates);
325 receive.setChecked(true);
326 } else {
327 receive.setText(R.string.ask_for_presence_updates);
328 if (contact.getOption(Contact.Options.ASKING)) {
329 receive.setChecked(true);
330 } else {
331 receive.setChecked(false);
332 }
333 }
334 if (contact.getAccount().isOnlineAndConnected()) {
335 receive.setEnabled(true);
336 send.setEnabled(true);
337 } else {
338 receive.setEnabled(false);
339 send.setEnabled(false);
340 }
341
342 send.setOnCheckedChangeListener(this.mOnSendCheckedChange);
343 receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange);
344 } else {
345 addContactButton.setVisibility(View.VISIBLE);
346 send.setVisibility(View.GONE);
347 receive.setVisibility(View.GONE);
348 }
349
350 if (contact.isBlocked() && !this.showDynamicTags) {
351 lastseen.setText(R.string.contact_blocked);
352 } else {
353 lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.lastseen.time));
354 }
355
356 if (contact.getPresences().size() > 1) {
357 contactJidTv.setText(contact.getJid() + " ("
358 + contact.getPresences().size() + ")");
359 } else {
360 contactJidTv.setText(contact.getJid().toString());
361 }
362 accountJidTv.setText(getString(R.string.using_account, contact.getAccount().getJid().toBareJid()));
363 badge.setImageBitmap(avatarService().get(contact, getPixel(72)));
364 badge.setOnClickListener(this.onBadgeClick);
365
366 keys.removeAllViews();
367 boolean hasKeys = false;
368 LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
369 for(final String otrFingerprint : contact.getOtrFingerprints()) {
370 hasKeys = true;
371 View view = inflater.inflate(R.layout.contact_key, keys, false);
372 TextView key = (TextView) view.findViewById(R.id.key);
373 TextView keyType = (TextView) view.findViewById(R.id.key_type);
374 ImageButton removeButton = (ImageButton) view
375 .findViewById(R.id.button_remove);
376 removeButton.setVisibility(View.VISIBLE);
377 keyType.setText("OTR Fingerprint");
378 key.setText(CryptoHelper.prettifyFingerprint(otrFingerprint));
379 keys.addView(view);
380 removeButton.setOnClickListener(new OnClickListener() {
381
382 @Override
383 public void onClick(View v) {
384 confirmToDeleteFingerprint(otrFingerprint);
385 }
386 });
387 }
388 for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
389 contact.getAccount(), contact.getJid().toBareJid().toString())) {
390 boolean highlight = identityKey.getFingerprint().replaceAll("\\s", "").equals(messageFingerprint);
391 hasKeys |= addFingerprintRow(keys, contact.getAccount(), identityKey, highlight);
392 }
393 if (contact.getPgpKeyId() != 0) {
394 hasKeys = true;
395 View view = inflater.inflate(R.layout.contact_key, keys, false);
396 TextView key = (TextView) view.findViewById(R.id.key);
397 TextView keyType = (TextView) view.findViewById(R.id.key_type);
398 keyType.setText("PGP Key ID");
399 key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
400 view.setOnClickListener(new OnClickListener() {
401
402 @Override
403 public void onClick(View v) {
404 PgpEngine pgp = ContactDetailsActivity.this.xmppConnectionService
405 .getPgpEngine();
406 if (pgp != null) {
407 PendingIntent intent = pgp.getIntentForKey(contact);
408 if (intent != null) {
409 try {
410 startIntentSenderForResult(
411 intent.getIntentSender(), 0, null, 0,
412 0, 0);
413 } catch (SendIntentException e) {
414
415 }
416 }
417 }
418 }
419 });
420 keys.addView(view);
421 }
422 if (hasKeys) {
423 keys.setVisibility(View.VISIBLE);
424 } else {
425 keys.setVisibility(View.GONE);
426 }
427
428 List<ListItem.Tag> tagList = contact.getTags();
429 if (tagList.size() == 0 || !this.showDynamicTags) {
430 tags.setVisibility(View.GONE);
431 } else {
432 tags.setVisibility(View.VISIBLE);
433 tags.removeAllViewsInLayout();
434 for(final ListItem.Tag tag : tagList) {
435 final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag,tags,false);
436 tv.setText(tag.getName());
437 tv.setBackgroundColor(tag.getColor());
438 tags.addView(tv);
439 }
440 }
441 }
442
443 protected void confirmToDeleteFingerprint(final String fingerprint) {
444 AlertDialog.Builder builder = new AlertDialog.Builder(this);
445 builder.setTitle(R.string.delete_fingerprint);
446 builder.setMessage(R.string.sure_delete_fingerprint);
447 builder.setNegativeButton(R.string.cancel, null);
448 builder.setPositiveButton(R.string.delete,
449 new android.content.DialogInterface.OnClickListener() {
450
451 @Override
452 public void onClick(DialogInterface dialog, int which) {
453 if (contact.deleteOtrFingerprint(fingerprint)) {
454 populateView();
455 xmppConnectionService.syncRosterToDisk(contact.getAccount());
456 }
457 }
458
459 });
460 builder.create().show();
461 }
462
463 @Override
464 public void onBackendConnected() {
465 if ((accountJid != null) && (contactJid != null)) {
466 Account account = xmppConnectionService
467 .findAccountByJid(accountJid);
468 if (account == null) {
469 return;
470 }
471 this.contact = account.getRoster().getContact(contactJid);
472 populateView();
473 }
474 }
475
476 @Override
477 public void onKeyStatusUpdated() {
478 refreshUi();
479 }
480}