/* * Copyright (C) 2015 Vincent Breitmoser * * 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 . */ package org.sufficientlysecure.keychain.ui.adapter; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; import java.util.List; import java.util.Date; import android.content.Context; import android.database.Cursor; import android.graphics.PorterDuff; import android.support.v4.widget.CursorAdapter; import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey; import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing; import org.sufficientlysecure.keychain.pgp.KeyRing; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.ui.util.Highlighter; import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils.State; public class KeyAdapter extends CursorAdapter { protected String mQuery; protected LayoutInflater mInflater; protected Context mContext; // These are the rows that we will retrieve. public static final String[] PROJECTION = new String[]{ KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.USER_ID, KeyRings.IS_REVOKED, KeyRings.IS_EXPIRED, KeyRings.VERIFIED, KeyRings.HAS_ANY_SECRET, KeyRings.HAS_DUPLICATE_USER_ID, KeyRings.FINGERPRINT, KeyRings.CREATION, KeyRings.HAS_ENCRYPT }; public static final int INDEX_MASTER_KEY_ID = 1; public static final int INDEX_USER_ID = 2; public static final int INDEX_IS_REVOKED = 3; public static final int INDEX_IS_EXPIRED = 4; public static final int INDEX_VERIFIED = 5; public static final int INDEX_HAS_ANY_SECRET = 6; public static final int INDEX_HAS_DUPLICATE_USER_ID = 7; public static final int INDEX_FINGERPRINT = 8; public static final int INDEX_CREATION = 9; public static final int INDEX_HAS_ENCRYPT = 10; public KeyAdapter(Context context, Cursor c, int flags) { super(context, c, flags); mContext = context; mInflater = LayoutInflater.from(context); } public void setSearchQuery(String query) { mQuery = query; } public static class KeyItemViewHolder { public View mView; public View mLayoutDummy; public View mLayoutData; public Long mMasterKeyId; public TextView mMainUserId; public TextView mMainUserIdRest; public TextView mCreationDate; public ImageView mStatus; public View mSlinger; public ImageButton mSlingerButton; public KeyItem mDisplayedItem; public KeyItemViewHolder(View view) { mView = view; mLayoutData = view.findViewById(R.id.key_list_item_data); mLayoutDummy = view.findViewById(R.id.key_list_item_dummy); mMainUserId = (TextView) view.findViewById(R.id.key_list_item_name); mMainUserIdRest = (TextView) view.findViewById(R.id.key_list_item_email); mStatus = (ImageView) view.findViewById(R.id.key_list_item_status_icon); mSlinger = view.findViewById(R.id.key_list_item_slinger_view); mSlingerButton = (ImageButton) view.findViewById(R.id.key_list_item_slinger_button); mCreationDate = (TextView) view.findViewById(R.id.key_list_item_creation); } public void setData(Context context, KeyItem item, Highlighter highlighter, boolean enabled) { mLayoutData.setVisibility(View.VISIBLE); mLayoutDummy.setVisibility(View.GONE); mDisplayedItem = item; { // set name and stuff, common to both key types KeyRing.UserId userIdSplit = item.mUserId; if (userIdSplit.name != null) { mMainUserId.setText(highlighter.highlight(userIdSplit.name)); } else { mMainUserId.setText(R.string.user_id_no_name); } if (userIdSplit.email != null) { mMainUserIdRest.setText(highlighter.highlight(userIdSplit.email)); mMainUserIdRest.setVisibility(View.VISIBLE); } else { mMainUserIdRest.setVisibility(View.GONE); } } // sort of a hack: if this item isn't enabled, we make it clickable // to intercept its click events. either way, no listener! mView.setClickable(!enabled); { // set edit button and status, specific by key type mMasterKeyId = item.mKeyId; int textColor; // Note: order is important! if (item.mIsRevoked) { KeyFormattingUtils .setStatusImage(context, mStatus, null, State.REVOKED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsExpired) { KeyFormattingUtils.setStatusImage(context, mStatus, null, State.EXPIRED, R.color.key_flag_gray); mStatus.setVisibility(View.VISIBLE); mSlinger.setVisibility(View.GONE); textColor = context.getResources().getColor(R.color.key_flag_gray); } else if (item.mIsSecret) { mStatus.setVisibility(View.GONE); if (mSlingerButton.hasOnClickListeners()) { mSlingerButton.setColorFilter( FormattingUtils.getColorFromAttr(context, R.attr.colorTertiaryText), PorterDuff.Mode.SRC_IN); mSlinger.setVisibility(View.VISIBLE); } else { mSlinger.setVisibility(View.GONE); } textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } else { // this is a public key - show if it's verified if (item.mIsVerified) { KeyFormattingUtils.setStatusImage(context, mStatus, State.VERIFIED); mStatus.setVisibility(View.VISIBLE); } else { KeyFormattingUtils.setStatusImage(context, mStatus, State.UNVERIFIED); mStatus.setVisibility(View.VISIBLE); } mSlinger.setVisibility(View.GONE); textColor = FormattingUtils.getColorFromAttr(context, R.attr.colorText); } if (!enabled) { textColor = context.getResources().getColor(R.color.key_flag_gray); } mMainUserId.setTextColor(textColor); mMainUserIdRest.setTextColor(textColor); if (item.mHasDuplicate) { String dateTime = DateUtils.formatDateTime(context, item.mCreation.getTime(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_ABBREV_MONTH); mCreationDate.setText(context.getString(R.string.label_key_created, dateTime)); mCreationDate.setTextColor(textColor); mCreationDate.setVisibility(View.VISIBLE); } else { mCreationDate.setVisibility(View.GONE); } } } /** Shows the "you have no keys yet" dummy view, and sets an OnClickListener. */ public void setDummy(OnClickListener listener) { // just reset everything to display the dummy layout mLayoutDummy.setVisibility(View.VISIBLE); mLayoutData.setVisibility(View.GONE); mSlinger.setVisibility(View.GONE); mStatus.setVisibility(View.GONE); mView.setClickable(false); mLayoutDummy.setOnClickListener(listener); } } public boolean isEnabled(Cursor cursor) { return true; } @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { View view = mInflater.inflate(R.layout.key_list_item, parent, false); KeyItemViewHolder holder = new KeyItemViewHolder(view); view.setTag(holder); return view; } @Override public void bindView(View view, Context context, Cursor cursor) { Highlighter highlighter = new Highlighter(context, mQuery); KeyItem item = new KeyItem(cursor); boolean isEnabled = isEnabled(cursor); KeyItemViewHolder h = (KeyItemViewHolder) view.getTag(); h.setData(context, item, highlighter, isEnabled); } public boolean isSecretAvailable(int id) { if (!mCursor.moveToPosition(id)) { throw new IllegalStateException("couldn't move cursor to position " + id); } return mCursor.getInt(INDEX_HAS_ANY_SECRET) != 0; } public long getMasterKeyId(int id) { if (!mCursor.moveToPosition(id)) { throw new IllegalStateException("couldn't move cursor to position " + id); } return mCursor.getLong(INDEX_MASTER_KEY_ID); } @Override public KeyItem getItem(int position) { Cursor c = getCursor(); if (c.isClosed() || !c.moveToPosition(position)) { return null; } return new KeyItem(c); } @Override public long getItemId(int position) { Cursor cursor = getCursor(); // prevent a crash on rapid cursor changes if (cursor != null && getCursor().isClosed()) { return 0L; } return super.getItemId(position); } // must be serializable for TokenCompleTextView state public static class KeyItem implements Serializable { public final String mUserIdFull; public final KeyRing.UserId mUserId; public final long mKeyId; public final boolean mHasDuplicate; public final boolean mHasEncrypt; public final Date mCreation; public final String mFingerprint; public final boolean mIsSecret, mIsRevoked, mIsExpired, mIsVerified; private KeyItem(Cursor cursor) { String userId = cursor.getString(INDEX_USER_ID); mUserId = KeyRing.splitUserId(userId); mUserIdFull = userId; mKeyId = cursor.getLong(INDEX_MASTER_KEY_ID); mHasDuplicate = cursor.getLong(INDEX_HAS_DUPLICATE_USER_ID) > 0; mHasEncrypt = cursor.getInt(INDEX_HAS_ENCRYPT) != 0; mCreation = new Date(cursor.getLong(INDEX_CREATION) * 1000); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( cursor.getBlob(INDEX_FINGERPRINT)); mIsSecret = cursor.getInt(INDEX_HAS_ANY_SECRET) != 0; mIsRevoked = cursor.getInt(INDEX_IS_REVOKED) > 0; mIsExpired = cursor.getInt(INDEX_IS_EXPIRED) > 0; mIsVerified = cursor.getInt(INDEX_VERIFIED) > 0; } public KeyItem(CanonicalizedPublicKeyRing ring) { CanonicalizedPublicKey key = ring.getPublicKey(); String userId = key.getPrimaryUserIdWithFallback(); mUserId = KeyRing.splitUserId(userId); mUserIdFull = userId; mKeyId = ring.getMasterKeyId(); mHasDuplicate = false; mHasEncrypt = key.getKeyRing().getEncryptIds().size() > 0; mCreation = key.getCreationTime(); mFingerprint = KeyFormattingUtils.convertFingerprintToHex( ring.getFingerprint()); mIsRevoked = key.isRevoked(); mIsExpired = key.isExpired(); // these two are actually "don't know"s mIsSecret = false; mIsVerified = false; } public String getReadableName() { if (mUserId.name != null) { return mUserId.name; } else { return mUserId.email; } } } public static String[] getProjectionWith(String[] projection) { List list = new ArrayList<>(); list.addAll(Arrays.asList(PROJECTION)); list.addAll(Arrays.asList(projection)); return list.toArray(new String[list.size()]); } }