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
@ -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
|
||||
|
@ -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',
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -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">
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
@ -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: {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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!
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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() {
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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,
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -82,12 +82,12 @@ public class ImportKeysListLoader
|
||||
|
||||
@Override
|
||||
protected void onStartLoading() {
|
||||
forceLoad();
|
||||
super.forceLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopLoading() {
|
||||
cancelLoad();
|
||||
super.cancelLoad();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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!");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
/**
|
@ -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;
|
||||
|
@ -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;
|
||||
|
After Width: | Height: | Size: 284 B |
BIN
OpenKeychain/src/main/res/drawable-hdpi/ic_share_black_24dp.png
Normal file
After Width: | Height: | Size: 499 B |
After Width: | Height: | Size: 214 B |
BIN
OpenKeychain/src/main/res/drawable-mdpi/ic_share_black_24dp.png
Normal file
After Width: | Height: | Size: 355 B |
After Width: | Height: | Size: 304 B |
BIN
OpenKeychain/src/main/res/drawable-xhdpi/ic_share_black_24dp.png
Normal file
After Width: | Height: | Size: 614 B |
After Width: | Height: | Size: 397 B |
After Width: | Height: | Size: 804 B |
After Width: | Height: | Size: 480 B |
After Width: | Height: | Size: 1.0 KiB |
29
OpenKeychain/src/main/res/layout/add_keyserver_dialog.xml
Normal 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>
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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>
|
@ -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>
|
||||
|
19
OpenKeychain/src/main/res/layout/main_activity.xml
Normal 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>
|
9
OpenKeychain/src/main/res/layout/main_drawer_header.xml
Normal 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>
|
@ -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"
|
||||
|
145
OpenKeychain/src/main/res/layout/toolbar_result_decrypt.xml
Normal 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>
|
@ -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>
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 -->
|
||||
|
@ -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">"<none>"</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>
|
||||
|
@ -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 -->
|
||||
|