Merge remote-tracking branch 'origin/master' into wrapped-key-ring

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyActivity.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ViewCertActivity.java
	OpenKeychain/src/main/res/values/strings.xml
This commit is contained in:
Vincent Breitmoser
2014-05-14 16:02:28 +02:00
505 changed files with 4694 additions and 5904 deletions

View File

@@ -26,10 +26,11 @@ import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.NavUtils;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
@@ -40,7 +41,6 @@ import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
@@ -64,7 +64,7 @@ import java.util.ArrayList;
*/
public class CertifyKeyActivity extends ActionBarActivity implements
SelectSecretKeyLayoutFragment.SelectSecretKeyCallback, LoaderManager.LoaderCallbacks<Cursor> {
private BootstrapButton mSignButton;
private View mSignButton;
private CheckBox mUploadKeyCheckbox;
private Spinner mSelectKeyserverSpinner;
@@ -86,20 +86,16 @@ public class CertifyKeyActivity extends ActionBarActivity implements
setContentView(R.layout.certify_key_activity);
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setHomeButtonEnabled(false);
mSelectKeyFragment = (SelectSecretKeyLayoutFragment) getSupportFragmentManager()
.findFragmentById(R.id.sign_key_select_key_fragment);
mSelectKeyFragment.setCallback(this);
mSelectKeyFragment.setFilterCertify(true);
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
mSelectKeyserverSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
.getKeyServers());
.getKeyServers()
);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
mSelectKeyserverSpinner.setAdapter(adapter);
@@ -122,14 +118,14 @@ public class CertifyKeyActivity extends ActionBarActivity implements
}
});
mSignButton = (BootstrapButton) findViewById(R.id.sign_key_sign_button);
mSignButton = findViewById(R.id.sign_key_sign_button);
mSignButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mPubKeyId != 0) {
if (mMasterKeyId == 0) {
mSelectKeyFragment.setError(getString(R.string.select_key_to_sign));
mSelectKeyFragment.setError(getString(R.string.select_key_to_certify));
} else {
initiateSigning();
}
@@ -145,7 +141,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
}
Log.e(Constants.TAG, "uri: " + mDataUri);
mUserIds = (ListView) findViewById(R.id.user_ids);
mUserIds = (ListView) findViewById(R.id.view_key_user_ids);
mUserIdsAdapter = new ViewKeyUserIdsAdapter(this, null, 0, true);
mUserIds.setAdapter(mUserIdsAdapter);
@@ -201,7 +197,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
byte[] fingerprintBlob = data.getBlob(INDEX_FINGERPRINT);
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
((TextView) findViewById(R.id.fingerprint))
((TextView) findViewById(R.id.view_key_fingerprint))
.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
}
break;
@@ -318,7 +314,7 @@ public class CertifyKeyActivity extends ActionBarActivity implements
// fill values for this action
Bundle data = new Bundle();
Spinner keyServer = (Spinner) findViewById(R.id.sign_key_keyserver);
Spinner keyServer = (Spinner) findViewById(R.id.upload_key_keyserver);
String server = (String) keyServer.getSelectedItem();
data.putString(KeychainIntentService.UPLOAD_KEY_SERVER, server);
@@ -359,4 +355,17 @@ public class CertifyKeyActivity extends ActionBarActivity implements
public void onKeySelected(long secretKeyId) {
mMasterKeyId = secretKeyId;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
Intent viewIntent = NavUtils.getParentActivityIntent(this);
viewIntent.setData(KeyRings.buildGenericKeyRingUri(mDataUri));
NavUtils.navigateUpTo(this, viewIntent);
return true;
}
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -27,7 +27,6 @@ import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.pgp.PgpHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
@@ -68,9 +67,6 @@ public class DecryptActivity extends DrawerActivity {
setContentView(R.layout.decrypt_activity);
// set actionbar without home button if called from another app
ActionBarHelper.setBackButton(this);
initView();
setupDrawerNavigation(savedInstanceState);

View File

@@ -54,7 +54,7 @@ public class DecryptFileFragment extends DecryptFragment {
private EditText mFilename;
private CheckBox mDeleteAfter;
private BootstrapButton mBrowse;
private BootstrapButton mDecryptButton;
private View mDecryptButton;
private String mInputFilename = null;
private String mOutputFilename = null;
@@ -71,7 +71,7 @@ public class DecryptFileFragment extends DecryptFragment {
mFilename = (EditText) view.findViewById(R.id.decrypt_file_filename);
mBrowse = (BootstrapButton) view.findViewById(R.id.decrypt_file_browse);
mDeleteAfter = (CheckBox) view.findViewById(R.id.decrypt_file_delete_after_decryption);
mDecryptButton = (BootstrapButton) view.findViewById(R.id.decrypt_file_action_decrypt);
mDecryptButton = view.findViewById(R.id.decrypt_file_action_decrypt);
mBrowse.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
FileHelper.openFile(DecryptFileFragment.this, mFilename.getText().toString(), "*/*",

View File

@@ -192,7 +192,7 @@ public class DecryptFragment extends Fragment {
mLookupKey.setVisibility(View.GONE);
// successful decryption-only
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_blue));
mResultLayout.setBackgroundColor(getResources().getColor(R.color.result_purple));
mResultText.setText(R.string.decrypt_result_decrypted);
}
}

View File

@@ -47,8 +47,8 @@ public class DecryptMessageFragment extends DecryptFragment {
// view
private EditText mMessage;
private BootstrapButton mDecryptButton;
private BootstrapButton mDecryptFromCLipboardButton;
private View mDecryptButton;
private View mDecryptFromCLipboardButton;
// model
private String mCiphertext;
@@ -61,8 +61,8 @@ public class DecryptMessageFragment extends DecryptFragment {
View view = inflater.inflate(R.layout.decrypt_message_fragment, container, false);
mMessage = (EditText) view.findViewById(R.id.message);
mDecryptButton = (BootstrapButton) view.findViewById(R.id.action_decrypt);
mDecryptFromCLipboardButton = (BootstrapButton) view.findViewById(R.id.action_decrypt_from_clipboard);
mDecryptButton = view.findViewById(R.id.action_decrypt);
mDecryptFromCLipboardButton = view.findViewById(R.id.action_decrypt_from_clipboard);
mDecryptButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -108,11 +108,11 @@ public class DecryptMessageFragment extends DecryptFragment {
mCiphertext = matcher.group(1);
decryptStart(null);
} else {
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
.show();
}
} else {
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_INFO)
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT)
.show();
}
}

View File

@@ -60,6 +60,7 @@ import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.service.PassphraseCacheService;
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
import org.sufficientlysecure.keychain.ui.dialog.CustomAlertDialogBuilder;
import org.sufficientlysecure.keychain.ui.dialog.PassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.SetPassphraseDialogFragment;
import org.sufficientlysecure.keychain.ui.widget.Editor;
@@ -502,7 +503,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
int curID = 0;
for (String userID : userIDs) {
if (userID.equals("") && (!userID.equals(originalIDs.get(curID)) || newIDs.get(curID))) {
AlertDialog.Builder alert = new AlertDialog.Builder(
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivity.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
@@ -525,7 +526,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
);
alert.setCancelable(false);
alert.create().show();
alert.show();
return;
}
curID++;
@@ -614,7 +615,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
private void cancelClicked() {
if (needsSaving()) { //ask if we want to save
AlertDialog.Builder alert = new AlertDialog.Builder(
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(
EditKeyActivity.this);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
@@ -637,7 +638,7 @@ public class EditKeyActivity extends ActionBarActivity implements EditorListener
}
});
alert.setCancelable(false);
alert.create().show();
alert.show();
} else {
setResult(RESULT_CANCELED);
finish();

View File

@@ -27,7 +27,6 @@ import android.widget.Toast;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.helper.FileHelper;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.util.Log;
@@ -145,26 +144,28 @@ public class EncryptActivity extends DrawerActivity implements
setContentView(R.layout.encrypt_activity);
// set actionbar without home button if called from another app
ActionBarHelper.setBackButton(this);
initView();
setupDrawerNavigation(savedInstanceState);
// if called with an intent action, do not init drawer navigation
if (ACTION_ENCRYPT.equals(getIntent().getAction())) {
// TODO: back button to key?
} else {
setupDrawerNavigation(savedInstanceState);
}
// Handle intent actions
handleActions(getIntent());
mTabsAdapterMode.addTab(EncryptAsymmetricFragment.class,
mAsymmetricFragmentBundle, getString(R.string.label_asymmetric));
mAsymmetricFragmentBundle, getString(R.string.label_asymmetric));
mTabsAdapterMode.addTab(EncryptSymmetricFragment.class,
mSymmetricFragmentBundle, getString(R.string.label_symmetric));
mSymmetricFragmentBundle, getString(R.string.label_symmetric));
mViewPagerMode.setCurrentItem(mSwitchToMode);
mTabsAdapterContent.addTab(EncryptMessageFragment.class,
mMessageFragmentBundle, getString(R.string.label_message));
mMessageFragmentBundle, getString(R.string.label_message));
mTabsAdapterContent.addTab(EncryptFileFragment.class,
mFileFragmentBundle, getString(R.string.label_file));
mFileFragmentBundle, getString(R.string.label_file));
mViewPagerContent.setCurrentItem(mSwitchToContent);
}
@@ -217,9 +218,9 @@ public class EncryptActivity extends DrawerActivity implements
// preselect keys given by intent
mAsymmetricFragmentBundle.putLongArray(EncryptAsymmetricFragment.ARG_ENCRYPTION_KEY_IDS,
encryptionKeyIds);
encryptionKeyIds);
mAsymmetricFragmentBundle.putLong(EncryptAsymmetricFragment.ARG_SIGNATURE_KEY_ID,
signatureKeyId);
signatureKeyId);
mSwitchToMode = PAGER_MODE_ASYMMETRIC;
/**
@@ -241,9 +242,10 @@ public class EncryptActivity extends DrawerActivity implements
} else {
Log.e(Constants.TAG,
"Direct binary data without actual file in filesystem is not supported " +
"by Intents. Please use the Remote Service API!");
Toast.makeText(this, R.string.error_only_files_are_supported, Toast.LENGTH_LONG)
.show();
"by Intents. Please use the Remote Service API!"
);
Toast.makeText(this, R.string.error_only_files_are_supported,
Toast.LENGTH_LONG).show();
// end activity
finish();
}

View File

@@ -220,9 +220,6 @@ public class EncryptAsymmetricFragment extends Fragment {
private void selectPublicKeys() {
Intent intent = new Intent(getActivity(), SelectPublicKeyActivity.class);
Vector<Long> keyIds = new Vector<Long>();
if (mSecretKeyId != 0) {
keyIds.add(mSecretKeyId);
}
if (mEncryptionKeyIds != null && mEncryptionKeyIds.length > 0) {
for (int i = 0; i < mEncryptionKeyIds.length; ++i) {
keyIds.add(mEncryptionKeyIds[i]);

View File

@@ -67,7 +67,7 @@ public class EncryptFileFragment extends Fragment {
private CheckBox mDeleteAfter = null;
private CheckBox mShareAfter = null;
private BootstrapButton mBrowse = null;
private BootstrapButton mEncryptFile;
private View mEncryptFile;
private FileDialogFragment mFileDialog;
@@ -92,7 +92,7 @@ public class EncryptFileFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_file_fragment, container, false);
mEncryptFile = (BootstrapButton) view.findViewById(R.id.action_encrypt_file);
mEncryptFile = view.findViewById(R.id.action_encrypt_file);
mEncryptFile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

View File

@@ -28,9 +28,8 @@ import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
@@ -46,9 +45,9 @@ import org.sufficientlysecure.keychain.util.Log;
public class EncryptMessageFragment extends Fragment {
public static final String ARG_TEXT = "text";
private EditText mMessage = null;
private BootstrapButton mEncryptShare;
private BootstrapButton mEncryptClipboard;
private TextView mMessage = null;
private View mEncryptShare;
private View mEncryptClipboard;
private EncryptActivityInterface mEncryptInterface;
@@ -70,9 +69,9 @@ public class EncryptMessageFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.encrypt_message_fragment, container, false);
mMessage = (EditText) view.findViewById(R.id.message);
mEncryptClipboard = (BootstrapButton) view.findViewById(R.id.action_encrypt_clipboard);
mEncryptShare = (BootstrapButton) view.findViewById(R.id.action_encrypt_share);
mMessage = (TextView) view.findViewById(R.id.message);
mEncryptClipboard = view.findViewById(R.id.action_encrypt_clipboard);
mEncryptShare = view.findViewById(R.id.action_encrypt_share);
mEncryptClipboard.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {

View File

@@ -45,7 +45,7 @@ public class HelpAboutFragment extends Fragment {
HtmlTextView aboutTextView = (HtmlTextView) view.findViewById(R.id.help_about_text);
// load html from raw resource (Parsing handled by HtmlTextView library)
aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about);
aboutTextView.setHtmlFromRawResource(getActivity(), R.raw.help_about, true);
// no flickering when clicking textview for Android < 4
aboutTextView.setTextColor(getResources().getColor(android.R.color.black));

View File

@@ -24,7 +24,9 @@ import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
import org.sufficientlysecure.keychain.util.SlidingTabLayout;
public class HelpActivity extends ActionBarActivity {
public static final String EXTRA_SELECTED_TAB = "selected_tab";
@@ -37,25 +39,27 @@ public class HelpActivity extends ActionBarActivity {
public static final int TAB_ABOUT = 5;
ViewPager mViewPager;
TabsAdapter mTabsAdapter;
private PagerTabStripAdapter mTabsAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.help_activity);
mViewPager = (ViewPager) findViewById(R.id.pager);
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setDisplayHomeAsUpEnabled(false);
actionBar.setHomeButtonEnabled(false);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mTabsAdapter = new TabsAdapter(this, mViewPager);
setContentView(R.layout.help_activity);
int selectedTab = 0;
mViewPager = (ViewPager) findViewById(R.id.pager);
SlidingTabLayout slidingTabLayout =
(SlidingTabLayout) findViewById(R.id.sliding_tab_layout);
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
int selectedTab = TAB_START;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
@@ -63,30 +67,36 @@ public class HelpActivity extends ActionBarActivity {
Bundle startBundle = new Bundle();
startBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_start);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_start)),
HelpHtmlFragment.class, startBundle, (selectedTab == TAB_START));
mTabsAdapter.addTab(HelpHtmlFragment.class, startBundle,
getString(R.string.help_tab_start));
Bundle faqBundle = new Bundle();
faqBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_faq);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_faq)),
HelpHtmlFragment.class, faqBundle, (selectedTab == TAB_FAQ));
mTabsAdapter.addTab(HelpHtmlFragment.class, faqBundle,
getString(R.string.help_tab_faq));
Bundle wotBundle = new Bundle();
wotBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_wot);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_wot)),
HelpHtmlFragment.class, wotBundle, (selectedTab == TAB_WOT));
mTabsAdapter.addTab(HelpHtmlFragment.class, wotBundle,
getString(R.string.help_tab_wot));
Bundle nfcBundle = new Bundle();
nfcBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_nfc_beam);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_nfc_beam)),
HelpHtmlFragment.class, nfcBundle, (selectedTab == TAB_NFC));
mTabsAdapter.addTab(HelpHtmlFragment.class, nfcBundle,
getString(R.string.help_tab_nfc_beam));
Bundle changelogBundle = new Bundle();
changelogBundle.putInt(HelpHtmlFragment.ARG_HTML_FILE, R.raw.help_changelog);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_changelog)),
HelpHtmlFragment.class, changelogBundle, (selectedTab == TAB_CHANGELOG));
mTabsAdapter.addTab(HelpHtmlFragment.class, changelogBundle,
getString(R.string.help_tab_changelog));
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.help_tab_about)),
HelpAboutFragment.class, null, (selectedTab == TAB_ABOUT));
mTabsAdapter.addTab(HelpAboutFragment.class, null,
getString(R.string.help_tab_about));
// NOTE: must be after adding the tabs!
slidingTabLayout.setViewPager(mViewPager);
// switch to tab selected by extra
mViewPager.setCurrentItem(selectedTab);
}
}

View File

@@ -66,7 +66,7 @@ public class HelpHtmlFragment extends Fragment {
scroller.addView(text);
// load html from raw resource (Parsing handled by HtmlTextView library)
text.setHtmlFromRawResource(getActivity(), mHtmlFile);
text.setHtmlFromRawResource(getActivity(), mHtmlFile, true);
// no flickering when clicking textview for Android < 4
text.setTextColor(getResources().getColor(android.R.color.black));

View File

@@ -38,16 +38,14 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.ActionBarHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.service.KeychainIntentService;
import org.sufficientlysecure.keychain.service.KeychainIntentServiceHandler;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.ui.dialog.BadImportKeyDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
@@ -62,6 +60,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
+ "IMPORT_KEY_FROM_KEYSERVER";
public static final String ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_KEY_SERVER_AND_RETURN";
public static final String ACTION_IMPORT_KEY_FROM_KEYBASE = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_KEYBASE";
// Actions for internal use only:
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
@@ -85,20 +85,22 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
private ImportKeysListFragment mListFragment;
private String[] mNavigationStrings;
private Fragment mCurrentFragment;
private BootstrapButton mImportButton;
private View mImportButton;
private static final Class[] NAVIGATION_CLASSES = new Class[]{
ImportKeysServerFragment.class,
ImportKeysFileFragment.class,
ImportKeysQrCodeFragment.class,
ImportKeysClipboardFragment.class,
ImportKeysNFCFragment.class
ImportKeysNFCFragment.class,
ImportKeysKeybaseFragment.class
};
private static final int NAV_SERVER = 0;
private static final int NAV_FILE = 1;
private static final int NAV_QR_CODE = 2;
private static final int NAV_CLIPBOARD = 3;
private static final int NAV_NFC = 4;
private static final int NAV_KEYBASE = 5;
private int mCurrentNavPosition = -1;
@@ -108,7 +110,7 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
setContentView(R.layout.import_keys_activity);
mImportButton = (BootstrapButton) findViewById(R.id.import_import);
mImportButton = findViewById(R.id.import_import);
mImportButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
@@ -121,7 +123,6 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
if (ACTION_IMPORT_KEY_FROM_KEYSERVER_AND_RETURN.equals(getIntent().getAction())) {
setTitle(R.string.nav_import);
} else {
ActionBarHelper.setBackButton(this);
getSupportActionBar().setDisplayShowTitleEnabled(false);
// set drop down navigation
@@ -236,6 +237,12 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_NFC, null);
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else if (ACTION_IMPORT_KEY_FROM_KEYBASE.equals(action)) {
// NOTE: this only displays the appropriate fragment, no actions are taken
loadNavFragment(NAV_KEYBASE, null);
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
} else {
@@ -340,8 +347,8 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
startListFragment(savedInstanceState, null, null, query);
}
public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer) {
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer);
public void loadCallback(byte[] importData, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
mListFragment.loadNew(importData, dataUri, serverQuery, keyServer, keybaseQuery);
}
/**
@@ -449,6 +456,31 @@ public class ImportKeysActivity extends ActionBarActivity implements ActionBar.O
// start service with intent
startService(intent);
} else if (mListFragment.getKeybaseQuery() != null) {
// Send all information needed to service to query keys in other thread
Intent intent = new Intent(this, KeychainIntentService.class);
intent.setAction(KeychainIntentService.ACTION_IMPORT_KEYBASE_KEYS);
// fill values for this action
Bundle data = new Bundle();
// get selected key entries
ArrayList<ImportKeysListEntry> selectedEntries = mListFragment.getSelectedData();
data.putParcelableArrayList(KeychainIntentService.DOWNLOAD_KEY_LIST, selectedEntries);
intent.putExtra(KeychainIntentService.EXTRA_DATA, data);
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(saveHandler);
intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);
// show progress dialog
saveHandler.showProgressDialog(this);
// start service with intent
startService(intent);
} else {
AppMsg.makeText(this, R.string.error_nothing_import, AppMsg.STYLE_ALERT).show();
}

View File

@@ -71,7 +71,7 @@ public class ImportKeysClipboardFragment extends Fragment {
return;
}
}
mImportActivity.loadCallback(sendText.getBytes(), null, null, null);
mImportActivity.loadCallback(sendText.getBytes(), null, null, null, null);
}
});

View File

@@ -85,7 +85,7 @@ public class ImportKeysFileFragment extends Fragment {
if (resultCode == Activity.RESULT_OK && data != null) {
// load data
mImportActivity.loadCallback(null, data.getData(), null, null);
mImportActivity.loadCallback(null, data.getData(), null, null, null);
}
break;

View File

@@ -0,0 +1,120 @@
/*
* 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;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
/**
* Import public keys from the Keybase.io directory. First cut: just raw search.
* TODO: make a pick list of the people youre following on keybase
*/
public class ImportKeysKeybaseFragment extends Fragment {
private ImportKeysActivity mImportActivity;
private BootstrapButton mSearchButton;
private EditText mQueryEditText;
public static final String ARG_QUERY = "query";
/**
* Creates new instance of this fragment
*/
public static ImportKeysKeybaseFragment newInstance() {
ImportKeysKeybaseFragment frag = new ImportKeysKeybaseFragment();
Bundle args = new Bundle();
frag.setArguments(args);
return frag;
}
/**
* Inflate the layout for this fragment
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.import_keys_keybase_fragment, container, false);
mQueryEditText = (EditText) view.findViewById(R.id.import_keybase_query);
mSearchButton = (BootstrapButton) view.findViewById(R.id.import_keybase_search);
mSearchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String query = mQueryEditText.getText().toString();
search(query);
// close keyboard after pressing search
InputMethodManager imm =
(InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mQueryEditText.getWindowToken(), 0);
}
});
mQueryEditText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
String query = mQueryEditText.getText().toString();
search(query);
// Don't return true to let the keyboard close itself after pressing search
return false;
}
return false;
}
});
return view;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mImportActivity = (ImportKeysActivity) getActivity();
// set displayed values
if (getArguments() != null) {
if (getArguments().containsKey(ARG_QUERY)) {
String query = getArguments().getString(ARG_QUERY);
mQueryEditText.setText(query, TextView.BufferType.EDITABLE);
}
}
}
private void search(String query) {
mImportActivity.loadCallback(null, null, null, null, query);
}
}

View File

@@ -33,11 +33,12 @@ import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
import org.sufficientlysecure.keychain.ui.adapter.AsyncTaskResultWrapper;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListKeybaseLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListLoader;
import org.sufficientlysecure.keychain.ui.adapter.ImportKeysListServerLoader;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.KeyServer;
import org.sufficientlysecure.keychain.keyimport.KeyServer;
import org.sufficientlysecure.keychain.util.Log;
import java.io.ByteArrayInputStream;
@@ -60,9 +61,11 @@ public class ImportKeysListFragment extends ListFragment implements
private Uri mDataUri;
private String mServerQuery;
private String mKeyServer;
private String mKeybaseQuery;
private static final int LOADER_ID_BYTES = 0;
private static final int LOADER_ID_SERVER_QUERY = 1;
private static final int LOADER_ID_KEYBASE = 2;
public byte[] getKeyBytes() {
return mKeyBytes;
@@ -76,6 +79,10 @@ public class ImportKeysListFragment extends ListFragment implements
return mServerQuery;
}
public String getKeybaseQuery() {
return mKeybaseQuery;
}
public String getKeyServer() {
return mKeyServer;
}
@@ -148,6 +155,16 @@ public class ImportKeysListFragment extends ListFragment implements
// give arguments to onCreateLoader()
getLoaderManager().initLoader(LOADER_ID_SERVER_QUERY, null, this);
}
if (mKeybaseQuery != null) {
// Start out with a progress indicator.
setListShown(false);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
// give arguments to onCreateLoader()
getLoaderManager().initLoader(LOADER_ID_KEYBASE, null, this);
}
}
@Override
@@ -157,16 +174,18 @@ public class ImportKeysListFragment extends ListFragment implements
// Select checkbox!
// Update underlying data and notify adapter of change. The adapter will
// update the view automatically.
ImportKeysListEntry entry = mAdapter.getItem(position);
entry.setSelected(!entry.isSelected());
mAdapter.notifyDataSetChanged();
}
public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer) {
public void loadNew(byte[] keyBytes, Uri dataUri, String serverQuery, String keyServer, String keybaseQuery) {
mKeyBytes = keyBytes;
mDataUri = dataUri;
mServerQuery = serverQuery;
mKeyServer = keyServer;
mKeybaseQuery = keybaseQuery;
if (mKeyBytes != null || mDataUri != null) {
// Start out with a progress indicator.
@@ -181,6 +200,13 @@ public class ImportKeysListFragment extends ListFragment implements
getLoaderManager().restartLoader(LOADER_ID_SERVER_QUERY, null, this);
}
if (mKeybaseQuery != null) {
// Start out with a progress indicator.
setListShown(false);
getLoaderManager().restartLoader(LOADER_ID_KEYBASE, null, this);
}
}
@Override
@@ -194,6 +220,9 @@ public class ImportKeysListFragment extends ListFragment implements
case LOADER_ID_SERVER_QUERY: {
return new ImportKeysListServerLoader(getActivity(), mServerQuery, mKeyServer);
}
case LOADER_ID_KEYBASE: {
return new ImportKeysListKeybaseLoader(getActivity(), mKeybaseQuery);
}
default:
return null;
@@ -248,7 +277,7 @@ public class ImportKeysListFragment extends ListFragment implements
if (error == null) {
AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
mAdapter.getCount(), mAdapter.getCount()),
mAdapter.getCount(), mAdapter.getCount()),
AppMsg.STYLE_INFO
).show();
} else if (error instanceof KeyServer.InsufficientQuery) {
@@ -263,6 +292,19 @@ public class ImportKeysListFragment extends ListFragment implements
}
break;
case LOADER_ID_KEYBASE:
if (error == null) {
AppMsg.makeText(
getActivity(), getResources().getQuantityString(R.plurals.keys_found,
mAdapter.getCount(), mAdapter.getCount()),
AppMsg.STYLE_INFO
).show();
} else if (error instanceof KeyServer.QueryException) {
AppMsg.makeText(getActivity(), R.string.error_keyserver_query,
AppMsg.STYLE_ALERT).show();
}
default:
break;
}
@@ -279,6 +321,10 @@ public class ImportKeysListFragment extends ListFragment implements
// Clear the data in the adapter.
mAdapter.clear();
break;
case LOADER_ID_KEYBASE:
// Clear the data in the adapter.
mAdapter.clear();
break;
default:
break;
}

View File

@@ -117,7 +117,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
// is this a full key encoded as qr code?
if (scannedContent.startsWith("-----BEGIN PGP")) {
mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null);
mImportActivity.loadCallback(scannedContent.getBytes(), null, null, null, null);
return;
}
@@ -197,7 +197,7 @@ public class ImportKeysQrCodeFragment extends Fragment {
for (String in : mScannedContent) {
result += in;
}
mImportActivity.loadCallback(result.getBytes(), null, null, null);
mImportActivity.loadCallback(result.getBytes(), null, null, null, null);
}
}

View File

@@ -151,7 +151,7 @@ public class ImportKeysServerFragment extends Fragment {
}
private void search(String query, String keyServer) {
mImportActivity.loadCallback(null, null, query, keyServer);
mImportActivity.loadCallback(null, null, query, keyServer, null);
}
}

View File

@@ -29,7 +29,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -45,11 +44,10 @@ import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import android.widget.AbsListView.MultiChoiceModeListener;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
@@ -77,18 +75,13 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
* Public key list with sticky list headers. It does _not_ extend ListFragment because it uses
* StickyListHeaders library which does not extend upon ListView.
*/
public class KeyListFragment extends Fragment
public class KeyListFragment extends LoaderFragment
implements SearchView.OnQueryTextListener, AdapterView.OnItemClickListener,
LoaderManager.LoaderCallbacks<Cursor> {
private KeyListAdapter mAdapter;
private StickyListHeadersListView mStickyList;
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
boolean mListShown;
View mProgressContainer;
View mListContainer;
private String mCurQuery;
private SearchView mSearchView;
// empty list layout
@@ -100,14 +93,15 @@ public class KeyListFragment extends Fragment
* Load custom layout with StickyListView from library
*/
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.key_list_fragment, container, false);
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.key_list_fragment, getContainer());
mStickyList = (StickyListHeadersListView) root.findViewById(R.id.key_list_list);
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.key_list_list);
mStickyList.setOnItemClickListener(this);
// empty view
mButtonEmptyCreate = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_create);
mButtonEmptyCreate = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_create);
mButtonEmptyCreate.setOnClickListener(new OnClickListener() {
@Override
@@ -119,7 +113,7 @@ public class KeyListFragment extends Fragment
startActivityForResult(intent, 0);
}
});
mButtonEmptyImport = (BootstrapButton) root.findViewById(R.id.key_list_empty_button_import);
mButtonEmptyImport = (BootstrapButton) view.findViewById(R.id.key_list_empty_button_import);
mButtonEmptyImport.setOnClickListener(new OnClickListener() {
@Override
@@ -130,11 +124,6 @@ public class KeyListFragment extends Fragment
}
});
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
mListContainer = root.findViewById(R.id.key_list_list_container);
mProgressContainer = root.findViewById(R.id.key_list_progress_container);
mListShown = true;
return root;
}
@@ -233,9 +222,8 @@ public class KeyListFragment extends Fragment
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// NOTE: Not supported by StickyListHeader, but reimplemented here
// Start out with a progress indicator.
setListShown(false);
setContentShown(false);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new KeyListAdapter(getActivity(), null, 0);
@@ -297,12 +285,11 @@ public class KeyListFragment extends Fragment
// this view is made visible if no data is available
mStickyList.setEmptyView(getActivity().findViewById(R.id.key_list_empty));
// NOTE: Not supported by StickyListHeader, but reimplemented here
// The list should now be shown.
if (isResumed()) {
setListShown(true);
setContentShown(true);
} else {
setListShownNoAnimation(true);
setContentShownNoAnimation(true);
}
}
@@ -380,6 +367,9 @@ public class KeyListFragment extends Fragment
// Execute this when searching
mSearchView.setOnQueryTextListener(this);
View searchPlate = mSearchView.findViewById(android.support.v7.appcompat.R.id.search_plate);
searchPlate.setBackgroundResource(R.drawable.keychaintheme_searchview_holo_light);
// Erase search result without focus
MenuItemCompat.setOnActionExpandListener(searchItem, new MenuItemCompat.OnActionExpandListener() {
@Override
@@ -414,43 +404,6 @@ public class KeyListFragment extends Fragment
return true;
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShown(boolean shown, boolean animate) {
if (mListShown == shown) {
return;
}
mListShown = shown;
if (shown) {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
}
mProgressContainer.setVisibility(View.GONE);
mListContainer.setVisibility(View.VISIBLE);
} else {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
}
mProgressContainer.setVisibility(View.VISIBLE);
mListContainer.setVisibility(View.INVISIBLE);
}
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShown(boolean shown) {
setListShown(shown, true);
}
// rebuild functionality of ListFragment, http://stackoverflow.com/a/12504097
public void setListShownNoAnimation(boolean shown) {
setListShown(shown, false);
}
/**
* Implements StickyListHeadersAdapter from library
*/
@@ -475,7 +428,7 @@ public class KeyListFragment extends Fragment
TextView mMainUserIdRest;
View mStatusDivider;
FrameLayout mStatusLayout;
Button mButton;
ImageButton mButton;
TextView mRevoked;
ImageView mVerified;
}
@@ -488,7 +441,7 @@ public class KeyListFragment extends Fragment
holder.mMainUserIdRest = (TextView) view.findViewById(R.id.mainUserIdRest);
holder.mStatusDivider = (View) view.findViewById(R.id.status_divider);
holder.mStatusLayout = (FrameLayout) view.findViewById(R.id.status_layout);
holder.mButton = (Button) view.findViewById(R.id.edit);
holder.mButton = (ImageButton) view.findViewById(R.id.edit);
holder.mRevoked = (TextView) view.findViewById(R.id.revoked);
holder.mVerified = (ImageView) view.findViewById(R.id.verified);
view.setTag(holder);

View File

@@ -0,0 +1,78 @@
package org.sufficientlysecure.keychain.ui;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import org.sufficientlysecure.keychain.R;
/** This is a fragment helper class, which implements a generic
* progressbar/container view.
*
* To use it in a fragment, simply subclass, use onCreateView to create the
* layout's root view, and ues getContainer() as root view of your subclass.
* The layout shows a progress bar by default, and can be switched to the
* actual contents by calling setContentShown().
*
*/
public class LoaderFragment extends Fragment {
private boolean mContentShown;
private View mProgressContainer;
private ViewGroup mContainer;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.loader_layout, container, false);
mContentShown = true;
mContainer = (ViewGroup) root.findViewById(R.id.loader_container);
mProgressContainer = root.findViewById(R.id.loader_progress);
// content is not shown (by visibility statuses in the layout files)
mContentShown = false;
return root;
}
protected ViewGroup getContainer() {
return mContainer;
}
public void setContentShown(boolean shown, boolean animate) {
if (mContentShown == shown) {
return;
}
mContentShown = shown;
if (shown) {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
mContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
}
mProgressContainer.setVisibility(View.GONE);
mContainer.setVisibility(View.VISIBLE);
} else {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_in));
mContainer.startAnimation(AnimationUtils.loadAnimation(
getActivity(), android.R.anim.fade_out));
}
mProgressContainer.setVisibility(View.VISIBLE);
mContainer.setVisibility(View.INVISIBLE);
}
}
public void setContentShown(boolean shown) {
setContentShown(shown, true);
}
public void setContentShownNoAnimation(boolean shown) {
setContentShown(shown, false);
}
}

View File

@@ -56,7 +56,7 @@ public class SelectSecretKeyFragment extends ListFragment implements
Bundle args = new Bundle();
args.putBoolean(ARG_FILTER_CERTIFY, filterCertify);
args.putBoolean(ARG_FILTER_CERTIFY, filterSign);
args.putBoolean(ARG_FILTER_SIGN, filterSign);
frag.setArguments(args);
return frag;
@@ -124,7 +124,8 @@ public class SelectSecretKeyFragment extends ListFragment implements
KeyRings.CAN_CERTIFY,
// has sign may be any subkey
KeyRings.HAS_SIGN,
KeyRings.HAS_ANY_SECRET
KeyRings.HAS_ANY_SECRET,
KeyRings.HAS_SECRET
};
String where = KeyRings.HAS_ANY_SECRET + " = 1";
@@ -158,7 +159,7 @@ public class SelectSecretKeyFragment extends ListFragment implements
private class SelectSecretKeyCursorAdapter extends SelectKeyCursorAdapter {
private int mIndexHasSign, mIndexCanCertify;
private int mIndexHasSign, mIndexCanCertify, mIndexHasSecret;
public SelectSecretKeyCursorAdapter(Context context, Cursor c, int flags, ListView listView) {
super(context, c, flags, listView);
@@ -170,6 +171,7 @@ public class SelectSecretKeyFragment extends ListFragment implements
if (cursor != null) {
mIndexCanCertify = cursor.getColumnIndexOrThrow(KeyRings.CAN_CERTIFY);
mIndexHasSign = cursor.getColumnIndexOrThrow(KeyRings.HAS_SIGN);
mIndexHasSecret = cursor.getColumnIndexOrThrow(KeyRings.HAS_SECRET);
}
}
@@ -187,7 +189,8 @@ public class SelectSecretKeyFragment extends ListFragment implements
// Check if key is viable for our purposes (certify or sign)
if(mFilterCertify) {
// Only enable if can certify
if (cursor.getInt(mIndexCanCertify) == 0) {
if (cursor.getInt(mIndexCanCertify) == 0
|| cursor.getInt(mIndexHasSecret) == 0) {
h.status.setText(R.string.can_certify_not);
} else {
h.status.setText(R.string.can_certify);

View File

@@ -23,15 +23,15 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.support.v4.app.NavUtils;
import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Spinner;
import android.widget.Toast;
import com.beardedhen.androidbootstrap.BootstrapButton;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.helper.Preferences;
@@ -44,7 +44,7 @@ import org.sufficientlysecure.keychain.util.Log;
* Sends the selected public key to a keyserver
*/
public class UploadKeyActivity extends ActionBarActivity {
private BootstrapButton mUploadButton;
private View mUploadButton;
private Spinner mKeyServerSpinner;
private Uri mDataUri;
@@ -53,10 +53,10 @@ public class UploadKeyActivity extends ActionBarActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.key_server_export);
setContentView(R.layout.upload_key_activity);
mUploadButton = (BootstrapButton) findViewById(R.id.btn_export_to_server);
mKeyServerSpinner = (Spinner) findViewById(R.id.sign_key_keyserver);
mUploadButton = findViewById(R.id.upload_key_action_upload);
mKeyServerSpinner = (Spinner) findViewById(R.id.upload_key_keyserver);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_spinner_item, Preferences.getPreferences(this)
@@ -129,4 +129,17 @@ public class UploadKeyActivity extends ActionBarActivity {
// start service with intent
startService(intent);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
Intent viewIntent = NavUtils.getParentActivityIntent(this);
viewIntent.setData(KeychainContract.KeyRings.buildGenericKeyRingUri(mDataUri));
NavUtils.navigateUpTo(this, viewIntent);
return true;
}
}
return super.onOptionsItemSelected(item);
}
}

View File

@@ -28,7 +28,6 @@ import android.support.v4.content.Loader;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.text.format.DateFormat;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.TextView;
@@ -73,11 +72,12 @@ public class ViewCertActivity extends ActionBarActivity
private Uri mDataUri;
private long mSignerKeyId;
private long mCertifierKeyId;
private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mRReason, mCreation;
private TextView mSignerKey, mSignerUid, mStatus;
private TextView mSigneeKey, mSigneeUid, mAlgorithm, mType, mReason, mCreation;
private TextView mCertifierKey, mCertifierUid, mStatus;
private View mRowReason;
private View mViewCertifierButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -93,14 +93,16 @@ public class ViewCertActivity extends ActionBarActivity
mSigneeUid = (TextView) findViewById(R.id.signee_uid);
mAlgorithm = (TextView) findViewById(R.id.algorithm);
mType = (TextView) findViewById(R.id.signature_type);
mRReason = (TextView) findViewById(R.id.reason);
mReason = (TextView) findViewById(R.id.reason);
mCreation = (TextView) findViewById(R.id.creation);
mSignerKey = (TextView) findViewById(R.id.signer_key_id);
mSignerUid = (TextView) findViewById(R.id.signer_uid);
mCertifierKey = (TextView) findViewById(R.id.signer_key_id);
mCertifierUid = (TextView) findViewById(R.id.signer_uid);
mRowReason = findViewById(R.id.row_reason);
mViewCertifierButton = findViewById(R.id.view_cert_view_cert_key);
mDataUri = getIntent().getData();
if (mDataUri == null) {
Log.e(Constants.TAG, "Intent data missing. Should be Uri of key!");
@@ -121,7 +123,7 @@ public class ViewCertActivity extends ActionBarActivity
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (data.moveToFirst()) {
String signeeKey = "0x" + PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID));
String signeeKey = PgpKeyHelper.convertKeyIdToHex(data.getLong(INDEX_MASTER_KEY_ID));
mSigneeKey.setText(signeeKey);
String signeeUid = data.getString(INDEX_USER_ID);
@@ -130,22 +132,24 @@ public class ViewCertActivity extends ActionBarActivity
Date creationDate = new Date(data.getLong(INDEX_CREATION) * 1000);
mCreation.setText(DateFormat.getDateFormat(getApplicationContext()).format(creationDate));
mSignerKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER);
String signerKey = "0x" + PgpKeyHelper.convertKeyIdToHex(mSignerKeyId);
mSignerKey.setText(signerKey);
mCertifierKeyId = data.getLong(INDEX_KEY_ID_CERTIFIER);
String certifierKey = PgpKeyHelper.convertKeyIdToHex(mCertifierKeyId);
mCertifierKey.setText(certifierKey);
String signerUid = data.getString(INDEX_SIGNER_UID);
if (signerUid != null) {
mSignerUid.setText(signerUid);
String certifierUid = data.getString(INDEX_SIGNER_UID);
if (certifierUid != null) {
mCertifierUid.setText(certifierUid);
} else {
mSignerUid.setText(R.string.unknown_uid);
mCertifierUid.setText(R.string.unknown_uid);
}
PGPSignature sig = PgpConversionHelper.BytesToPGPSignature(data.getBlob(INDEX_DATA));
try {
ProviderHelper providerHelper = new ProviderHelper(this);
CachedPublicKeyRing signeeRing = providerHelper.getCachedPublicKeyRing(data.getLong(INDEX_MASTER_KEY_ID));
CachedPublicKeyRing signerRing = providerHelper.getCachedPublicKeyRing(sig.getKeyID());
try {
signerRing.getSubkey().initSignature(sig);
if (signeeRing.getSubkey().verifySignature(sig, signeeUid)) {
@@ -191,27 +195,41 @@ public class ViewCertActivity extends ActionBarActivity
p = new RevocationReason(false, p.getData());
}
String reason = ((RevocationReason) p).getRevocationDescription();
mRReason.setText(reason);
mReason.setText(reason);
mRowReason.setVisibility(View.VISIBLE);
}
break;
}
}
}
// can't do this before the data is initialized
mViewCertifierButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent viewIntent = new Intent(ViewCertActivity.this, ViewKeyActivity.class);
try {
ProviderHelper providerHelper = new ProviderHelper(ViewCertActivity.this);
long signerMasterKeyId = providerHelper.getMasterKeyId(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mCertifierKeyId))
);
viewIntent.setData(KeyRings.buildGenericKeyRingUri(
Long.toString(signerMasterKeyId))
);
startActivity(viewIntent);
} catch (ProviderHelper.NotFoundException e) {
// TODO notify user of this, maybe offer download?
Log.e(Constants.TAG, "key not found!", e);
}
}
});
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getMenuInflater().inflate(R.menu.view_cert, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
@@ -221,25 +239,6 @@ public class ViewCertActivity extends ActionBarActivity
NavUtils.navigateUpTo(this, viewIntent);
return true;
}
case R.id.menu_view_cert_view_signer:
// can't do this before the data is initialized
Intent viewIntent = new Intent(this, ViewKeyActivity.class);
try {
ProviderHelper providerHelper = new ProviderHelper(this);
long signerMasterKeyId = providerHelper.getMasterKeyId(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(Long.toString(mSignerKeyId))
);
viewIntent.setData(KeyRings.buildGenericKeyRingUri(
Long.toString(signerMasterKeyId))
);
startActivity(viewIntent);
} catch (ProviderHelper.NotFoundException e) {
// TODO notify user of this, maybe offer download?
Log.e(Constants.TAG, "key not found!", e);
}
return true;
}
return super.onOptionsItemSelected(item);
}

View File

@@ -21,6 +21,7 @@ package org.sufficientlysecure.keychain.ui;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
@@ -31,41 +32,54 @@ import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.helper.ExportHelper;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.TabsAdapter;
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
import org.sufficientlysecure.keychain.ui.adapter.PagerTabStripAdapter;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.SlidingTabLayout;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
public class ViewKeyActivity extends ActionBarActivity {
public class ViewKeyActivity extends ActionBarActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
ExportHelper mExportHelper;
ProviderHelper mProviderHelper;
protected Uri mDataUri;
public static final String EXTRA_SELECTED_TAB = "selectedTab";
public static final String EXTRA_SELECTED_TAB = "selected_tab";
public static final int TAB_MAIN = 0;
public static final int TAB_SHARE = 1;
public static final int TAB_KEYS = 2;
public static final int TAB_CERTS = 3;
ViewPager mViewPager;
TabsAdapter mTabsAdapter;
// view
private ViewPager mViewPager;
private SlidingTabLayout mSlidingTabLayout;
private PagerTabStripAdapter mTabsAdapter;
private View mStatusDivider;
private View mStatusRevoked;
private View mStatusExpired;
public static final int REQUEST_CODE_LOOKUP_KEY = 0x00007006;
@@ -76,6 +90,9 @@ public class ViewKeyActivity extends ActionBarActivity {
private byte[] mNfcKeyringBytes;
private static final int NFC_SENT = 1;
private static final int LOADER_ID_UNIFIED = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
@@ -89,33 +106,71 @@ public class ViewKeyActivity extends ActionBarActivity {
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setIcon(android.R.color.transparent);
actionBar.setHomeButtonEnabled(true);
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
setContentView(R.layout.view_key_activity);
mViewPager = (ViewPager) findViewById(R.id.pager);
mStatusDivider = findViewById(R.id.status_divider);
mStatusRevoked = findViewById(R.id.view_key_revoked);
mStatusExpired = findViewById(R.id.view_key_expired);
mTabsAdapter = new TabsAdapter(this, mViewPager);
mViewPager = (ViewPager) findViewById(R.id.view_key_pager);
mSlidingTabLayout = (SlidingTabLayout) findViewById(R.id.view_key_sliding_tab_layout);
int selectedTab = 0;
mTabsAdapter = new PagerTabStripAdapter(this);
mViewPager.setAdapter(mTabsAdapter);
int switchToTab = TAB_MAIN;
Intent intent = getIntent();
if (intent.getExtras() != null && intent.getExtras().containsKey(EXTRA_SELECTED_TAB)) {
selectedTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
switchToTab = intent.getExtras().getInt(EXTRA_SELECTED_TAB);
}
mDataUri = getIntent().getData();
Uri dataUri = getIntent().getData();
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
finish();
return;
}
initNfc(mDataUri);
loadData(dataUri);
initNfc(dataUri);
Bundle mainBundle = new Bundle();
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, mDataUri);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_main)),
ViewKeyMainFragment.class, mainBundle, (selectedTab == 0));
mainBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyMainFragment.class,
mainBundle, getString(R.string.key_view_tab_main));
Bundle shareBundle = new Bundle();
shareBundle.putParcelable(ViewKeyMainFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyShareFragment.class,
mainBundle, getString(R.string.key_view_tab_share));
Bundle keyDetailsBundle = new Bundle();
keyDetailsBundle.putParcelable(ViewKeyKeysFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyKeysFragment.class,
keyDetailsBundle, getString(R.string.key_view_tab_keys));
Bundle certBundle = new Bundle();
certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, mDataUri);
mTabsAdapter.addTab(actionBar.newTab().setText(getString(R.string.key_view_tab_certs)),
ViewKeyCertsFragment.class, certBundle, (selectedTab == 1));
certBundle.putParcelable(ViewKeyCertsFragment.ARG_DATA_URI, dataUri);
mTabsAdapter.addTab(ViewKeyCertsFragment.class,
certBundle, getString(R.string.key_view_tab_certs));
// NOTE: must be after adding the tabs!
mSlidingTabLayout.setViewPager(mViewPager);
// switch to tab selected by extra
mViewPager.setCurrentItem(switchToTab);
}
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
}
@Override
@@ -143,24 +198,6 @@ public class ViewKeyActivity extends ActionBarActivity {
case R.id.menu_key_view_export_file:
exportToFile(mDataUri, mExportHelper, mProviderHelper);
return true;
case R.id.menu_key_view_share_default_fingerprint:
shareKey(mDataUri, true, mProviderHelper);
return true;
case R.id.menu_key_view_share_default:
shareKey(mDataUri, false, mProviderHelper);
return true;
case R.id.menu_key_view_share_qr_code_fingerprint:
shareKeyQrCode(mDataUri, true);
return true;
case R.id.menu_key_view_share_qr_code:
shareKeyQrCode(mDataUri, false);
return true;
case R.id.menu_key_view_share_nfc:
shareNfc();
return true;
case R.id.menu_key_view_share_clipboard:
copyToClipboard(mDataUri, mProviderHelper);
return true;
case R.id.menu_key_view_delete: {
deleteKey(mDataUri, mExportHelper);
return true;
@@ -209,84 +246,6 @@ public class ViewKeyActivity extends ActionBarActivity {
startActivityForResult(queryIntent, REQUEST_CODE_LOOKUP_KEY);
}
private void shareKey(Uri dataUri, boolean fingerprintOnly, ProviderHelper providerHelper)
throws ProviderHelper.NotFoundException {
String content = null;
if (fingerprintOnly) {
byte[] data = (byte[]) providerHelper.getGenericData(
KeychainContract.KeyRings.buildUnifiedKeyRingUri(dataUri),
KeychainContract.Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (data != null) {
String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
} else {
AppMsg.makeText(this, "Bad key selected!",
AppMsg.STYLE_ALERT).show();
return;
}
} else {
// get public keyring as ascii armored string
try {
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
content = providerHelper.getKeyRingAsArmoredString(uri);
// Android will fail with android.os.TransactionTooLargeException if key is too big
// see http://www.lonestarprod.com/?p=34
if (content.length() >= 86389) {
AppMsg.makeText(this, R.string.key_too_big_for_sharing,
AppMsg.STYLE_ALERT).show();
return;
}
} catch (IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(this, R.string.error_invalid_data, AppMsg.STYLE_ALERT).show();
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
}
}
if (content != null) {
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent,
getResources().getText(R.string.action_share_key_with)));
} else {
Log.e(Constants.TAG, "content is null!");
}
}
private void shareKeyQrCode(Uri dataUri, boolean fingerprintOnly) {
ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(dataUri,
fingerprintOnly);
dialog.show(getSupportFragmentManager(), "shareQrCodeDialog");
}
private void copyToClipboard(Uri dataUri, ProviderHelper providerHelper) {
// get public keyring as ascii armored string
try {
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
String keyringArmored = providerHelper.getKeyRingAsArmoredString(uri);
ClipboardReflection.copyToClipboard(this, keyringArmored);
AppMsg.makeText(this, R.string.key_copied_to_clipboard, AppMsg.STYLE_INFO)
.show();
} catch (IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(this, R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
AppMsg.makeText(this, R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
}
}
private void shareNfc() {
ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
dialog.show(getSupportFragmentManager(), "shareNfcDialog");
}
private void deleteKey(Uri dataUri, ExportHelper exportHelper) {
// Message is received after key is deleted
Handler returnHandler = new Handler() {
@@ -409,4 +368,85 @@ public class ViewKeyActivity extends ActionBarActivity {
}
};
static final String[] UNIFIED_PROJECTION = new String[]{
KeychainContract.KeyRings._ID,
KeychainContract.KeyRings.MASTER_KEY_ID,
KeychainContract.KeyRings.USER_ID,
KeychainContract.KeyRings.IS_REVOKED,
KeychainContract.KeyRings.EXPIRY,
};
static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
static final int INDEX_UNIFIED_USER_ID = 2;
static final int INDEX_UNIFIED_IS_REVOKED = 3;
static final int INDEX_UNIFIED_EXPIRY = 4;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeychainContract.KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(this, baseUri, UNIFIED_PROJECTION, null, null, null);
}
default:
return null;
}
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
// get name, email, and comment from USER_ID
String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_USER_ID));
if (mainUserId[0] != null) {
setTitle(mainUserId[0]);
} else {
setTitle(R.string.user_id_no_name);
}
// get key id from MASTER_KEY_ID
long masterKeyId = data.getLong(INDEX_UNIFIED_MASTER_KEY_ID);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
getSupportActionBar().setSubtitle(keyIdStr);
// If this key is revoked, it cannot be used for anything!
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
mStatusDivider.setVisibility(View.VISIBLE);
mStatusRevoked.setVisibility(View.VISIBLE);
mStatusExpired.setVisibility(View.GONE);
} else {
mStatusRevoked.setVisibility(View.GONE);
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
mStatusDivider.setVisibility(View.VISIBLE);
mStatusExpired.setVisibility(View.VISIBLE);
} else {
mStatusDivider.setVisibility(View.GONE);
mStatusExpired.setVisibility(View.GONE);
}
}
break;
}
}
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}

View File

@@ -23,7 +23,6 @@ import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
@@ -46,7 +45,7 @@ import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
public class ViewKeyCertsFragment extends Fragment
public class ViewKeyCertsFragment extends LoaderFragment
implements LoaderManager.LoaderCallbacks<Cursor>, AdapterView.OnItemClickListener {
// These are the rows that we will retrieve.
@@ -75,19 +74,23 @@ public class ViewKeyCertsFragment extends Fragment
private Uri mDataUri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_key_certs_fragment, container, false);
// starting with 4 for this fragment
private static final int LOADER_ID = 4;
return view;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_certs_fragment, getContainer());
mStickyList = (StickyListHeadersListView) view.findViewById(R.id.list);
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mStickyList = (StickyListHeadersListView) getActivity().findViewById(R.id.list);
if (!getArguments().containsKey(ARG_DATA_URI)) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
@@ -112,11 +115,12 @@ public class ViewKeyCertsFragment extends Fragment
mAdapter = new CertListAdapter(getActivity(), null);
mStickyList.setAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
getLoaderManager().initLoader(LOADER_ID, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
return new CursorLoader(getActivity(), mDataUri, PROJECTION, null, null, SORT_ORDER);
@@ -129,6 +133,8 @@ public class ViewKeyCertsFragment extends Fragment
mAdapter.swapCursor(data);
mStickyList.setAdapter(mAdapter);
setContentShown(true);
}
/**
@@ -208,11 +214,18 @@ public class ViewKeyCertsFragment extends Fragment
// set name and stuff, common to both key types
TextView wSignerKeyId = (TextView) view.findViewById(R.id.signerKeyId);
TextView wSignerUserId = (TextView) view.findViewById(R.id.signerUserId);
TextView wSignerName = (TextView) view.findViewById(R.id.signerName);
TextView wSignStatus = (TextView) view.findViewById(R.id.signStatus);
String signerKeyId = PgpKeyHelper.convertKeyIdToHex(cursor.getLong(mIndexSignerKeyId));
String signerUserId = cursor.getString(mIndexSignerUserId);
String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexSignerUserId));
if (userId[0] != null) {
wSignerName.setText(userId[0]);
} else {
wSignerName.setText(R.string.user_id_no_name);
}
wSignerKeyId.setText(signerKeyId);
switch (cursor.getInt(mIndexType)) {
case PGPSignature.DEFAULT_CERTIFICATION: // 0x10
wSignStatus.setText(R.string.cert_default);
@@ -231,8 +244,6 @@ public class ViewKeyCertsFragment extends Fragment
break;
}
wSignerUserId.setText(signerUserId);
wSignerKeyId.setText(signerKeyId);
view.setTag(R.id.tag_mki, cursor.getLong(mIndexMasterKeyId));
view.setTag(R.id.tag_rank, cursor.getLong(mIndexRank));

View File

@@ -0,0 +1,128 @@
/*
* 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;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
public class ViewKeyKeysFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private ListView mKeys;
private ViewKeyKeysAdapter mKeysAdapter;
private Uri mDataUri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_keys_fragment, getContainer());
mKeys = (ListView) view.findViewById(R.id.keys);
return root;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0);
mKeys.setAdapter(mKeysAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(0, null, this);
}
static final String[] KEYS_PROJECTION = new String[] {
Keys._ID,
Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET,
Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
Uri baseUri = Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Avoid NullPointerExceptions, if we get an empty result set.
if(data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mKeysAdapter.swapCursor(data);
setContentShown(true);
}
/**
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
* We need to make sure we are no longer using it.
*/
public void onLoaderReset(Loader<Cursor> loader) {
mKeysAdapter.swapCursor(null);
}
}

View File

@@ -19,90 +19,64 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Color;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.format.DateFormat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.BootstrapButton;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
import org.sufficientlysecure.keychain.R;import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserIds;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyKeysAdapter;
import org.sufficientlysecure.keychain.ui.adapter.ViewKeyUserIdsAdapter;
import org.sufficientlysecure.keychain.util.Log;
import java.util.Date;
public class ViewKeyMainFragment extends Fragment implements
public class ViewKeyMainFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private LinearLayout mContainer;
private TextView mName;
private TextView mEmail;
private TextView mComment;
private TextView mAlgorithm;
private TextView mKeyId;
private TextView mExpiry;
private TextView mCreation;
private TextView mFingerprint;
private TextView mSecretKey;
private BootstrapButton mActionEdit;
private BootstrapButton mActionEncrypt;
private BootstrapButton mActionCertify;
private View mActionEdit;
private View mActionEditDivider;
private View mActionEncrypt;
private View mActionCertify;
private View mActionCertifyDivider;
private ListView mUserIds;
private ListView mKeys;
private static final int LOADER_ID_UNIFIED = 0;
private static final int LOADER_ID_USER_IDS = 1;
private static final int LOADER_ID_KEYS = 2;
// conservative attitude
private boolean mHasEncrypt = true;
private ViewKeyUserIdsAdapter mUserIdsAdapter;
private ViewKeyKeysAdapter mKeysAdapter;
private Uri mDataUri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.view_key_main_fragment, container, false);
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_main_fragment, getContainer());
mContainer = (LinearLayout) view.findViewById(R.id.container);
mName = (TextView) view.findViewById(R.id.name);
mEmail = (TextView) view.findViewById(R.id.email);
mComment = (TextView) view.findViewById(R.id.comment);
mKeyId = (TextView) view.findViewById(R.id.key_id);
mAlgorithm = (TextView) view.findViewById(R.id.algorithm);
mCreation = (TextView) view.findViewById(R.id.creation);
mExpiry = (TextView) view.findViewById(R.id.expiry);
mFingerprint = (TextView) view.findViewById(R.id.fingerprint);
mSecretKey = (TextView) view.findViewById(R.id.secret_key);
mUserIds = (ListView) view.findViewById(R.id.user_ids);
mKeys = (ListView) view.findViewById(R.id.keys);
mActionEdit = (BootstrapButton) view.findViewById(R.id.action_edit);
mActionEncrypt = (BootstrapButton) view.findViewById(R.id.action_encrypt);
mActionCertify = (BootstrapButton) view.findViewById(R.id.action_certify);
mUserIds = (ListView) view.findViewById(R.id.view_key_user_ids);
mActionEdit = view.findViewById(R.id.view_key_action_edit);
mActionEditDivider = view.findViewById(R.id.view_key_action_edit_divider);
mActionEncrypt = view.findViewById(R.id.view_key_action_encrypt);
mActionCertify = view.findViewById(R.id.view_key_action_certify);
mActionCertifyDivider = view.findViewById(R.id.view_key_action_certify_divider);
return view;
return root;
}
@Override
@@ -120,14 +94,6 @@ public class ViewKeyMainFragment extends Fragment implements
}
private void loadData(Uri dataUri) {
if (dataUri.equals(mDataUri)) {
Log.d(Constants.TAG, "Same URI, no need to load the data again!");
return;
}
getActivity().setProgressBarIndeterminateVisibility(true);
mContainer.setVisibility(View.GONE);
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
@@ -135,52 +101,42 @@ public class ViewKeyMainFragment extends Fragment implements
mActionEncrypt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
encryptToContact(mDataUri);
encrypt(mDataUri);
}
});
mActionCertify.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
certifyKey(mDataUri);
certify(mDataUri);
}
});
mActionEdit.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
editKey(mDataUri);
}
});
mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
mUserIds.setAdapter(mUserIdsAdapter);
mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0);
mKeys.setAdapter(mKeysAdapter);
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
getLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
}
static final String[] UNIFIED_PROJECTION = new String[] {
KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET,
KeyRings.USER_ID, KeyRings.FINGERPRINT,
KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
static final String[] UNIFIED_PROJECTION = new String[]{
KeyRings._ID, KeyRings.MASTER_KEY_ID,
KeyRings.HAS_ANY_SECRET, KeyRings.IS_REVOKED, KeyRings.EXPIRY, KeyRings.HAS_ENCRYPT
};
static final int INDEX_UNIFIED_MKI = 1;
static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2;
static final int INDEX_UNIFIED_UID = 3;
static final int INDEX_UNIFIED_FINGERPRINT = 4;
static final int INDEX_UNIFIED_ALGORITHM = 5;
static final int INDEX_UNIFIED_KEY_SIZE = 6;
static final int INDEX_UNIFIED_CREATION = 7;
static final int INDEX_UNIFIED_EXPIRY = 8;
static final String[] KEYS_PROJECTION = new String[] {
Keys._ID,
Keys.KEY_ID, Keys.RANK, Keys.ALGORITHM, Keys.KEY_SIZE, Keys.HAS_SECRET,
Keys.CAN_CERTIFY, Keys.CAN_ENCRYPT, Keys.CAN_SIGN, Keys.IS_REVOKED,
Keys.CREATION, Keys.EXPIRY, Keys.FINGERPRINT
};
static final int KEYS_INDEX_CAN_ENCRYPT = 7;
static final int INDEX_UNIFIED_IS_REVOKED = 3;
static final int INDEX_UNIFIED_EXPIRY = 4;
static final int INDEX_UNIFIED_HAS_ENCRYPT = 5;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
@@ -188,11 +144,8 @@ public class ViewKeyMainFragment extends Fragment implements
}
case LOADER_ID_USER_IDS: {
Uri baseUri = UserIds.buildUserIdsUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
}
case LOADER_ID_KEYS: {
Uri baseUri = Keys.buildKeysUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, KEYS_PROJECTION, null, null, null);
return new CursorLoader(getActivity(), baseUri,
ViewKeyUserIdsAdapter.USER_IDS_PROJECTION, null, null, null);
}
default:
@@ -205,7 +158,7 @@ public class ViewKeyMainFragment extends Fragment implements
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if(data.getCount() == 0) {
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
@@ -213,80 +166,43 @@ public class ViewKeyMainFragment extends Fragment implements
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
// get name, email, and comment from USER_ID
String[] mainUserId = PgpKeyHelper.splitUserId(data.getString(INDEX_UNIFIED_UID));
if (mainUserId[0] != null) {
getActivity().setTitle(mainUserId[0]);
mName.setText(mainUserId[0]);
} else {
getActivity().setTitle(R.string.user_id_no_name);
mName.setText(R.string.user_id_no_name);
}
mEmail.setText(mainUserId[1]);
mComment.setText(mainUserId[2]);
if (data.getInt(INDEX_UNIFIED_HAS_ANY_SECRET) != 0) {
mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
mSecretKey.setText(R.string.secret_key_yes);
// certify button
mActionCertify.setVisibility(View.GONE);
mActionCertifyDivider.setVisibility(View.GONE);
// edit button
mActionEdit.setVisibility(View.VISIBLE);
mActionEdit.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
editIntent.setData(
KeyRingData.buildSecretKeyRingUri(mDataUri));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
}
});
mActionEditDivider.setVisibility(View.VISIBLE);
} else {
mSecretKey.setTextColor(Color.BLACK);
mSecretKey.setText(getResources().getString(R.string.secret_key_no));
// certify button
mActionCertify.setVisibility(View.VISIBLE);
mActionCertifyDivider.setVisibility(View.VISIBLE);
// edit button
mActionEdit.setVisibility(View.GONE);
mActionEditDivider.setVisibility(View.GONE);
}
// get key id from MASTER_KEY_ID
long masterKeyId = data.getLong(INDEX_UNIFIED_MKI);
String keyIdStr = PgpKeyHelper.convertKeyIdToHex(masterKeyId);
mKeyId.setText(keyIdStr);
// get creation date from CREATION
if (data.isNull(INDEX_UNIFIED_CREATION)) {
mCreation.setText(R.string.none);
// If this key is revoked, it cannot be used for anything!
if (data.getInt(INDEX_UNIFIED_IS_REVOKED) != 0) {
mActionEdit.setEnabled(false);
mActionCertify.setEnabled(false);
mActionEncrypt.setEnabled(false);
} else {
Date creationDate = new Date(data.getLong(INDEX_UNIFIED_CREATION) * 1000);
mActionEdit.setEnabled(true);
mCreation.setText(
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
creationDate));
}
// get expiry date from EXPIRY
if (data.isNull(INDEX_UNIFIED_EXPIRY)) {
mExpiry.setText(R.string.none);
} else {
Date expiryDate = new Date(data.getLong(INDEX_UNIFIED_EXPIRY) * 1000);
mExpiry.setText(
DateFormat.getDateFormat(getActivity().getApplicationContext()).format(
expiryDate));
if (!data.isNull(INDEX_UNIFIED_EXPIRY) && expiryDate.before(new Date())) {
mActionCertify.setEnabled(false);
mActionEncrypt.setEnabled(false);
} else {
mActionCertify.setEnabled(true);
mActionEncrypt.setEnabled(true);
}
}
String algorithmStr = PgpKeyHelper.getAlgorithmInfo(
getActivity(),
data.getInt(INDEX_UNIFIED_ALGORITHM),
data.getInt(INDEX_UNIFIED_KEY_SIZE)
);
mAlgorithm.setText(algorithmStr);
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
mHasEncrypt = data.getInt(INDEX_UNIFIED_HAS_ENCRYPT) != 0;
break;
}
@@ -296,26 +212,8 @@ public class ViewKeyMainFragment extends Fragment implements
mUserIdsAdapter.swapCursor(data);
break;
case LOADER_ID_KEYS:
// hide encrypt button if no encryption key is available
// TODO: do with subquery!
boolean canEncrypt = false;
data.moveToFirst();
do {
if (data.getInt(KEYS_INDEX_CAN_ENCRYPT) == 1) {
canEncrypt = true;
break;
}
} while (data.moveToNext());
if (!canEncrypt) {
mActionEncrypt.setVisibility(View.GONE);
}
mKeysAdapter.swapCursor(data);
break;
}
getActivity().setProgressBarIndeterminateVisibility(false);
mContainer.setVisibility(View.VISIBLE);
setContentShown(true);
}
/**
@@ -327,16 +225,18 @@ public class ViewKeyMainFragment extends Fragment implements
case LOADER_ID_USER_IDS:
mUserIdsAdapter.swapCursor(null);
break;
case LOADER_ID_KEYS:
mKeysAdapter.swapCursor(null);
break;
}
}
private void encryptToContact(Uri dataUri) {
private void encrypt(Uri dataUri) {
// If there is no encryption key, don't bother.
if (!mHasEncrypt) {
AppMsg.makeText(getActivity(), R.string.error_no_encrypt_subkey, AppMsg.STYLE_ALERT).show();
return;
}
try {
long keyId = new ProviderHelper(getActivity()).extractOrGetMasterKeyId(dataUri);
long[] encryptionKeyIds = new long[]{ keyId };
long[] encryptionKeyIds = new long[]{keyId};
Intent intent = new Intent(getActivity(), EncryptActivity.class);
intent.setAction(EncryptActivity.ACTION_ENCRYPT);
intent.putExtra(EncryptActivity.EXTRA_ENCRYPTION_KEY_IDS, encryptionKeyIds);
@@ -347,10 +247,17 @@ public class ViewKeyMainFragment extends Fragment implements
}
}
private void certifyKey(Uri dataUri) {
private void certify(Uri dataUri) {
Intent signIntent = new Intent(getActivity(), CertifyKeyActivity.class);
signIntent.setData(dataUri);
startActivity(signIntent);
}
private void editKey(Uri dataUri) {
Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
editIntent.setData(KeychainContract.KeyRingData.buildSecretKeyRingUri(dataUri));
editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
startActivityForResult(editIntent, 0);
}
}

View File

@@ -0,0 +1,308 @@
/*
* 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;
import android.annotation.TargetApi;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.support.v4.app.Fragment;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.compatibility.ClipboardReflection;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.KeychainContract.Keys;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.ui.dialog.ShareNfcDialogFragment;
import org.sufficientlysecure.keychain.ui.dialog.ShareQrCodeDialogFragment;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.QrCodeUtils;
import java.io.IOException;
public class ViewKeyShareFragment extends LoaderFragment implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String ARG_DATA_URI = "uri";
private TextView mFingerprint;
private ImageView mFingerprintQrCode;
private View mFingerprintShareButton;
private View mFingerprintClipboardButton;
private View mKeyShareButton;
private View mKeyClipboardButton;
private View mNfcHelpButton;
private View mNfcPrefsButton;
ProviderHelper mProviderHelper;
private static final int QR_CODE_SIZE = 1000;
private static final int LOADER_ID_UNIFIED = 0;
private Uri mDataUri;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup superContainer, Bundle savedInstanceState) {
View root = super.onCreateView(inflater, superContainer, savedInstanceState);
View view = inflater.inflate(R.layout.view_key_share_fragment, getContainer());
mProviderHelper = new ProviderHelper(ViewKeyShareFragment.this.getActivity());
mFingerprint = (TextView) view.findViewById(R.id.view_key_fingerprint);
mFingerprintQrCode = (ImageView) view.findViewById(R.id.view_key_fingerprint_qr_code_image);
mFingerprintShareButton = view.findViewById(R.id.view_key_action_fingerprint_share);
mFingerprintClipboardButton = view.findViewById(R.id.view_key_action_fingerprint_clipboard);
mKeyShareButton = view.findViewById(R.id.view_key_action_key_share);
mKeyClipboardButton = view.findViewById(R.id.view_key_action_key_clipboard);
mNfcHelpButton = view.findViewById(R.id.view_key_action_nfc_help);
mNfcPrefsButton = view.findViewById(R.id.view_key_action_nfc_prefs);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mNfcPrefsButton.setVisibility(View.VISIBLE);
} else {
mNfcPrefsButton.setVisibility(View.GONE);
}
mFingerprintQrCode.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showQrCodeDialog();
}
});
mFingerprintShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, true, false);
}
});
mFingerprintClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, true, true);
}
});
mKeyShareButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, false, false);
}
});
mKeyClipboardButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
share(mDataUri, mProviderHelper, false, true);
}
});
mNfcHelpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showNfcHelpDialog();
}
});
mNfcPrefsButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showNfcPrefs();
}
});
return root;
}
private void share(Uri dataUri, ProviderHelper providerHelper, boolean fingerprintOnly,
boolean toClipboard) {
try {
String content;
if (fingerprintOnly) {
byte[] data = (byte[]) providerHelper.getGenericData(
KeyRings.buildUnifiedKeyRingUri(dataUri),
Keys.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
String fingerprint = PgpKeyHelper.convertFingerprintToHex(data);
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
} else {
// get public keyring as ascii armored string
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
content = providerHelper.getKeyRingAsArmoredString(uri);
}
if (toClipboard) {
ClipboardReflection.copyToClipboard(getActivity(), content);
String message;
if (fingerprintOnly) {
message = getResources().getString(R.string.fingerprint_copied_to_clipboard);
} else {
message = getResources().getString(R.string.key_copied_to_clipboard);
}
AppMsg.makeText(getActivity(), message, AppMsg.STYLE_INFO).show();
} else {
// Android will fail with android.os.TransactionTooLargeException if key is too big
// see http://www.lonestarprod.com/?p=34
if (content.length() >= 86389) {
AppMsg.makeText(getActivity(), R.string.key_too_big_for_sharing,
AppMsg.STYLE_ALERT).show();
return;
}
// let user choose application
Intent sendIntent = new Intent(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, content);
sendIntent.setType("text/plain");
String title;
if (fingerprintOnly) {
title = getResources().getString(R.string.title_share_fingerprint_with);
} else {
title = getResources().getString(R.string.title_share_key);
}
startActivity(Intent.createChooser(sendIntent, title));
}
} catch (IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(getActivity(), R.string.error_key_processing, AppMsg.STYLE_ALERT).show();
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
}
}
private void showQrCodeDialog() {
ShareQrCodeDialogFragment dialog = ShareQrCodeDialogFragment.newInstance(mDataUri);
dialog.show(ViewKeyShareFragment.this.getActivity().getSupportFragmentManager(), "shareQrCodeDialog");
}
private void showNfcHelpDialog() {
ShareNfcDialogFragment dialog = ShareNfcDialogFragment.newInstance();
dialog.show(getActivity().getSupportFragmentManager(), "shareNfcDialog");
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
private void showNfcPrefs() {
Intent intentSettings = new Intent(
Settings.ACTION_NFCSHARING_SETTINGS);
startActivity(intentSettings);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Uri dataUri = getArguments().getParcelable(ARG_DATA_URI);
if (dataUri == null) {
Log.e(Constants.TAG, "Data missing. Should be Uri of key!");
getActivity().finish();
return;
}
loadData(dataUri);
}
private void loadData(Uri dataUri) {
mDataUri = dataUri;
Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());
// Prepare the loaders. Either re-connect with an existing ones,
// or start new ones.
getLoaderManager().initLoader(LOADER_ID_UNIFIED, null, this);
}
static final String[] UNIFIED_PROJECTION = new String[]{
KeyRings._ID, KeyRings.MASTER_KEY_ID, KeyRings.HAS_ANY_SECRET,
KeyRings.USER_ID, KeyRings.FINGERPRINT,
KeyRings.ALGORITHM, KeyRings.KEY_SIZE, KeyRings.CREATION, KeyRings.EXPIRY,
};
static final int INDEX_UNIFIED_MASTER_KEY_ID = 1;
static final int INDEX_UNIFIED_HAS_ANY_SECRET = 2;
static final int INDEX_UNIFIED_USER_ID = 3;
static final int INDEX_UNIFIED_FINGERPRINT = 4;
static final int INDEX_UNIFIED_ALGORITHM = 5;
static final int INDEX_UNIFIED_KEY_SIZE = 6;
static final int INDEX_UNIFIED_CREATION = 7;
static final int INDEX_UNIFIED_EXPIRY = 8;
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
setContentShown(false);
switch (id) {
case LOADER_ID_UNIFIED: {
Uri baseUri = KeyRings.buildUnifiedKeyRingUri(mDataUri);
return new CursorLoader(getActivity(), baseUri, UNIFIED_PROJECTION, null, null, null);
}
default:
return null;
}
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
/* TODO better error handling? May cause problems when a key is deleted,
* because the notification triggers faster than the activity closes.
*/
// Avoid NullPointerExceptions...
if (data.getCount() == 0) {
return;
}
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
switch (loader.getId()) {
case LOADER_ID_UNIFIED: {
if (data.moveToFirst()) {
byte[] fingerprintBlob = data.getBlob(INDEX_UNIFIED_FINGERPRINT);
String fingerprint = PgpKeyHelper.convertFingerprintToHex(fingerprintBlob);
mFingerprint.setText(PgpKeyHelper.colorizeFingerprint(fingerprint));
String qrCodeContent = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
mFingerprintQrCode.setImageBitmap(
QrCodeUtils.getQRCodeBitmap(qrCodeContent, QR_CODE_SIZE)
);
break;
}
}
}
setContentShown(true);
}
/**
* This is called when the last Cursor provided to onLoadFinished() above is about to be closed.
* We need to make sure we are no longer using it.
*/
public void onLoaderReset(Loader<Cursor> loader) {
}
}

View File

@@ -28,10 +28,10 @@ import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import java.util.ArrayList;
@@ -106,7 +106,7 @@ public class ImportKeysAdapter extends ArrayAdapter<ImportKeysListEntry> {
holder.mainUserId = (TextView) convertView.findViewById(R.id.mainUserId);
holder.mainUserIdRest = (TextView) convertView.findViewById(R.id.mainUserIdRest);
holder.keyId = (TextView) convertView.findViewById(R.id.keyId);
holder.fingerprint = (TextView) convertView.findViewById(R.id.fingerprint);
holder.fingerprint = (TextView) convertView.findViewById(R.id.view_key_fingerprint);
holder.algorithm = (TextView) convertView.findViewById(R.id.algorithm);
holder.status = (TextView) convertView.findViewById(R.id.status);
holder.userIdsList = (LinearLayout) convertView.findViewById(R.id.user_ids_list);

View File

@@ -1,269 +0,0 @@
/*
* Copyright (C) 2013 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.adapter;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.SparseArray;
import org.spongycastle.bcpg.SignatureSubpacketTags;
import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
public class ImportKeysListEntry implements Serializable, Parcelable {
private static final long serialVersionUID = -7797972103284992662L;
public ArrayList<String> userIds;
public long keyId;
public String keyIdHex;
public boolean revoked;
public Date date; // TODO: not displayed
public String fingerPrintHex;
public int bitStrength;
public String algorithm;
public boolean secretKey;
public String mPrimaryUserId;
private boolean mSelected;
private byte[] mBytes = new byte[]{};
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mPrimaryUserId);
dest.writeStringList(userIds);
dest.writeLong(keyId);
dest.writeByte((byte) (revoked ? 1 : 0));
dest.writeSerializable(date);
dest.writeString(fingerPrintHex);
dest.writeString(keyIdHex);
dest.writeInt(bitStrength);
dest.writeString(algorithm);
dest.writeByte((byte) (secretKey ? 1 : 0));
dest.writeByte((byte) (mSelected ? 1 : 0));
dest.writeInt(mBytes.length);
dest.writeByteArray(mBytes);
}
public static final Creator<ImportKeysListEntry> CREATOR = new Creator<ImportKeysListEntry>() {
public ImportKeysListEntry createFromParcel(final Parcel source) {
ImportKeysListEntry vr = new ImportKeysListEntry();
vr.mPrimaryUserId = source.readString();
vr.userIds = new ArrayList<String>();
source.readStringList(vr.userIds);
vr.keyId = source.readLong();
vr.revoked = source.readByte() == 1;
vr.date = (Date) source.readSerializable();
vr.fingerPrintHex = source.readString();
vr.keyIdHex = source.readString();
vr.bitStrength = source.readInt();
vr.algorithm = source.readString();
vr.secretKey = source.readByte() == 1;
vr.mSelected = source.readByte() == 1;
vr.mBytes = new byte[source.readInt()];
source.readByteArray(vr.mBytes);
return vr;
}
public ImportKeysListEntry[] newArray(final int size) {
return new ImportKeysListEntry[size];
}
};
public String getKeyIdHex() {
return keyIdHex;
}
public byte[] getBytes() {
return mBytes;
}
public void setBytes(byte[] bytes) {
this.mBytes = bytes;
}
public boolean isSelected() {
return mSelected;
}
public void setSelected(boolean selected) {
this.mSelected = selected;
}
public long getKeyId() {
return keyId;
}
public void setKeyId(long keyId) {
this.keyId = keyId;
}
public void setKeyIdHex(String keyIdHex) {
this.keyIdHex = keyIdHex;
}
public boolean isRevoked() {
return revoked;
}
public void setRevoked(boolean revoked) {
this.revoked = revoked;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
public String getFingerPrintHex() {
return fingerPrintHex;
}
public void setFingerPrintHex(String fingerPrintHex) {
this.fingerPrintHex = fingerPrintHex;
}
public int getBitStrength() {
return bitStrength;
}
public void setBitStrength(int bitStrength) {
this.bitStrength = bitStrength;
}
public String getAlgorithm() {
return algorithm;
}
public void setAlgorithm(String algorithm) {
this.algorithm = algorithm;
}
public boolean isSecretKey() {
return secretKey;
}
public void setSecretKey(boolean secretKey) {
this.secretKey = secretKey;
}
public ArrayList<String> getUserIds() {
return userIds;
}
public void setUserIds(ArrayList<String> userIds) {
this.userIds = userIds;
}
public String getPrimaryUserId() {
return mPrimaryUserId;
}
public void setPrimaryUserId(String uid) {
mPrimaryUserId = uid;
}
/**
* Constructor for later querying from keyserver
*/
public ImportKeysListEntry() {
// keys from keyserver are always public keys
secretKey = false;
// do not select by default
mSelected = false;
userIds = new ArrayList<String>();
}
/**
* Constructor based on key object, used for import from NFC, QR Codes, files
*/
@SuppressWarnings("unchecked")
public ImportKeysListEntry(Context context, PGPKeyRing pgpKeyRing) {
// save actual key object into entry, used to import it later
try {
this.mBytes = pgpKeyRing.getEncoded();
} catch (IOException e) {
Log.e(Constants.TAG, "IOException on pgpKeyRing.getEncoded()", e);
}
// selected is default
this.mSelected = true;
if (pgpKeyRing instanceof PGPSecretKeyRing) {
secretKey = true;
} else {
secretKey = false;
}
PGPPublicKey key = pgpKeyRing.getPublicKey();
userIds = new ArrayList<String>();
for (String userId : new IterableIterator<String>(key.getUserIDs())) {
userIds.add(userId);
for (PGPSignature sig : new IterableIterator<PGPSignature>(key.getSignaturesForID(userId))) {
if (sig.getHashedSubPackets() != null
&& sig.getHashedSubPackets().hasSubpacket(SignatureSubpacketTags.PRIMARY_USER_ID)) {
try {
// make sure it's actually valid
sig.init(new JcaPGPContentVerifierBuilderProvider().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME), key);
if (sig.verifyCertification(userId, key)) {
mPrimaryUserId = userId;
}
} catch (Exception e) {
// nothing bad happens, the key is just not considered the primary key id
}
}
}
}
// if there was no user id flagged as primary, use the first one
if (mPrimaryUserId == null) {
mPrimaryUserId = userIds.get(0);
}
this.keyId = key.getKeyID();
this.keyIdHex = PgpKeyHelper.convertKeyIdToHex(keyId);
this.revoked = key.isRevoked();
this.fingerPrintHex = PgpKeyHelper.convertFingerprintToHex(key.getFingerprint());
this.bitStrength = key.getBitStrength();
final int algorithm = key.getAlgorithm();
this.algorithm = PgpKeyHelper.getAlgorithmInfo(context, algorithm);
}
}

View File

@@ -0,0 +1,107 @@
/*
* 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.adapter;
import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeyServer;
import org.sufficientlysecure.keychain.keyimport.KeybaseKeyServer;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
public class ImportKeysListKeybaseLoader
extends AsyncTaskLoader<AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>> {
Context mContext;
String mKeybaseQuery;
private ArrayList<ImportKeysListEntry> mEntryList = new ArrayList<ImportKeysListEntry>();
private AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> mEntryListWrapper;
public ImportKeysListKeybaseLoader(Context context, String keybaseQuery) {
super(context);
mContext = context;
mKeybaseQuery = keybaseQuery;
}
@Override
public AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> loadInBackground() {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
if (mKeybaseQuery == null) {
Log.e(Constants.TAG, "mKeybaseQery is null!");
return mEntryListWrapper;
}
queryServer(mKeybaseQuery);
return mEntryListWrapper;
}
@Override
protected void onReset() {
super.onReset();
// Ensure the loader is stopped
onStopLoading();
}
@Override
protected void onStartLoading() {
forceLoad();
}
@Override
protected void onStopLoading() {
cancelLoad();
}
@Override
public void deliverResult(AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>> data) {
super.deliverResult(data);
}
/**
* Query keybase
*/
private void queryServer(String query) {
KeybaseKeyServer server = new KeybaseKeyServer();
try {
ArrayList<ImportKeysListEntry> searchResult = server.search(query);
mEntryList.clear();
mEntryList.addAll(searchResult);
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
} catch (KeyServer.InsufficientQuery e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (KeyServer.QueryException e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (KeyServer.TooManyResponses e) {
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
}
}
}

View File

@@ -24,6 +24,7 @@ import org.spongycastle.openpgp.PGPKeyRing;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPUtil;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.PositionAwareInputStream;

View File

@@ -21,8 +21,9 @@ import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.util.HkpKeyServer;
import org.sufficientlysecure.keychain.util.KeyServer;
import org.sufficientlysecure.keychain.keyimport.HkpKeyServer;
import org.sufficientlysecure.keychain.keyimport.ImportKeysListEntry;
import org.sufficientlysecure.keychain.keyimport.KeyServer;
import org.sufficientlysecure.keychain.util.Log;
import java.util.ArrayList;
@@ -116,13 +117,10 @@ public class ImportKeysListServerLoader
}
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, null);
} catch (KeyServer.InsufficientQuery e) {
Log.e(Constants.TAG, "InsufficientQuery", e);
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (KeyServer.QueryException e) {
Log.e(Constants.TAG, "QueryException", e);
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
} catch (KeyServer.TooManyResponses e) {
Log.e(Constants.TAG, "TooManyResponses", e);
mEntryListWrapper = new AsyncTaskResultWrapper<ArrayList<ImportKeysListEntry>>(mEntryList, e);
}
}

View File

@@ -17,7 +17,7 @@
package org.sufficientlysecure.keychain.ui.adapter;
import android.content.Context;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
@@ -26,8 +26,8 @@ import android.support.v7.app.ActionBarActivity;
import java.util.ArrayList;
public class PagerTabStripAdapter extends FragmentPagerAdapter {
private final Context mContext;
private final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
protected final Activity mActivity;
protected final ArrayList<TabInfo> mTabs = new ArrayList<TabInfo>();
static final class TabInfo {
public final Class<?> clss;
@@ -43,7 +43,7 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter {
public PagerTabStripAdapter(ActionBarActivity activity) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActivity = activity;
}
public void addTab(Class<?> clss, Bundle args, String title) {
@@ -60,7 +60,7 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter {
@Override
public Fragment getItem(int position) {
TabInfo info = mTabs.get(position);
return Fragment.instantiate(mContext, info.clss.getName(), info.args);
return Fragment.instantiate(mActivity, info.clss.getName(), info.args);
}
@Override

View File

@@ -121,35 +121,17 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
keyId.setText(keyIdStr);
// may be set with additional "stripped" later on
if (hasAnySecret && cursor.getInt(mIndexHasSecret) == 0) {
keyDetails.setText("(" + algorithmStr + ", " +
context.getString(R.string.key_stripped) + ")");
keyDetails.setText(algorithmStr + ", " +
context.getString(R.string.key_stripped));
} else {
keyDetails.setText("(" + algorithmStr + ")");
keyDetails.setText(algorithmStr);
}
if (cursor.getInt(mIndexRank) == 0) {
masterKeyIcon.setVisibility(View.INVISIBLE);
} else {
masterKeyIcon.setVisibility(View.VISIBLE);
}
if (cursor.getInt(mIndexCanCertify) != 1) {
certifyIcon.setVisibility(View.GONE);
} else {
certifyIcon.setVisibility(View.VISIBLE);
}
if (cursor.getInt(mIndexCanEncrypt) != 1) {
encryptIcon.setVisibility(View.GONE);
} else {
encryptIcon.setVisibility(View.VISIBLE);
}
if (cursor.getInt(mIndexCanSign) != 1) {
signIcon.setVisibility(View.GONE);
} else {
signIcon.setVisibility(View.VISIBLE);
}
// Set icons according to properties
masterKeyIcon.setVisibility(cursor.getInt(mIndexRank) == 0 ? View.VISIBLE : View.INVISIBLE);
certifyIcon.setVisibility(cursor.getInt(mIndexCanCertify) != 0 ? View.VISIBLE : View.GONE);
encryptIcon.setVisibility(cursor.getInt(mIndexCanEncrypt) != 0 ? View.VISIBLE : View.GONE);
signIcon.setVisibility(cursor.getInt(mIndexCanSign) != 0 ? View.VISIBLE : View.GONE);
boolean valid = true;
if (cursor.getInt(mIndexRevokedKey) > 0) {
@@ -168,13 +150,13 @@ public class ViewKeyKeysAdapter extends CursorAdapter {
Date expiryDate = new Date(cursor.getLong(mIndexExpiry) * 1000);
valid = valid && expiryDate.after(new Date());
keyExpiry.setText("(" +
keyExpiry.setText(
context.getString(R.string.label_expiry) + ": " +
DateFormat.getDateFormat(context).format(expiryDate) + ")");
keyExpiry.setVisibility(View.VISIBLE);
DateFormat.getDateFormat(context).format(expiryDate));
} else {
keyExpiry.setVisibility(View.GONE);
keyExpiry.setText(
context.getString(R.string.label_expiry) + ": " +
context.getString(R.string.none));
}
// if key is expired or revoked, strike through text

View File

@@ -27,6 +27,7 @@ import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
@@ -106,46 +107,58 @@ public class ViewKeyUserIdsAdapter extends CursorAdapter implements AdapterView.
@Override
public void bindView(View view, Context context, Cursor cursor) {
TextView vRank = (TextView) view.findViewById(R.id.rank);
TextView vUserId = (TextView) view.findViewById(R.id.userId);
TextView vName = (TextView) view.findViewById(R.id.userId);
TextView vAddress = (TextView) view.findViewById(R.id.address);
TextView vComment = (TextView) view.findViewById(R.id.comment);
ImageView vVerified = (ImageView) view.findViewById(R.id.certified);
if (cursor.getInt(mIsPrimary) > 0) {
vRank.setText("+");
} else {
vRank.setText(Integer.toString(cursor.getInt(mIndexRank)));
}
String[] userId = PgpKeyHelper.splitUserId(cursor.getString(mIndexUserId));
if (userId[0] != null) {
vUserId.setText(userId[0]);
vName.setText(userId[0]);
} else {
vUserId.setText(R.string.user_id_no_name);
vName.setText(R.string.user_id_no_name);
}
vAddress.setText(userId[1]);
if (userId[1] != null) {
vAddress.setText(userId[1]);
vAddress.setVisibility(View.VISIBLE);
} else {
vAddress.setVisibility(View.GONE);
}
if (userId[2] != null) {
vComment.setText(userId[2]);
vComment.setVisibility(View.VISIBLE);
} else {
vComment.setVisibility(View.GONE);
}
// show small star icon for primary user ids
boolean isPrimary = cursor.getInt(mIsPrimary) != 0;
if (cursor.getInt(mIsRevoked) > 0) {
vRank.setText(" ");
// set revocation icon (can this even be primary?)
vVerified.setImageResource(R.drawable.key_certify_revoke);
// disable and strike through text for revoked user ids
vUserId.setEnabled(false);
vName.setEnabled(false);
vAddress.setEnabled(false);
vUserId.setText(OtherHelper.strikeOutText(vUserId.getText()));
vName.setText(OtherHelper.strikeOutText(vName.getText()));
vAddress.setText(OtherHelper.strikeOutText(vAddress.getText()));
} else {
vUserId.setEnabled(true);
vName.setEnabled(true);
vAddress.setEnabled(true);
int verified = cursor.getInt(mVerifiedId);
// TODO introduce own resources for this :)
switch (verified) {
case Certs.VERIFIED_SECRET:
vVerified.setImageResource(R.drawable.key_certify_ok_depth0);
vVerified.setImageResource(isPrimary
? R.drawable.key_certify_primary_ok_depth0
: R.drawable.key_certify_ok_depth0);
break;
case Certs.VERIFIED_SELF:
vVerified.setImageResource(R.drawable.key_certify_ok_self);
vVerified.setImageResource(isPrimary
? R.drawable.key_certify_primary_ok_self
: R.drawable.key_certify_ok_self);
break;
default:
vVerified.setImageResource(R.drawable.key_certify_error);

View File

@@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -50,7 +49,7 @@ public class BadImportKeyDialogFragment extends DialogFragment {
final FragmentActivity activity = getActivity();
final int badImport = getArguments().getInt(ARG_BAD_IMPORT);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
alert.setTitle(R.string.warning);
alert.setMessage(activity.getResources()
@@ -63,6 +62,6 @@ public class BadImportKeyDialogFragment extends DialogFragment {
});
alert.setCancelable(true);
return alert.create();
return alert.show();
}
}

View File

@@ -83,7 +83,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
final int childCount = getArguments().getInt(ARG_EDITOR_CHILD_COUNT);
mInflater = context.getLayoutInflater();
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
CustomAlertDialogBuilder dialog = new CustomAlertDialogBuilder(context);
View view = mInflater.inflate(R.layout.create_key_dialog, null);
dialog.setView(view);
@@ -146,7 +146,7 @@ public class CreateKeyDialogFragment extends DialogFragment {
}
});
final AlertDialog alertDialog = dialog.create();
final AlertDialog alertDialog = dialog.show();
mCustomKeyEditText.addTextChangedListener(new TextWatcher() {
@Override

View File

@@ -0,0 +1,40 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.view.View;
import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
/** This class extends AlertDiaog.Builder, styling the header using emphasis color.
* Note that this class is a huge hack, because dialog boxes aren't easily stylable.
* Also, the dialog NEEDS to be called with show() directly, not create(), otherwise
* the order of internal operations will lead to a crash!
*/
public class CustomAlertDialogBuilder extends AlertDialog.Builder {
public CustomAlertDialogBuilder(Activity activity) {
super(activity);
}
@Override
public AlertDialog show() {
AlertDialog dialog = super.show();
int dividerId = dialog.getContext().getResources().getIdentifier("android:id/titleDivider", null, null);
View divider = dialog.findViewById(dividerId);
if (divider != null) {
divider.setBackgroundColor(dialog.getContext().getResources().getColor(R.color.emphasis));
}
int textViewId = dialog.getContext().getResources().getIdentifier("android:id/alertTitle", null, null);
TextView tv = (TextView) dialog.findViewById(textViewId);
if (tv != null) {
tv.setTextColor(dialog.getContext().getResources().getColor(R.color.emphasis));
}
return dialog;
}
}

View File

@@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
@@ -59,7 +58,7 @@ public class DeleteFileDialogFragment extends DialogFragment {
final String deleteFile = getArguments().getString(ARG_DELETE_FILE);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setIcon(R.drawable.ic_dialog_alert_holo_light);
@@ -120,6 +119,6 @@ public class DeleteFileDialogFragment extends DialogFragment {
});
alert.setCancelable(true);
return alert.create();
return alert.show();
}
}

View File

@@ -17,7 +17,6 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.os.Bundle;
@@ -73,7 +72,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
final long[] masterKeyIds = getArguments().getLongArray(ARG_DELETE_MASTER_KEY_IDS);
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder builder = new CustomAlertDialogBuilder(activity);
// Setup custom View to display in AlertDialog
LayoutInflater inflater = activity.getLayoutInflater();
@@ -144,7 +143,7 @@ public class DeleteKeyDialogFragment extends DialogFragment {
}
});
return builder.create();
return builder.show();
}
/**

View File

@@ -97,7 +97,7 @@ public class FileDialogFragment extends DialogFragment {
LayoutInflater inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(title);
View view = inflater.inflate(R.layout.file_dialog, null);
@@ -157,7 +157,7 @@ public class FileDialogFragment extends DialogFragment {
dismiss();
}
});
return alert.create();
return alert.show();
}
/**

View File

@@ -134,7 +134,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
final long secretKeyId = getArguments().getLong(ARG_SECRET_KEY_ID);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(R.string.title_authentication);
@@ -243,7 +243,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor
});
mCanKB = true;
return alert.create();
return alert.show();
}
@Override

View File

@@ -81,7 +81,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
int title = getArguments().getInt(ARG_TITLE);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(title);
alert.setMessage(R.string.enter_passphrase_twice);
@@ -135,7 +135,7 @@ public class SetPassphraseDialogFragment extends DialogFragment implements OnEdi
}
});
return alert.create();
return alert.show();
}
@Override

View File

@@ -18,7 +18,6 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.annotation.TargetApi;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
@@ -51,7 +50,7 @@ public class ShareNfcDialogFragment extends DialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
final FragmentActivity activity = getActivity();
AlertDialog.Builder alert = new AlertDialog.Builder(activity);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(R.string.share_nfc_dialog);
alert.setCancelable(true);
@@ -78,7 +77,7 @@ public class ShareNfcDialogFragment extends DialogFragment {
+ getString(R.string.error_nfc_needed));
} else {
// nfc works...
textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share);
textView.setHtmlFromRawResource(getActivity(), R.raw.nfc_beam_share, true);
alert.setNegativeButton(R.string.menu_beam_preferences,
new DialogInterface.OnClickListener() {
@@ -93,6 +92,6 @@ public class ShareNfcDialogFragment extends DialogFragment {
}
}
return alert.create();
return alert.show();
}
}

View File

@@ -18,14 +18,12 @@
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
@@ -34,37 +32,26 @@ import com.devspark.appmsg.AppMsg;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.pgp.PgpKeyHelper;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.QrCodeUtils;
import java.io.IOException;
import java.util.ArrayList;
public class ShareQrCodeDialogFragment extends DialogFragment {
private static final String ARG_KEY_URI = "uri";
private static final String ARG_FINGERPRINT_ONLY = "fingerprint_only";
private ImageView mImage;
private TextView mText;
private boolean mFingerprintOnly;
private ArrayList<String> mContentList;
private int mCounter;
private static final int QR_CODE_SIZE = 1000;
/**
* Creates new instance of this dialog fragment
*/
public static ShareQrCodeDialogFragment newInstance(Uri dataUri, boolean fingerprintOnly) {
public static ShareQrCodeDialogFragment newInstance(Uri dataUri) {
ShareQrCodeDialogFragment frag = new ShareQrCodeDialogFragment();
Bundle args = new Bundle();
args.putParcelable(ARG_KEY_URI, dataUri);
args.putBoolean(ARG_FINGERPRINT_ONLY, fingerprintOnly);
frag.setArguments(args);
@@ -79,9 +66,8 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
final Activity activity = getActivity();
Uri dataUri = getArguments().getParcelable(ARG_KEY_URI);
mFingerprintOnly = getArguments().getBoolean(ARG_FINGERPRINT_ONLY);
AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(getActivity());
alert.setTitle(R.string.share_qr_code_dialog_title);
LayoutInflater inflater = activity.getLayoutInflater();
@@ -94,131 +80,32 @@ public class ShareQrCodeDialogFragment extends DialogFragment {
ProviderHelper providerHelper = new ProviderHelper(getActivity());
String content;
try {
if (mFingerprintOnly) {
alert.setPositiveButton(R.string.btn_okay, null);
alert.setPositiveButton(R.string.btn_okay, null);
byte[] blob = (byte[]) providerHelper.getGenericData(
KeyRings.buildUnifiedKeyRingUri(dataUri),
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (blob == null) {
Log.e(Constants.TAG, "key not found!");
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
return null;
}
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
setQrCode(content);
} else {
mText.setText(R.string.share_qr_code_dialog_start);
try {
Uri uri = KeychainContract.KeyRingData.buildPublicKeyRingUri(dataUri);
content = providerHelper.getKeyRingAsArmoredString(uri);
} catch (IOException e) {
Log.e(Constants.TAG, "error processing key!", e);
AppMsg.makeText(getActivity(), R.string.error_invalid_data, AppMsg.STYLE_ALERT).show();
return null;
}
// OnClickListener are set in onResume to prevent automatic dismissing of Dialogs
// http://bit.ly/O5vfaR
alert.setPositiveButton(R.string.btn_next, null);
alert.setNegativeButton(android.R.string.cancel, null);
mContentList = splitString(content, 1000);
// start with first
mCounter = 0;
updatePartsQrCode();
byte[] blob = (byte[]) providerHelper.getGenericData(
KeyRings.buildUnifiedKeyRingUri(dataUri),
KeyRings.FINGERPRINT, ProviderHelper.FIELD_TYPE_BLOB);
if (blob == null) {
Log.e(Constants.TAG, "key not found!");
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
return null;
}
String fingerprint = PgpKeyHelper.convertFingerprintToHex(blob);
mText.setText(getString(R.string.share_qr_code_dialog_fingerprint_text) + " " + fingerprint);
content = Constants.FINGERPRINT_SCHEME + ":" + fingerprint;
setQrCode(content);
} catch (ProviderHelper.NotFoundException e) {
Log.e(Constants.TAG, "key not found!", e);
AppMsg.makeText(getActivity(), R.string.error_key_not_found, AppMsg.STYLE_ALERT).show();
return null;
}
return alert.create();
}
@Override
public void onResume() {
super.onResume();
if (!mFingerprintOnly) {
AlertDialog alertDialog = (AlertDialog) getDialog();
final Button backButton = alertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
final Button nextButton = alertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
backButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mCounter > 0) {
mCounter--;
updatePartsQrCode();
updateDialog(backButton, nextButton);
} else {
dismiss();
}
}
});
nextButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mCounter < mContentList.size() - 1) {
mCounter++;
updatePartsQrCode();
updateDialog(backButton, nextButton);
} else {
dismiss();
}
}
});
}
}
private void updatePartsQrCode() {
// Content: <counter>,<size>,<content>
setQrCode(mCounter + "," + mContentList.size() + "," + mContentList.get(mCounter));
return alert.show();
}
private void setQrCode(String data) {
mImage.setImageBitmap(QrCodeUtils.getQRCodeBitmap(data, QR_CODE_SIZE));
}
private void updateDialog(Button backButton, Button nextButton) {
if (mCounter == 0) {
backButton.setText(android.R.string.cancel);
} else {
backButton.setText(R.string.btn_back);
}
if (mCounter == mContentList.size() - 1) {
nextButton.setText(android.R.string.ok);
} else {
nextButton.setText(R.string.btn_next);
}
mText.setText(getResources().getString(R.string.share_qr_code_dialog_progress,
mCounter + 1, mContentList.size()));
}
/**
* Split String by number of characters
*
* @param text
* @param size
* @return
*/
private ArrayList<String> splitString(String text, int size) {
ArrayList<String> strings = new ArrayList<String>();
int index = 0;
while (index < text.length()) {
strings.add(text.substring(index, Math.min(index + size, text.length())));
index += size;
}
return strings;
}
}

View File

@@ -1,69 +0,0 @@
/*
* Copyright (C) 2013 Eric Frohnhoefer
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;
/**
* Copied from StickyListHeaders lib example
*
* @author Eric Frohnhoefer
*/
public class UnderlineTextView extends TextView {
private final Paint mPaint = new Paint();
private int mUnderlineHeight = 0;
public UnderlineTextView(Context context) {
this(context, null);
}
public UnderlineTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public UnderlineTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
Resources r = getResources();
mUnderlineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2,
r.getDisplayMetrics());
}
@Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom + mUnderlineHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// Draw the underline the same color as the text
mPaint.setColor(getTextColors().getDefaultColor());
canvas.drawRect(0, getHeight() - mUnderlineHeight, getWidth(), getHeight(), mPaint);
}
}