Merge remote-tracking branch 'origin/development' into linked-identities

Conflicts:
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptFragment.java
	OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/DecryptTextFragment.java
	OpenKeychain/src/main/res/menu/decrypt_menu.xml
This commit is contained in:
Vincent Breitmoser 2015-04-26 01:02:22 +02:00
commit 1865852ccc
55 changed files with 1113 additions and 238 deletions

View File

@ -41,6 +41,8 @@ python copy OpenKeychain navigation grey chevron_left 24
python copy OpenKeychain navigation grey chevron_right 24
python copy OpenKeychain social grey person 48
python copy OpenKeychain communication grey email 24
python copy OpenKeychain social black share 24
python copy OpenKeychain content black content_copy 24
# navigation drawer sections
python copy OpenKeychain communication black vpn_key 24

View File

@ -6,10 +6,10 @@ dependencies {
// NOTE: libraries are pinned to a specific build, see below
// from local Android SDK
compile 'com.android.support:support-v4:22.0.0'
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.android.support:recyclerview-v7:22.0.0'
compile 'com.android.support:cardview-v7:22.0.0'
compile 'com.android.support:support-v4:22.1.0'
compile 'com.android.support:appcompat-v7:22.1.0'
compile 'com.android.support:recyclerview-v7:22.1.0'
compile 'com.android.support:cardview-v7:22.1.0'
// JCenter etc.
compile 'com.eftimoff:android-patternview:1.0.1@aar'
@ -17,13 +17,17 @@ dependencies {
compile 'com.journeyapps:zxing-android-integration:2.3.0@aar'
compile 'com.google.zxing:core:3.2.0'
compile 'com.jpardogo.materialtabstrip:library:1.0.9'
compile 'it.neokree:MaterialNavigationDrawer:1.3.2'
compile 'com.getbase:floatingactionbutton:1.9.0'
compile 'org.commonjava.googlecode.markdown4j:markdown4j:2.2-cj-1.0'
compile 'org.ocpsoft.prettytime:prettytime:3.2.7.Final'
compile "com.splitwise:tokenautocomplete:1.3.3@aar"
compile 'se.emilsjolander:stickylistheaders:2.6.0'
compile 'org.sufficientlysecure:html-textview:1.1'
compile 'com.mikepenz.materialdrawer:library:2.7.9@aar'
compile 'com.mikepenz.iconics:library:0.9.1@aar'
compile 'com.mikepenz.iconics:octicons-typeface:2.2.0@aar'
compile 'com.mikepenz.iconics:meteocons-typeface:1.1.1@aar'
compile 'com.mikepenz.iconics:community-material-typeface:1.0.0@aar'
// libs as submodules
compile project(':extern:openpgp-api-lib')
@ -42,21 +46,25 @@ dependencies {
// Comment out the libs referenced as git submodules!
dependencyVerification {
verify = [
'com.android.support:support-v4:355a11466727e8ba00e239416aec55ac3cd3fb4ffc9d20c4a33373085c050bd1',
'com.android.support:appcompat-v7:40114cb756fecffa4a51c5645593cf64509c576594f77e41e801368051115c7b',
'com.android.support:recyclerview-v7:859ed80e3761f8fc3126901260b208505120b5678bcf36ad2cfe9c453958b9c7',
'com.android.support:cardview-v7:4c03f2acce9925aa4f8845cb8cb37b3772c712b2438ff15f76c9e3d3bc63ead7',
'com.android.support:support-v4:74cb322740317b11a785eee1a94969426fade946123c4ae3f471276adaaaf54b',
'com.android.support:appcompat-v7:6cc7fc2df4be0676f78ecfc5d3cda388e59890d11308811944f54efd84b047b7',
'com.android.support:recyclerview-v7:522d323079a29bcd76173bd9bc7535223b4af3e5eefef9d9287df1f9e54d0c10',
'com.android.support:cardview-v7:8dc99af71fec000baa4470c3907755264f15f816920861bc015b2babdbb49807',
'com.eftimoff:android-patternview:cec80e7265b8d8278b3c55b5fcdf551e4600ac2c8bf60d8dd76adca538af0b1e',
'com.journeyapps:zxing-android-embedded:702a4f58154dbd9baa80f66b6a15410f7a4d403f3e73b66537a8bfb156b4b718',
'com.journeyapps:zxing-android-integration:562737821b6d34c899b6fd2234ce0a8a31e02ff1fd7c59f6211961ce9767c7c8',
'com.google.zxing:core:7fe5a8ff437635a540e56317649937b768b454795ce999ed5f244f83373dee7b',
'com.jpardogo.materialtabstrip:library:c6ef812fba4f74be7dc4a905faa4c2908cba261a94c13d4f96d5e67e4aad4aaa',
'it.neokree:MaterialNavigationDrawer:a1221a410c5f71bf078c5c4768fdf06b402d6006c74f8e7b61199e4edc2aea57',
'com.getbase:floatingactionbutton:052aa2a94e49e5dccc97cb99f2add87e8698b84859f0e3ac181100c0bc7640ca',
'org.commonjava.googlecode.markdown4j:markdown4j:e952e825d29e1317d96f79f346bfb6786c7c5eef50bd26e54a80823704b62e13',
'com.splitwise:tokenautocomplete:20bee71cc59b3828eb000b684d46ddf738efd56b8fee453a509cd16fda42c8cb',
'se.emilsjolander:stickylistheaders:8c05981ec5725be33f7cee5e68c13f3db49cd5c75f1aaeb04024920b1ef96ad4',
'org.sufficientlysecure:html-textview:ca24b1522be88378634093815ce9ff1b4920c72e7513a045a7846e14069ef988',
'com.mikepenz.materialdrawer:library:3ef80c6e1ca1b29cfcbb27fa7927c02b2246e068c17fe52283703c4897449923',
'com.mikepenz.iconics:library:4698a36ee4c2af765d0a85779c61474d755b90d66a59020105b6760a8a909e9e',
'com.mikepenz.iconics:octicons-typeface:67ed7d456a9ce5f5307b85f955797bfb3dd674e2f6defb31c6b8bbe2ede290be',
'com.mikepenz.iconics:meteocons-typeface:39a8a9e70cd8287cdb119af57a672a41dd09240dba6697f5a0dbda1ccc33298b',
'com.mikepenz.iconics:community-material-typeface:f1c5afee5f0f10d66beb3ed0df977246a02a9c46de4e05d7c0264bcde53b6b7f',
// 'OpenKeychain.extern:openpgp-api-lib:f05a9215cdad3a6597e4c5ece6fcec92b178d218195a3e88d2c0937c48dd9580',
// 'OpenKeychain.extern:openkeychain-api-lib:50f6ebb5452d3fdc7be137ccf857a0ff44d55539fcb7b91baef495766ed7f429',
// 'com.madgag.spongycastle:core:df8fcc028a95ac5ffab3b78c9163f5cfa672e41cd50128ca55d458b6cfbacf4b',
@ -67,8 +75,7 @@ dependencyVerification {
// 'OpenKeychain.extern.KeybaseLib:Lib:c91cda4a75692d8664644cd17d8ac962ce5bc0e266ea26673a639805f1eccbdf',
// 'OpenKeychain.extern:safeslinger-exchange:d222721bb35408daaab9f46449364b2657112705ee571d7532f81cbeb9c4a73f',
// 'OpenKeychain.extern.snackbar:lib:52357426e5275412e2063bdf6f0e6b957a3ea74da45e0aef35d22d9afc542e23',
'com.android.support:support-annotations:ab6b131ab0e1edd165d21fb4c3edadeacbee9539aa166f7f7cbae05b60dc207a',
'com.balysv:material-ripple:b2580520bcb5e5d77bd8c42b030317accaf8f88e7e57c46a29c47c8a62d4ff45',
'com.android.support:support-annotations:9c59286413a2bb93e199c73261e58d5af32da7ae0a12cbd075f581a5de1fb446',
]
}

View File

@ -628,6 +628,23 @@
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\..*\\.bin" />
</intent-filter>
<!-- VIEW from keyserver urls opened in a browser -->
<intent-filter android:label="@string/intent_import_key">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="https"/>
<data android:scheme="http"/>
<!-- if we don't specify a host, pathPattern will be ignored-->
<data android:host="*"/>
<!-- convention for keyserver paths specified by internet draft
draft-shaw-openpgp-hkp-00.txt
(http://tools.ietf.org/html/draft-shaw-openpgp-hkp-00#section-3) -->
<data android:pathPattern="/pks/lookup.*"/>
</intent-filter>
<!-- Keychain's own Actions -->
<!-- IMPORT_KEY with files TODO: does this work? -->
<intent-filter android:label="@string/intent_import_key">

View File

@ -242,7 +242,7 @@ public class HkpKeyserver extends Keyserver {
String encodedQuery;
try {
encodedQuery = URLEncoder.encode(query, "utf8");
encodedQuery = URLEncoder.encode(query, "UTF8");
} catch (UnsupportedEncodingException e) {
return null;
}
@ -286,7 +286,7 @@ public class HkpKeyserver extends Keyserver {
entry.setAlgorithm(KeyFormattingUtils.getAlgorithmInfo(algorithmId, bitSize, null));
// group 1 contains the full fingerprint (v4) or the long key id if available
// see http://bit.ly/1d4bxbk and http://bit.ly/1gD1wwr
// see https://bitbucket.org/skskeyserver/sks-keyserver/pull-request/12/fixes-for-machine-readable-indexes/diff
String fingerprintOrKeyId = matcher.group(1).toLowerCase(Locale.ENGLISH);
if (fingerprintOrKeyId.length() > 16) {
entry.setFingerprintHex(fingerprintOrKeyId);
@ -312,14 +312,13 @@ public class HkpKeyserver extends Keyserver {
String tmp = uidMatcher.group(1).trim();
if (tmp.contains("%")) {
if (tmp.contains("%%")) {
// This is a fix for issue #683
// The server encodes a percent sign as %%, so it is swapped out with its
// urlencoded counterpart to prevent errors
tmp = tmp.replace("%%", "%25");
}
try {
// converts Strings like "Universit%C3%A4t" to a proper encoding form "Universität".
tmp = (URLDecoder.decode(tmp, "UTF8"));
tmp = URLDecoder.decode(tmp, "UTF8");
} catch (UnsupportedEncodingException ignored) {
// will never happen, because "UTF8" is supported
}

View File

@ -123,7 +123,8 @@ public class CreateKeyYubiImportFragment extends Fragment implements NfcListener
});
}
mListFragment = ImportKeysListFragment.newInstance(null, null, "0x" + mNfcFingerprint, true);
mListFragment = ImportKeysListFragment.newInstance(null, null,
"0x" + mNfcFingerprint, true, null);
view.findViewById(R.id.button_search).setOnClickListener(new OnClickListener() {
@Override

View File

@ -79,17 +79,18 @@ public abstract class DecryptFragment extends CryptoOperationFragment implements
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mResultLayout = (LinearLayout) view.findViewById(R.id.result_main_layout);
// NOTE: These views are inside the activity!
mResultLayout = (LinearLayout) getActivity().findViewById(R.id.result_main_layout);
mResultLayout.setVisibility(View.GONE);
mEncryptionIcon = (ImageView) view.findViewById(R.id.result_encryption_icon);
mEncryptionText = (TextView) view.findViewById(R.id.result_encryption_text);
mSignatureIcon = (ImageView) view.findViewById(R.id.result_signature_icon);
mSignatureText = (TextView) view.findViewById(R.id.result_signature_text);
mSignatureLayout = view.findViewById(R.id.result_signature_layout);
mSignatureName = (TextView) view.findViewById(R.id.result_signature_name);
mSignatureEmail = (TextView) view.findViewById(R.id.result_signature_email);
mSignatureAction = (TextView) view.findViewById(R.id.result_signature_action);
mEncryptionIcon = (ImageView) getActivity().findViewById(R.id.result_encryption_icon);
mEncryptionText = (TextView) getActivity().findViewById(R.id.result_encryption_text);
mSignatureIcon = (ImageView) getActivity().findViewById(R.id.result_signature_icon);
mSignatureText = (TextView) getActivity().findViewById(R.id.result_signature_text);
mSignatureLayout = getActivity().findViewById(R.id.result_signature_layout);
mSignatureName = (TextView) getActivity().findViewById(R.id.result_signature_name);
mSignatureEmail = (TextView) getActivity().findViewById(R.id.result_signature_email);
mSignatureAction = (TextView) getActivity().findViewById(R.id.result_signature_action);
}

View File

@ -97,9 +97,9 @@ public class DecryptTextFragment extends DecryptFragment {
/**
* Create Intent Chooser but exclude decrypt activites
*/
private Intent sendWithChooserExcludingEncrypt(String text) {
private Intent sendWithChooserExcludingDecrypt(String text) {
Intent prototype = createSendIntent(text);
String title = getString(R.string.title_share_file);
String title = getString(R.string.title_share_message);
// we don't want to decrypt the decrypted, no inception ;)
String[] blacklist = new String[]{
@ -147,7 +147,7 @@ public class DecryptTextFragment extends DecryptFragment {
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.decrypt_share: {
startActivity(sendWithChooserExcludingEncrypt(mText.getText().toString()));
startActivity(sendWithChooserExcludingDecrypt(mText.getText().toString()));
break;
}
case R.id.decrypt_copy: {

View File

@ -202,7 +202,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
} catch (IOException e) {
Notify.create(getActivity(),
getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)),
Notify.Style.ERROR).show();
Notify.Style.ERROR).show(this);
return;
}
mSelectedFiles.requestFocus();
@ -230,7 +230,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
private void encryptClicked(boolean share) {
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.error_no_file_selected,
Notify.Style.ERROR).show();
Notify.Style.ERROR).show(this);
return;
}
if (share) {
@ -247,7 +247,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
} else {
if (mFilesModels.size() > 1) {
Notify.create(getActivity(), R.string.error_multi_not_supported,
Notify.Style.ERROR).show();
Notify.Style.ERROR).show(this);
return;
}
showOutputFileDialog();
@ -330,7 +330,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
if (mFilesModels.isEmpty()) {
Notify.create(getActivity(), R.string.no_file_selected, Notify.Style.ERROR)
.show();
.show(this);
return false;
} else if (mFilesModels.size() > 1 && !mShareAfterEncrypt) {
Log.e(Constants.TAG, "Aborting: mInputUris.size() > 1 && !mShareAfterEncrypt");
@ -347,12 +347,12 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
if (mPassphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
if (mPassphrase.isEmpty()) {
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
@ -365,7 +365,7 @@ public class EncryptFilesFragment extends CryptoOperationFragment {
// Files must be encrypted, only text can be signed-only right now
if (!gotEncryptionKeys) {
Notify.create(getActivity(), R.string.select_encryption_key, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
}

View File

@ -98,23 +98,20 @@ public class EncryptTextActivity extends BaseActivity implements
// handle like normal text encryption, override action and extras to later
// executeServiceMethod ACTION_ENCRYPT_TEXT in main actions
extras.putString(EXTRA_TEXT, sharedText);
action = ACTION_ENCRYPT_TEXT;
}
}
}
String textData = extras.getString(EXTRA_TEXT);
if (ACTION_ENCRYPT_TEXT.equals(action) && textData == null) {
Log.e(Constants.TAG, "Include the extra 'text' in your Intent!");
return;
if (textData == null) {
textData = "";
}
// preselect keys given by intent
long mSigningKeyId = extras.getLong(EXTRA_SIGNATURE_KEY_ID);
long[] mEncryptionKeyIds = extras.getLongArray(EXTRA_ENCRYPTION_KEY_IDS);
if (savedInstanceState == null) {
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

View File

@ -288,9 +288,9 @@ public class EncryptTextFragment extends CryptoOperationFragment {
}
protected boolean inputIsValid() {
if (mMessage == null) {
Notify.create(getActivity(), R.string.error_message, Notify.Style.ERROR)
.show();
if (mMessage == null || mMessage.isEmpty()) {
Notify.create(getActivity(), R.string.error_empty_text, Notify.Style.ERROR)
.show(this);
return false;
}
@ -299,12 +299,12 @@ public class EncryptTextFragment extends CryptoOperationFragment {
if (mSymmetricPassphrase == null) {
Notify.create(getActivity(), R.string.passphrases_do_not_match, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
if (mSymmetricPassphrase.isEmpty()) {
Notify.create(getActivity(), R.string.passphrase_must_not_be_empty, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
@ -316,7 +316,7 @@ public class EncryptTextFragment extends CryptoOperationFragment {
if (!gotEncryptionKeys && mSigningKeyId == 0) {
Notify.create(getActivity(), R.string.select_encryption_or_signature_key, Notify.Style.ERROR)
.show();
.show(this);
return false;
}
}

View File

@ -39,6 +39,7 @@ import org.sufficientlysecure.keychain.ui.base.BaseNfcActivity;
import org.sufficientlysecure.keychain.service.CloudImportService;
import org.sufficientlysecure.keychain.service.ServiceProgressHandler;
import org.sufficientlysecure.keychain.ui.dialog.ProgressDialogFragment;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.util.Log;
@ -62,6 +63,8 @@ public class ImportKeysActivity extends BaseNfcActivity {
// Actions for internal use only:
public static final String ACTION_IMPORT_KEY_FROM_FILE = Constants.INTENT_PREFIX
+ "IMPORT_KEY_FROM_FILE";
public static final String ACTION_SEARCH_KEYSERVER_FROM_URL = Constants.INTENT_PREFIX
+ "SEARCH_KEYSERVER_FROM_URL";
public static final String EXTRA_RESULT = "result";
// only used by ACTION_IMPORT_KEY
@ -112,16 +115,20 @@ public class ImportKeysActivity extends BaseNfcActivity {
}
if (action == null) {
startCloudFragment(savedInstanceState, null, false);
startListFragment(savedInstanceState, null, null, null);
startCloudFragment(savedInstanceState, null, false, null);
startListFragment(savedInstanceState, null, null, null, null);
return;
}
if (Intent.ACTION_VIEW.equals(action)) {
if (scheme.equals("http") || scheme.equals("https")) {
action = ACTION_SEARCH_KEYSERVER_FROM_URL;
} else {
// Android's Action when opening file associated to Keychain (see AndroidManifest.xml)
// delegate action to ACTION_IMPORT_KEY
action = ACTION_IMPORT_KEY;
}
}
switch (action) {
case ACTION_IMPORT_KEY: {
@ -130,12 +137,12 @@ public class ImportKeysActivity extends BaseNfcActivity {
if (dataUri != null) {
// action: directly load data
startListFragment(savedInstanceState, null, dataUri, null);
startListFragment(savedInstanceState, null, dataUri, null, null);
} else if (extras.containsKey(EXTRA_KEY_BYTES)) {
byte[] importData = extras.getByteArray(EXTRA_KEY_BYTES);
// action: directly load data
startListFragment(savedInstanceState, importData, null, null);
startListFragment(savedInstanceState, importData, null, null, null);
}
break;
}
@ -162,10 +169,10 @@ public class ImportKeysActivity extends BaseNfcActivity {
if (query != null && query.length() > 0) {
// display keyserver fragment with query
startCloudFragment(savedInstanceState, query, false);
startCloudFragment(savedInstanceState, query, false, null);
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
startListFragment(savedInstanceState, null, null, query, null);
} else {
Log.e(Constants.TAG, "Query is empty!");
return;
@ -181,10 +188,10 @@ public class ImportKeysActivity extends BaseNfcActivity {
String query = "0x" + fingerprint;
// display keyserver fragment with query
startCloudFragment(savedInstanceState, query, true);
startCloudFragment(savedInstanceState, query, true, null);
// action: search immediately
startListFragment(savedInstanceState, null, null, query);
startListFragment(savedInstanceState, null, null, query, null);
}
} else {
Log.e(Constants.TAG,
@ -200,7 +207,29 @@ public class ImportKeysActivity extends BaseNfcActivity {
startFileFragment(savedInstanceState);
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
startListFragment(savedInstanceState, null, null, null, null);
break;
}
case ACTION_SEARCH_KEYSERVER_FROM_URL: {
// need to process URL to get search query and keyserver authority
String query = dataUri.getQueryParameter("search");
String keyserver = dataUri.getAuthority();
// if query not specified, we still allow users to search the keyserver in the link
if (query == null) {
Notify.create(this, R.string.import_url_warn_no_search_parameter, Notify.LENGTH_INDEFINITE,
Notify.Style.WARN).show(mTopFragment);
// we just set the keyserver
startCloudFragment(savedInstanceState, null, false, keyserver);
// it's not necessary to set the keyserver for ImportKeysListFragment since
// it'll be taken care of by ImportKeysCloudFragment when the user clicks
// the search button
startListFragment(savedInstanceState, null, null, null, null);
} else {
// we allow our users to edit the query if they wish
startCloudFragment(savedInstanceState, query, false, keyserver);
// search immediately
startListFragment(savedInstanceState, null, null, query, keyserver);
}
break;
}
case ACTION_IMPORT_KEY_FROM_FILE_AND_RETURN: {
@ -208,18 +237,31 @@ public class ImportKeysActivity extends BaseNfcActivity {
startFileFragment(savedInstanceState);
// no immediate actions!
startListFragment(savedInstanceState, null, null, null);
startListFragment(savedInstanceState, null, null, null, null);
break;
}
default: {
startCloudFragment(savedInstanceState, null, false);
startListFragment(savedInstanceState, null, null, null);
startCloudFragment(savedInstanceState, null, false, null);
startListFragment(savedInstanceState, null, null, null, null);
break;
}
}
}
private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri, String serverQuery) {
/**
* if the fragment is started with non-null bytes/dataUri/serverQuery, it will immediately
* load content
*
* @param savedInstanceState
* @param bytes bytes containing list of keyrings to import
* @param dataUri uri to file to import keyrings from
* @param serverQuery query to search for on the keyserver
* @param keyserver keyserver authority to search on. If null will use keyserver from
* user preferences
*/
private void startListFragment(Bundle savedInstanceState, byte[] bytes, Uri dataUri,
String serverQuery, String keyserver) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
@ -227,8 +269,8 @@ public class ImportKeysActivity extends BaseNfcActivity {
return;
}
// Create an instance of the fragment
mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery);
mListFragment = ImportKeysListFragment.newInstance(bytes, dataUri, serverQuery, false,
keyserver);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!
@ -259,7 +301,18 @@ public class ImportKeysActivity extends BaseNfcActivity {
getSupportFragmentManager().executePendingTransactions();
}
private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit) {
/**
* loads the CloudFragment, which consists of the search bar, search button and settings icon
* visually.
*
* @param savedInstanceState
* @param query search query
* @param disableQueryEdit if true, user will not be able to edit the search query
* @param keyserver keyserver authority to use for search, if null will use keyserver
* specified in user preferences
*/
private void startCloudFragment(Bundle savedInstanceState, String query, boolean disableQueryEdit, String keyserver) {
// However, if we're being restored from a previous state,
// then we don't need to do anything and should return or else
// we could end up with overlapping fragments.
@ -268,7 +321,7 @@ public class ImportKeysActivity extends BaseNfcActivity {
}
// Create an instance of the fragment
mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit);
mTopFragment = ImportKeysCloudFragment.newInstance(query, disableQueryEdit, keyserver);
// Add the fragment to the 'fragment_container' FrameLayout
// NOTE: We use commitAllowingStateLoss() to prevent weird crashes!

View File

@ -45,6 +45,7 @@ import java.util.List;
public class ImportKeysCloudFragment extends Fragment {
public static final String ARG_QUERY = "query";
public static final String ARG_DISABLE_QUERY_EDIT = "disable_query_edit";
public static final String ARG_KEYSERVER = "keyserver";
private ImportKeysActivity mImportActivity;
@ -54,13 +55,20 @@ public class ImportKeysCloudFragment extends Fragment {
/**
* Creates new instance of this fragment
*
* @param query query to search for
* @param disableQueryEdit if true, user cannot edit query
* @param keyserver specified keyserver authority to use. If null, will use keyserver
* specified in user preferences
*/
public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit) {
public static ImportKeysCloudFragment newInstance(String query, boolean disableQueryEdit,
String keyserver) {
ImportKeysCloudFragment frag = new ImportKeysCloudFragment();
Bundle args = new Bundle();
args.putString(ARG_QUERY, query);
args.putBoolean(ARG_DISABLE_QUERY_EDIT, disableQueryEdit);
args.putString(ARG_KEYSERVER, keyserver);
frag.setArguments(args);
@ -151,8 +159,17 @@ public class ImportKeysCloudFragment extends Fragment {
}
private void search(String query) {
Preferences prefs = Preferences.getPreferences(getActivity());
mImportActivity.loadCallback(new ImportKeysListFragment.CloudLoaderState(query, prefs.getCloudSearchPrefs()));
Preferences.CloudSearchPrefs cloudSearchPrefs;
String explicitKeyserver = getArguments().getString(ARG_KEYSERVER);
// no explicit keyserver passed
if (explicitKeyserver == null) {
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
} else {
// assume we are also meant to search keybase.io
cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, explicitKeyserver);
}
mImportActivity.loadCallback(
new ImportKeysListFragment.CloudLoaderState(query, cloudSearchPrefs));
toggleKeyboard(false);
}

View File

@ -58,6 +58,7 @@ public class ImportKeysListFragment extends ListFragment implements
private static final String ARG_BYTES = "bytes";
public static final String ARG_SERVER_QUERY = "query";
public static final String ARG_NON_INTERACTIVE = "non_interactive";
public static final String ARG_KEYSERVER_URL = "keyserver_url";
private Activity mActivity;
private ImportKeysAdapter mAdapter;
@ -78,7 +79,8 @@ public class ImportKeysListFragment extends ListFragment implements
return mAdapter.getData();
}
/** Returns an Iterator (with size) of the selected data items.
/**
* Returns an Iterator (with size) of the selected data items.
* This iterator is sort of a tradeoff, it's slightly more complex than an
* ArrayList would have been, but we save some memory by just returning
* relevant elements on demand.
@ -121,12 +123,36 @@ public class ImportKeysListFragment extends ListFragment implements
}
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery) {
return newInstance(bytes, dataUri, serverQuery, false);
/**
* Creates an interactive ImportKeyListFragment which reads keyrings from bytes, or file specified
* by dataUri, or searches a keyserver for serverQuery, if parameter is not null, in that order
*
* @param bytes byte data containing list of keyrings to be imported
* @param dataUri file from which keyrings are to be imported
* @param serverQuery query to search for on keyserver
* @param keyserver if not null, will perform search on specified keyserver. Else, uses
* keyserver specified in user preferences
* @return fragment with arguments set based on passed parameters
*/
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery,
String keyserver) {
return newInstance(bytes, dataUri, serverQuery, false, keyserver);
}
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri,
String serverQuery, boolean nonInteractive) {
/**
* Visually consists of a list of keyrings with checkboxes to specify which are to be imported
* Can immediately load keyrings specified by any of its parameters
*
* @param bytes byte data containing list of keyrings to be imported
* @param dataUri file from which keyrings are to be imported
* @param serverQuery query to search for on keyserver
* @param nonInteractive if true, users will not be able to check/uncheck items in the list
* @param keyserver if set, will perform search on specified keyserver. If null, falls back
* to keyserver specified in user preferences
* @return fragment with arguments set based on passed parameters
*/
public static ImportKeysListFragment newInstance(byte[] bytes, Uri dataUri, String serverQuery,
boolean nonInteractive, String keyserver) {
ImportKeysListFragment frag = new ImportKeysListFragment();
Bundle args = new Bundle();
@ -134,6 +160,7 @@ public class ImportKeysListFragment extends ListFragment implements
args.putParcelable(ARG_DATA_URI, dataUri);
args.putString(ARG_SERVER_QUERY, serverQuery);
args.putBoolean(ARG_NON_INTERACTIVE, nonInteractive);
args.putString(ARG_KEYSERVER_URL, keyserver);
frag.setArguments(args);
@ -180,16 +207,23 @@ public class ImportKeysListFragment extends ListFragment implements
setListAdapter(mAdapter);
Bundle args = getArguments();
Uri dataUri = args.containsKey(ARG_DATA_URI) ? args.<Uri>getParcelable(ARG_DATA_URI) : null;
byte[] bytes = args.containsKey(ARG_BYTES) ? args.getByteArray(ARG_BYTES) : null;
String query = args.containsKey(ARG_SERVER_QUERY) ? args.getString(ARG_SERVER_QUERY) : null;
mNonInteractive = args.containsKey(ARG_NON_INTERACTIVE) ? args.getBoolean(ARG_NON_INTERACTIVE) : false;
Uri dataUri = args.getParcelable(ARG_DATA_URI);
byte[] bytes = args.getByteArray(ARG_BYTES);
String query = args.getString(ARG_SERVER_QUERY);
String keyserver = args.getString(ARG_KEYSERVER_URL);
mNonInteractive = args.getBoolean(ARG_NON_INTERACTIVE, false);
if (dataUri != null || bytes != null) {
mLoaderState = new BytesLoaderState(bytes, dataUri);
} else if (query != null) {
Preferences prefs = Preferences.getPreferences(getActivity());
mLoaderState = new CloudLoaderState(query, prefs.getCloudSearchPrefs());
Preferences.CloudSearchPrefs cloudSearchPrefs;
if (keyserver == null) {
cloudSearchPrefs = Preferences.getPreferences(getActivity()).getCloudSearchPrefs();
} else {
cloudSearchPrefs = new Preferences.CloudSearchPrefs(true, true, keyserver);
}
mLoaderState = new CloudLoaderState(query, cloudSearchPrefs);
}
getListView().setOnTouchListener(new OnTouchListener() {

View File

@ -58,13 +58,12 @@ import java.util.Locale;
public class ImportKeysProxyActivity extends FragmentActivity {
public static final String ACTION_QR_CODE_API = OpenKeychainIntents.IMPORT_KEY_FROM_QR_CODE;
// implies activity returns scanned fingerprint as extra and does not import
public static final String ACTION_SCAN_WITH_RESULT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_WITH_RESULT";
public static final String ACTION_SCAN_IMPORT = Constants.INTENT_PREFIX + "SCAN_QR_CODE_IMPORT";
public static final String EXTRA_FINGERPRINT = "fingerprint";
boolean returnResult;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@ -82,18 +81,9 @@ public class ImportKeysProxyActivity extends FragmentActivity {
if (scheme != null && scheme.toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
// Scanning a fingerprint directly with Barcode Scanner, thus we already have scanned
returnResult = false;
processScannedContent(dataUri);
} else if (ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
returnResult = false;
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text))
.setResultDisplayDuration(0);
integrator.setOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
integrator.initiateScan();
} else if (ACTION_SCAN_WITH_RESULT.equals(action)) {
returnResult = true;
} else if (ACTION_SCAN_WITH_RESULT.equals(action)
|| ACTION_SCAN_IMPORT.equals(action) || ACTION_QR_CODE_API.equals(action)) {
IntentIntegrator integrator = new IntentIntegrator(this);
integrator.setDesiredBarcodeFormats(IntentIntegrator.QR_CODE_TYPES)
.setPrompt(getString(R.string.import_qr_code_text))
@ -103,7 +93,6 @@ public class ImportKeysProxyActivity extends FragmentActivity {
} else if (NfcAdapter.ACTION_NDEF_DISCOVERED.equals(getIntent().getAction())) {
// Check to see if the Activity started due to an Android Beam
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
returnResult = false;
handleActionNdefDiscovered(getIntent());
} else {
Log.e(Constants.TAG, "Android Beam not supported by Android < 4.1");
@ -147,26 +136,15 @@ public class ImportKeysProxyActivity extends FragmentActivity {
}
private void processScannedContent(Uri uri) {
String action = getIntent().getAction();
Log.d(Constants.TAG, "scanned: " + uri);
String fingerprint = null;
// example: openpgp4fpr:73EE2314F65FA92EC2390D3A718C070100012282
if (uri != null && uri.getScheme() != null && uri.getScheme().toLowerCase(Locale.ENGLISH).equals(Constants.FINGERPRINT_SCHEME)) {
fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
}
String fingerprint = uri.getEncodedSchemeSpecificPart().toLowerCase(Locale.ENGLISH);
if (fingerprint == null) {
SingletonResult result = new SingletonResult(
SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_WRONG_QR_CODE);
Intent intent = new Intent();
intent.putExtra(SingletonResult.EXTRA_RESULT, result);
returnResult(intent);
return;
}
if (returnResult) {
if (ACTION_SCAN_WITH_RESULT.equals(action)) {
Intent result = new Intent();
result.putExtra(EXTRA_FINGERPRINT, fingerprint);
setResult(RESULT_OK, result);
@ -174,42 +152,47 @@ public class ImportKeysProxyActivity extends FragmentActivity {
} else {
importKeys(fingerprint);
}
} else {
SingletonResult result = new SingletonResult(
SingletonResult.RESULT_ERROR, OperationResult.LogType.MSG_WRONG_QR_CODE);
Intent intent = new Intent();
intent.putExtra(SingletonResult.EXTRA_RESULT, result);
returnResult(intent);
}
}
public void returnResult(Intent data) {
if (returnResult) {
setResult(RESULT_OK, data);
finish();
} else {
String action = getIntent().getAction();
if (ACTION_QR_CODE_API.equals(action)) {
// display last log message but as Toast for calls from outside OpenKeychain
OperationResult result = data.getParcelableExtra(OperationResult.EXTRA_RESULT);
String str = getString(result.getLog().getLast().mType.getMsgId());
Toast.makeText(this, str, Toast.LENGTH_LONG).show();
finish();
} else {
setResult(RESULT_OK, data);
finish();
}
}
public void importKeys(byte[] keyringData) {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(keyringData);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
startImportService(selectedEntries);
}
public void importKeys(String fingerprint) {
ParcelableKeyRing keyEntry = new ParcelableKeyRing(fingerprint, null, null);
ArrayList<ParcelableKeyRing> selectedEntries = new ArrayList<>();
selectedEntries.add(keyEntry);
startImportService(selectedEntries);
}
private void startImportService (ArrayList<ParcelableKeyRing> keyRings) {
private void startImportService(ArrayList<ParcelableKeyRing> keyRings) {
// Message is received after importing is done in KeychainIntentService
ServiceProgressHandler serviceHandler = new ServiceProgressHandler(
@ -282,7 +265,6 @@ public class ImportKeysProxyActivity extends FragmentActivity {
// start service with intent
startService(intent);
}
/**

View File

@ -682,17 +682,16 @@ public class KeyListFragment extends LoaderFragment
}
private void showMultiExportDialog(long[] masterKeyIds) {
mIdsForRepeatAskPassphrase = new ArrayList<Long>();
for(long id: masterKeyIds) {
mIdsForRepeatAskPassphrase = new ArrayList<>();
for (long id : masterKeyIds) {
try {
if (PassphraseCacheService.getCachedPassphrase(
getActivity(), id, id) == null) {
mIdsForRepeatAskPassphrase.add(Long.valueOf(id));
mIdsForRepeatAskPassphrase.add(id);
}
} catch (PassphraseCacheService.KeyNotFoundException e) {
// This happens when the master key is stripped
// and ignore this key.
continue;
}
}
mIndex = 0;
@ -701,8 +700,8 @@ public class KeyListFragment extends LoaderFragment
return;
}
long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()];
for(int i=0; i<mIdsForRepeatAskPassphrase.size(); ++i) {
idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i).longValue();
for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) {
idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i);
}
mExportHelper.showExportKeysDialog(idsForMultiExport,
Constants.Path.APP_DIR_FILE,
@ -711,7 +710,7 @@ public class KeyListFragment extends LoaderFragment
private void startPassphraseActivity() {
Intent intent = new Intent(getActivity(), PassphraseDialogActivity.class);
long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++).longValue();
long masterKeyId = mIdsForRepeatAskPassphrase.get(mIndex++);
intent.putExtra(PassphraseDialogActivity.EXTRA_SUBKEY_ID, masterKeyId);
startActivityForResult(intent, REQUEST_REPEAT_PASSPHRASE);
}
@ -719,7 +718,7 @@ public class KeyListFragment extends LoaderFragment
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_REPEAT_PASSPHRASE) {
if(resultCode != Activity.RESULT_OK) {
if (resultCode != Activity.RESULT_OK) {
return;
}
if (mIndex < mIdsForRepeatAskPassphrase.size()) {
@ -727,8 +726,8 @@ public class KeyListFragment extends LoaderFragment
return;
}
long[] idsForMultiExport = new long[mIdsForRepeatAskPassphrase.size()];
for(int i=0; i<mIdsForRepeatAskPassphrase.size(); ++i) {
idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i).longValue();
for (int i = 0; i < mIdsForRepeatAskPassphrase.size(); ++i) {
idsForMultiExport[i] = mIdsForRepeatAskPassphrase.get(i);
}
mExportHelper.showExportKeysDialog(idsForMultiExport,
Constants.Path.APP_DIR_FILE,

View File

@ -1,6 +1,7 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
* Copyright (C) 2015 Kai Jiang <jiangkai@gmail.com>
*
* 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
@ -20,36 +21,98 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.AdapterView;
import com.mikepenz.community_material_typeface_library.CommunityMaterial;
import com.mikepenz.google_material_typeface_library.GoogleMaterial;
import com.mikepenz.iconics.typeface.FontAwesome;
import com.mikepenz.materialdrawer.Drawer;
import com.mikepenz.materialdrawer.model.PrimaryDrawerItem;
import com.mikepenz.materialdrawer.model.interfaces.IDrawerItem;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.remote.ui.AppsListFragment;
import org.sufficientlysecure.keychain.util.Preferences;
import org.sufficientlysecure.keychain.util.FabContainer;
import org.sufficientlysecure.keychain.util.Preferences;
import it.neokree.materialnavigationdrawer.MaterialNavigationDrawer;
public class MainActivity extends AppCompatActivity implements FabContainer {
public class MainActivity extends MaterialNavigationDrawer implements FabContainer {
public Drawer.Result result;
private KeyListFragment mKeyListFragment ;
private AppsListFragment mAppsListFragment;
private EncryptDecryptOverviewFragment mEncryptDecryptOverviewFragment;
private Fragment mLastUsedFragment;
private Toolbar mToolbar;
@Override
public void init(Bundle savedInstanceState) {
// don't open drawer on first run
disableLearningPattern();
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
// addMultiPaneSupport();
//initialize FragmentLayout with KeyListFragment at first
Fragment mainFragment = new KeyListFragment();
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
transaction.replace(R.id.main_fragment_container, mainFragment);
transaction.commit();
// set the header image
// create and set the header
setDrawerHeaderImage(R.drawable.drawer_header);
mToolbar = (Toolbar) findViewById(R.id.activity_main_toolbar);
mToolbar.setTitle(R.string.app_name);
setSupportActionBar(mToolbar);
// create sections
addSection(newSection(getString(R.string.nav_keys), R.drawable.ic_vpn_key_black_24dp, new KeyListFragment()));
addSection(newSection(getString(R.string.nav_encrypt_decrypt), R.drawable.ic_lock_black_24dp, new EncryptDecryptOverviewFragment()));
addSection(newSection(getString(R.string.title_api_registered_apps), R.drawable.ic_apps_black_24dp, new AppsListFragment()));
// create bottom section
addBottomSection(newSection(getString(R.string.menu_preferences), R.drawable.ic_settings_black_24dp, new Intent(this, SettingsActivity.class)));
addBottomSection(newSection(getString(R.string.menu_help), R.drawable.ic_help_black_24dp, new Intent(this, HelpActivity.class)));
result = new Drawer()
.withActivity(this)
.withHeader(R.layout.main_drawer_header)
.withToolbar(mToolbar)
.addDrawerItems(
new PrimaryDrawerItem().withName(R.string.nav_keys).withIcon(CommunityMaterial.Icon.cmd_key).withIdentifier(1).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.nav_encrypt_decrypt).withIcon(FontAwesome.Icon.faw_lock).withIdentifier(2).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.title_api_registered_apps).withIcon(CommunityMaterial.Icon.cmd_apps).withIdentifier(3).withCheckable(false)
)
.addStickyDrawerItems(
// display and stick on bottom of drawer
new PrimaryDrawerItem().withName(R.string.menu_preferences).withIcon(GoogleMaterial.Icon.gmd_settings).withIdentifier(4).withCheckable(false),
new PrimaryDrawerItem().withName(R.string.menu_help).withIcon(CommunityMaterial.Icon.cmd_help_circle).withIdentifier(5).withCheckable(false)
)
.withOnDrawerItemClickListener(new Drawer.OnDrawerItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id, IDrawerItem drawerItem) {
if (drawerItem != null) {
Intent intent = null;
switch(drawerItem.getIdentifier()) {
case 1:
onKeysSelected();
break;
case 2:
onEnDecryptSelected();
break;
case 3:
onAppsSelected();
break;
case 4:
intent = new Intent(MainActivity.this, SettingsActivity.class);
break;
case 5:
intent = new Intent(MainActivity.this, HelpActivity.class);
break;
}
if (intent != null) {
MainActivity.this.startActivity(intent);
}
}
}
})
.withSelectedItem(-1)
.withSavedInstance(savedInstanceState)
.build();
// if this is the first time show first time activity
Preferences prefs = Preferences.getPreferences(this);
@ -69,9 +132,83 @@ public class MainActivity extends MaterialNavigationDrawer implements FabContain
}
}
private void clearFragments() {
mKeyListFragment = null;
mAppsListFragment = null;
mEncryptDecryptOverviewFragment = null;
getSupportFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
}
private void setFragment(Fragment fragment) {
setFragment(fragment, true);
}
private void setFragment(Fragment fragment, boolean addToBackStack) {
this.mLastUsedFragment = fragment;
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.main_fragment_container, fragment);
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.commit();
}
private boolean onKeysSelected() {
mToolbar.setTitle(R.string.app_name);
clearFragments();
if (mKeyListFragment == null) {
mKeyListFragment = new KeyListFragment();
}
setFragment(mKeyListFragment, false);
return true;
}
private boolean onEnDecryptSelected() {
mToolbar.setTitle(R.string.nav_encrypt_decrypt);
clearFragments();
if (mEncryptDecryptOverviewFragment == null) {
mEncryptDecryptOverviewFragment = new EncryptDecryptOverviewFragment();
}
setFragment(mEncryptDecryptOverviewFragment);
return true;
}
private boolean onAppsSelected() {
mToolbar.setTitle(R.string.nav_apps);
clearFragments();
if (mAppsListFragment == null) {
mAppsListFragment = new AppsListFragment();
}
setFragment(mAppsListFragment);
return true;
}
@Override
protected void onSaveInstanceState(Bundle outState) {
//add the values which need to be saved from the drawer to the bundle
outState = result.saveInstanceState(outState);
super.onSaveInstanceState(outState);
}
@Override
public void onBackPressed(){
//handle the back press :D close the drawer first and if the drawer is closed close the activity
if (result != null && result.isDrawerOpen()) {
result.closeDrawer();
} else {
super.onBackPressed();
}
}
@Override
public void fabMoveUp(int height) {
Object fragment = getCurrentSection().getTargetFragment();
Object fragment = getSupportFragmentManager()
.findFragmentById(R.id.main_fragment_container);
if (fragment instanceof FabContainer) {
((FabContainer) fragment).fabMoveUp(height);
}
@ -79,7 +216,8 @@ public class MainActivity extends MaterialNavigationDrawer implements FabContain
@Override
public void fabRestorePosition() {
Object fragment = getCurrentSection().getTargetFragment();
Object fragment = getSupportFragmentManager()
.findFragmentById(R.id.main_fragment_container);
if (fragment instanceof FabContainer) {
((FabContainer) fragment).fabRestorePosition();
}

View File

@ -20,6 +20,9 @@ package org.sufficientlysecure.keychain.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Messenger;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
@ -28,6 +31,8 @@ import android.widget.TextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.base.BaseActivity;
import org.sufficientlysecure.keychain.ui.dialog.AddKeyserverDialogFragment;
import org.sufficientlysecure.keychain.ui.util.Notify;
import org.sufficientlysecure.keychain.ui.widget.Editor;
import org.sufficientlysecure.keychain.ui.widget.Editor.EditorListener;
import org.sufficientlysecure.keychain.ui.widget.KeyServerEditor;
@ -124,10 +129,63 @@ public class SettingsKeyServerActivity extends BaseActivity implements OnClickLi
}
// button to add keyserver clicked
public void onClick(View v) {
Handler returnHandler = new Handler() {
@Override
public void handleMessage(Message message) {
Bundle data = message.getData();
switch (message.what) {
case AddKeyserverDialogFragment.MESSAGE_OKAY: {
boolean verified = data.getBoolean(AddKeyserverDialogFragment.MESSAGE_VERIFIED);
if (verified) {
Notify.create(SettingsKeyServerActivity.this,
R.string.add_keyserver_verified, Notify.Style.OK).show();
} else {
Notify.create(SettingsKeyServerActivity.this,
R.string.add_keyserver_without_verification,
Notify.Style.WARN).show();
}
String keyserver = data.getString(AddKeyserverDialogFragment.MESSAGE_KEYSERVER);
addKeyserver(keyserver);
break;
}
case AddKeyserverDialogFragment.MESSAGE_VERIFICATION_FAILED: {
AddKeyserverDialogFragment.FailureReason failureReason =
(AddKeyserverDialogFragment.FailureReason) data.getSerializable(
AddKeyserverDialogFragment.MESSAGE_FAILURE_REASON);
switch (failureReason) {
case CONNECTION_FAILED: {
Notify.create(SettingsKeyServerActivity.this,
R.string.add_keyserver_connection_failed,
Notify.Style.ERROR).show();
break;
}
case INVALID_URL: {
Notify.create(SettingsKeyServerActivity.this,
R.string.add_keyserver_invalid_url,
Notify.Style.ERROR).show();
break;
}
}
break;
}
}
}
};
// Create a new Messenger for the communication back
Messenger messenger = new Messenger(returnHandler);
AddKeyserverDialogFragment dialogFragment = AddKeyserverDialogFragment
.newInstance(messenger, R.string.add_keyserver_dialog_title);
dialogFragment.show(getSupportFragmentManager(), "addKeyserverDialog");
}
public void addKeyserver(String keyserverUrl) {
KeyServerEditor view = (KeyServerEditor) mInflater.inflate(R.layout.key_server_editor,
mEditors, false);
view.setEditorListener(this);
view.setValue(keyserverUrl);
mEditors.addView(view);
}

View File

@ -82,12 +82,12 @@ public class ImportKeysListLoader
@Override
protected void onStartLoading() {
forceLoad();
super.forceLoad();
}
@Override
protected void onStopLoading() {
cancelLoad();
super.cancelLoad();
}
@Override

View File

@ -21,7 +21,7 @@ import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
@ -41,7 +41,7 @@ public class PagerTabStripAdapter extends FragmentPagerAdapter {
}
}
public PagerTabStripAdapter(ActionBarActivity activity) {
public PagerTabStripAdapter(AppCompatActivity activity) {
super(activity.getSupportFragmentManager());
mActivity = activity;
}

View File

@ -24,7 +24,7 @@ import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import java.util.ArrayList;
@ -45,7 +45,7 @@ public class TabsAdapter extends FragmentStatePagerAdapter implements ActionBar.
}
}
public TabsAdapter(ActionBarActivity activity, ViewPager pager) {
public TabsAdapter(AppCompatActivity activity, ViewPager pager) {
super(activity.getSupportFragmentManager());
mContext = activity;
mActionBar = activity.getSupportActionBar();

View File

@ -20,7 +20,7 @@ package org.sufficientlysecure.keychain.ui.base;
import android.app.Activity;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -33,7 +33,7 @@ import org.sufficientlysecure.keychain.R;
/**
* Setups Toolbar
*/
public abstract class BaseActivity extends ActionBarActivity {
public abstract class BaseActivity extends AppCompatActivity {
protected Toolbar mToolbar;
protected View mStatusBar;

View File

@ -57,6 +57,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
public static final int REQUEST_CODE_PASSPHRASE = 1;
protected Passphrase mPin;
protected boolean mPw1ValidForMultipleSignatures;
protected boolean mPw1ValidatedForSignature;
protected boolean mPw1ValidatedForDecrypt; // Mode 82 does other things; consider renaming?
private NfcAdapter mNfcAdapter;
private IsoDep mIsoDep;
@ -197,10 +200,15 @@ public abstract class BaseNfcActivity extends BaseActivity {
+ "06" // Lc (number of bytes)
+ "D27600012401" // Data (6 bytes)
+ "00"; // Le
if ( ! nfcCommunicate(opening).equals(accepted)) { // activate connection
if ( ! nfcCommunicate(opening).endsWith(accepted)) { // activate connection
throw new IOException("Initialization failed!");
}
byte[] pwStatusBytes = nfcGetPwStatusBytes();
mPw1ValidForMultipleSignatures = (pwStatusBytes[0] == 1);
mPw1ValidatedForSignature = false;
mPw1ValidatedForDecrypt = false;
onNfcPerform();
mIsoDep.close();
@ -278,6 +286,15 @@ public abstract class BaseNfcActivity extends BaseActivity {
return fptlv.mV;
}
/** Return the PW Status Bytes from the card. This is a simple DO; no TLV decoding needed.
*
* @return Seven bytes in fixed format, plus 0x9000 status word at the end.
*/
public byte[] nfcGetPwStatusBytes() throws IOException {
String data = "00CA00C400";
return mIsoDep.transceive(Hex.decode(data));
}
/** Return the fingerprint from application specific data stored on tag, or
* null if it doesn't exist.
*
@ -316,7 +333,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
* @return a big integer representing the MPI for the given hash
*/
public byte[] nfcCalculateSignature(byte[] hash, int hashAlgo) throws IOException {
if (!mPw1ValidatedForSignature) {
nfcVerifyPIN(0x81); // (Verify PW1 with mode 81 for signing)
}
// dsi, including Lc
String dsi;
@ -391,6 +410,10 @@ public abstract class BaseNfcActivity extends BaseActivity {
Log.d(Constants.TAG, "final response:" + status);
if (!mPw1ValidForMultipleSignatures) {
mPw1ValidatedForSignature = false;
}
if ( ! "9000".equals(status)) {
throw new IOException("Bad NFC response code: " + status);
}
@ -410,7 +433,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
* @return the decoded session key
*/
public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException {
if (!mPw1ValidatedForDecrypt) {
nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption)
}
String firstApdu = "102a8086fe";
String secondApdu = "002a808603";
@ -458,6 +483,12 @@ public abstract class BaseNfcActivity extends BaseActivity {
handlePinError();
throw new IOException("Bad PIN!");
}
if (mode == 0x81) {
mPw1ValidatedForSignature = true;
} else if (mode == 0x82) {
mPw1ValidatedForDecrypt = true;
}
}
}
@ -476,6 +507,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
*/
public void enableNfcForegroundDispatch() {
mNfcAdapter = NfcAdapter.getDefaultAdapter(this);
if (mNfcAdapter == null) {
return;
}
Intent nfcI = new Intent(this, getClass())
.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
PendingIntent nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcI, PendingIntent.FLAG_CANCEL_CURRENT);
@ -497,6 +531,9 @@ public abstract class BaseNfcActivity extends BaseActivity {
* Disable foreground dispatch in onPause!
*/
public void disableNfcForegroundDispatch() {
if (mNfcAdapter == null) {
return;
}
mNfcAdapter.disableForegroundDispatch(this);
Log.d(Constants.TAG, "NfcForegroundDispatch has been disabled!");
}

View File

@ -0,0 +1,320 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.sufficientlysecure.keychain.ui.dialog;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.support.v4.app.DialogFragment;
import android.test.suitebuilder.TestSuiteBuilder;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.TlsHelper;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
public class AddKeyserverDialogFragment extends DialogFragment implements OnEditorActionListener {
private static final String ARG_MESSENGER = "messenger";
private static final String ARG_TITLE = "title";
public static final int MESSAGE_OKAY = 1;
public static final int MESSAGE_VERIFICATION_FAILED = 2;
public static final String MESSAGE_KEYSERVER = "new_keyserver";
public static final String MESSAGE_VERIFIED = "verified";
public static final String MESSAGE_FAILURE_REASON = "failure_reason";
private Messenger mMessenger;
private EditText mKeyserverEditText;
private CheckBox mVerifyKeyserverCheckBox;
public static enum FailureReason {
INVALID_URL,
CONNECTION_FAILED
}
;
/**
* Creates new instance of this dialog fragment
*
* @param title title of dialog
* @param messenger to communicate back after setting the passphrase
* @return
*/
public static AddKeyserverDialogFragment newInstance(Messenger messenger, int title) {
AddKeyserverDialogFragment frag = new AddKeyserverDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_TITLE, title);
args.putParcelable(ARG_MESSENGER, messenger);
frag.setArguments(args);
return frag;
}
/**
* Creates dialog
*/
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final Activity activity = getActivity();
int title = getArguments().getInt(ARG_TITLE);
mMessenger = getArguments().getParcelable(ARG_MESSENGER);
CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(activity);
alert.setTitle(title);
LayoutInflater inflater = activity.getLayoutInflater();
View view = inflater.inflate(R.layout.add_keyserver_dialog, null);
alert.setView(view);
mKeyserverEditText = (EditText) view.findViewById(R.id.keyserver_url_edit_text);
mVerifyKeyserverCheckBox = (CheckBox) view.findViewById(R.id.verify_keyserver_checkbox);
// we don't want dialog to be dismissed on click, thereby requiring the hack seen below
// and in onStart
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
// we need to have an empty listener to prevent errors on some devices as mentioned
// at http://stackoverflow.com/q/13746412/3000919
// actual listener set in onStart
}
});
alert.setNegativeButton(android.R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
dismiss();
}
});
// Hack to open keyboard.
// This is the only method that I found to work across all Android versions
// http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
// Notes: * onCreateView can't be used because we want to add buttons to the dialog
// * opening in onActivityCreated does not work on Android 4.4
mKeyserverEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
mKeyserverEditText.post(new Runnable() {
@Override
public void run() {
InputMethodManager imm = (InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showSoftInput(mKeyserverEditText, InputMethodManager.SHOW_IMPLICIT);
}
});
}
});
mKeyserverEditText.requestFocus();
mKeyserverEditText.setImeActionLabel(getString(android.R.string.ok),
EditorInfo.IME_ACTION_DONE);
mKeyserverEditText.setOnEditorActionListener(this);
return alert.show();
}
@Override
public void onStart() {
super.onStart();
AlertDialog addKeyserverDialog = (AlertDialog) getDialog();
if (addKeyserverDialog != null) {
Button positiveButton = addKeyserverDialog.getButton(Dialog.BUTTON_POSITIVE);
positiveButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String keyserverUrl = mKeyserverEditText.getText().toString();
if (mVerifyKeyserverCheckBox.isChecked()) {
verifyConnection(keyserverUrl);
} else {
dismiss();
// return unverified keyserver back to activity
addKeyserver(keyserverUrl, false);
}
}
});
}
}
public void addKeyserver(String keyserver, boolean verified) {
dismiss();
Bundle data = new Bundle();
data.putString(MESSAGE_KEYSERVER, keyserver);
data.putBoolean(MESSAGE_VERIFIED, verified);
sendMessageToHandler(MESSAGE_OKAY, data);
}
public void verificationFailed(FailureReason reason) {
Bundle data = new Bundle();
data.putSerializable(MESSAGE_FAILURE_REASON, reason);
sendMessageToHandler(MESSAGE_VERIFICATION_FAILED, data);
}
public void verifyConnection(String keyserver) {
new AsyncTask<String, Void, FailureReason>() {
ProgressDialog mProgressDialog;
String mKeyserver;
@Override
protected void onPreExecute() {
mProgressDialog = new ProgressDialog(getActivity());
mProgressDialog.setMessage(getString(R.string.progress_verifying_keyserver_url));
mProgressDialog.setCancelable(false);
mProgressDialog.show();
}
@Override
protected FailureReason doInBackground(String... keyservers) {
mKeyserver = keyservers[0];
FailureReason reason = null;
try {
// replace hkps/hkp scheme and reconstruct Uri
Uri keyserverUri = Uri.parse(mKeyserver);
String scheme = keyserverUri.getScheme();
String schemeSpecificPart = keyserverUri.getSchemeSpecificPart();
String fragment = keyserverUri.getFragment();
if (scheme == null) throw new MalformedURLException();
if (scheme.equalsIgnoreCase("hkps")) scheme = "https";
else if (scheme.equalsIgnoreCase("hkp")) scheme = "http";
URI newKeyserver = new URI(scheme, schemeSpecificPart, fragment);
Log.d("Converted URL", newKeyserver.toString());
TlsHelper.openConnection(newKeyserver.toURL()).getInputStream();
} catch (TlsHelper.TlsHelperException e) {
reason = FailureReason.CONNECTION_FAILED;
} catch (MalformedURLException e) {
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
reason = FailureReason.INVALID_URL;
} catch (URISyntaxException e) {
Log.w(Constants.TAG, "Invalid keyserver URL entered by user.");
reason = FailureReason.INVALID_URL;
} catch (IOException e) {
Log.w(Constants.TAG, "Could not connect to entered keyserver url");
reason = FailureReason.CONNECTION_FAILED;
}
return reason;
}
@Override
protected void onPostExecute(FailureReason failureReason) {
mProgressDialog.dismiss();
if (failureReason == null) {
addKeyserver(mKeyserver, true);
} else {
verificationFailed(failureReason);
}
}
}.execute(keyserver);
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
// hide keyboard on dismiss
hideKeyboard();
}
private void hideKeyboard() {
if (getActivity() == null) {
return;
}
InputMethodManager inputManager = (InputMethodManager) getActivity()
.getSystemService(Context.INPUT_METHOD_SERVICE);
//check if no view has focus:
View v = getActivity().getCurrentFocus();
if (v == null)
return;
inputManager.hideSoftInputFromWindow(v.getWindowToken(), 0);
}
/**
* Associate the "done" button on the soft keyboard with the okay button in the view
*/
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (EditorInfo.IME_ACTION_DONE == actionId) {
AlertDialog dialog = ((AlertDialog) getDialog());
Button bt = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
bt.performClick();
return true;
}
return false;
}
/**
* Send message back to handler which is initialized in a activity
*
* @param what Message integer you want to send
*/
private void sendMessageToHandler(Integer what, Bundle data) {
Message msg = Message.obtain();
msg.what = what;
if (data != null) {
msg.setData(data);
}
try {
mMessenger.send(msg);
} catch (RemoteException e) {
Log.w(Constants.TAG, "Exception sending message, Is handler present?", e);
} catch (NullPointerException e) {
Log.w(Constants.TAG, "Messenger is null!", e);
}
}
}

View File

@ -17,9 +17,8 @@
package org.sufficientlysecure.keychain.ui.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
@ -27,14 +26,13 @@ import android.util.AttributeSet;
import android.util.Patterns;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.util.ContactHelper;
import java.util.regex.Matcher;
public class EmailEditText extends AutoCompleteTextView {
public class EmailEditText extends AppCompatAutoCompleteTextView {
public EmailEditText(Context context) {
super(context);
@ -51,12 +49,6 @@ public class EmailEditText extends AutoCompleteTextView {
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public EmailEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS);
reenableKeyboardSuggestions();

View File

@ -25,7 +25,7 @@ import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.Loader;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.internal.widget.TintSpinner;
import android.support.v7.widget.AppCompatSpinner;
import android.text.format.DateFormat;
import android.util.AttributeSet;
import android.view.View;
@ -47,10 +47,10 @@ import java.util.Date;
import java.util.TimeZone;
/**
* Use TintSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Use AppCompatSpinner from AppCompat lib instead of Spinner. Fixes white dropdown icon.
* Related: http://stackoverflow.com/a/27713090
*/
public abstract class KeySpinner extends TintSpinner implements LoaderManager.LoaderCallbacks<Cursor> {
public abstract class KeySpinner extends AppCompatSpinner implements LoaderManager.LoaderCallbacks<Cursor> {
public interface OnKeyChangedListener {
public void onKeyChanged(long masterKeyId);
}

View File

@ -17,17 +17,15 @@
package org.sufficientlysecure.keychain.ui.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
import android.util.AttributeSet;
import android.view.inputmethod.EditorInfo;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import org.sufficientlysecure.keychain.util.ContactHelper;
public class NameEditText extends AutoCompleteTextView {
public class NameEditText extends AppCompatAutoCompleteTextView {
public NameEditText(Context context) {
super(context);
init();
@ -43,12 +41,6 @@ public class NameEditText extends AutoCompleteTextView {
init();
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public NameEditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
reenableKeyboardSuggestions();
initAdapter();

View File

@ -19,15 +19,13 @@ package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.EditText;
import org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator.PasswordStrengthBarView;
public class PassphraseEditText extends EditText {
public class PassphraseEditText extends AppCompatEditText {
PasswordStrengthBarView mPasswordStrengthBarView;
int mPasswordBarWidth;

View File

@ -22,13 +22,10 @@
* SOFTWARE.
*/
package org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator;
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.util.AttributeSet;
/**

View File

@ -22,7 +22,7 @@
* SOFTWARE.
*/
package org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator;
package org.sufficientlysecure.keychain.ui.widget;
import android.content.Context;
import android.content.res.TypedArray;
@ -56,7 +56,6 @@ import org.sufficientlysecure.keychain.R;
*/
public class PasswordStrengthView extends View {
protected int mMinWidth;
protected int mMinHeight;

View File

@ -193,6 +193,11 @@ public class Preferences {
public final boolean searchKeybase;
public final String keyserver;
/**
* @param searchKeyserver should passed keyserver be searched
* @param searchKeybase should keybase.io be searched
* @param keyserver the keyserver url authority to search on
*/
public CloudSearchPrefs(boolean searchKeyserver, boolean searchKeybase, String keyserver) {
this.searchKeyserver = searchKeyserver;
this.searchKeybase = searchKeybase;

Binary file not shown.

After

Width:  |  Height:  |  Size: 284 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 499 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 214 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 355 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 614 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 397 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 804 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 480 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingBottom="16dp"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:paddingTop="16dp">
<EditText
android:id="@+id/keyserver_url_edit_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginBottom="8dp"
android:ems="10"
android:hint="@string/label_enter_keyserver_url"
android:imeOptions="actionDone" />
<CheckBox
android:id="@+id/verify_keyserver_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_verify_keyserver"
android:checked="true"/>
</LinearLayout>

View File

@ -5,7 +5,7 @@
<include
android:id="@+id/toolbar_include"
layout="@layout/toolbar_standalone_white" />
layout="@layout/toolbar_result_decrypt" />
<!--
fitsSystemWindows and layout_marginTop from

View File

@ -5,7 +5,7 @@
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/decrypt_result_include" />
<!--<include layout="@layout/decrypt_result_include" />-->
<LinearLayout
android:visibility="gone"

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -12,4 +12,4 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,25 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="4dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
<EditText
android:id="@+id/encrypt_text_text"
android:layout_width="match_parent"
android:layout_height="0dip"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:gravity="top"
android:inputType="text|textCapSentences|textMultiLine|textLongMessage"
android:hint="@string/encrypt_content_edit_text_hint"
android:layout_weight="1" />
android:hint="@string/encrypt_content_edit_text_hint" />
</LinearLayout>
</ScrollView>
</ScrollView>
</RelativeLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.Toolbar
android:id="@+id/activity_main_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:elevation="4dp"
android:background="?attr/colorPrimary"/>
<FrameLayout
android:id="@+id/main_fragment_container"
android:layout_gravity="center"
android:layout_height="match_parent"
android:layout_width="match_parent"/>
</LinearLayout>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/drawer_header"
android:adjustViewBounds="true"
android:scaleType="fitCenter">
</ImageView>

View File

@ -33,7 +33,7 @@
android:ems="10"
android:layout_gravity="center_horizontal" />
<org.sufficientlysecure.keychain.ui.widget.passwordstrengthindicator.PasswordStrengthBarView
<org.sufficientlysecure.keychain.ui.widget.PasswordStrengthBarView
android:id="@+id/passphrase_repeat_passphrase_strength"
android:layout_width="48dp"
android:layout_height="8dp"

View File

@ -0,0 +1,145 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/toolbar_include"
android:elevation="4dp"
android:background="@color/white"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include
android:id="@+id/toolbar_inner_layout"
layout="@layout/toolbar_inner_layout_white" />
<LinearLayout
android:layout_below="@id/toolbar"
android:id="@+id/result_main_layout"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:showIn="@layout/decrypt_text_fragment">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:animateLayoutChanges="true">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/result_encryption_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/status_lock_open_24dp"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/result_encryption_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
tools:text="Encryption status text" />
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/result_signature_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/status_signature_unverified_cutout_24dp"
android:layout_gravity="center_vertical" />
<TextView
android:id="@+id/result_signature_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
tools:text="Signature status text" />
</LinearLayout>
<LinearLayout
android:id="@+id/result_signature_layout"
android:layout_width="match_parent"
android:layout_height="?android:attr/listPreferredItemHeight"
android:clickable="true"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:background="?android:selectableItemBackground"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:paddingRight="4dp"
android:paddingLeft="4dp"
android:gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/result_signature_name"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:text="Alice" />
<TextView
android:id="@+id/result_signature_email"
android:textAppearance="?android:attr/textAppearanceSmall"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
tools:text="alice@example.com" />
</LinearLayout>
<View
android:layout_width="1dip"
android:layout_height="match_parent"
android:gravity="right"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="?android:attr/listDivider" />
<TextView
android:id="@+id/result_signature_action"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:text="Show"
android:drawableRight="@drawable/ic_vpn_key_grey_24dp"
android:drawablePadding="8dp"
android:gravity="center_vertical" />
</LinearLayout>
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
</LinearLayout>
</RelativeLayout>

View File

@ -5,13 +5,13 @@
<item
android:id="@+id/decrypt_copy"
android:title="@string/btn_copy_decrypted_text"
android:icon="@drawable/ic_action_encrypt_copy_24dp"
android:icon="@drawable/ic_content_copy_black_24dp"
app:showAsAction="ifRoom" />
<item
android:id="@+id/decrypt_share"
android:title="@string/btn_share_decrypted_text"
android:icon="@drawable/ic_action_encrypt_share_24dp"
android:icon="@drawable/ic_share_black_24dp"
app:showAsAction="ifRoom" />
</menu>

View File

@ -6,8 +6,12 @@
License: GPLv3+
## Developers
## Main Developers
* Dominik Schürmann (Maintainer)
* Vincent Breitmoser
## Contributors
* Adithya Abraham Philip
* Art O Cathain
* Ash Hughes
* Brian C. Barnes
@ -15,18 +19,33 @@ License: GPLv3+
* Daniel Albert
* Daniel Hammann
* Daniel Haß
* Daniel Nelz
* Daniel Ramos
* Greg Witczak
* 'iseki'
* Ishan Khanna
* 'jellysheep'
* 'Jesperbk'
* 'jkolo'
* Joey Castillo
* Kai Jiang
* Kartik Arora
* 'Kent'
* 'ligi'
* Lukas Zorich
* Manoj Khanna
* 'mar-v-in'
* Markus Doits
* Miroojin Bakshi
* Morgan Gangwere
* Nikhil Peter Raj
* Paul Sarbinowski
* 'Senecaso'
* Signe Rüsch
* Sreeram Boyapati
* Thialfihar (APG 1.x)
* 'steelman'
* 'Thialfihar' (APG developer)
* Tim Bray
* Vincent Breitmoser
## Libraries
* [SpongyCastle](http://rtyley.github.com/spongycastle/) (MIT X11 License)
@ -39,8 +58,8 @@ License: GPLv3+
* [ZXing](https://github.com/zxing/zxing) (Apache License v2)
* [ZXing Android Minimal](https://github.com/journeyapps/zxing-android-embedded) (Apache License v2)
* [PagerSlidingTabStrip](https://github.com/jpardogo/PagerSlidingTabStrip) (Material Design)</a> (Apache License v2)
* [MaterialNavigationDrawer](https://github.com/neokree/MaterialNavigationDrawer) (Apache License v2)
* [MaterialDrawer](https://github.com/mikepenz/MaterialDrawer) (Apache License v2)
* [Snackbar](https://github.com/nispok/snackbar) (MIT License)
* [FloatingActionButton](https://github.com/futuresimple/android-floating-action-button) (Apache License v2)
* [HtmlTextView](https://github.com/dschuermann/html-textview) (Apache License v2)
* [HtmlTextView](https://github.com/sufficientlysecure/html-textview) (Apache License v2)
* [Markdown4J](https://github.com/jdcasey/markdown4j) (Apache License v2)

View File

@ -2,8 +2,9 @@
## 3.2beta2
* First version with full YubiKey support available from the user interface: Edit keys, bind YubiKey to keys,...
* Material design
* Integration of QR Scanner (New permissions required)
* Integration of QR Code Scanning (New permissions required)
* Improved key creation wizard
* Fix missing contacts after sync
* Requires Android 4
@ -13,6 +14,7 @@
* Fix: Some valid keys were shown revoked or expired
* Don't accept signatures by expired or revoked subkeys
* Keybase.io support in advanced view
* Method to update all keys at once
## 3.1.2
@ -35,7 +37,7 @@
* Redesigned decrypt screen
* New icon usage and colors
* Fix import of secret keys from Symantec Encryption Desktop
* Subkey IDs on YubiKeys are now checked correctly
* Experimental YubiKey support: Subkey IDs are now checked correctly
## 3.0.1
@ -46,7 +48,6 @@
## 3.0
* Full support for YubiKey signature generation and decryption!
* Propose installable compatible apps in apps list
* New design for decryption screens
* Many fixes for key import, also fixes stripped keys
@ -55,12 +56,13 @@
* Fixing user id revocation certificates
* New cloud search (searches over traditional keyservers and keybase.io)
* Support for stripping keys inside OpenKeychain
* Experimental YubiKey support: Support for signature generation and decryption
## 2.9.2
* Fix keys broken in 2.9.1
* YubiKey decryption now working via API
* Experimental YubiKey support: Decryption now working via API
## 2.9.1
@ -69,7 +71,7 @@
* Fix key flags handling (now supporting Mailvelope 0.7 keys)
* Improved passphrase handling
* Key sharing via SafeSlinger
* YubiKey: preference to allow other PINs, currently only signing via the OpenPGP API works, not inside of OpenKeychain
* Experimental YubiKey support: Preference to allow other PINs, currently only signing via the OpenPGP API works, not inside of OpenKeychain
* Fix usage of stripped keys
* SHA256 as default for compatibility
* Intent API has changed, see https://github.com/open-keychain/open-keychain/wiki/Intent-API
@ -80,7 +82,7 @@
* Fixing crashes introduced in v2.8
* Experimental ECC support
* Experimental YubiKey support (signing-only with imported keys)
* Experimental YubiKey support: Only signing with imported keys
## 2.8

View File

@ -22,6 +22,7 @@
<color name="secondary_text">#727272</color>
<color name="icons">#FFFFFF</color>
<color name="divider">#B6B6B6</color>
<color name="transparent">#00FFFFFF</color>
<color name="header_text">#212121</color>
<!-- item selection, search highlight -->

View File

@ -88,6 +88,7 @@
<string name="btn_encrypt_text">"Encrypt text"</string>
<string name="btn_add_email">"Add additional email address"</string>
<string name="btn_unlock">"Unlock"</string>
<string name="btn_add_keyserver">"Add"</string>
<!-- menu -->
<string name="menu_preferences">"Settings"</string>
@ -154,6 +155,8 @@
<string name="label_enable_compression">"Enable compression"</string>
<string name="label_encrypt_filenames">"Encrypt filenames"</string>
<string name="label_hidden_recipients">"Hide recipients"</string>
<string name="label_verify_keyserver">"Verify Keyserver"</string>
<string name="label_enter_keyserver_url">"Enter Keyserver URL"</string>
<string name="pref_keyserver">"Search Keyserver"</string>
<string name="pref_keyserver_summary">"Search HKP keyserver"</string>
@ -356,6 +359,8 @@
<string name="progress_con_saving">"consolidate: saving to cache…"</string>
<string name="progress_con_reimport">"consolidate: reimporting…"</string>
<string name="progress_verifying_keyserver_url">"verifying keyserver…"</string>
<!-- action strings -->
<string name="hint_cloud_search_hint">"Search via Name, Email…"</string>
@ -407,6 +412,9 @@
<string name="import_qr_code_button">"Scan QR Code"</string>
<string name="import_qr_code_text">"Place your camera over the QR Code!"</string>
<!-- Import from URL -->
<string name="import_url_warn_no_search_parameter">"No search parameter found. You may still attempt manually searching the keyserver."</string>
<!-- Generic result toast -->
<string name="view_log">"Details"</string>
<string name="with_warnings">", with warnings"</string>
@ -645,6 +653,13 @@
<string name="view_key_fragment_no_system_contact">"&lt;none&gt;"</string>
<!-- Add keyserver -->
<string name="add_keyserver_dialog_title">"Add Keyserver"</string>
<string name="add_keyserver_verified">"Keyserver verified!"</string>
<string name="add_keyserver_without_verification">"Keyserver added without verification."</string>
<string name="add_keyserver_invalid_url">"Invalid URL!"</string>
<string name="add_keyserver_connection_failed">"Failed to connect to keyserver. Please check the URL and your internet connection."</string>
<!-- Navigation Drawer -->
<string name="nav_keys">"Keys"</string>
<string name="nav_encrypt_decrypt">"Encrypt/Decrypt"</string>
@ -1248,6 +1263,7 @@
<string name="swipe_to_update">"Swipe down to update from keyserver"</string>
<string name="error_no_file_selected">"Select at least one file to encrypt!"</string>
<string name="error_multi_not_supported">"Saving of multiple files not supported. This is a limitation on current Android."</string>
<string name="error_empty_text">"Type some text to encrypt!"</string>
<string name="key_colon">"Key:"</string>
<string name="exchange_description">"To start a key exchange, choose the number of participants on the right side, then hit the “Start exchange” button.\n\nYou will be asked two more questions to make sure only the right participants are in the exchange and their fingerprints are correct."</string>
<string name="btn_start_exchange">"Start exchange"</string>

View File

@ -8,26 +8,17 @@
<item name="colorPrimaryDark">@color/primary_dark</item>
<item name="colorAccent">@color/accent</item>
<item name="android:windowNoTitle">true</item>
<item name="windowNoTitle">true</item>
<!-- remove actionbar, we use toolbar! -->
<item name="windowActionBar">false</item>
<!-- multi selection should overlay Toolbar! http://stackoverflow.com/a/26450875 -->
<item name="windowActionModeOverlay">true</item>
<item name="searchViewStyle">@style/MySearchViewStyle</item>
<!-- Navigation Drawer library -->
<item name="drawerType">@integer/DRAWERTYPE_IMAGE</item>
<!-- dark action bar... -->
<item name="theme">@style/ThemeOverlay.AppCompat.Dark</item>
<!-- ...but light popup menu (white background) -->
<item name="popupTheme">@style/ThemeOverlay.AppCompat.Light</item>
<item name="drawerColor">#fafafa</item>
<item name="singleAccount">false</item>
<item name="sectionStyle">@style/MaterialSectionTheme.Light</item>
<item name="subheaderStyle">@style/MaterialSubheaderTheme.Light</item>
<item name="multipaneSupport">false</item>
<item name="rippleBackport">false</item>
<item name="uniqueToolbarColor">false</item>
</style>
<!-- http://android-developers.blogspot.de/2014/10/appcompat-v21-material-design-for-pre.html -->