diff options
8 files changed, 405 insertions, 36 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 47a6cab1d..020b808b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -41,6 +41,11 @@ public class SaveKeyringParcel implements Parcelable {      public SaveKeyringParcel(long masterKeyId, byte[] fingerprint) {          mMasterKeyId = masterKeyId;          mFingerprint = fingerprint; +        addUserIds = new ArrayList<String>(); +        addSubKeys = new ArrayList<SubkeyAdd>(); +        changeSubKeys = new ArrayList<SubkeyChange>(); +        revokeUserIds = new ArrayList<String>(); +        revokeSubKeys = new ArrayList<Long>();      }      // performance gain for using Parcelable here would probably be negligible, diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java index db00d5f69..7ac229b91 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java @@ -149,7 +149,6 @@ public class CertifyKeyActivity extends ActionBarActivity implements          getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);          getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this); -      }      static final String USER_IDS_SELECTION = UserIds.IS_REVOKED + " = 0"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index b3516f5d6..dac19a0c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -20,6 +20,9 @@ package org.sufficientlysecure.keychain.ui;  import android.database.Cursor;  import android.net.Uri;  import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.Messenger;  import android.support.v4.app.LoaderManager;  import android.support.v4.content.CursorLoader;  import android.support.v4.content.Loader; @@ -28,14 +31,22 @@ import android.view.LayoutInflater;  import android.view.View;  import android.view.View.OnClickListener;  import android.view.ViewGroup; +import android.widget.AdapterView;  import android.widget.ListView; +import android.widget.Toast;  import org.sufficientlysecure.keychain.Constants;  import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.compatibility.DialogFragmentWorkaround;  import org.sufficientlysecure.keychain.helper.ActionBarHelper; +import org.sufficientlysecure.keychain.pgp.WrappedSecretKeyRing;  import org.sufficientlysecure.keychain.provider.KeychainContract; +import org.sufficientlysecure.keychain.provider.ProviderHelper; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel;  import org.sufficientlysecure.keychain.ui.adapter.SubkeysAdapter;  import org.sufficientlysecure.keychain.ui.adapter.UserIdsAdapter; +import org.sufficientlysecure.keychain.ui.dialog.AddUserIdDialogFragment; +import org.sufficientlysecure.keychain.ui.dialog.EditUserIdDialogFragment;  import org.sufficientlysecure.keychain.util.Log;  public class EditKeyFragment extends LoaderFragment implements @@ -43,8 +54,13 @@ public class EditKeyFragment extends LoaderFragment implements      public static final String ARG_DATA_URI = "uri"; -    private ListView mUserIds; -    private ListView mKeys; +    private ListView mUserIdsList; +    private ListView mKeysList; +    private ListView mUserIdsAddedList; +    private ListView mKeysAddedList; +    private View mChangePassphrase; +    private View mAddUserId; +    private View mAddKey;      private static final int LOADER_ID_USER_IDS = 0;      private static final int LOADER_ID_KEYS = 1; @@ -54,6 +70,8 @@ public class EditKeyFragment extends LoaderFragment implements      private Uri mDataUri; +    private SaveKeyringParcel mSaveKeyringParcel; +      /**       * Creates new instance of this fragment       */ @@ -73,9 +91,13 @@ public class EditKeyFragment extends LoaderFragment implements          View root = super.onCreateView(inflater, superContainer, savedInstanceState);          View view = inflater.inflate(R.layout.edit_key_fragment, getContainer()); -        mUserIds = (ListView) view.findViewById(R.id.edit_key_user_ids); -        mKeys = (ListView) view.findViewById(R.id.edit_key_keys); -//        mActionEdit = view.findViewById(R.id.view_key_action_edit); +        mUserIdsList = (ListView) view.findViewById(R.id.edit_key_user_ids); +        mKeysList = (ListView) view.findViewById(R.id.edit_key_keys); +        mUserIdsAddedList = (ListView) view.findViewById(R.id.edit_key_user_ids_added); +        mKeysAddedList = (ListView) view.findViewById(R.id.edit_key_keys_added); +        mChangePassphrase = view.findViewById(R.id.edit_key_action_change_passphrase); +        mAddUserId = view.findViewById(R.id.edit_key_action_add_user_id); +        mAddKey = view.findViewById(R.id.edit_key_action_add_key);          return root;      } @@ -84,7 +106,6 @@ public class EditKeyFragment extends LoaderFragment implements      public void onActivityCreated(Bundle savedInstanceState) {          super.onActivityCreated(savedInstanceState); -          // Inflate a "Done"/"Cancel" custom action bar view          ActionBarHelper.setTwoButtonView(((ActionBarActivity) getActivity()).getSupportActionBar(),                  R.string.btn_save, R.drawable.ic_action_save, @@ -114,23 +135,108 @@ public class EditKeyFragment extends LoaderFragment implements          loadData(dataUri);      } +    private void editUserId(final String userId) { +        Handler returnHandler = new Handler() { +            @Override +            public void handleMessage(Message message) { +                switch (message.what) { +                    case EditUserIdDialogFragment.MESSAGE_CHANGE_PRIMARY_USER_ID: +                        // toggle +                        if (mSaveKeyringParcel.changePrimaryUserId != null +                                && mSaveKeyringParcel.changePrimaryUserId.equals(userId)) { +                            mSaveKeyringParcel.changePrimaryUserId = null; +                        } else { +                            mSaveKeyringParcel.changePrimaryUserId = userId; +                        } +                        break; +                    case EditUserIdDialogFragment.MESSAGE_REVOKE: +                        // toggle +                        if (mSaveKeyringParcel.revokeUserIds.contains(userId)) { +                            mSaveKeyringParcel.revokeUserIds.remove(userId); +                        } else { +                            mSaveKeyringParcel.revokeUserIds.add(userId); +                        } +                        break; +                } +                getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); +            } +        }; + +        // Create a new Messenger for the communication back +        final Messenger messenger = new Messenger(returnHandler); + +        DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { +            public void run() { +                EditUserIdDialogFragment dialogFragment = +                        EditUserIdDialogFragment.newInstance(messenger); + +                dialogFragment.show(getActivity().getSupportFragmentManager(), "editUserIdDialog"); +            } +        }); +    } + +    private void addUserId() { +        Handler returnHandler = new Handler() { +            @Override +            public void handleMessage(Message message) { +                if (message.what == AddUserIdDialogFragment.MESSAGE_OK) { + +                } +//                getLoaderManager().restartLoader(LOADER_ID_USER_IDS, null, EditKeyFragment.this); +            } +        }; + +        // Create a new Messenger for the communication back +        final Messenger messenger = new Messenger(returnHandler); + +        DialogFragmentWorkaround.INTERFACE.runnableRunDelayed(new Runnable() { +            public void run() { +                AddUserIdDialogFragment dialogFragment = +                        AddUserIdDialogFragment.newInstance(messenger); + +                dialogFragment.show(getActivity().getSupportFragmentManager(), "addUserIdDialog"); +            } +        }); +    } +      private void loadData(Uri dataUri) {          mDataUri = dataUri;          Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString()); -//        mActionEncrypt.setOnClickListener(new View.OnClickListener() { -//            @Override -//            public void onClick(View v) { -//                encrypt(mDataUri); -//            } -//        }); +        try { +            Uri secretUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri); +            WrappedSecretKeyRing keyRing = +                    new ProviderHelper(getActivity()).getWrappedSecretKeyRing(secretUri); +            mSaveKeyringParcel = new SaveKeyringParcel(keyRing.getMasterKeyId(), +                    keyRing.getUncachedKeyRing().getFingerprint()); +        } catch (ProviderHelper.NotFoundException e) { +            Log.e(Constants.TAG, "Keyring not found: " + e.getMessage(), e); +            Toast.makeText(getActivity(), R.string.error_no_secret_key_found, Toast.LENGTH_SHORT).show(); +            getActivity().finish(); +        } + +        mAddUserId.setOnClickListener(new View.OnClickListener() { +            @Override +            public void onClick(View v) { +                addUserId(); +            } +        }); + +        mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0, mSaveKeyringParcel); +        mUserIdsList.setAdapter(mUserIdsAdapter); + +        mUserIdsList.setOnItemClickListener(new AdapterView.OnItemClickListener() { +            @Override +            public void onItemClick(AdapterView<?> parent, View view, int position, long id) { +                String userId = mUserIdsAdapter.getUserId(position); +                editUserId(userId); +            } +        }); -        mUserIdsAdapter = new UserIdsAdapter(getActivity(), null, 0); -        mUserIds.setAdapter(mUserIdsAdapter);          mKeysAdapter = new SubkeysAdapter(getActivity(), null, 0); -        mKeys.setAdapter(mKeysAdapter); +        mKeysList.setAdapter(mKeysAdapter);          // Prepare the loaders. Either re-connect with an existing ones,          // or start new ones. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java index 77da1d0e3..7a7acfe89 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/adapter/UserIdsAdapter.java @@ -122,21 +122,23 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC          TextView vAddress = (TextView) view.findViewById(R.id.address);          TextView vComment = (TextView) view.findViewById(R.id.comment);          ImageView vVerified = (ImageView) view.findViewById(R.id.certified); +        ImageView vHasChanges = (ImageView) view.findViewById(R.id.has_changes); -        String[] userId = KeyRing.splitUserId(cursor.getString(mIndexUserId)); -        if (userId[0] != null) { -            vName.setText(userId[0]); +        String userId = cursor.getString(mIndexUserId); +        String[] splitUserId = KeyRing.splitUserId(userId); +        if (splitUserId[0] != null) { +            vName.setText(splitUserId[0]);          } else {              vName.setText(R.string.user_id_no_name);          } -        if (userId[1] != null) { -            vAddress.setText(userId[1]); +        if (splitUserId[1] != null) { +            vAddress.setText(splitUserId[1]);              vAddress.setVisibility(View.VISIBLE);          } else {              vAddress.setVisibility(View.GONE);          } -        if (userId[2] != null) { -            vComment.setText(userId[2]); +        if (splitUserId[2] != null) { +            vComment.setText(splitUserId[2]);              vComment.setVisibility(View.VISIBLE);          } else {              vComment.setVisibility(View.GONE); @@ -144,9 +146,33 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC          // show small star icon for primary user ids          boolean isPrimary = cursor.getInt(mIsPrimary) != 0; +        boolean isRevoked = cursor.getInt(mIsRevoked) > 0; -        if (cursor.getInt(mIsRevoked) > 0) { +        // for edit key +        if (mSaveKeyringParcel != null) { +            boolean changeUserId = (mSaveKeyringParcel.changePrimaryUserId != null +                    && mSaveKeyringParcel.changePrimaryUserId.equals(userId)); +            boolean revoke = (mSaveKeyringParcel.revokeUserIds.contains(userId)); +            if (changeUserId) { +                isPrimary = !isPrimary; +            } +            if (revoke) { +                if (!isRevoked) { +                    isRevoked = true; +                } +            } + +            if (changeUserId || revoke) { +                vHasChanges.setVisibility(View.VISIBLE); +            } else { +                vHasChanges.setVisibility(View.GONE); +            } +        } else { +            vHasChanges.setVisibility(View.GONE); +        } + +        if (isRevoked) {              // set revocation icon (can this even be primary?)              vVerified.setImageResource(R.drawable.key_certify_revoke); @@ -213,6 +239,11 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC          return result;      } +    public String getUserId(int position) { +        mCursor.moveToPosition(position); +        return mCursor.getString(mIndexUserId); +    } +      @Override      public View newView(Context context, Cursor cursor, ViewGroup parent) {          View view = mInflater.inflate(R.layout.view_key_userids_item, null); @@ -224,7 +255,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC      // Disable selection of items for lists without checkboxes, http://stackoverflow.com/a/4075045      @Override      public boolean areAllItemsEnabled() { -        if (mCheckStates == null) { +        if (mCheckStates == null && mSaveKeyringParcel == null) {              return false;          } else {              return super.areAllItemsEnabled(); @@ -234,7 +265,7 @@ public class UserIdsAdapter extends CursorAdapter implements AdapterView.OnItemC      // Disable selection of items for lists without checkboxes, http://stackoverflow.com/a/4075045      @Override      public boolean isEnabled(int position) { -        if (mCheckStates == null) { +        if (mCheckStates == null && mSaveKeyringParcel == null) {              return false;          } else {              return super.isEnabled(position); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java new file mode 100644 index 000000000..db7c38e71 --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddUserIdDialogFragment.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class AddUserIdDialogFragment extends DialogFragment { +    private static final String ARG_MESSENGER = "messenger"; + +    public static final int MESSAGE_OK = 1; + +    private Messenger mMessenger; + +    /** +     * Creates new instance of this dialog fragment +     */ +    public static AddUserIdDialogFragment newInstance(Messenger messenger) { +        AddUserIdDialogFragment frag = new AddUserIdDialogFragment(); +        Bundle args = new Bundle(); +        args.putParcelable(ARG_MESSENGER, messenger); + +        frag.setArguments(args); + +        return frag; +    } + +    /** +     * Creates dialog +     */ +    @Override +    public Dialog onCreateDialog(Bundle savedInstanceState) { +        mMessenger = getArguments().getParcelable(ARG_MESSENGER); + +        CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity()); +//        CharSequence[] array = {"change to primary user id", "revoke"}; +// +//        builder.setTitle("select action!"); +//        builder.setItems(array, new DialogInterface.OnClickListener() { +// +//            @Override +//            public void onClick(DialogInterface dialog, int which) { +//                switch (which) { +//                    case 0: +//                        sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null); +//                        break; +//                    case 1: +//                        sendMessageToHandler(MESSAGE_REVOKE, null); +//                        break; +//                    default: +//                        break; +//                } +//            } +//        }); +//        builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { +//            @Override +//            public void onClick(DialogInterface dialog, int id) { +//                dismiss(); +//            } +//        }); + +        return builder.show(); +    } + +    /** +     * Send message back to handler which is initialized in a activity +     * +     * @param what Message integer you want to send +     */ +    private void sendMessageToHandler(Integer what, Bundle data) { +        Message msg = Message.obtain(); +        msg.what = what; +        if (data != null) { +            msg.setData(data); +        } + +        try { +            mMessenger.send(msg); +        } catch (RemoteException e) { +            Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); +        } catch (NullPointerException e) { +            Log.w(Constants.TAG, "Messenger is null!", e); +        } +    } +} diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java new file mode 100644 index 000000000..f0ca73f0d --- /dev/null +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/EditUserIdDialogFragment.java @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2014 Dominik Schürmann <dominik@dominikschuermann.de> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + +package org.sufficientlysecure.keychain.ui.dialog; + +import android.app.Dialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Message; +import android.os.Messenger; +import android.os.RemoteException; +import android.support.v4.app.DialogFragment; + +import org.sufficientlysecure.keychain.Constants; +import org.sufficientlysecure.keychain.util.Log; + +public class EditUserIdDialogFragment extends DialogFragment { +    private static final String ARG_MESSENGER = "messenger"; + +    public static final int MESSAGE_CHANGE_PRIMARY_USER_ID = 1; +    public static final int MESSAGE_REVOKE = 2; + +    private Messenger mMessenger; + +    /** +     * Creates new instance of this dialog fragment +     */ +    public static EditUserIdDialogFragment newInstance(Messenger messenger) { +        EditUserIdDialogFragment frag = new EditUserIdDialogFragment(); +        Bundle args = new Bundle(); +        args.putParcelable(ARG_MESSENGER, messenger); + +        frag.setArguments(args); + +        return frag; +    } + +    /** +     * Creates dialog +     */ +    @Override +    public Dialog onCreateDialog(Bundle savedInstanceState) { +        mMessenger = getArguments().getParcelable(ARG_MESSENGER); + +        CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(getActivity()); +        CharSequence[] array = {"change to primary user id", "revoke"}; + +        builder.setTitle("select action!"); +        builder.setItems(array, new DialogInterface.OnClickListener() { + +            @Override +            public void onClick(DialogInterface dialog, int which) { +                switch (which) { +                    case 0: +                        sendMessageToHandler(MESSAGE_CHANGE_PRIMARY_USER_ID, null); +                        break; +                    case 1: +                        sendMessageToHandler(MESSAGE_REVOKE, null); +                        break; +                    default: +                        break; +                } +            } +        }); +        builder.setNegativeButton("cancel", new DialogInterface.OnClickListener() { +            @Override +            public void onClick(DialogInterface dialog, int id) { +                dismiss(); +            } +        }); + +        return builder.show(); +    } + +    /** +     * Send message back to handler which is initialized in a activity +     * +     * @param what Message integer you want to send +     */ +    private void sendMessageToHandler(Integer what, Bundle data) { +        Message msg = Message.obtain(); +        msg.what = what; +        if (data != null) { +            msg.setData(data); +        } + +        try { +            mMessenger.send(msg); +        } catch (RemoteException e) { +            Log.w(Constants.TAG, "Exception sending message, Is handler present?", e); +        } catch (NullPointerException e) { +            Log.w(Constants.TAG, "Messenger is null!", e); +        } +    } +} diff --git a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml index 9d63ea38a..f652269e5 100644 --- a/OpenKeychain/src/main/res/layout/edit_key_fragment.xml +++ b/OpenKeychain/src/main/res/layout/edit_key_fragment.xml @@ -13,7 +13,6 @@              style="@style/SectionHeader"              android:layout_width="wrap_content"              android:layout_height="0dp" -            android:layout_marginBottom="4dp"              android:layout_marginTop="8dp"              android:text="Passphrase"              android:layout_weight="1" /> @@ -37,7 +36,6 @@              style="@style/SectionHeader"              android:layout_width="wrap_content"              android:layout_height="0dp" -            android:layout_marginBottom="4dp"              android:layout_marginTop="8dp"              android:text="@string/section_user_ids"              android:layout_weight="1" /> @@ -45,9 +43,12 @@          <org.sufficientlysecure.keychain.ui.widget.FixedListView              android:id="@+id/edit_key_user_ids"              android:layout_width="match_parent" -            android:layout_height="0dp" -            android:layout_marginBottom="4dp" -            android:layout_weight="1" /> +            android:layout_height="wrap_content" /> + +        <org.sufficientlysecure.keychain.ui.widget.FixedListView +            android:id="@+id/edit_key_user_ids_added" +            android:layout_width="match_parent" +            android:layout_height="wrap_content" />          <View              android:layout_width="match_parent" @@ -55,7 +56,7 @@              android:background="?android:attr/listDivider" />          <TextView -            android:id="@+id/edit_key_add_user_id" +            android:id="@+id/edit_key_action_add_user_id"              android:paddingLeft="8dp"              android:paddingRight="8dp"              android:textAppearance="?android:attr/textAppearanceMedium" @@ -73,15 +74,18 @@              style="@style/SectionHeader"              android:layout_width="wrap_content"              android:layout_height="wrap_content" -            android:layout_marginBottom="4dp"              android:layout_marginTop="8dp"              android:text="@string/section_keys" />          <org.sufficientlysecure.keychain.ui.widget.FixedListView              android:id="@+id/edit_key_keys"              android:layout_width="match_parent" -            android:layout_height="wrap_content" -            android:layout_marginBottom="8dp" /> +            android:layout_height="wrap_content" /> + +        <org.sufficientlysecure.keychain.ui.widget.FixedListView +            android:id="@+id/edit_key_keys_added" +            android:layout_width="match_parent" +            android:layout_height="wrap_content" />          <View              android:layout_width="match_parent" diff --git a/OpenKeychain/src/main/res/layout/view_key_userids_item.xml b/OpenKeychain/src/main/res/layout/view_key_userids_item.xml index 03e619aff..f812127a9 100644 --- a/OpenKeychain/src/main/res/layout/view_key_userids_item.xml +++ b/OpenKeychain/src/main/res/layout/view_key_userids_item.xml @@ -6,10 +6,17 @@      android:orientation="horizontal"      android:singleLine="true"> -    <CheckBox +    <ImageView +        android:id="@+id/has_changes"          android:layout_width="wrap_content"          android:layout_height="match_parent" +        android:minWidth="10dp" +        android:background="@color/emphasis" /> + +    <CheckBox          android:id="@+id/checkBox" +        android:layout_width="wrap_content" +        android:layout_height="match_parent"          android:clickable="false"          android:focusable="false" />  | 
