diff options
Diffstat (limited to 'OpenKeychain/src')
23 files changed, 283 insertions, 190 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java index e5a128e32..6a584a939 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/LinkedTokenResource.java @@ -2,12 +2,10 @@ package org.sufficientlysecure.keychain.linked; import android.content.Context; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; +import com.squareup.okhttp.CertificatePinner; +import com.squareup.okhttp.OkHttpClient; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; import org.json.JSONException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.linked.resources.GenericHttpsResource; @@ -18,12 +16,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.Log; -import org.thoughtcrime.ssl.pinning.util.PinningHelper; -import java.io.BufferedReader; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URI; import java.util.HashMap; @@ -233,46 +227,35 @@ public abstract class LinkedTokenResource extends LinkedResource { } - @SuppressWarnings("deprecation") // HttpRequestBase is deprecated - public static String getResponseBody(Context context, HttpRequestBase request) - throws IOException, HttpStatusException { - return getResponseBody(context, request, null); + + private static CertificatePinner getCertificatePinner(String hostname, String[] pins){ + CertificatePinner.Builder builder = new CertificatePinner.Builder(); + for(String pin : pins){ + builder.add(hostname,pin); + } + return builder.build(); } - @SuppressWarnings("deprecation") // HttpRequestBase is deprecated - public static String getResponseBody(Context context, HttpRequestBase request, String[] pins) - throws IOException, HttpStatusException { - StringBuilder sb = new StringBuilder(); + public static String getResponseBody(Request request, String... pins) + throws IOException, HttpStatusException { - request.setHeader("User-Agent", "Open Keychain"); + Log.d("Connection to: "+request.url().getHost(),""); + OkHttpClient client = new OkHttpClient(); + if(pins !=null){ + client.setCertificatePinner(getCertificatePinner(request.url().getHost(),pins)); + } + Response response = client.newCall(request).execute(); - HttpClient httpClient; - if (pins == null) { - httpClient = new DefaultHttpClient(new BasicHttpParams()); - } else { - httpClient = PinningHelper.getPinnedHttpClient(context, pins); - } - HttpResponse response = httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - String reason = response.getStatusLine().getReasonPhrase(); + int statusCode = response.code(); + String reason = response.message(); if (statusCode != 200) { throw new HttpStatusException(statusCode, reason); } - HttpEntity entity = response.getEntity(); - InputStream inputStream = entity.getContent(); - - BufferedReader bReader = new BufferedReader( - new InputStreamReader(inputStream, "UTF-8"), 8); - String line; - while ((line = bReader.readLine()) != null) { - sb.append(line); - } - - return sb.toString(); + return response.body().string(); } public static class HttpStatusException extends Throwable { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java index 82240c405..d6b8640e3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GenericHttpsResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import org.apache.http.client.methods.HttpGet; +import com.squareup.okhttp.Request; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType; import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; @@ -32,14 +32,16 @@ public class GenericHttpsResource extends LinkedTokenResource { token, "0x" + KeyFormattingUtils.convertFingerprintToHex(fingerprint).substring(24)); } - @SuppressWarnings("deprecation") // HttpGet is deprecated @Override protected String fetchResource (Context context, OperationLog log, int indent) throws HttpStatusException, IOException { log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); - HttpGet httpGet = new HttpGet(mSubUri); - return getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url(mSubUri.toURL()) + .addHeader("User-Agent", "OpenKeychain") + .build(); + return getResponseBody(request); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java index 7a97ffd96..134582ae1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/GithubResource.java @@ -6,7 +6,7 @@ import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; -import org.apache.http.client.methods.HttpGet; +import com.squareup.okhttp.Request; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -47,7 +47,7 @@ public class GithubResource extends LinkedTokenResource { return String.format(context.getResources().getString(R.string.linked_id_github_text), token); } - @SuppressWarnings("deprecation") // HttpGet is deprecated + @Override protected String fetchResource (Context context, OperationLog log, int indent) throws HttpStatusException, IOException, JSONException { @@ -55,8 +55,11 @@ public class GithubResource extends LinkedTokenResource { log.add(LogType.MSG_LV_FETCH, indent, mSubUri.toString()); indent += 1; - HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + mGistId); - String response = getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url("https://api.github.com/gists/" + mGistId) + .addHeader("User-Agent", "OpenKeychain") + .build(); + String response = getResponseBody(request); JSONObject obj = new JSONObject(response); @@ -79,7 +82,7 @@ public class GithubResource extends LinkedTokenResource { } - @Deprecated // not used for now, but could be used to pick up earlier posted gist if already present? + @SuppressWarnings({ "deprecation", "unused" }) public static GithubResource searchInGithubStream( Context context, String screenName, String needle, OperationLog log) { @@ -94,12 +97,12 @@ public class GithubResource extends LinkedTokenResource { try { JSONArray array; { - HttpGet httpGet = - new HttpGet("https://api.github.com/users/" + screenName + "/gists"); - httpGet.setHeader("Content-Type", "application/json"); - httpGet.setHeader("User-Agent", "OpenKeychain"); - - String response = getResponseBody(context, httpGet); + Request request = new Request.Builder() + .url("https://api.github.com/users/" + screenName + "/gists") + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); + String response = getResponseBody(request); array = new JSONArray(response); } @@ -116,10 +119,13 @@ public class GithubResource extends LinkedTokenResource { continue; } String id = obj.getString("id"); - HttpGet httpGet = new HttpGet("https://api.github.com/gists/" + id); - httpGet.setHeader("User-Agent", "OpenKeychain"); - JSONObject gistObj = new JSONObject(getResponseBody(context, httpGet)); + Request request = new Request.Builder() + .url("https://api.github.com/gists/" + id) + .addHeader("User-Agent", "OpenKeychain") + .build(); + + JSONObject gistObj = new JSONObject(getResponseBody(request)); JSONObject gistFiles = gistObj.getJSONObject("files"); Iterator<String> gistIt = gistFiles.keys(); if (!gistIt.hasNext()) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java index 73e3d3643..b0f02c43c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/linked/resources/TwitterResource.java @@ -7,11 +7,11 @@ import android.support.annotation.DrawableRes; import android.support.annotation.StringRes; import android.util.Log; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.RequestBody; import com.textuality.keybase.lib.JWalk; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -84,18 +84,19 @@ public class TwitterResource extends LinkedTokenResource { return null; } - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/show.json" - + "?id=" + mTweetId - + "&include_entities=false"); - // construct a normal HTTPS request and include an Authorization // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + authToken); - httpGet.setHeader("Content-Type", "application/json"); + Request request = new Request.Builder() + .url("https://api.twitter.com/1.1/statuses/show.json" + + "?id=" + mTweetId + + "&include_entities=false") + .addHeader("Authorization", "Bearer " + authToken) + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); try { - String response = getResponseBody(context, httpGet, CERT_PINS); + String response = getResponseBody(request, CERT_PINS); JSONObject obj = new JSONObject(response); JSONObject user = obj.getJSONObject("user"); if (!mHandle.equalsIgnoreCase(user.getString("screen_name"))) { @@ -157,21 +158,20 @@ public class TwitterResource extends LinkedTokenResource { return null; } - HttpGet httpGet = - new HttpGet("https://api.twitter.com/1.1/statuses/user_timeline.json" + Request request = new Request.Builder() + .url("https://api.twitter.com/1.1/statuses/user_timeline.json" + "?screen_name=" + screenName + "&count=15" + "&include_rts=false" + "&trim_user=true" - + "&exclude_replies=true"); - - // construct a normal HTTPS request and include an Authorization - // header with the value of Bearer <> - httpGet.setHeader("Authorization", "Bearer " + authToken); - httpGet.setHeader("Content-Type", "application/json"); + + "&exclude_replies=true") + .addHeader("Authorization", "Bearer " + authToken) + .addHeader("Content-Type", "application/json") + .addHeader("User-Agent", "OpenKeychain") + .build(); try { - String response = getResponseBody(context, httpGet, CERT_PINS); + String response = getResponseBody(request, CERT_PINS); JSONArray array = new JSONArray(response); for (int i = 0; i < array.length(); i++) { @@ -216,12 +216,20 @@ public class TwitterResource extends LinkedTokenResource { String base64Encoded = rot13("D293FQqanH0jH29KIaWJER5DomqSGRE2Ewc1LJACn3cbD1c" + "Fq1bmqSAQAz5MI2cIHKOuo3cPoRAQI1OyqmIVFJS6LHMXq2g6MRLkIj") + "=="; + RequestBody requestBody = RequestBody.create( + MediaType.parse("application/x-www-form-urlencoded;charset=UTF-8"), + "grant_type=client_credentials"); + // Step 2: Obtain a bearer token - HttpPost httpPost = new HttpPost("https://api.twitter.com/oauth2/token"); - httpPost.setHeader("Authorization", "Basic " + base64Encoded); - httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); - httpPost.setEntity(new StringEntity("grant_type=client_credentials")); - JSONObject rawAuthorization = new JSONObject(getResponseBody(context, httpPost, CERT_PINS)); + Request request = new Request.Builder() + .url("https://api.twitter.com/oauth2/token") + .addHeader("Authorization", "Basic " + base64Encoded) + .addHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8") + .addHeader("User-Agent", "OpenKeychain") + .post(requestBody) + .build(); + + JSONObject rawAuthorization = new JSONObject(getResponseBody(request, CERT_PINS)); // Applications should verify that the value associated with the // token_type key of the returned object is bearer diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java index 43fc11b84..6682cc6e7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/InputDataOperation.java @@ -119,8 +119,9 @@ public class InputDataOperation extends BaseOperation<InputDataParcel> { // inform the storage provider about the mime type for this uri if (decryptResult.getDecryptionMetadata() != null) { - TemporaryFileProvider.setMimeType(mContext, currentInputUri, - decryptResult.getDecryptionMetadata().getMimeType()); + OpenPgpMetadata meta = decryptResult.getDecryptionMetadata(); + TemporaryFileProvider.setName(mContext, currentInputUri, meta.getFilename()); + TemporaryFileProvider.setMimeType(mContext, currentInputUri, meta.getMimeType()); } } else { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java index 009876045..328daa7ff 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -470,7 +470,13 @@ public class PgpSignEncryptOperation extends BaseOperation { InputStream in = new BufferedInputStream(inputData.getInputStream()); if (enableCompression) { - compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm()); + // Use preferred compression algo + int algo = input.getCompressionAlgorithm(); + if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) { + algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM; + } + + compressGen = new PGPCompressedDataGenerator(algo); bcpgOut = new BCPGOutputStream(compressGen.open(out)); } else { bcpgOut = new BCPGOutputStream(out); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java index 68963d595..bb44314d7 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/TemporaryFileProvider.java @@ -99,6 +99,12 @@ public class TemporaryFileProvider extends ContentProvider { return context.getContentResolver().insert(CONTENT_URI, contentValues); } + public static int setName(Context context, Uri uri, String name) { + ContentValues values = new ContentValues(); + values.put(TemporaryFileColumns.COLUMN_NAME, name); + return context.getContentResolver().update(uri, values, null, null); + } + public static int setMimeType(Context context, Uri uri, String mimetype) { ContentValues values = new ContentValues(); values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype); @@ -283,8 +289,11 @@ public class TemporaryFileProvider extends ContentProvider { @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - if (values.size() != 1 || !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) { - throw new UnsupportedOperationException("Update supported only for type field!"); + if (values.size() != 1) { + throw new UnsupportedOperationException("Update supported only for one field at a time!"); + } + if (!values.containsKey(TemporaryFileColumns.COLUMN_NAME) && !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) { + throw new UnsupportedOperationException("Update supported only for name and type field!"); } if (selection != null || selectionArgs != null) { throw new UnsupportedOperationException("Update supported only for plain uri!"); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 2d70f3681..02d9ba62d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -103,20 +103,20 @@ public class OpenPgpService extends Service { } private static class KeyIdResult { - final Intent mRequiredUserInteraction; + final Intent mResultIntent; final HashSet<Long> mKeyIds; - KeyIdResult(Intent requiredUserInteraction) { - mRequiredUserInteraction = requiredUserInteraction; + KeyIdResult(Intent resultIntent) { + mResultIntent = resultIntent; mKeyIds = null; } KeyIdResult(HashSet<Long> keyIds) { - mRequiredUserInteraction = null; + mResultIntent = null; mKeyIds = keyIds; } } - private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds) { + private KeyIdResult returnKeyIdsFromEmails(Intent data, String[] encryptionUserIds, boolean isOpportunistic) { boolean noUserIdsCheck = (encryptionUserIds == null || encryptionUserIds.length == 0); boolean missingUserIdsCheck = false; boolean duplicateUserIdsCheck = false; @@ -159,9 +159,15 @@ public class OpenPgpService extends Service { } } - if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { - // allow the user to verify pub key selection + if (isOpportunistic && (noUserIdsCheck || missingUserIdsCheck)) { + Intent result = new Intent(); + result.putExtra(OpenPgpApi.RESULT_ERROR, + new OpenPgpError(OpenPgpError.OPPORTUNISTIC_MISSING_KEYS, "missing keys in opportunistic mode")); + result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR); + return new KeyIdResult(result); + } + if (noUserIdsCheck || missingUserIdsCheck || duplicateUserIdsCheck) { // convert ArrayList<Long> to long[] long[] keyIdsArray = getUnboxedLongArray(keyIds); ApiPendingIntentFactory piFactory = new ApiPendingIntentFactory(getBaseContext()); @@ -173,15 +179,14 @@ public class OpenPgpService extends Service { result.putExtra(OpenPgpApi.RESULT_INTENT, pi); result.putExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED); return new KeyIdResult(result); - } else { - // everything was easy, we have exactly one key for every email - - if (keyIds.isEmpty()) { - Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); - } + } - return new KeyIdResult(keyIds); + // everything was easy, we have exactly one key for every email + if (keyIds.isEmpty()) { + Log.e(Constants.TAG, "keyIdsArray.length == 0, should never happen!"); } + + return new KeyIdResult(keyIds); } private Intent signImpl(Intent data, InputStream inputStream, @@ -302,12 +307,12 @@ public class OpenPgpService extends Service { // get key ids based on given user ids if (data.hasExtra(OpenPgpApi.EXTRA_USER_IDS)) { String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); + boolean isOpportunistic = data.getBooleanExtra(OpenPgpApi.EXTRA_OPPORTUNISTIC_ENCRYPTION, false); // give params through to activity... - KeyIdResult result = returnKeyIdsFromEmails(data, userIds); + KeyIdResult result = returnKeyIdsFromEmails(data, userIds, isOpportunistic); - if (result.mRequiredUserInteraction != null) { - return result.mRequiredUserInteraction; + if (result.mResultIntent != null) { + return result.mResultIntent; } encryptKeyIds.addAll(result.mKeyIds); } @@ -695,10 +700,9 @@ public class OpenPgpService extends Service { } else { // get key ids based on given user ids String[] userIds = data.getStringArrayExtra(OpenPgpApi.EXTRA_USER_IDS); - data.removeExtra(OpenPgpApi.EXTRA_USER_IDS); - KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds); - if (keyResult.mRequiredUserInteraction != null) { - return keyResult.mRequiredUserInteraction; + KeyIdResult keyResult = returnKeyIdsFromEmails(data, userIds, false); + if (keyResult.mResultIntent != null) { + return keyResult.mResultIntent; } if (keyResult.mKeyIds == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java index 47552bf13..f1d9b270d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/BackupCodeFragment.java @@ -170,11 +170,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar mTitleAnimator.setDisplayedChild(1, animate); mStatusAnimator.setDisplayedChild(1, animate); mCodeFieldsAnimator.setDisplayedChild(1, animate); - // use non-breaking spaces to enlarge the empty EditText appropriately - String empty = "\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" + - "-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0" + - "-\u00a0\u00a0\u00a0\u00a0-\u00a0\u00a0\u00a0\u00a0"; - mCodeEditText.setText(empty); + mCodeEditText.setText(" - - - - - "); pushBackStackEntry(); @@ -354,8 +350,7 @@ public class BackupCodeFragment extends CryptoOperationFragment<BackupKeyringPar String currentBackupCode = backupCode.getText().toString(); boolean inInputState = mCurrentState == BackupCodeState.STATE_INPUT || mCurrentState == BackupCodeState.STATE_INPUT_ERROR; - boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1) - && (currentBackupCode.indexOf('\u00a0') == -1); + boolean partIsComplete = (currentBackupCode.indexOf(' ') == -1); if (!inInputState || !partIsComplete) { return; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java index be08f6a53..22202a35f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EncryptFilesFragment.java @@ -247,10 +247,10 @@ public class EncryptFilesFragment try { mFilesAdapter.add(inputUri); } catch (IOException e) { + String fileName = FileHelper.getFilename(getActivity(), inputUri); Notify.create(getActivity(), - getActivity().getString(R.string.error_file_added_already, FileHelper.getFilename(getActivity(), inputUri)), + getActivity().getString(R.string.error_file_added_already, fileName), Notify.Style.ERROR).show(this); - return; } // remove from pending input uris diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java index b399af950..4d4219f56 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/ImportKeysListFragment.java @@ -330,9 +330,16 @@ public class ImportKeysListFragment extends ListFragment implements } public void loadNew(LoaderState loaderState) { - mLoaderState = loaderState; + if (mLoaderState instanceof BytesLoaderState) { + BytesLoaderState ls = (BytesLoaderState) mLoaderState; + + if ( ls.mDataUri != null && ! checkAndRequestReadPermission(ls.mDataUri)) { + return; + } + } + restartLoaders(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index fd4f27176..e7f91a07e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -192,6 +192,8 @@ public class PassphraseDialogActivity extends FragmentActivity { mBackupCodeEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mBackupCodeEditText.setOnEditorActionListener(this); + openKeyboard(mBackupCodeEditText); + AlertDialog dialog = alert.create(); dialog.setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.btn_unlock), (DialogInterface.OnClickListener) null); @@ -281,28 +283,7 @@ public class PassphraseDialogActivity extends FragmentActivity { mPassphraseText.setText(message); mPassphraseEditText.setHint(hint); - // 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 - mPassphraseEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - mPassphraseEditText.post(new Runnable() { - @Override - public void run() { - if (getActivity() == null || mPassphraseEditText == null) { - return; - } - InputMethodManager imm = (InputMethodManager) getActivity() - .getSystemService(Context.INPUT_METHOD_SERVICE); - imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT); - } - }); - } - }); - mPassphraseEditText.requestFocus(); + openKeyboard(mPassphraseEditText); mPassphraseEditText.setImeActionLabel(getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE); mPassphraseEditText.setOnEditorActionListener(this); @@ -325,6 +306,34 @@ public class PassphraseDialogActivity extends FragmentActivity { } /** + * 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 + */ + private void openKeyboard(final TextView textView) { + textView.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + textView.post(new Runnable() { + @Override + public void run() { + if (getActivity() == null || textView == null) { + return; + } + InputMethodManager imm = (InputMethodManager) getActivity() + .getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(textView, InputMethodManager.SHOW_IMPLICIT); + } + }); + } + }); + textView.requestFocus(); + } + + /** * Automatic line break with max 6 lines for smaller displays * <p/> * NOTE: I was not able to get this behaviour using XML! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java index 78d82d436..d2ecce242 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SecurityTokenOperationActivity.java @@ -249,13 +249,13 @@ public class SecurityTokenOperationActivity extends BaseSecurityTokenNfcActivity } // change PINs afterwards - nfcModifyPIN(0x81, newPin); - nfcModifyPIN(0x83, newAdminPin); + nfcModifyPin(0x81, newPin); + nfcModifyPin(0x83, newAdminPin); break; } case NFC_RESET_CARD: { - nfcResetCard(); + nfcReset(); break; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java index 5a8ab36bc..488558aa3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/SettingsKeyserverFragment.java @@ -40,6 +40,7 @@ import android.widget.TextView; import org.sufficientlysecure.keychain.R; import org.sufficientlysecure.keychain.ui.dialog.AddEditKeyserverDialogFragment; +import org.sufficientlysecure.keychain.ui.util.FormattingUtils; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperAdapter; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperViewHolder; import org.sufficientlysecure.keychain.ui.util.recyclerview.ItemTouchHelperDragCallback; @@ -312,19 +313,19 @@ public class SettingsKeyserverFragment extends Fragment implements RecyclerItemC public void showAsSelectedKeyserver() { isSelectedKeyserver = true; selectedServerLabel.setVisibility(View.VISIBLE); - outerLayout.setBackgroundColor(getResources().getColor(R.color.android_green_dark)); + outerLayout.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorPrimaryDark)); } public void showAsUnselectedKeyserver() { isSelectedKeyserver = false; selectedServerLabel.setVisibility(View.GONE); - outerLayout.setBackgroundColor(Color.WHITE); + outerLayout.setBackgroundColor(0); } @Override public void onItemSelected() { selectedServerLabel.setVisibility(View.GONE); - itemView.setBackgroundColor(Color.LTGRAY); + itemView.setBackgroundColor(FormattingUtils.getColorFromAttr(getContext(), R.attr.colorBrightToolbar)); } @Override diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java index 107c63e0b..beaf68372 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseActivity.java @@ -45,8 +45,8 @@ public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { - initTheme(); super.onCreate(savedInstanceState); + initTheme(); initLayout(); initToolbar(); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java index c3352363a..77044cd2b 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/base/BaseSecurityTokenNfcActivity.java @@ -26,14 +26,11 @@ import java.nio.ByteBuffer; import java.security.interfaces.RSAPrivateCrtKey; import android.app.Activity; -import android.app.PendingIntent; import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageManager; import android.nfc.NfcAdapter; import android.nfc.Tag; import android.nfc.TagLostException; -import android.nfc.tech.IsoDep; import android.os.AsyncTask; import android.os.Bundle; @@ -319,7 +316,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } // 6A82 app not installed on security token! case 0x6A82: { - if (isFidesmoDevice()) { + if (isFidesmoToken()) { // Check if the Fidesmo app is installed if (isAndroidAppInstalled(FIDESMO_APP_PACKAGE)) { promptFidesmoPgpInstall(); @@ -530,18 +527,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen public String nfcGetUserId() throws IOException { String info = "00CA006500"; - return nfcGetHolderName(nfcCommunicate(info)); + return getHolderName(nfcCommunicate(info)); } /** - * Calls to calculate the signature and returns the MPI value + * Call COMPUTE DIGITAL SIGNATURE command and returns the MPI value * * @param hash the hash for signing * @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) + nfcVerifyPin(0x81); // (Verify PW1 with mode 81 for signing) } // dsi, including Lc @@ -634,14 +631,14 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Calls to calculate the signature and returns the MPI value + * Call DECIPHER command * * @param encryptedSessionKey the encoded session key * @return the decoded session key */ public byte[] nfcDecryptSessionKey(byte[] encryptedSessionKey) throws IOException { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 with mode 82 for decryption) + nfcVerifyPin(0x82); // (Verify PW1 with mode 82 for decryption) } String firstApdu = "102a8086fe"; @@ -657,10 +654,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen two[i] = encryptedSessionKey[i + one.length + 1]; } - String first = nfcCommunicate(firstApdu + getHex(one)); + nfcCommunicate(firstApdu + getHex(one)); String second = nfcCommunicate(secondApdu + getHex(two) + le); - String decryptedSessionKey = nfcGetDataField(second); + String decryptedSessionKey = getDataField(second); return Hex.decode(decryptedSessionKey); } @@ -670,7 +667,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @param mode For PW1, this is 0x81 for signing, 0x82 for everything else. * For PW3 (Admin PIN), mode is 0x83. */ - public void nfcVerifyPIN(int mode) throws IOException { + public void nfcVerifyPin(int mode) throws IOException { if (mPin != null || mode == 0x83) { byte[] pin; @@ -683,7 +680,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // SW1/2 0x9000 is the generic "ok" response, which we expect most of the time. // See specification, page 51 String accepted = "9000"; - String response = tryPin(mode, pin); // login + String response = nfcTryPin(mode, pin); // login if (!response.equals(accepted)) { throw new CardException("Bad PIN!", parseCardStatus(response)); } @@ -698,13 +695,18 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public void nfcResetCard() throws IOException { + /** + * Resets security token, which deletes all keys and data objects. + * This works by entering a wrong PIN and then Admin PIN 4 times respectively. + * Afterwards, the token is reactivated. + */ + public void nfcReset() throws IOException { String accepted = "9000"; // try wrong PIN 4 times until counter goes to C0 byte[] pin = "XXXXXX".getBytes(); for (int i = 0; i <= 4; i++) { - String response = tryPin(0x81, pin); + String response = nfcTryPin(0x81, pin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXX has been accepted!", parseCardStatus(response)); } @@ -713,7 +715,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen // try wrong Admin PIN 4 times until counter goes to C0 byte[] adminPin = "XXXXXXXX".getBytes(); for (int i = 0; i <= 4; i++) { - String response = tryPin(0x83, adminPin); + String response = nfcTryPin(0x83, adminPin); if (response.equals(accepted)) { // Should NOT accept! throw new CardException("Should never happen, XXXXXXXX has been accepted", parseCardStatus(response)); } @@ -730,7 +732,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private String tryPin(int mode, byte[] pin) throws IOException { + private String nfcTryPin(int mode, byte[] pin) throws IOException { // Command APDU for VERIFY command (page 32) String login = "00" // CLA @@ -749,7 +751,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * @param pw For PW1, this is 0x81. For PW3 (Admin PIN), mode is 0x83. * @param newPin The new PW1 or PW3. */ - public void nfcModifyPIN(int pw, byte[] newPin) throws IOException { + public void nfcModifyPin(int pw, byte[] newPin) throws IOException { final int MAX_PW1_LENGTH_INDEX = 1; final int MAX_PW3_LENGTH_INDEX = 3; @@ -802,10 +804,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (dataObject == 0x0101 || dataObject == 0x0103) { if (!mPw1ValidatedForDecrypt) { - nfcVerifyPIN(0x82); // (Verify PW1 for non-signing operations) + nfcVerifyPin(0x82); // (Verify PW1 for non-signing operations) } } else if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3) + nfcVerifyPin(0x83); // (Verify PW3) } String putDataApdu = "00" // CLA @@ -854,7 +856,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } if (!mPw3Validated) { - nfcVerifyPIN(0x83); // (Verify PW3 with mode 83) + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) } byte[] header= Hex.decode( @@ -934,6 +936,53 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** + * Generates a key on the card in the given slot. If the slot is 0xB6 (the signature key), + * this command also has the effect of resetting the digital signature counter. + * NOTE: This does not set the key fingerprint data object! After calling this command, you + * must construct a public key packet using the returned public key data objects, compute the + * key fingerprint, and store it on the card using: nfcPutData(0xC8, key.getFingerprint()) + * + * @param slot The slot on the card where the key should be generated: + * 0xB6: Signature Key + * 0xB8: Decipherment Key + * 0xA4: Authentication Key + * @return the public key data objects, in TLV format. For RSA this will be the public modulus + * (0x81) and exponent (0x82). These may come out of order; proper TLV parsing is required. + */ + public byte[] nfcGenerateKey(int slot) throws IOException { + if (slot != 0xB6 && slot != 0xB8 && slot != 0xA4) { + throw new IOException("Invalid key slot"); + } + + if (!mPw3Validated) { + nfcVerifyPin(0x83); // (Verify PW3 with mode 83) + } + + String generateKeyApdu = "0047800002" + String.format("%02x", slot) + "0000"; + String getResponseApdu = "00C00000"; + + String first = nfcCommunicate(generateKeyApdu); + String second = nfcCommunicate(getResponseApdu); + + if (!second.endsWith("9000")) { + throw new IOException("On-card key generation failed"); + } + + String publicKeyData = getDataField(first) + getDataField(second); + + Log.d(Constants.TAG, "Public Key Data Objects: " + publicKeyData); + + return Hex.decode(publicKeyData); + } + + /** + * Transceive data via NFC encoded as Hex + */ + public String nfcCommunicate(String apdu) throws IOException { + return getHex(mIsoCard.transceive(Hex.decode(apdu))); + } + + /** * Parses out the status word from a JavaCard response string. * * @param response A hex string with the response from the token @@ -951,7 +1000,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - public String nfcGetHolderName(String name) { + public String getHolderName(String name) { try { String slength; int ilength; @@ -971,14 +1020,10 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } } - private String nfcGetDataField(String output) { + private String getDataField(String output) { return output.substring(0, output.length() - 4); } - public String nfcCommunicate(String apdu) throws IOException { - return getHex(mIsoCard.transceive(Hex.decode(apdu))); - } - public static String getHex(byte[] raw) { return new String(Hex.encode(raw)); } @@ -1005,7 +1050,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } - private boolean isFidesmoDevice() { + private boolean isFidesmoToken() { if (isNfcConnected()) { // Check if we can still talk to the card try { // By trying to select any apps that have the Fidesmo AID prefix we can @@ -1021,11 +1066,11 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen } /** - * Ask user if she wants to install PGP onto her Fidesmo device + * Ask user if she wants to install PGP onto her Fidesmo token */ private void promptFidesmoPgpInstall() { - FidesmoPgpInstallDialog mFidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); - mFidesmoPgpInstallDialog.show(getSupportFragmentManager(), "mFidesmoPgpInstallDialog"); + FidesmoPgpInstallDialog fidesmoPgpInstallDialog = new FidesmoPgpInstallDialog(); + fidesmoPgpInstallDialog.show(getSupportFragmentManager(), "fidesmoPgpInstallDialog"); } /** @@ -1033,8 +1078,8 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen * to launch the Google Play store. */ private void promptFidesmoAppInstall() { - FidesmoInstallDialog mFidesmoInstallDialog = new FidesmoInstallDialog(); - mFidesmoInstallDialog.show(getSupportFragmentManager(), "mFidesmoInstallDialog"); + FidesmoInstallDialog fidesmoInstallDialog = new FidesmoInstallDialog(); + fidesmoInstallDialog.show(getSupportFragmentManager(), "fidesmoInstallDialog"); } /** @@ -1044,7 +1089,7 @@ public abstract class BaseSecurityTokenNfcActivity extends BaseActivity implemen */ private boolean isAndroidAppInstalled(String uri) { PackageManager mPackageManager = getPackageManager(); - boolean mAppInstalled = false; + boolean mAppInstalled; try { mPackageManager.getPackageInfo(uri, PackageManager.GET_ACTIVITIES); mAppInstalled = true; diff --git a/OpenKeychain/src/main/res/layout-land/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout-w600dp/backup_code_fragment.xml index 0a4f97d53..6cea50a32 100644 --- a/OpenKeychain/src/main/res/layout-land/backup_code_fragment.xml +++ b/OpenKeychain/src/main/res/layout-w600dp/backup_code_fragment.xml @@ -84,11 +84,9 @@ android:textSize="18dp" android:textStyle="bold" android:typeface="monospace" - app:deleteChar="\u00a0" app:mask="****-****-****-****-****-****" app:maskIconColor="@color/colorPrimary" app:notMaskedSymbol="*" - app:replacementChar="\u00a0" tools:ignore="SpUsage" /> </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> diff --git a/OpenKeychain/src/main/res/layout-land/security_token_operation_activity.xml b/OpenKeychain/src/main/res/layout-w600dp/security_token_operation_activity.xml index cf0edac8c..cf0edac8c 100644 --- a/OpenKeychain/src/main/res/layout-land/security_token_operation_activity.xml +++ b/OpenKeychain/src/main/res/layout-w600dp/security_token_operation_activity.xml diff --git a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml index 330f18d1c..619a1b3b1 100644 --- a/OpenKeychain/src/main/res/layout/backup_code_fragment.xml +++ b/OpenKeychain/src/main/res/layout/backup_code_fragment.xml @@ -84,11 +84,9 @@ android:textSize="18dp" android:textStyle="bold" android:typeface="monospace" - app:deleteChar="\u00a0" app:mask="****-****-****-****-****-****" app:maskIconColor="@color/colorPrimary" app:notMaskedSymbol="*" - app:replacementChar="\u00a0" tools:ignore="SpUsage" /> </org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator> diff --git a/OpenKeychain/src/main/res/layout/passphrase_dialog_backup_code.xml b/OpenKeychain/src/main/res/layout/passphrase_dialog_backup_code.xml index b17c9dba7..f18b5193e 100644 --- a/OpenKeychain/src/main/res/layout/passphrase_dialog_backup_code.xml +++ b/OpenKeychain/src/main/res/layout/passphrase_dialog_backup_code.xml @@ -30,19 +30,17 @@ <com.github.pinball83.maskededittext.MaskedEditText android:id="@+id/backup_code" + style="@style/BackupCodeTextStyle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" - android:textSize="18dp" android:textStyle="bold" android:typeface="monospace" - app:deleteChar="\u00a0" app:mask="****-****-****-****-****-****" app:maskIconColor="@color/colorPrimary" app:notMaskedSymbol="*" - app:replacementChar="\u00a0" tools:ignore="SpUsage" /> </LinearLayout> diff --git a/OpenKeychain/src/main/res/values-sw360dp/styles.xml b/OpenKeychain/src/main/res/values-sw360dp/styles.xml new file mode 100644 index 000000000..70587b7dc --- /dev/null +++ b/OpenKeychain/src/main/res/values-sw360dp/styles.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:tools="http://schemas.android.com/tools"> + + <!-- constrain size of backup code input for smaller devices --> + <style name="BackupCodeTextStyle"> + <item name="android:textSize" tools:ignore="SpUsage">16dp</item> + </style> + +</resources> diff --git a/OpenKeychain/src/main/res/values-sw400dp/styles.xml b/OpenKeychain/src/main/res/values-sw400dp/styles.xml new file mode 100644 index 000000000..b18c708d9 --- /dev/null +++ b/OpenKeychain/src/main/res/values-sw400dp/styles.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources xmlns:tools="http://schemas.android.com/tools"> + + <!-- constrain size of backup code input for smaller devices --> + <style name="BackupCodeTextStyle"> + <item name="android:textSize" tools:ignore="SpUsage">18dp</item> + </style> + +</resources> diff --git a/OpenKeychain/src/main/res/values/styles.xml b/OpenKeychain/src/main/res/values/styles.xml index 55c4e2220..5d7486d19 100644 --- a/OpenKeychain/src/main/res/values/styles.xml +++ b/OpenKeychain/src/main/res/values/styles.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="utf-8"?> -<resources> +<resources xmlns:tools="http://schemas.android.com/tools"> <style name="CardViewHeader"> <item name="android:drawableBottom">?attr/cardViewHeaderDrawable</item> @@ -38,4 +38,9 @@ <item name="android:textColor">?attr/colorAccent</item> </style> + <!-- constrain size of backup code input for smaller devices --> + <style name="BackupCodeTextStyle"> + <item name="android:textSize" tools:ignore="SpUsage">14dp</item> + </style> + </resources> |