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