Refactor trust key ui and show in account details

Andreas Straub created

Refactored the trust key row UI element so it can be used in multiple
places. It now also uses a slider to toggle the trust state, and the
redundant trust state description was removed.

EditAccountActivity now shows the keys of other devices associated with
that account.

Change summary

build.gradle                                                        |  1 
src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java | 49 
src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java    | 22 
src/main/java/eu/siacs/conversations/ui/XmppActivity.java           | 79 
src/main/res/layout/activity_edit_account.xml                       | 33 
src/main/res/layout/contact_key.xml                                 | 20 
src/main/res/values/strings.xml                                     |  2 
src/main/res/values/styles.xml                                      |  6 
8 files changed, 154 insertions(+), 58 deletions(-)

Detailed changes

build.gradle 🔗

@@ -38,6 +38,7 @@ dependencies {
 	compile 'me.leolin:ShortcutBadger:1.1.1@aar'
 	compile 'com.kyleduo.switchbutton:library:1.2.8'
 	compile 'org.whispersystems:axolotl-android:1.3.4'
+	compile 'com.kyleduo.switchbutton:library:1.2.8'
 }
 
 android {

src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java 🔗

@@ -35,7 +35,6 @@ import java.util.List;
 
 import eu.siacs.conversations.R;
 import eu.siacs.conversations.crypto.PgpEngine;
-import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.ListItem;
@@ -381,53 +380,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
 		for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
 				contact.getAccount(), contact.getJid().toBareJid().toString())) {
 			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);
-			TextView keyTrust = (TextView) view.findViewById(R.id.key_trust);
-			ImageButton removeButton = (ImageButton) view
-					.findViewById(R.id.button_remove);
-			ImageButton trustButton = (ImageButton) view
-					.findViewById(R.id.button_trust);
-			final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
-			final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
-			final Jid bareJid = contactJid.toBareJid();
-			AxolotlService.SQLiteAxolotlStore.Trust trust = contact.getAccount().getAxolotlService()
-					.getFingerprintTrust(fingerprint);
-			switch (trust) {
-				case TRUSTED:
-					removeButton.setVisibility(View.VISIBLE);
-					//Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting remove button visible!");
-					break;
-				case UNDECIDED:
-				case UNTRUSTED:
-					//Log.d(Config.LOGTAG, AxolotlService.getLogprefix(contact.getAccount()) + "Setting trust button visible!");
-					trustButton.setVisibility(View.VISIBLE);
-					break;
-			}
-			keyType.setText("Axolotl Fingerprint");
-			key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
-			keyTrust.setText(trust.toString());
-			keyTrust.setVisibility(View.VISIBLE);
-			keys.addView(view);
-			removeButton.setOnClickListener(new OnClickListener() {
-				@Override
-				public void onClick(View v) {
-					axolotlService.setFingerprintTrust(fingerprint,
-							AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
-					refreshUi();
-					xmppConnectionService.updateConversationUi();
-				}
-			});
-			trustButton.setOnClickListener(new OnClickListener() {
-				@Override
-				public void onClick(View v) {
-					axolotlService.setFingerprintTrust(fingerprint,
-							AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED);
-					refreshUi();
-					xmppConnectionService.updateConversationUi();
-				}
-			});
+			addFingerprintRow(keys, contact.getAccount(), identityKey);
 		}
 		if (contact.getPgpKeyId() != 0) {
 			hasKeys = true;

src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java 🔗

@@ -26,6 +26,8 @@ import android.widget.TableLayout;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import org.whispersystems.libaxolotl.IdentityKey;
+
 import java.util.Set;
 
 import eu.siacs.conversations.R;
@@ -69,6 +71,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 	private ImageButton mAxolotlFingerprintToClipboardButton;
 	private ImageButton mWipeAxolotlPepButton;
 	private ImageButton mRegenerateAxolotlKeyButton;
+	private LinearLayout keys;
+	private LinearLayout keysCard;
 
 	private Jid jidToEdit;
 	private Account mAccount;
@@ -329,6 +333,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 		this.mAxolotlDevicelist = (TextView) findViewById(R.id.axolotl_devicelist);
 		this.mAxolotlDevicelistBox = (RelativeLayout) findViewById(R.id.axolotl_devices_box);
 		this.mWipeAxolotlPepButton = (ImageButton) findViewById(R.id.action_wipe_axolotl_pep);
+		this.keysCard = (LinearLayout) findViewById(R.id.other_device_keys_card);
+		this.keys = (LinearLayout) findViewById(R.id.other_device_keys);
 		this.mSaveButton = (Button) findViewById(R.id.save_button);
 		this.mCancelButton = (Button) findViewById(R.id.cancel_button);
 		this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener);
@@ -568,6 +574,22 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
 			} else {
 				this.mAxolotlFingerprintBox.setVisibility(View.GONE);
 			}
+			final IdentityKey ownKey = mAccount.getAxolotlService().getOwnPublicKey();
+			boolean hasKeys = false;
+			keys.removeAllViews();
+			for(final IdentityKey identityKey : xmppConnectionService.databaseBackend.loadIdentityKeys(
+					mAccount, mAccount.getJid().toBareJid().toString())) {
+				if(ownKey.equals(identityKey)) {
+					continue;
+				}
+				hasKeys = true;
+				addFingerprintRow(keys, mAccount, identityKey);
+			}
+			if (hasKeys) {
+				keysCard.setVisibility(View.VISIBLE);
+			} else {
+				keysCard.setVisibility(View.GONE);
+			}
 		} else {
 			if (this.mAccount.errorStatus()) {
 				this.mAccountJid.setError(getString(this.mAccount.getStatus().getReadableId()));

src/main/java/eu/siacs/conversations/ui/XmppActivity.java 🔗

@@ -43,8 +43,11 @@ 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;
@@ -53,9 +56,12 @@ 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 com.kyleduo.switchbutton.SwitchButton;
 
 import net.java.otr4j.session.SessionID;
 
+import org.whispersystems.libaxolotl.IdentityKey;
+
 import java.io.FileNotFoundException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
@@ -65,6 +71,7 @@ import java.util.concurrent.RejectedExecutionException;
 
 import eu.siacs.conversations.Config;
 import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.axolotl.AxolotlService;
 import eu.siacs.conversations.entities.Account;
 import eu.siacs.conversations.entities.Contact;
 import eu.siacs.conversations.entities.Conversation;
@@ -74,6 +81,8 @@ import eu.siacs.conversations.entities.Presences;
 import eu.siacs.conversations.services.AvatarService;
 import eu.siacs.conversations.services.XmppConnectionService;
 import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
+import eu.siacs.conversations.ui.widget.Switch;
+import eu.siacs.conversations.utils.CryptoHelper;
 import eu.siacs.conversations.utils.ExceptionHelper;
 import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
 import eu.siacs.conversations.xmpp.jid.InvalidJidException;
@@ -588,6 +597,76 @@ public abstract class XmppActivity extends Activity {
 		builder.create().show();
 	}
 
+	protected void addFingerprintRow(LinearLayout keys, final Account account, IdentityKey identityKey) {
+		final String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
+		final AxolotlService.SQLiteAxolotlStore.Trust trust = account.getAxolotlService()
+				.getFingerprintTrust(fingerprint);
+		addFingerprintRowWithListeners(keys, account, identityKey, trust, true,
+				new CompoundButton.OnCheckedChangeListener() {
+					@Override
+					public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+						if (isChecked != (trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED)) {
+							account.getAxolotlService().setFingerprintTrust(fingerprint,
+									(isChecked) ? AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED :
+											AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
+						}
+						refreshUi();
+						xmppConnectionService.updateAccountUi();
+						xmppConnectionService.updateConversationUi();
+					}
+				},
+				new View.OnClickListener() {
+					@Override
+					public void onClick(View v) {
+						account.getAxolotlService().setFingerprintTrust(fingerprint,
+								AxolotlService.SQLiteAxolotlStore.Trust.UNTRUSTED);
+						refreshUi();
+						xmppConnectionService.updateAccountUi();
+						xmppConnectionService.updateConversationUi();
+					}
+				}
+
+		);
+	}
+
+	protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account,
+	                                              IdentityKey identityKey,
+	                                              AxolotlService.SQLiteAxolotlStore.Trust trust,
+	                                              boolean showTag,
+	                                              CompoundButton.OnCheckedChangeListener
+			                                             onCheckedChangeListener,
+	                                              View.OnClickListener onClickListener) {
+		View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
+		TextView key = (TextView) view.findViewById(R.id.key);
+		TextView keyType = (TextView) view.findViewById(R.id.key_type);
+		Switch trustToggle = (Switch) view.findViewById(R.id.tgl_trust);
+		trustToggle.setVisibility(View.VISIBLE);
+		trustToggle.setOnCheckedChangeListener(onCheckedChangeListener);
+		trustToggle.setOnClickListener(onClickListener);
+
+		switch (trust) {
+			case UNTRUSTED:
+			case TRUSTED:
+				trustToggle.setChecked(trust == AxolotlService.SQLiteAxolotlStore.Trust.TRUSTED, false);
+				trustToggle.setEnabled(true);
+				break;
+			case UNDECIDED:
+				trustToggle.setChecked(false, false);
+				trustToggle.setEnabled(false);
+				break;
+		}
+
+		if (showTag) {
+			keyType.setText(getString(R.string.axolotl_fingerprint));
+		} else {
+			keyType.setVisibility(View.GONE);
+		}
+
+		key.setText(CryptoHelper.prettifyFingerprint(identityKey.getFingerprint()));
+		keys.addView(view);
+
+	}
+
 	public void selectPresence(final Conversation conversation,
 			final OnPresenceSelected listener) {
 		final Contact contact = conversation.getContact();

src/main/res/layout/activity_edit_account.xml 🔗

@@ -368,7 +368,7 @@
                            android:layout_height="wrap_content"
                            android:textColor="@color/black54"
                            android:textSize="?attr/TextSizeInfo"
-                           android:text="@string/axolotl_fingerprint"/>
+                           android:text="@string/this_device_axolotl_fingerprint"/>
                    </LinearLayout>
 
                    <LinearLayout
@@ -444,6 +444,37 @@
 
                </RelativeLayout>
             </LinearLayout>
+            <LinearLayout
+                android:id="@+id/other_device_keys_card"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="@dimen/activity_horizontal_margin"
+                android:layout_marginRight="@dimen/activity_horizontal_margin"
+                android:layout_marginTop="@dimen/activity_vertical_margin"
+                android:layout_marginBottom="@dimen/activity_vertical_margin"
+                android:background="@drawable/infocard_border"
+                android:orientation="vertical"
+                android:padding="@dimen/infocard_padding"
+                android:visibility="gone">
+
+                <TextView
+                    android:id="@+id/other_device_keys_title"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:textColor="@color/black87"
+                    android:textSize="?attr/TextSizeHeadline"
+                    android:textStyle="bold"
+                    android:text="@string/other_devices"/>
+
+                <LinearLayout
+                    android:id="@+id/other_device_keys"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:divider="?android:dividerHorizontal"
+                    android:orientation="vertical"
+                    android:showDividers="middle" >
+                </LinearLayout>
+            </LinearLayout>
         </LinearLayout>
     </ScrollView>
 

src/main/res/layout/contact_key.xml 🔗

@@ -4,10 +4,10 @@
     android:layout_height="match_parent" >
 
     <RelativeLayout
+        android:id="@+id/key_data"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentLeft="true"
-        android:layout_toLeftOf="@+id/button_remove"
         android:padding="8dp" >
 
         <TextView
@@ -16,6 +16,7 @@
             android:layout_height="wrap_content"
             android:textColor="@color/black87"
             android:layout_alignParentLeft="true"
+            android:layout_toLeftOf="@+id/tgl_trust"
             android:textSize="?attr/TextSizeBody"
             android:typeface="monospace" />
 
@@ -37,27 +38,28 @@
             android:visibility="gone"
             android:textColor="@color/black54"
             android:textSize="?attr/TextSizeInfo"/>
-    </RelativeLayout>
 
     <ImageButton
         android:id="@+id/button_remove"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
+        android:layout_toRightOf="@+id/key"
         android:layout_centerVertical="true"
         android:background="?android:selectableItemBackground"
         android:padding="@dimen/image_button_padding"
         android:src="?attr/icon_remove"
-        android:visibility="invisible" />
+        android:visibility="gone" />
 
-    <ImageButton
-        android:id="@+id/button_trust"
+
+    <eu.siacs.conversations.ui.widget.Switch
+        android:id="@+id/tgl_trust"
+        android:visibility="invisible"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_alignParentRight="true"
         android:layout_centerVertical="true"
-        android:background="?android:selectableItemBackground"
-        android:padding="@dimen/image_button_padding"
-        android:src="?attr/icon_done"
-        android:visibility="invisible" />
+        style="@style/MaterialDesignButton"/>
+
+    </RelativeLayout>
 </RelativeLayout>

src/main/res/values/strings.xml 🔗

@@ -209,6 +209,8 @@
 	<string name="your_fingerprint">Your fingerprint</string>
 	<string name="otr_fingerprint">OTR fingerprint</string>
 	<string name="axolotl_fingerprint">Axolotl fingerprint</string>
+	<string name="this_device_axolotl_fingerprint">Own Axolotl fingerprint</string>
+	<string name="other_devices">Other devices</string>
 	<string name="axolotl_devicelist">Other own Axolotl Devices</string>
 	<string name="verify">Verify</string>
 	<string name="decrypt">Decrypt</string>

src/main/res/values/styles.xml 🔗

@@ -18,4 +18,10 @@
         <item name="android:padding">16dp</item>
     </style>
 
+    <style name="MaterialDesignButton" parent="MD">
+        <item name="android:layout_width">wrap_content</item>
+        <item name="android:layout_height">wrap_content</item>
+        <item name="android:padding">16dp</item>
+    </style>
+
 </resources>